google-protobuf 3.21.2 → 4.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/ext/google/protobuf_c/Rakefile +3 -0
  3. data/ext/google/protobuf_c/convert.c +67 -86
  4. data/ext/google/protobuf_c/convert.h +3 -28
  5. data/ext/google/protobuf_c/defs.c +538 -77
  6. data/ext/google/protobuf_c/defs.h +3 -28
  7. data/ext/google/protobuf_c/extconf.rb +4 -4
  8. data/ext/google/protobuf_c/glue.c +72 -0
  9. data/ext/google/protobuf_c/map.c +114 -85
  10. data/ext/google/protobuf_c/map.h +12 -30
  11. data/ext/google/protobuf_c/message.c +264 -238
  12. data/ext/google/protobuf_c/message.h +11 -33
  13. data/ext/google/protobuf_c/protobuf.c +63 -187
  14. data/ext/google/protobuf_c/protobuf.h +27 -39
  15. data/ext/google/protobuf_c/repeated_field.c +72 -38
  16. data/ext/google/protobuf_c/repeated_field.h +11 -29
  17. data/ext/google/protobuf_c/ruby-upb.c +13783 -8236
  18. data/ext/google/protobuf_c/ruby-upb.h +14077 -4495
  19. data/ext/google/protobuf_c/shared_convert.c +69 -0
  20. data/ext/google/protobuf_c/shared_convert.h +26 -0
  21. data/ext/google/protobuf_c/shared_message.c +37 -0
  22. data/ext/google/protobuf_c/shared_message.h +21 -0
  23. data/ext/google/protobuf_c/third_party/utf8_range/LICENSE +1 -0
  24. data/ext/google/protobuf_c/third_party/utf8_range/utf8_range.c +467 -0
  25. data/ext/google/protobuf_c/third_party/utf8_range/utf8_range.h +20 -7
  26. data/ext/google/protobuf_c/wrap_memcpy.c +3 -26
  27. data/lib/google/protobuf/any_pb.rb +6 -8
  28. data/lib/google/protobuf/api_pb.rb +6 -26
  29. data/lib/google/protobuf/descriptor_pb.rb +23 -226
  30. data/lib/google/protobuf/duration_pb.rb +6 -8
  31. data/lib/google/protobuf/empty_pb.rb +6 -6
  32. data/lib/google/protobuf/ffi/descriptor.rb +165 -0
  33. data/lib/google/protobuf/ffi/descriptor_pool.rb +77 -0
  34. data/lib/google/protobuf/ffi/enum_descriptor.rb +173 -0
  35. data/lib/google/protobuf/ffi/ffi.rb +215 -0
  36. data/lib/google/protobuf/ffi/field_descriptor.rb +330 -0
  37. data/lib/google/protobuf/ffi/file_descriptor.rb +49 -0
  38. data/lib/google/protobuf/ffi/internal/arena.rb +60 -0
  39. data/lib/google/protobuf/ffi/internal/convert.rb +296 -0
  40. data/lib/google/protobuf/ffi/internal/pointer_helper.rb +35 -0
  41. data/lib/google/protobuf/ffi/internal/type_safety.rb +25 -0
  42. data/lib/google/protobuf/ffi/map.rb +433 -0
  43. data/lib/google/protobuf/ffi/message.rb +785 -0
  44. data/lib/google/protobuf/ffi/method_descriptor.rb +114 -0
  45. data/lib/google/protobuf/ffi/object_cache.rb +30 -0
  46. data/lib/google/protobuf/ffi/oneof_descriptor.rb +97 -0
  47. data/lib/google/protobuf/ffi/repeated_field.rb +411 -0
  48. data/lib/google/protobuf/ffi/service_descriptor.rb +107 -0
  49. data/lib/google/protobuf/field_mask_pb.rb +6 -7
  50. data/lib/google/protobuf/internal/object_cache.rb +99 -0
  51. data/lib/google/protobuf/message_exts.rb +8 -26
  52. data/lib/google/protobuf/plugin_pb.rb +25 -0
  53. data/lib/google/protobuf/repeated_field.rb +7 -31
  54. data/lib/google/protobuf/source_context_pb.rb +6 -7
  55. data/lib/google/protobuf/struct_pb.rb +6 -23
  56. data/lib/google/protobuf/timestamp_pb.rb +6 -8
  57. data/lib/google/protobuf/type_pb.rb +6 -71
  58. data/lib/google/protobuf/well_known_types.rb +5 -34
  59. data/lib/google/protobuf/wrappers_pb.rb +6 -31
  60. data/lib/google/protobuf.rb +27 -45
  61. data/lib/google/protobuf_ffi.rb +51 -0
  62. data/lib/google/protobuf_native.rb +19 -0
  63. data/lib/google/tasks/ffi.rake +100 -0
  64. metadata +92 -16
  65. data/ext/google/protobuf_c/third_party/utf8_range/naive.c +0 -92
  66. data/ext/google/protobuf_c/third_party/utf8_range/range2-neon.c +0 -157
  67. data/ext/google/protobuf_c/third_party/utf8_range/range2-sse.c +0 -170
  68. data/lib/google/protobuf/descriptor_dsl.rb +0 -465
  69. data/tests/basic.rb +0 -739
  70. data/tests/generated_code_test.rb +0 -23
  71. data/tests/stress.rb +0 -38
@@ -0,0 +1,785 @@
1
+ # Protocol Buffers - Google's data interchange format
2
+ # Copyright 2023 Google Inc. All rights reserved.
3
+ #
4
+ # Use of this source code is governed by a BSD-style
5
+ # license that can be found in the LICENSE file or at
6
+ # https://developers.google.com/open-source/licenses/bsd
7
+
8
+
9
+ # Decorates Descriptor with the `build_message_class` method that defines
10
+ # Message classes.
11
+ module Google
12
+ module Protobuf
13
+ class FFI
14
+ # Message
15
+ attach_function :clear_message_field, :upb_Message_ClearFieldByDef, [:Message, FieldDescriptor], :void
16
+ attach_function :get_message_value, :upb_Message_GetFieldByDef, [:Message, FieldDescriptor], MessageValue.by_value
17
+ attach_function :get_message_has, :upb_Message_HasFieldByDef, [:Message, FieldDescriptor], :bool
18
+ attach_function :set_message_field, :upb_Message_SetFieldByDef, [:Message, FieldDescriptor, MessageValue.by_value, Internal::Arena], :bool
19
+ attach_function :encode_message, :upb_Encode, [:Message, MiniTable.by_ref, :size_t, Internal::Arena, :pointer, :pointer], EncodeStatus
20
+ attach_function :json_decode_message_detecting_nonconformance, :upb_JsonDecodeDetectingNonconformance, [:binary_string, :size_t, :Message, Descriptor, :DefPool, :int, Internal::Arena, Status.by_ref], :int
21
+ attach_function :json_encode_message, :upb_JsonEncode, [:Message, Descriptor, :DefPool, :int, :binary_string, :size_t, Status.by_ref], :size_t
22
+ attach_function :decode_message, :upb_Decode, [:binary_string, :size_t, :Message, MiniTable.by_ref, :ExtensionRegistry, :int, Internal::Arena], DecodeStatus
23
+ attach_function :get_mutable_message, :upb_Message_Mutable, [:Message, FieldDescriptor, Internal::Arena], MutableMessageValue.by_value
24
+ attach_function :get_message_which_oneof, :upb_Message_WhichOneofByDef, [:Message, OneofDescriptor], FieldDescriptor
25
+ attach_function :message_discard_unknown, :upb_Message_DiscardUnknown, [:Message, Descriptor, :int], :bool
26
+ attach_function :message_next, :upb_Message_Next, [:Message, Descriptor, :DefPool, :FieldDefPointer, MessageValue.by_ref, :pointer], :bool
27
+ attach_function :message_freeze, :upb_Message_Freeze, [:Message, MiniTable.by_ref], :void
28
+ attach_function :message_frozen?, :upb_Message_IsFrozen, [:Message], :bool
29
+
30
+ # MessageValue
31
+ attach_function :message_value_equal, :shared_Msgval_IsEqual, [MessageValue.by_value, MessageValue.by_value, CType, Descriptor], :bool
32
+ attach_function :message_value_hash, :shared_Msgval_GetHash, [MessageValue.by_value, CType, Descriptor, :uint64_t], :uint64_t
33
+ end
34
+
35
+ class Descriptor
36
+ def build_message_class
37
+ descriptor = self
38
+ Class.new(Google::Protobuf::const_get(:AbstractMessage)) do
39
+ @descriptor = descriptor
40
+ class << self
41
+ attr_accessor :descriptor
42
+ private
43
+ attr_accessor :oneof_field_names
44
+ include ::Google::Protobuf::Internal::Convert
45
+ end
46
+
47
+ alias original_method_missing method_missing
48
+ def method_missing(method_name, *args)
49
+ method_missing_internal method_name, *args, mode: :method_missing
50
+ end
51
+
52
+ def respond_to_missing?(method_name, include_private = false)
53
+ method_missing_internal(method_name, mode: :respond_to_missing?) || super
54
+ end
55
+
56
+ ##
57
+ # Public constructor. Automatically allocates from a new Arena.
58
+ def self.new(initial_value = nil)
59
+ instance = allocate
60
+ instance.send(:initialize, initial_value)
61
+ instance
62
+ end
63
+
64
+ ##
65
+ # Is this object frozen?
66
+ # Returns true if either this Ruby wrapper or the underlying
67
+ # representation are frozen. Freezes the wrapper if the underlying
68
+ # representation is already frozen but this wrapper isn't.
69
+ def frozen?
70
+ unless Google::Protobuf::FFI.message_frozen? @msg
71
+ raise RuntimeError.new "Ruby frozen Message with mutable representation" if super
72
+ return false
73
+ end
74
+ method(:freeze).super_method.call unless super
75
+ true
76
+ end
77
+
78
+ ##
79
+ # Freezes the map object. We have to intercept this so we can freeze the
80
+ # underlying representation, not just the Ruby wrapper. Returns self.
81
+ def freeze
82
+ if method(:frozen?).super_method.call
83
+ unless Google::Protobuf::FFI.message_frozen? @msg
84
+ raise RuntimeError.new "Underlying representation of message still mutable despite frozen wrapper"
85
+ end
86
+ return self
87
+ end
88
+ unless Google::Protobuf::FFI.message_frozen? @msg
89
+ Google::Protobuf::FFI.message_freeze(@msg, Google::Protobuf::FFI.get_mini_table(self.class.descriptor))
90
+ end
91
+ super
92
+ end
93
+
94
+ def dup
95
+ duplicate = self.class.private_constructor(@arena)
96
+ mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor)
97
+ size = mini_table[:size]
98
+ duplicate.instance_variable_get(:@msg).write_string_length(@msg.read_string_length(size), size)
99
+ duplicate
100
+ end
101
+ alias clone dup
102
+
103
+ def eql?(other)
104
+ return false unless self.class === other
105
+ encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown
106
+ temporary_arena = Google::Protobuf::FFI.create_arena
107
+ mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor)
108
+ size_one = ::FFI::MemoryPointer.new(:size_t, 1)
109
+ encoding_one = ::FFI::MemoryPointer.new(:pointer, 1)
110
+ encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table, encoding_options, temporary_arena, encoding_one.to_ptr, size_one)
111
+ raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding LHS of `eql?()`" unless encoding_status == :Ok
112
+
113
+ size_two = ::FFI::MemoryPointer.new(:size_t, 1)
114
+ encoding_two = ::FFI::MemoryPointer.new(:pointer, 1)
115
+ encoding_status = Google::Protobuf::FFI.encode_message(other.instance_variable_get(:@msg), mini_table, encoding_options, temporary_arena, encoding_two.to_ptr, size_two)
116
+ raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding RHS of `eql?()`" unless encoding_status == :Ok
117
+
118
+ if encoding_one.null? or encoding_two.null?
119
+ raise ParseError.new "Error comparing messages"
120
+ end
121
+ size_one.read(:size_t) == size_two.read(:size_t) and Google::Protobuf::FFI.memcmp(encoding_one.read(:pointer), encoding_two.read(:pointer), size_one.read(:size_t)).zero?
122
+ end
123
+ alias == eql?
124
+
125
+ def hash
126
+ encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown
127
+ temporary_arena = Google::Protobuf::FFI.create_arena
128
+ mini_table_ptr = Google::Protobuf::FFI.get_mini_table(self.class.descriptor)
129
+ size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
130
+ encoding = ::FFI::MemoryPointer.new(:pointer, 1)
131
+ encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table_ptr, encoding_options, temporary_arena, encoding.to_ptr, size_ptr)
132
+ if encoding_status != :Ok or encoding.null?
133
+ raise ParseError.new "Error calculating hash"
134
+ end
135
+ encoding.read(:pointer).read_string(size_ptr.read(:size_t)).hash
136
+ end
137
+
138
+ def to_h
139
+ to_h_internal @msg, self.class.descriptor
140
+ end
141
+
142
+ ##
143
+ # call-seq:
144
+ # Message.inspect => string
145
+ #
146
+ # Returns a human-readable string representing this message. It will be
147
+ # formatted as "<MessageType: field1: value1, field2: value2, ...>". Each
148
+ # field's value is represented according to its own #inspect method.
149
+ def inspect
150
+ self.class.inspect_internal @msg
151
+ end
152
+
153
+ def to_s
154
+ self.inspect
155
+ end
156
+
157
+ ##
158
+ # call-seq:
159
+ # Message.[](index) => value
160
+ # Accesses a field's value by field name. The provided field name
161
+ # should be a string.
162
+ def [](name)
163
+ raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String
164
+ index_internal name
165
+ end
166
+
167
+ ##
168
+ # call-seq:
169
+ # Message.[]=(index, value)
170
+ # Sets a field's value by field name. The provided field name should
171
+ # be a string.
172
+ # @param name [String] Name of the field to be set
173
+ # @param value [Object] Value to set the field to
174
+ def []=(name, value)
175
+ raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String
176
+ index_assign_internal(value, name: name)
177
+ end
178
+
179
+ ##
180
+ # call-seq:
181
+ # MessageClass.decode(data, options) => message
182
+ #
183
+ # Decodes the given data (as a string containing bytes in protocol buffers wire
184
+ # format) under the interpretation given by this message class's definition
185
+ # and returns a message object with the corresponding field values.
186
+ # @param data [String] Binary string in Protobuf wire format to decode
187
+ # @param options [Hash] options for the decoder
188
+ # @option options [Integer] :recursion_limit Set to maximum decoding depth for message (default is 64)
189
+ def self.decode(data, options = {})
190
+ raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash
191
+ raise ArgumentError.new "Expected string for binary protobuf data." unless data.is_a? String
192
+ decoding_options = 0
193
+ depth = options[:recursion_limit]
194
+
195
+ if depth.is_a? Numeric
196
+ decoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i)
197
+ end
198
+
199
+ message = new
200
+ mini_table_ptr = Google::Protobuf::FFI.get_mini_table(message.class.descriptor)
201
+ status = Google::Protobuf::FFI.decode_message(
202
+ data,
203
+ data.bytesize,
204
+ message.instance_variable_get(:@msg),
205
+ mini_table_ptr,
206
+ Google::Protobuf::FFI.get_extension_registry(message.class.descriptor.send(:pool).descriptor_pool),
207
+ decoding_options,
208
+ message.instance_variable_get(:@arena)
209
+ )
210
+ raise ParseError.new "Error occurred during parsing" unless status == :Ok
211
+ message
212
+ end
213
+
214
+ ##
215
+ # call-seq:
216
+ # MessageClass.encode(msg, options) => bytes
217
+ #
218
+ # Encodes the given message object to its serialized form in protocol buffers
219
+ # wire format.
220
+ # @param options [Hash] options for the encoder
221
+ # @option options [Integer] :recursion_limit Set to maximum encoding depth for message (default is 64)
222
+ def self.encode(message, options = {})
223
+ raise ArgumentError.new "Message of wrong type." unless message.is_a? self
224
+ raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash
225
+
226
+ encoding_options = 0
227
+ depth = options[:recursion_limit]
228
+
229
+ if depth.is_a? Numeric
230
+ encoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i)
231
+ end
232
+
233
+ encode_internal(message.instance_variable_get(:@msg), encoding_options) do |encoding, size, _|
234
+ if encoding.nil? or encoding.null?
235
+ raise RuntimeError.new "Exceeded maximum depth (possibly cycle)"
236
+ else
237
+ encoding.read_string_length(size).force_encoding("ASCII-8BIT").freeze
238
+ end
239
+ end
240
+ end
241
+
242
+ ##
243
+ # all-seq:
244
+ # MessageClass.decode_json(data, options = {}) => message
245
+ #
246
+ # Decodes the given data (as a string containing bytes in protocol buffers wire
247
+ # format) under the interpretation given by this message class's definition
248
+ # and returns a message object with the corresponding field values.
249
+ #
250
+ # @param options [Hash] options for the decoder
251
+ # @option options [Boolean] :ignore_unknown_fields Set true to ignore unknown fields (default is to raise an error)
252
+ # @return [Message]
253
+ def self.decode_json(data, options = {})
254
+ decoding_options = 0
255
+ unless options.is_a? Hash
256
+ if options.respond_to? :to_h
257
+ options options.to_h
258
+ else
259
+ #TODO can this error message be improve to include what was received?
260
+ raise ArgumentError.new "Expected hash arguments"
261
+ end
262
+ end
263
+ raise ArgumentError.new "Expected string for JSON data." unless data.is_a? String
264
+ raise RuntimeError.new "Cannot parse a wrapper directly" if descriptor.send(:wrapper?)
265
+
266
+ if options[:ignore_unknown_fields]
267
+ decoding_options |= Google::Protobuf::FFI::Upb_JsonDecode_IgnoreUnknown
268
+ end
269
+
270
+ message = new
271
+ pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool
272
+ status = Google::Protobuf::FFI::Status.new
273
+ result = Google::Protobuf::FFI.json_decode_message_detecting_nonconformance(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status)
274
+ case result
275
+ when Google::Protobuf::FFI::Upb_JsonDecodeResult_OkWithEmptyStringNumerics
276
+ warn Google::Protobuf::FFI.error_message(status)
277
+ when Google::Protobuf::FFI::Upb_JsonDecodeResult_Error
278
+ raise ParseError.new "Error occurred during parsing: #{Google::Protobuf::FFI.error_message(status)}"
279
+ end
280
+ message
281
+ end
282
+
283
+ def self.encode_json(message, options = {})
284
+ encoding_options = 0
285
+ unless options.is_a? Hash
286
+ if options.respond_to? :to_h
287
+ options = options.to_h
288
+ else
289
+ #TODO can this error message be improve to include what was received?
290
+ raise ArgumentError.new "Expected hash arguments"
291
+ end
292
+ end
293
+
294
+ if options[:preserve_proto_fieldnames]
295
+ encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_UseProtoNames
296
+ end
297
+ if options[:emit_defaults]
298
+ encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_EmitDefaults
299
+ end
300
+ if options[:format_enums_as_integers]
301
+ encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_FormatEnumsAsIntegers
302
+ end
303
+
304
+ buffer_size = 1024
305
+ buffer = ::FFI::MemoryPointer.new(:char, buffer_size)
306
+ status = Google::Protobuf::FFI::Status.new
307
+ msg = message.instance_variable_get(:@msg)
308
+ pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool
309
+ size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status)
310
+ unless status[:ok]
311
+ raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}"
312
+ end
313
+
314
+ if size >= buffer_size
315
+ buffer_size = size + 1
316
+ buffer = ::FFI::MemoryPointer.new(:char, buffer_size)
317
+ status.clear
318
+ size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status)
319
+ unless status[:ok]
320
+ raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}"
321
+ end
322
+ if size >= buffer_size
323
+ raise ParseError.new "Inconsistent JSON encoding sizes - was #{buffer_size - 1}, now #{size}"
324
+ end
325
+ end
326
+
327
+ buffer.read_string_length(size).force_encoding("UTF-8").freeze
328
+ end
329
+
330
+ private
331
+ # Implementation details below are subject to breaking changes without
332
+ # warning and are intended for use only within the gem.
333
+
334
+ include Google::Protobuf::Internal::Convert
335
+
336
+ ##
337
+ # Checks ObjectCache for a sentinel empty frozen Map of the key and
338
+ # value types matching the field descriptor's MessageDef and returns
339
+ # the cache entry. If an entry is not found, one is created and added
340
+ # to the cache keyed by the MessageDef pointer first.
341
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
342
+ def empty_frozen_map(field_descriptor)
343
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor
344
+ frozen_map = OBJECT_CACHE.get sub_message_def
345
+ if frozen_map.nil?
346
+ frozen_map = Google::Protobuf::Map.send(:construct_for_field, field_descriptor)
347
+ OBJECT_CACHE.try_add(sub_message_def, frozen_map.freeze)
348
+ end
349
+ raise "Empty Frozen Map is not frozen" unless frozen_map.frozen?
350
+ frozen_map
351
+ end
352
+
353
+ ##
354
+ # Returns a frozen Map instance for the given field. If the message
355
+ # already has a value for that field, it is used. If not, a sentinel
356
+ # (per FieldDescriptor) empty frozen Map is returned instead.
357
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
358
+ def frozen_map_from_field_descriptor(field_descriptor)
359
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
360
+ return empty_frozen_map field_descriptor if message_value[:map_val].null?
361
+ get_map_field(message_value[:map_val], field_descriptor).freeze
362
+ end
363
+
364
+ ##
365
+ # Returns a Map instance for the given field. If the message is frozen
366
+ # the return value is also frozen. If not, a mutable instance is
367
+ # returned instead.
368
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
369
+ def map_from_field_descriptor(field_descriptor)
370
+ return frozen_map_from_field_descriptor field_descriptor if frozen?
371
+ mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
372
+ get_map_field(mutable_message_value[:map], field_descriptor)
373
+ end
374
+
375
+ ##
376
+ # Checks ObjectCache for a sentinel empty frozen RepeatedField of the
377
+ # value type matching the field descriptor's MessageDef and returns
378
+ # the cache entry. If an entry is not found, one is created and added
379
+ # to the cache keyed by the MessageDef pointer first.
380
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
381
+ def empty_frozen_repeated_field(field_descriptor)
382
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor
383
+ frozen_repeated_field = OBJECT_CACHE.get sub_message_def
384
+ if frozen_repeated_field.nil?
385
+ frozen_repeated_field = Google::Protobuf::RepeatedField.send(:construct_for_field, field_descriptor)
386
+ OBJECT_CACHE.try_add(sub_message_def, frozen_repeated_field.freeze)
387
+ end
388
+ raise "Empty frozen RepeatedField is not frozen" unless frozen_repeated_field.frozen?
389
+ frozen_repeated_field
390
+ end
391
+
392
+ ##
393
+ # Returns a frozen RepeatedField instance for the given field. If the
394
+ # message already has a value for that field, it is used. If not, a
395
+ # sentinel (per FieldDescriptor) empty frozen RepeatedField is
396
+ # returned instead.
397
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
398
+ def frozen_repeated_field_from_field_descriptor(field_descriptor)
399
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
400
+ return empty_frozen_repeated_field field_descriptor if message_value[:array_val].null?
401
+ get_repeated_field(message_value[:array_val], field_descriptor).freeze
402
+ end
403
+
404
+ ##
405
+ # Returns a RepeatedField instance for the given field. If the message
406
+ # is frozen the return value is also frozen. If not, a mutable
407
+ # instance is returned instead.
408
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
409
+ def repeated_field_from_field_descriptor(field_descriptor)
410
+ return frozen_repeated_field_from_field_descriptor field_descriptor if frozen?
411
+ mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
412
+ get_repeated_field(mutable_message_value[:array], field_descriptor)
413
+ end
414
+
415
+ ##
416
+ # Returns a Message instance for the given field. If the message
417
+ # is frozen nil is always returned. Otherwise, a mutable instance is
418
+ # returned instead.
419
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
420
+ def message_from_field_descriptor(field_descriptor)
421
+ return nil if frozen?
422
+ return nil unless Google::Protobuf::FFI.get_message_has @msg, field_descriptor
423
+ mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
424
+ sub_message = mutable_message[:msg]
425
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor
426
+ Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
427
+ end
428
+
429
+ ##
430
+ # Returns a scalar value for the given field. If the message
431
+ # is frozen the return value is also frozen.
432
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
433
+ def scalar_from_field_descriptor(field_descriptor)
434
+ c_type = field_descriptor.send(:c_type)
435
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
436
+ msg_or_enum_def = c_type == :enum ? Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor) : nil
437
+ return_value = convert_upb_to_ruby message_value, c_type, msg_or_enum_def
438
+ frozen? ? return_value.freeze : return_value
439
+ end
440
+
441
+ ##
442
+ # Dynamically define accessors methods for every field of @descriptor.
443
+ # Methods with names that conflict with existing methods are skipped.
444
+ def self.setup_accessors!
445
+ @descriptor.each do |field_descriptor|
446
+ field_name = field_descriptor.name
447
+ unless instance_methods(true).include?(field_name.to_sym)
448
+ # Dispatching to either index_internal or get_field is logically
449
+ # correct, but slightly slower due to having to perform extra
450
+ # lookups on each invocation rather than doing it once here.
451
+ if field_descriptor.map?
452
+ define_method(field_name) do
453
+ map_from_field_descriptor field_descriptor
454
+ end
455
+ elsif field_descriptor.repeated?
456
+ define_method(field_name) do
457
+ repeated_field_from_field_descriptor field_descriptor
458
+ end
459
+ elsif field_descriptor.sub_message?
460
+ define_method(field_name) do
461
+ message_from_field_descriptor field_descriptor
462
+ end
463
+ else
464
+ define_method(field_name) do
465
+ scalar_from_field_descriptor field_descriptor
466
+ end
467
+ end
468
+ define_method("#{field_name}=") do |value|
469
+ index_assign_internal(value, field_descriptor: field_descriptor)
470
+ end
471
+ define_method("clear_#{field_name}") do
472
+ clear_internal(field_descriptor)
473
+ end
474
+ if field_descriptor.type == :enum
475
+ if field_descriptor.repeated?
476
+ define_method("#{field_name}_const") do
477
+ return_value = []
478
+ repeated_field_from_field_descriptor(field_descriptor).send(:each_msg_val) do |msg_val|
479
+ return_value << msg_val[:int32_val]
480
+ end
481
+ return_value
482
+ end
483
+ else
484
+ define_method("#{field_name}_const") do
485
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
486
+ message_value[:int32_val]
487
+ end
488
+ end
489
+ end
490
+ if !field_descriptor.repeated? and field_descriptor.wrapper?
491
+ define_method("#{field_name}_as_value") do
492
+ get_field(field_descriptor, unwrap: true)
493
+ end
494
+ define_method("#{field_name}_as_value=") do |value|
495
+ if value.nil?
496
+ clear_internal(field_descriptor)
497
+ else
498
+ index_assign_internal(value, field_descriptor: field_descriptor, wrap: true)
499
+ end
500
+ end
501
+ end
502
+ if field_descriptor.has_presence?
503
+ define_method("has_#{field_name}?") do
504
+ Google::Protobuf::FFI.get_message_has(@msg, field_descriptor)
505
+ end
506
+ end
507
+ end
508
+ end
509
+ end
510
+
511
+ ##
512
+ # Dynamically define accessors methods for every OneOf field of
513
+ # @descriptor.
514
+ def self.setup_oneof_accessors!
515
+ @oneof_field_names = []
516
+ @descriptor.each_oneof do |oneof_descriptor|
517
+ self.add_oneof_accessors_for! oneof_descriptor
518
+ end
519
+ end
520
+
521
+ ##
522
+ # Dynamically define accessors methods for the given OneOf field.
523
+ # Methods with names that conflict with existing methods are skipped.
524
+ # @param oneof_descriptor [OneofDescriptor] Field to create accessors
525
+ # for.
526
+ def self.add_oneof_accessors_for!(oneof_descriptor)
527
+ field_name = oneof_descriptor.name.to_sym
528
+ @oneof_field_names << field_name
529
+ unless instance_methods(true).include?(field_name)
530
+ define_method(field_name) do
531
+ field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor)
532
+ if field_descriptor.nil?
533
+ return
534
+ else
535
+ return field_descriptor.name.to_sym
536
+ end
537
+ end
538
+ define_method("clear_#{field_name}") do
539
+ field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor)
540
+ unless field_descriptor.nil?
541
+ clear_internal(field_descriptor)
542
+ end
543
+ end
544
+ define_method("has_#{field_name}?") do
545
+ !Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor).nil?
546
+ end
547
+ end
548
+ end
549
+
550
+ setup_accessors!
551
+ setup_oneof_accessors!
552
+
553
+ def self.private_constructor(arena, msg: nil, initial_value: nil)
554
+ instance = allocate
555
+ instance.send(:initialize, initial_value, arena, msg)
556
+ instance
557
+ end
558
+
559
+ def self.inspect_field(field_descriptor, c_type, message_value)
560
+ if field_descriptor.sub_message?
561
+ sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor)
562
+ sub_msg_descriptor.msgclass.send(:inspect_internal, message_value[:msg_val])
563
+ else
564
+ convert_upb_to_ruby(message_value, c_type, field_descriptor.subtype).inspect
565
+ end
566
+ end
567
+
568
+ # @param msg [::FFI::Pointer] Pointer to the Message
569
+ def self.inspect_internal(msg)
570
+ field_output = []
571
+ descriptor.each do |field_descriptor|
572
+ next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(msg, field_descriptor)
573
+ if field_descriptor.map?
574
+ # TODO Adapted - from map#each_msg_val and map#inspect- can this be refactored to reduce echo without introducing a arena allocation?
575
+ message_descriptor = field_descriptor.subtype
576
+ key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1)
577
+ key_field_type = Google::Protobuf::FFI.get_type(key_field_def)
578
+
579
+ value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2)
580
+ value_field_type = Google::Protobuf::FFI.get_type(value_field_def)
581
+
582
+ message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor)
583
+ iter = ::FFI::MemoryPointer.new(:size_t, 1)
584
+ iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin)
585
+ key_value_pairs = []
586
+ while Google::Protobuf::FFI.map_next(message_value[:map_val], iter) do
587
+ iter_size_t = iter.read(:size_t)
588
+ key_message_value = Google::Protobuf::FFI.map_key(message_value[:map_val], iter_size_t)
589
+ value_message_value = Google::Protobuf::FFI.map_value(message_value[:map_val], iter_size_t)
590
+ key_string = convert_upb_to_ruby(key_message_value, key_field_type).inspect
591
+ value_string = inspect_field(value_field_def, value_field_type, value_message_value)
592
+ key_value_pairs << "#{key_string}=>#{value_string}"
593
+ end
594
+ field_output << "#{field_descriptor.name}: {#{key_value_pairs.join(", ")}}"
595
+ elsif field_descriptor.repeated?
596
+ # TODO Adapted - from repeated_field#each - can this be refactored to reduce echo?
597
+ repeated_field_output = []
598
+ message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor)
599
+ array = message_value[:array_val]
600
+ n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array)
601
+ 0.upto(n - 1) do |i|
602
+ element = Google::Protobuf::FFI.get_msgval_at(array, i)
603
+ repeated_field_output << inspect_field(field_descriptor, field_descriptor.send(:c_type), element)
604
+ end
605
+ field_output << "#{field_descriptor.name}: [#{repeated_field_output.join(", ")}]"
606
+ else
607
+ message_value = Google::Protobuf::FFI.get_message_value msg, field_descriptor
608
+ rendered_value = inspect_field(field_descriptor, field_descriptor.send(:c_type), message_value)
609
+ field_output << "#{field_descriptor.name}: #{rendered_value}"
610
+ end
611
+ end
612
+ "<#{name}: #{field_output.join(', ')}>"
613
+ end
614
+
615
+ def self.deep_copy(msg, arena = nil)
616
+ arena ||= Google::Protobuf::FFI.create_arena
617
+ encode_internal(msg) do |encoding, size, mini_table_ptr|
618
+ message = private_constructor(arena)
619
+ if encoding.nil? or encoding.null? or Google::Protobuf::FFI.decode_message(encoding, size, message.instance_variable_get(:@msg), mini_table_ptr, nil, 0, arena) != :Ok
620
+ raise ParseError.new "Error occurred copying proto"
621
+ end
622
+ message
623
+ end
624
+ end
625
+
626
+ def self.encode_internal(msg, encoding_options = 0)
627
+ temporary_arena = Google::Protobuf::FFI.create_arena
628
+
629
+ mini_table_ptr = Google::Protobuf::FFI.get_mini_table(descriptor)
630
+ size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
631
+ pointer_ptr = ::FFI::MemoryPointer.new(:pointer, 1)
632
+ encoding_status = Google::Protobuf::FFI.encode_message(msg, mini_table_ptr, encoding_options, temporary_arena, pointer_ptr.to_ptr, size_ptr)
633
+ raise "Encoding failed due to #{encoding_status}" unless encoding_status == :Ok
634
+ yield pointer_ptr.read(:pointer), size_ptr.read(:size_t), mini_table_ptr
635
+ end
636
+
637
+ def method_missing_internal(method_name, *args, mode: nil)
638
+ raise ArgumentError.new "method_missing_internal called with invalid mode #{mode.inspect}" unless [:respond_to_missing?, :method_missing].include? mode
639
+
640
+ #TODO not being allowed is not the same thing as not responding, but this is needed to pass tests
641
+ if method_name.to_s.end_with? '='
642
+ if self.class.send(:oneof_field_names).include? method_name.to_s[0..-2].to_sym
643
+ return false if mode == :respond_to_missing?
644
+ raise RuntimeError.new "Oneof accessors are read-only."
645
+ end
646
+ end
647
+
648
+ original_method_missing(method_name, *args) if mode == :method_missing
649
+ end
650
+
651
+ def clear_internal(field_def)
652
+ raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
653
+ Google::Protobuf::FFI.clear_message_field(@msg, field_def)
654
+ end
655
+
656
+ # Accessor for field by name. Does not delegate to methods setup by
657
+ # self.setup_accessors! in order to avoid conflicts with bad field
658
+ # names e.g. `dup` or `class` which are perfectly valid for proto
659
+ # fields.
660
+ def index_internal(name)
661
+ field_descriptor = self.class.descriptor.lookup(name)
662
+ get_field field_descriptor unless field_descriptor.nil?
663
+ end
664
+
665
+ #TODO - well known types keeps us on our toes by overloading methods.
666
+ # How much of the public API needs to be defended?
667
+ def index_assign_internal(value, name: nil, field_descriptor: nil, wrap: false)
668
+ raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
669
+ if field_descriptor.nil?
670
+ field_descriptor = self.class.descriptor.lookup(name)
671
+ if field_descriptor.nil?
672
+ raise ArgumentError.new "Unknown field: #{name}"
673
+ end
674
+ end
675
+ unless field_descriptor.send :set_value_on_message, value, @msg, @arena, wrap: wrap
676
+ raise RuntimeError.new "allocation failed"
677
+ end
678
+ end
679
+
680
+ ##
681
+ # @param initial_value [Object] initial value of this Message
682
+ # @param arena [Arena] Optional; Arena where this message will be allocated
683
+ # @param msg [::FFI::Pointer] Optional; Message to initialize; creates
684
+ # one if omitted or nil.
685
+ def initialize(initial_value = nil, arena = nil, msg = nil)
686
+ @arena = arena || Google::Protobuf::FFI.create_arena
687
+ @msg = msg || Google::Protobuf::FFI.new_message_from_def(Google::Protobuf::FFI.get_mini_table(self.class.descriptor), @arena)
688
+
689
+ unless initial_value.nil?
690
+ raise ArgumentError.new "Expected hash arguments or message, not #{initial_value.class}" unless initial_value.respond_to? :each
691
+
692
+ field_def_ptr = ::FFI::MemoryPointer.new :pointer
693
+ oneof_def_ptr = ::FFI::MemoryPointer.new :pointer
694
+
695
+ initial_value.each do |key, value|
696
+ raise ArgumentError.new "Expected string or symbols as hash keys when initializing proto from hash." unless [String, Symbol].include? key.class
697
+
698
+ unless Google::Protobuf::FFI.find_msg_def_by_name self.class.descriptor, key.to_s, key.to_s.bytesize, field_def_ptr, oneof_def_ptr
699
+ raise ArgumentError.new "Unknown field name '#{key}' in initialization map entry."
700
+ end
701
+ raise NotImplementedError.new "Haven't added oneofsupport yet" unless oneof_def_ptr.get_pointer(0).null?
702
+ raise NotImplementedError.new "Expected a field def" if field_def_ptr.get_pointer(0).null?
703
+
704
+ field_descriptor = FieldDescriptor.from_native field_def_ptr.get_pointer(0)
705
+
706
+ next if value.nil?
707
+ if field_descriptor.map?
708
+ index_assign_internal(Google::Protobuf::Map.send(:construct_for_field, field_descriptor, arena: @arena, value: value), name: key.to_s)
709
+ elsif field_descriptor.repeated?
710
+ index_assign_internal(RepeatedField.send(:construct_for_field, field_descriptor, arena: @arena, values: value), name: key.to_s)
711
+ else
712
+ index_assign_internal(value, name: key.to_s)
713
+ end
714
+ end
715
+ end
716
+
717
+ # Should always be the last expression of the initializer to avoid
718
+ # leaking references to this object before construction is complete.
719
+ Google::Protobuf::OBJECT_CACHE.try_add @msg.address, self
720
+ end
721
+
722
+ ##
723
+ # Gets a field of this message identified by FieldDescriptor.
724
+ #
725
+ # @param field [FieldDescriptor] Field to retrieve.
726
+ # @param unwrap [Boolean](false) If true, unwraps wrappers.
727
+ def get_field(field, unwrap: false)
728
+ if field.map?
729
+ map_from_field_descriptor field
730
+ elsif field.repeated?
731
+ repeated_field_from_field_descriptor field
732
+ elsif field.sub_message?
733
+ return nil unless Google::Protobuf::FFI.get_message_has @msg, field
734
+ if unwrap
735
+ if field.has?(self)
736
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field
737
+ wrapper_message_value = Google::Protobuf::FFI.get_message_value @msg, field
738
+ fields = Google::Protobuf::FFI.field_count(sub_message_def)
739
+ raise "Sub message has #{fields} fields! Expected exactly 1." unless fields == 1
740
+ value_field_def = Google::Protobuf::FFI.get_field_by_number sub_message_def, 1
741
+ message_value = Google::Protobuf::FFI.get_message_value wrapper_message_value[:msg_val], value_field_def
742
+ convert_upb_to_ruby message_value, Google::Protobuf::FFI.get_c_type(value_field_def)
743
+ else
744
+ nil
745
+ end
746
+ else
747
+ message_from_field_descriptor field
748
+ end
749
+ else
750
+ scalar_from_field_descriptor field
751
+ end
752
+ end
753
+
754
+ ##
755
+ # Gets a RepeatedField from the ObjectCache or creates a new one.
756
+ # @param array [::FFI::Pointer] Pointer to the upb_Array
757
+ # @param field [Google::Protobuf::FieldDescriptor] Type of the repeated field
758
+ def get_repeated_field(array, field)
759
+ return nil if array.nil? or array.null?
760
+ repeated_field = OBJECT_CACHE.get(array.address)
761
+ if repeated_field.nil?
762
+ repeated_field = RepeatedField.send(:construct_for_field, field, arena: @arena, array: array)
763
+ repeated_field.freeze if frozen?
764
+ end
765
+ repeated_field
766
+ end
767
+
768
+ ##
769
+ # Gets a Map from the ObjectCache or creates a new one.
770
+ # @param map [::FFI::Pointer] Pointer to the upb_Map
771
+ # @param field [Google::Protobuf::FieldDescriptor] Type of the map field
772
+ def get_map_field(map, field)
773
+ return nil if map.nil? or map.null?
774
+ map_field = OBJECT_CACHE.get(map.address)
775
+ if map_field.nil?
776
+ map_field = Google::Protobuf::Map.send(:construct_for_field, field, arena: @arena, map: map)
777
+ map_field.freeze if frozen?
778
+ end
779
+ map_field
780
+ end
781
+ end
782
+ end
783
+ end
784
+ end
785
+ end