google-protobuf 3.21.0 → 4.28.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of google-protobuf might be problematic. Click here for more details.

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 +537 -75
  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 +250 -234
  12. data/ext/google/protobuf_c/message.h +11 -33
  13. data/ext/google/protobuf_c/protobuf.c +63 -185
  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 +14513 -9003
  18. data/ext/google/protobuf_c/ruby-upb.h +13990 -4496
  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 +210 -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 +781 -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,781 @@
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, :upb_JsonDecode, [:binary_string, :size_t, :Message, Descriptor, :DefPool, :int, Internal::Arena, Status.by_ref], :bool
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
+ unless Google::Protobuf::FFI.json_decode_message(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status)
274
+ raise ParseError.new "Error occurred during parsing: #{Google::Protobuf::FFI.error_message(status)}"
275
+ end
276
+ message
277
+ end
278
+
279
+ def self.encode_json(message, options = {})
280
+ encoding_options = 0
281
+ unless options.is_a? Hash
282
+ if options.respond_to? :to_h
283
+ options = options.to_h
284
+ else
285
+ #TODO can this error message be improve to include what was received?
286
+ raise ArgumentError.new "Expected hash arguments"
287
+ end
288
+ end
289
+
290
+ if options[:preserve_proto_fieldnames]
291
+ encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_UseProtoNames
292
+ end
293
+ if options[:emit_defaults]
294
+ encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_EmitDefaults
295
+ end
296
+ if options[:format_enums_as_integers]
297
+ encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_FormatEnumsAsIntegers
298
+ end
299
+
300
+ buffer_size = 1024
301
+ buffer = ::FFI::MemoryPointer.new(:char, buffer_size)
302
+ status = Google::Protobuf::FFI::Status.new
303
+ msg = message.instance_variable_get(:@msg)
304
+ pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool
305
+ size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status)
306
+ unless status[:ok]
307
+ raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}"
308
+ end
309
+
310
+ if size >= buffer_size
311
+ buffer_size = size + 1
312
+ buffer = ::FFI::MemoryPointer.new(:char, buffer_size)
313
+ status.clear
314
+ size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status)
315
+ unless status[:ok]
316
+ raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}"
317
+ end
318
+ if size >= buffer_size
319
+ raise ParseError.new "Inconsistent JSON encoding sizes - was #{buffer_size - 1}, now #{size}"
320
+ end
321
+ end
322
+
323
+ buffer.read_string_length(size).force_encoding("UTF-8").freeze
324
+ end
325
+
326
+ private
327
+ # Implementation details below are subject to breaking changes without
328
+ # warning and are intended for use only within the gem.
329
+
330
+ include Google::Protobuf::Internal::Convert
331
+
332
+ ##
333
+ # Checks ObjectCache for a sentinel empty frozen Map of the key and
334
+ # value types matching the field descriptor's MessageDef and returns
335
+ # the cache entry. If an entry is not found, one is created and added
336
+ # to the cache keyed by the MessageDef pointer first.
337
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
338
+ def empty_frozen_map(field_descriptor)
339
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor
340
+ frozen_map = OBJECT_CACHE.get sub_message_def
341
+ if frozen_map.nil?
342
+ frozen_map = Google::Protobuf::Map.send(:construct_for_field, field_descriptor)
343
+ OBJECT_CACHE.try_add(sub_message_def, frozen_map.freeze)
344
+ end
345
+ raise "Empty Frozen Map is not frozen" unless frozen_map.frozen?
346
+ frozen_map
347
+ end
348
+
349
+ ##
350
+ # Returns a frozen Map instance for the given field. If the message
351
+ # already has a value for that field, it is used. If not, a sentinel
352
+ # (per FieldDescriptor) empty frozen Map is returned instead.
353
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
354
+ def frozen_map_from_field_descriptor(field_descriptor)
355
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
356
+ return empty_frozen_map field_descriptor if message_value[:map_val].null?
357
+ get_map_field(message_value[:map_val], field_descriptor).freeze
358
+ end
359
+
360
+ ##
361
+ # Returns a Map instance for the given field. If the message is frozen
362
+ # the return value is also frozen. If not, a mutable instance is
363
+ # returned instead.
364
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
365
+ def map_from_field_descriptor(field_descriptor)
366
+ return frozen_map_from_field_descriptor field_descriptor if frozen?
367
+ mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
368
+ get_map_field(mutable_message_value[:map], field_descriptor)
369
+ end
370
+
371
+ ##
372
+ # Checks ObjectCache for a sentinel empty frozen RepeatedField of the
373
+ # value type matching the field descriptor's MessageDef and returns
374
+ # the cache entry. If an entry is not found, one is created and added
375
+ # to the cache keyed by the MessageDef pointer first.
376
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
377
+ def empty_frozen_repeated_field(field_descriptor)
378
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor
379
+ frozen_repeated_field = OBJECT_CACHE.get sub_message_def
380
+ if frozen_repeated_field.nil?
381
+ frozen_repeated_field = Google::Protobuf::RepeatedField.send(:construct_for_field, field_descriptor)
382
+ OBJECT_CACHE.try_add(sub_message_def, frozen_repeated_field.freeze)
383
+ end
384
+ raise "Empty frozen RepeatedField is not frozen" unless frozen_repeated_field.frozen?
385
+ frozen_repeated_field
386
+ end
387
+
388
+ ##
389
+ # Returns a frozen RepeatedField instance for the given field. If the
390
+ # message already has a value for that field, it is used. If not, a
391
+ # sentinel (per FieldDescriptor) empty frozen RepeatedField is
392
+ # returned instead.
393
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
394
+ def frozen_repeated_field_from_field_descriptor(field_descriptor)
395
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
396
+ return empty_frozen_repeated_field field_descriptor if message_value[:array_val].null?
397
+ get_repeated_field(message_value[:array_val], field_descriptor).freeze
398
+ end
399
+
400
+ ##
401
+ # Returns a RepeatedField instance for the given field. If the message
402
+ # is frozen the return value is also frozen. If not, a mutable
403
+ # instance is returned instead.
404
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
405
+ def repeated_field_from_field_descriptor(field_descriptor)
406
+ return frozen_repeated_field_from_field_descriptor field_descriptor if frozen?
407
+ mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
408
+ get_repeated_field(mutable_message_value[:array], field_descriptor)
409
+ end
410
+
411
+ ##
412
+ # Returns a Message instance for the given field. If the message
413
+ # is frozen nil is always returned. Otherwise, a mutable instance is
414
+ # returned instead.
415
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
416
+ def message_from_field_descriptor(field_descriptor)
417
+ return nil if frozen?
418
+ return nil unless Google::Protobuf::FFI.get_message_has @msg, field_descriptor
419
+ mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
420
+ sub_message = mutable_message[:msg]
421
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor
422
+ Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
423
+ end
424
+
425
+ ##
426
+ # Returns a scalar value for the given field. If the message
427
+ # is frozen the return value is also frozen.
428
+ # @param field_descriptor [FieldDescriptor] Field to retrieve.
429
+ def scalar_from_field_descriptor(field_descriptor)
430
+ c_type = field_descriptor.send(:c_type)
431
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
432
+ msg_or_enum_def = c_type == :enum ? Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor) : nil
433
+ return_value = convert_upb_to_ruby message_value, c_type, msg_or_enum_def
434
+ frozen? ? return_value.freeze : return_value
435
+ end
436
+
437
+ ##
438
+ # Dynamically define accessors methods for every field of @descriptor.
439
+ # Methods with names that conflict with existing methods are skipped.
440
+ def self.setup_accessors!
441
+ @descriptor.each do |field_descriptor|
442
+ field_name = field_descriptor.name
443
+ unless instance_methods(true).include?(field_name.to_sym)
444
+ # Dispatching to either index_internal or get_field is logically
445
+ # correct, but slightly slower due to having to perform extra
446
+ # lookups on each invocation rather than doing it once here.
447
+ if field_descriptor.map?
448
+ define_method(field_name) do
449
+ map_from_field_descriptor field_descriptor
450
+ end
451
+ elsif field_descriptor.repeated?
452
+ define_method(field_name) do
453
+ repeated_field_from_field_descriptor field_descriptor
454
+ end
455
+ elsif field_descriptor.sub_message?
456
+ define_method(field_name) do
457
+ message_from_field_descriptor field_descriptor
458
+ end
459
+ else
460
+ define_method(field_name) do
461
+ scalar_from_field_descriptor field_descriptor
462
+ end
463
+ end
464
+ define_method("#{field_name}=") do |value|
465
+ index_assign_internal(value, field_descriptor: field_descriptor)
466
+ end
467
+ define_method("clear_#{field_name}") do
468
+ clear_internal(field_descriptor)
469
+ end
470
+ if field_descriptor.type == :enum
471
+ if field_descriptor.repeated?
472
+ define_method("#{field_name}_const") do
473
+ return_value = []
474
+ repeated_field_from_field_descriptor(field_descriptor).send(:each_msg_val) do |msg_val|
475
+ return_value << msg_val[:int32_val]
476
+ end
477
+ return_value
478
+ end
479
+ else
480
+ define_method("#{field_name}_const") do
481
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
482
+ message_value[:int32_val]
483
+ end
484
+ end
485
+ end
486
+ if !field_descriptor.repeated? and field_descriptor.wrapper?
487
+ define_method("#{field_name}_as_value") do
488
+ get_field(field_descriptor, unwrap: true)
489
+ end
490
+ define_method("#{field_name}_as_value=") do |value|
491
+ if value.nil?
492
+ clear_internal(field_descriptor)
493
+ else
494
+ index_assign_internal(value, field_descriptor: field_descriptor, wrap: true)
495
+ end
496
+ end
497
+ end
498
+ if field_descriptor.has_presence?
499
+ define_method("has_#{field_name}?") do
500
+ Google::Protobuf::FFI.get_message_has(@msg, field_descriptor)
501
+ end
502
+ end
503
+ end
504
+ end
505
+ end
506
+
507
+ ##
508
+ # Dynamically define accessors methods for every OneOf field of
509
+ # @descriptor.
510
+ def self.setup_oneof_accessors!
511
+ @oneof_field_names = []
512
+ @descriptor.each_oneof do |oneof_descriptor|
513
+ self.add_oneof_accessors_for! oneof_descriptor
514
+ end
515
+ end
516
+
517
+ ##
518
+ # Dynamically define accessors methods for the given OneOf field.
519
+ # Methods with names that conflict with existing methods are skipped.
520
+ # @param oneof_descriptor [OneofDescriptor] Field to create accessors
521
+ # for.
522
+ def self.add_oneof_accessors_for!(oneof_descriptor)
523
+ field_name = oneof_descriptor.name.to_sym
524
+ @oneof_field_names << field_name
525
+ unless instance_methods(true).include?(field_name)
526
+ define_method(field_name) do
527
+ field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor)
528
+ if field_descriptor.nil?
529
+ return
530
+ else
531
+ return field_descriptor.name.to_sym
532
+ end
533
+ end
534
+ define_method("clear_#{field_name}") do
535
+ field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor)
536
+ unless field_descriptor.nil?
537
+ clear_internal(field_descriptor)
538
+ end
539
+ end
540
+ define_method("has_#{field_name}?") do
541
+ !Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor).nil?
542
+ end
543
+ end
544
+ end
545
+
546
+ setup_accessors!
547
+ setup_oneof_accessors!
548
+
549
+ def self.private_constructor(arena, msg: nil, initial_value: nil)
550
+ instance = allocate
551
+ instance.send(:initialize, initial_value, arena, msg)
552
+ instance
553
+ end
554
+
555
+ def self.inspect_field(field_descriptor, c_type, message_value)
556
+ if field_descriptor.sub_message?
557
+ sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor)
558
+ sub_msg_descriptor.msgclass.send(:inspect_internal, message_value[:msg_val])
559
+ else
560
+ convert_upb_to_ruby(message_value, c_type, field_descriptor.subtype).inspect
561
+ end
562
+ end
563
+
564
+ # @param msg [::FFI::Pointer] Pointer to the Message
565
+ def self.inspect_internal(msg)
566
+ field_output = []
567
+ descriptor.each do |field_descriptor|
568
+ next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(msg, field_descriptor)
569
+ if field_descriptor.map?
570
+ # TODO Adapted - from map#each_msg_val and map#inspect- can this be refactored to reduce echo without introducing a arena allocation?
571
+ message_descriptor = field_descriptor.subtype
572
+ key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1)
573
+ key_field_type = Google::Protobuf::FFI.get_type(key_field_def)
574
+
575
+ value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2)
576
+ value_field_type = Google::Protobuf::FFI.get_type(value_field_def)
577
+
578
+ message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor)
579
+ iter = ::FFI::MemoryPointer.new(:size_t, 1)
580
+ iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin)
581
+ key_value_pairs = []
582
+ while Google::Protobuf::FFI.map_next(message_value[:map_val], iter) do
583
+ iter_size_t = iter.read(:size_t)
584
+ key_message_value = Google::Protobuf::FFI.map_key(message_value[:map_val], iter_size_t)
585
+ value_message_value = Google::Protobuf::FFI.map_value(message_value[:map_val], iter_size_t)
586
+ key_string = convert_upb_to_ruby(key_message_value, key_field_type).inspect
587
+ value_string = inspect_field(value_field_def, value_field_type, value_message_value)
588
+ key_value_pairs << "#{key_string}=>#{value_string}"
589
+ end
590
+ field_output << "#{field_descriptor.name}: {#{key_value_pairs.join(", ")}}"
591
+ elsif field_descriptor.repeated?
592
+ # TODO Adapted - from repeated_field#each - can this be refactored to reduce echo?
593
+ repeated_field_output = []
594
+ message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor)
595
+ array = message_value[:array_val]
596
+ n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array)
597
+ 0.upto(n - 1) do |i|
598
+ element = Google::Protobuf::FFI.get_msgval_at(array, i)
599
+ repeated_field_output << inspect_field(field_descriptor, field_descriptor.send(:c_type), element)
600
+ end
601
+ field_output << "#{field_descriptor.name}: [#{repeated_field_output.join(", ")}]"
602
+ else
603
+ message_value = Google::Protobuf::FFI.get_message_value msg, field_descriptor
604
+ rendered_value = inspect_field(field_descriptor, field_descriptor.send(:c_type), message_value)
605
+ field_output << "#{field_descriptor.name}: #{rendered_value}"
606
+ end
607
+ end
608
+ "<#{name}: #{field_output.join(', ')}>"
609
+ end
610
+
611
+ def self.deep_copy(msg, arena = nil)
612
+ arena ||= Google::Protobuf::FFI.create_arena
613
+ encode_internal(msg) do |encoding, size, mini_table_ptr|
614
+ message = private_constructor(arena)
615
+ 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
616
+ raise ParseError.new "Error occurred copying proto"
617
+ end
618
+ message
619
+ end
620
+ end
621
+
622
+ def self.encode_internal(msg, encoding_options = 0)
623
+ temporary_arena = Google::Protobuf::FFI.create_arena
624
+
625
+ mini_table_ptr = Google::Protobuf::FFI.get_mini_table(descriptor)
626
+ size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
627
+ pointer_ptr = ::FFI::MemoryPointer.new(:pointer, 1)
628
+ encoding_status = Google::Protobuf::FFI.encode_message(msg, mini_table_ptr, encoding_options, temporary_arena, pointer_ptr.to_ptr, size_ptr)
629
+ raise "Encoding failed due to #{encoding_status}" unless encoding_status == :Ok
630
+ yield pointer_ptr.read(:pointer), size_ptr.read(:size_t), mini_table_ptr
631
+ end
632
+
633
+ def method_missing_internal(method_name, *args, mode: nil)
634
+ raise ArgumentError.new "method_missing_internal called with invalid mode #{mode.inspect}" unless [:respond_to_missing?, :method_missing].include? mode
635
+
636
+ #TODO not being allowed is not the same thing as not responding, but this is needed to pass tests
637
+ if method_name.to_s.end_with? '='
638
+ if self.class.send(:oneof_field_names).include? method_name.to_s[0..-2].to_sym
639
+ return false if mode == :respond_to_missing?
640
+ raise RuntimeError.new "Oneof accessors are read-only."
641
+ end
642
+ end
643
+
644
+ original_method_missing(method_name, *args) if mode == :method_missing
645
+ end
646
+
647
+ def clear_internal(field_def)
648
+ raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
649
+ Google::Protobuf::FFI.clear_message_field(@msg, field_def)
650
+ end
651
+
652
+ # Accessor for field by name. Does not delegate to methods setup by
653
+ # self.setup_accessors! in order to avoid conflicts with bad field
654
+ # names e.g. `dup` or `class` which are perfectly valid for proto
655
+ # fields.
656
+ def index_internal(name)
657
+ field_descriptor = self.class.descriptor.lookup(name)
658
+ get_field field_descriptor unless field_descriptor.nil?
659
+ end
660
+
661
+ #TODO - well known types keeps us on our toes by overloading methods.
662
+ # How much of the public API needs to be defended?
663
+ def index_assign_internal(value, name: nil, field_descriptor: nil, wrap: false)
664
+ raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
665
+ if field_descriptor.nil?
666
+ field_descriptor = self.class.descriptor.lookup(name)
667
+ if field_descriptor.nil?
668
+ raise ArgumentError.new "Unknown field: #{name}"
669
+ end
670
+ end
671
+ unless field_descriptor.send :set_value_on_message, value, @msg, @arena, wrap: wrap
672
+ raise RuntimeError.new "allocation failed"
673
+ end
674
+ end
675
+
676
+ ##
677
+ # @param initial_value [Object] initial value of this Message
678
+ # @param arena [Arena] Optional; Arena where this message will be allocated
679
+ # @param msg [::FFI::Pointer] Optional; Message to initialize; creates
680
+ # one if omitted or nil.
681
+ def initialize(initial_value = nil, arena = nil, msg = nil)
682
+ @arena = arena || Google::Protobuf::FFI.create_arena
683
+ @msg = msg || Google::Protobuf::FFI.new_message_from_def(Google::Protobuf::FFI.get_mini_table(self.class.descriptor), @arena)
684
+
685
+ unless initial_value.nil?
686
+ raise ArgumentError.new "Expected hash arguments or message, not #{initial_value.class}" unless initial_value.respond_to? :each
687
+
688
+ field_def_ptr = ::FFI::MemoryPointer.new :pointer
689
+ oneof_def_ptr = ::FFI::MemoryPointer.new :pointer
690
+
691
+ initial_value.each do |key, value|
692
+ raise ArgumentError.new "Expected string or symbols as hash keys when initializing proto from hash." unless [String, Symbol].include? key.class
693
+
694
+ 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
695
+ raise ArgumentError.new "Unknown field name '#{key}' in initialization map entry."
696
+ end
697
+ raise NotImplementedError.new "Haven't added oneofsupport yet" unless oneof_def_ptr.get_pointer(0).null?
698
+ raise NotImplementedError.new "Expected a field def" if field_def_ptr.get_pointer(0).null?
699
+
700
+ field_descriptor = FieldDescriptor.from_native field_def_ptr.get_pointer(0)
701
+
702
+ next if value.nil?
703
+ if field_descriptor.map?
704
+ index_assign_internal(Google::Protobuf::Map.send(:construct_for_field, field_descriptor, arena: @arena, value: value), name: key.to_s)
705
+ elsif field_descriptor.repeated?
706
+ index_assign_internal(RepeatedField.send(:construct_for_field, field_descriptor, arena: @arena, values: value), name: key.to_s)
707
+ else
708
+ index_assign_internal(value, name: key.to_s)
709
+ end
710
+ end
711
+ end
712
+
713
+ # Should always be the last expression of the initializer to avoid
714
+ # leaking references to this object before construction is complete.
715
+ Google::Protobuf::OBJECT_CACHE.try_add @msg.address, self
716
+ end
717
+
718
+ ##
719
+ # Gets a field of this message identified by FieldDescriptor.
720
+ #
721
+ # @param field [FieldDescriptor] Field to retrieve.
722
+ # @param unwrap [Boolean](false) If true, unwraps wrappers.
723
+ def get_field(field, unwrap: false)
724
+ if field.map?
725
+ map_from_field_descriptor field
726
+ elsif field.repeated?
727
+ repeated_field_from_field_descriptor field
728
+ elsif field.sub_message?
729
+ return nil unless Google::Protobuf::FFI.get_message_has @msg, field
730
+ if unwrap
731
+ if field.has?(self)
732
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field
733
+ wrapper_message_value = Google::Protobuf::FFI.get_message_value @msg, field
734
+ fields = Google::Protobuf::FFI.field_count(sub_message_def)
735
+ raise "Sub message has #{fields} fields! Expected exactly 1." unless fields == 1
736
+ value_field_def = Google::Protobuf::FFI.get_field_by_number sub_message_def, 1
737
+ message_value = Google::Protobuf::FFI.get_message_value wrapper_message_value[:msg_val], value_field_def
738
+ convert_upb_to_ruby message_value, Google::Protobuf::FFI.get_c_type(value_field_def)
739
+ else
740
+ nil
741
+ end
742
+ else
743
+ message_from_field_descriptor field
744
+ end
745
+ else
746
+ scalar_from_field_descriptor field
747
+ end
748
+ end
749
+
750
+ ##
751
+ # Gets a RepeatedField from the ObjectCache or creates a new one.
752
+ # @param array [::FFI::Pointer] Pointer to the upb_Array
753
+ # @param field [Google::Protobuf::FieldDescriptor] Type of the repeated field
754
+ def get_repeated_field(array, field)
755
+ return nil if array.nil? or array.null?
756
+ repeated_field = OBJECT_CACHE.get(array.address)
757
+ if repeated_field.nil?
758
+ repeated_field = RepeatedField.send(:construct_for_field, field, arena: @arena, array: array)
759
+ repeated_field.freeze if frozen?
760
+ end
761
+ repeated_field
762
+ end
763
+
764
+ ##
765
+ # Gets a Map from the ObjectCache or creates a new one.
766
+ # @param map [::FFI::Pointer] Pointer to the upb_Map
767
+ # @param field [Google::Protobuf::FieldDescriptor] Type of the map field
768
+ def get_map_field(map, field)
769
+ return nil if map.nil? or map.null?
770
+ map_field = OBJECT_CACHE.get(map.address)
771
+ if map_field.nil?
772
+ map_field = Google::Protobuf::Map.send(:construct_for_field, field, arena: @arena, map: map)
773
+ map_field.freeze if frozen?
774
+ end
775
+ map_field
776
+ end
777
+ end
778
+ end
779
+ end
780
+ end
781
+ end