google-protobuf 3.21.2 → 4.29.1

Sign up to get free protection for your applications and to get access to all the features.
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