google-protobuf 3.21.0 → 4.28.0

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.

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