diff --git a/ruby/ext/google/protobuf_c/defs.c b/ruby/ext/google/protobuf_c/defs.c index ed1b9e17d265..4e805471773c 100644 --- a/ruby/ext/google/protobuf_c/defs.c +++ b/ruby/ext/google/protobuf_c/defs.c @@ -44,7 +44,7 @@ static VALUE rb_str_maybe_null(const char* s) { } return rb_str_new2(s); } - +static ID options_instancevar_interned; // ----------------------------------------------------------------------------- // DescriptorPool. // ----------------------------------------------------------------------------- @@ -192,6 +192,7 @@ static void DescriptorPool_register(VALUE module) { rb_gc_register_address(&generated_pool); generated_pool = rb_class_new_instance(0, NULL, klass); + options_instancevar_interned = rb_intern("options"); } // ----------------------------------------------------------------------------- @@ -226,6 +227,35 @@ static Descriptor* ruby_to_Descriptor(VALUE val) { return ret; } +// Decode and return a frozen instance of a Descriptor Option for the given pool +static VALUE decode_options(VALUE self, const char* option_type, int size, + const char* bytes, VALUE descriptor_pool) { + VALUE options_rb = rb_ivar_get(self, options_instancevar_interned); + if (options_rb != Qnil) { + return options_rb; + } + + static const char* prefix = "google.protobuf."; + char fullname + [/*strlen(prefix)*/ 16 + + /*strln(longest option type supported e.g. "MessageOptions")*/ 14 + + /*null terminator*/ 1]; + + snprintf(fullname, sizeof(fullname), "%s%s", prefix, option_type); + const upb_MessageDef* msgdef = upb_DefPool_FindMessageByName( + ruby_to_DescriptorPool(descriptor_pool)->symtab, fullname); + if (!msgdef) { + rb_raise(rb_eRuntimeError, "Cannot find %s in DescriptorPool", option_type); + } + + VALUE desc_rb = get_msgdef_obj(descriptor_pool, msgdef); + const Descriptor* desc = ruby_to_Descriptor(desc_rb); + + options_rb = Message_decode_bytes(size, bytes, 0, desc->klass, true); + rb_ivar_set(self, options_instancevar_interned, options_rb); + return options_rb; +} + /* * call-seq: * Descriptor.new => descriptor @@ -374,6 +404,26 @@ static VALUE Descriptor_msgclass(VALUE _self) { return self->klass; } +/* + * call-seq: + * Descriptor.options => options + * + * Returns the `MessageOptions` for this `Descriptor`. + */ +static VALUE Descriptor_options(VALUE _self) { + Descriptor* self = ruby_to_Descriptor(_self); + const google_protobuf_MessageOptions* opts = + upb_MessageDef_Options(self->msgdef); + upb_Arena* arena = upb_Arena_New(); + size_t size; + char* serialized = + google_protobuf_MessageOptions_serialize(opts, arena, &size); + VALUE message_options = decode_options(_self, "MessageOptions", size, + serialized, self->descriptor_pool); + upb_Arena_Free(arena); + return message_options; +} + static void Descriptor_register(VALUE module) { VALUE klass = rb_define_class_under(module, "Descriptor", rb_cObject); rb_define_alloc_func(klass, Descriptor_alloc); @@ -385,6 +435,7 @@ static void Descriptor_register(VALUE module) { rb_define_method(klass, "msgclass", Descriptor_msgclass, 0); rb_define_method(klass, "name", Descriptor_name, 0); rb_define_method(klass, "file_descriptor", Descriptor_file_descriptor, 0); + rb_define_method(klass, "options", Descriptor_options, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cDescriptor); cDescriptor = klass; @@ -484,12 +535,31 @@ static VALUE FileDescriptor_syntax(VALUE _self) { } } +/* + * call-seq: + * FileDescriptor.options => options + * + * Returns the `FileOptions` for this `FileDescriptor`. + */ +static VALUE FileDescriptor_options(VALUE _self) { + FileDescriptor* self = ruby_to_FileDescriptor(_self); + const google_protobuf_FileOptions* opts = upb_FileDef_Options(self->filedef); + upb_Arena* arena = upb_Arena_New(); + size_t size; + char* serialized = google_protobuf_FileOptions_serialize(opts, arena, &size); + VALUE file_options = decode_options(_self, "FileOptions", size, serialized, + self->descriptor_pool); + upb_Arena_Free(arena); + return file_options; +} + static void FileDescriptor_register(VALUE module) { VALUE klass = rb_define_class_under(module, "FileDescriptor", rb_cObject); rb_define_alloc_func(klass, FileDescriptor_alloc); rb_define_method(klass, "initialize", FileDescriptor_initialize, 3); rb_define_method(klass, "name", FileDescriptor_name, 0); rb_define_method(klass, "syntax", FileDescriptor_syntax, 0); + rb_define_method(klass, "options", FileDescriptor_options, 0); rb_gc_register_address(&cFileDescriptor); cFileDescriptor = klass; } @@ -540,7 +610,7 @@ static VALUE FieldDescriptor_alloc(VALUE klass) { /* * call-seq: - * EnumDescriptor.new(c_only_cookie, pool, ptr) => EnumDescriptor + * FieldDescriptor.new(c_only_cookie, pool, ptr) => FieldDescriptor * * Creates a descriptor wrapper object. May only be called from C. */ @@ -841,6 +911,25 @@ static VALUE FieldDescriptor_set(VALUE _self, VALUE msg_rb, VALUE value) { return Qnil; } +/* + * call-seq: + * FieldDescriptor.options => options + * + * Returns the `FieldOptions` for this `FieldDescriptor`. + */ +static VALUE FieldDescriptor_options(VALUE _self) { + FieldDescriptor* self = ruby_to_FieldDescriptor(_self); + const google_protobuf_FieldOptions* opts = + upb_FieldDef_Options(self->fielddef); + upb_Arena* arena = upb_Arena_New(); + size_t size; + char* serialized = google_protobuf_FieldOptions_serialize(opts, arena, &size); + VALUE field_options = decode_options(_self, "FieldOptions", size, serialized, + self->descriptor_pool); + upb_Arena_Free(arena); + return field_options; +} + static void FieldDescriptor_register(VALUE module) { VALUE klass = rb_define_class_under(module, "FieldDescriptor", rb_cObject); rb_define_alloc_func(klass, FieldDescriptor_alloc); @@ -857,6 +946,7 @@ static void FieldDescriptor_register(VALUE module) { rb_define_method(klass, "clear", FieldDescriptor_clear, 1); rb_define_method(klass, "get", FieldDescriptor_get, 1); rb_define_method(klass, "set", FieldDescriptor_set, 2); + rb_define_method(klass, "options", FieldDescriptor_options, 0); rb_gc_register_address(&cFieldDescriptor); cFieldDescriptor = klass; } @@ -956,12 +1046,32 @@ static VALUE OneofDescriptor_each(VALUE _self) { return Qnil; } +/* + * call-seq: + * OneofDescriptor.options => options + * + * Returns the `OneofOptions` for this `OneofDescriptor`. + */ +static VALUE OneOfDescriptor_options(VALUE _self) { + OneofDescriptor* self = ruby_to_OneofDescriptor(_self); + const google_protobuf_OneofOptions* opts = + upb_OneofDef_Options(self->oneofdef); + upb_Arena* arena = upb_Arena_New(); + size_t size; + char* serialized = google_protobuf_OneofOptions_serialize(opts, arena, &size); + VALUE oneof_options = decode_options(_self, "OneofOptions", size, serialized, + self->descriptor_pool); + upb_Arena_Free(arena); + return oneof_options; +} + static void OneofDescriptor_register(VALUE module) { VALUE klass = rb_define_class_under(module, "OneofDescriptor", rb_cObject); rb_define_alloc_func(klass, OneofDescriptor_alloc); rb_define_method(klass, "initialize", OneofDescriptor_initialize, 3); rb_define_method(klass, "name", OneofDescriptor_name, 0); rb_define_method(klass, "each", OneofDescriptor_each, 0); + rb_define_method(klass, "options", OneOfDescriptor_options, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cOneofDescriptor); cOneofDescriptor = klass; @@ -1131,6 +1241,24 @@ static VALUE EnumDescriptor_enummodule(VALUE _self) { return self->module; } +/* + * call-seq: + * EnumDescriptor.options => options + * + * Returns the `EnumOptions` for this `EnumDescriptor`. + */ +static VALUE EnumDescriptor_options(VALUE _self) { + EnumDescriptor* self = ruby_to_EnumDescriptor(_self); + const google_protobuf_EnumOptions* opts = upb_EnumDef_Options(self->enumdef); + upb_Arena* arena = upb_Arena_New(); + size_t size; + char* serialized = google_protobuf_EnumOptions_serialize(opts, arena, &size); + VALUE enum_options = decode_options(_self, "EnumOptions", size, serialized, + self->descriptor_pool); + upb_Arena_Free(arena); + return enum_options; +} + static void EnumDescriptor_register(VALUE module) { VALUE klass = rb_define_class_under(module, "EnumDescriptor", rb_cObject); rb_define_alloc_func(klass, EnumDescriptor_alloc); @@ -1141,6 +1269,7 @@ static void EnumDescriptor_register(VALUE module) { rb_define_method(klass, "each", EnumDescriptor_each, 0); rb_define_method(klass, "enummodule", EnumDescriptor_enummodule, 0); rb_define_method(klass, "file_descriptor", EnumDescriptor_file_descriptor, 0); + rb_define_method(klass, "options", EnumDescriptor_options, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cEnumDescriptor); cEnumDescriptor = klass; diff --git a/ruby/ext/google/protobuf_c/glue.c b/ruby/ext/google/protobuf_c/glue.c index 3505437c8b95..e51e364279f5 100644 --- a/ruby/ext/google/protobuf_c/glue.c +++ b/ruby/ext/google/protobuf_c/glue.c @@ -14,8 +14,43 @@ upb_Arena* Arena_create() { return upb_Arena_Init(NULL, 0, &upb_alloc_global); } google_protobuf_FileDescriptorProto* FileDescriptorProto_parse( - const char* serialized_file_proto, size_t length) { - upb_Arena* arena = Arena_create(); + const char* serialized_file_proto, size_t length, upb_Arena* arena) { return google_protobuf_FileDescriptorProto_parse(serialized_file_proto, length, arena); } + +char* EnumDescriptor_serialized_options(const upb_EnumDef* enumdef, + size_t* size, upb_Arena* arena) { + const google_protobuf_EnumOptions* opts = upb_EnumDef_Options(enumdef); + char* serialized = google_protobuf_EnumOptions_serialize(opts, arena, size); + return serialized; +} + +char* FileDescriptor_serialized_options(const upb_FileDef* filedef, + size_t* size, upb_Arena* arena) { + const google_protobuf_FileOptions* opts = upb_FileDef_Options(filedef); + char* serialized = google_protobuf_FileOptions_serialize(opts, arena, size); + return serialized; +} + +char* Descriptor_serialized_options(const upb_MessageDef* msgdef, size_t* size, + upb_Arena* arena) { + const google_protobuf_MessageOptions* opts = upb_MessageDef_Options(msgdef); + char* serialized = + google_protobuf_MessageOptions_serialize(opts, arena, size); + return serialized; +} + +char* OneOfDescriptor_serialized_options(const upb_OneofDef* oneofdef, + size_t* size, upb_Arena* arena) { + const google_protobuf_OneofOptions* opts = upb_OneofDef_Options(oneofdef); + char* serialized = google_protobuf_OneofOptions_serialize(opts, arena, size); + return serialized; +} + +char* FieldDescriptor_serialized_options(const upb_FieldDef* fielddef, + size_t* size, upb_Arena* arena) { + const google_protobuf_FieldOptions* opts = upb_FieldDef_Options(fielddef); + char* serialized = google_protobuf_FieldOptions_serialize(opts, arena, size); + return serialized; +} diff --git a/ruby/ext/google/protobuf_c/map.c b/ruby/ext/google/protobuf_c/map.c index 98ee489bc673..e3bd80c05964 100644 --- a/ruby/ext/google/protobuf_c/map.c +++ b/ruby/ext/google/protobuf_c/map.c @@ -572,6 +572,26 @@ static VALUE Map_freeze(VALUE _self) { return _self; } +/* + * Deep freezes the map and values recursively. + * Internal use only. + */ +VALUE Map_internal_deep_freeze(VALUE _self) { + Map* self = ruby_to_Map(_self); + Map_freeze(_self); + if (self->value_type_info.type == kUpb_CType_Message) { + size_t iter = kUpb_Map_Begin; + upb_MessageValue key, val; + + while (upb_Map_Next(self->map, &key, &val, &iter)) { + VALUE val_val = + Convert_UpbToRuby(val, self->value_type_info, self->arena); + Message_internal_deep_freeze(val_val); + } + } + return _self; +} + /* * call-seq: * Map.hash => hash_value diff --git a/ruby/ext/google/protobuf_c/map.h b/ruby/ext/google/protobuf_c/map.h index 016a50c141dd..d3cebc6a6909 100644 --- a/ruby/ext/google/protobuf_c/map.h +++ b/ruby/ext/google/protobuf_c/map.h @@ -38,4 +38,7 @@ extern VALUE cMap; // Call at startup to register all types in this module. void Map_register(VALUE module); +// Recursively freeze map +VALUE Map_internal_deep_freeze(VALUE _self); + #endif // RUBY_PROTOBUF_MAP_H_ diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c index a15e0fa09895..2dec31a89ccf 100644 --- a/ruby/ext/google/protobuf_c/message.c +++ b/ruby/ext/google/protobuf_c/message.c @@ -859,6 +859,32 @@ static VALUE Message_freeze(VALUE _self) { return _self; } +/* + * Deep freezes the message object recursively. + * Internal use only. + */ +VALUE Message_internal_deep_freeze(VALUE _self) { + Message* self = ruby_to_Message(_self); + Message_freeze(_self); + + int n = upb_MessageDef_FieldCount(self->msgdef); + for (int i = 0; i < n; i++) { + const upb_FieldDef* f = upb_MessageDef_Field(self->msgdef, i); + VALUE field = Message_getfield(_self, f); + + if (field != Qnil) { + if (upb_FieldDef_IsMap(f)) { + Map_internal_deep_freeze(field); + } else if (upb_FieldDef_IsRepeated(f)) { + RepeatedField_internal_deep_freeze(field); + } else if (upb_FieldDef_IsSubMessage(f)) { + Message_internal_deep_freeze(field); + } + } + } + return _self; +} + /* * call-seq: * Message.[](index) => value @@ -911,7 +937,7 @@ static VALUE Message_index_set(VALUE _self, VALUE field_name, VALUE value) { * MessageClass.decode(data, options) => message * * Decodes the given data (as a string containing bytes in protocol buffers wire - * format) under the interpretration given by this message class's definition + * format) under the interpretation given by this message class's definition * and returns a message object with the corresponding field values. * @param options [Hash] options for the decoder * recursion_limit: set to maximum decoding depth for message (default is 64) @@ -942,18 +968,24 @@ static VALUE Message_decode(int argc, VALUE* argv, VALUE klass) { rb_raise(rb_eArgError, "Expected string for binary protobuf data."); } + return Message_decode_bytes(RSTRING_LEN(data), RSTRING_PTR(data), options, + klass, /*freeze*/ false); +} + +VALUE Message_decode_bytes(int size, const char* bytes, int options, + VALUE klass, bool freeze) { VALUE msg_rb = initialize_rb_class_with_no_args(klass); Message* msg = ruby_to_Message(msg_rb); - upb_DecodeStatus status = - upb_Decode(RSTRING_PTR(data), RSTRING_LEN(data), (upb_Message*)msg->msg, - upb_MessageDef_MiniTable(msg->msgdef), NULL, options, - Arena_get(msg->arena)); - + upb_DecodeStatus status = upb_Decode(bytes, size, (upb_Message*)msg->msg, + upb_MessageDef_MiniTable(msg->msgdef), + NULL, options, Arena_get(msg->arena)); if (status != kUpb_DecodeStatus_Ok) { rb_raise(cParseError, "Error occurred during parsing"); } - + if (freeze) { + Message_internal_deep_freeze(msg_rb); + } return msg_rb; } diff --git a/ruby/ext/google/protobuf_c/message.h b/ruby/ext/google/protobuf_c/message.h index 5e354b05d7f4..cb6897f0d45b 100644 --- a/ruby/ext/google/protobuf_c/message.h +++ b/ruby/ext/google/protobuf_c/message.h @@ -73,6 +73,13 @@ VALUE build_module_from_enumdesc(VALUE _enumdesc); // module. VALUE MessageOrEnum_GetDescriptor(VALUE klass); +// Decodes a Message from a byte sequence. +VALUE Message_decode_bytes(int size, const char* bytes, int options, + VALUE klass, bool freeze); + +// Recursively freeze message +VALUE Message_internal_deep_freeze(VALUE _self); + // Call at startup to register all types in this module. void Message_register(VALUE protobuf); diff --git a/ruby/ext/google/protobuf_c/repeated_field.c b/ruby/ext/google/protobuf_c/repeated_field.c index f5ca3cae4632..196012674955 100644 --- a/ruby/ext/google/protobuf_c/repeated_field.c +++ b/ruby/ext/google/protobuf_c/repeated_field.c @@ -487,6 +487,25 @@ static VALUE RepeatedField_freeze(VALUE _self) { return _self; } +/* + * Deep freezes the repeated field and values recursively. + * Internal use only. + */ +VALUE RepeatedField_internal_deep_freeze(VALUE _self) { + RepeatedField* self = ruby_to_RepeatedField(_self); + RepeatedField_freeze(_self); + if (self->type_info.type == kUpb_CType_Message) { + int size = upb_Array_Size(self->array); + int i; + for (i = 0; i < size; i++) { + upb_MessageValue msgval = upb_Array_Get(self->array, i); + VALUE val = Convert_UpbToRuby(msgval, self->type_info, self->arena); + Message_internal_deep_freeze(val); + } + } + return _self; +} + /* * call-seq: * RepeatedField.hash => hash_value diff --git a/ruby/ext/google/protobuf_c/repeated_field.h b/ruby/ext/google/protobuf_c/repeated_field.h index 97a908e6d6d6..f3f7a50cd587 100644 --- a/ruby/ext/google/protobuf_c/repeated_field.h +++ b/ruby/ext/google/protobuf_c/repeated_field.h @@ -35,4 +35,7 @@ extern VALUE cRepeatedField; // Call at startup to register all types in this module. void RepeatedField_register(VALUE module); +// Recursively freeze RepeatedField. +VALUE RepeatedField_internal_deep_freeze(VALUE _self); + #endif // RUBY_PROTOBUF_REPEATED_FIELD_H_ diff --git a/ruby/lib/google/protobuf/ffi/descriptor.rb b/ruby/lib/google/protobuf/ffi/descriptor.rb index c878eb5b77f7..25d226abd1d4 100644 --- a/ruby/lib/google/protobuf/ffi/descriptor.rb +++ b/ruby/lib/google/protobuf/ffi/descriptor.rb @@ -95,6 +95,15 @@ def msgclass @msg_class ||= build_message_class end + def options + @options ||= begin + size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) + temporary_arena = Google::Protobuf::FFI.create_arena + buffer = Google::Protobuf::FFI.message_options(self, size_ptr, temporary_arena) + Google::Protobuf::MessageOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze).send(:internal_deep_freeze) + end + end + private extend Google::Protobuf::Internal::Convert @@ -129,6 +138,7 @@ def self.get_message(msg, descriptor, arena) message = OBJECT_CACHE.get(msg.address) if message.nil? message = descriptor.msgclass.send(:private_constructor, arena, msg: msg) + message.send :internal_deep_freeze if frozen? end message end @@ -146,6 +156,7 @@ class FFI attach_function :get_message_fullname, :upb_MessageDef_FullName, [Descriptor], :string attach_function :get_mini_table, :upb_MessageDef_MiniTable, [Descriptor], MiniTable.ptr attach_function :oneof_count, :upb_MessageDef_OneofCount, [Descriptor], :int + attach_function :message_options, :Descriptor_serialized_options, [Descriptor, :pointer, Internal::Arena], :pointer attach_function :get_well_known_type, :upb_MessageDef_WellKnownType, [Descriptor], WellKnown attach_function :message_def_syntax, :upb_MessageDef_Syntax, [Descriptor], Syntax attach_function :find_msg_def_by_name, :upb_MessageDef_FindByNameWithSize, [Descriptor, :string, :size_t, :FieldDefPointer, :OneofDefPointer], :bool diff --git a/ruby/lib/google/protobuf/ffi/descriptor_pool.rb b/ruby/lib/google/protobuf/ffi/descriptor_pool.rb index bd9c96df99bc..f0543adbb323 100644 --- a/ruby/lib/google/protobuf/ffi/descriptor_pool.rb +++ b/ruby/lib/google/protobuf/ffi/descriptor_pool.rb @@ -15,7 +15,7 @@ class FFI attach_function :lookup_enum, :upb_DefPool_FindEnumByName, [:DefPool, :string], EnumDescriptor attach_function :lookup_msg, :upb_DefPool_FindMessageByName, [:DefPool, :string], Descriptor # FileDescriptorProto - attach_function :parse, :FileDescriptorProto_parse, [:binary_string, :size_t], :FileDescriptorProto + attach_function :parse, :FileDescriptorProto_parse, [:binary_string, :size_t, Internal::Arena], :FileDescriptorProto end class DescriptorPool attr :descriptor_pool @@ -35,7 +35,8 @@ def add_serialized_file(file_contents) memBuf = ::FFI::MemoryPointer.new(:char, file_contents.bytesize) # Insert the data memBuf.put_bytes(0, file_contents) - file_descriptor_proto = Google::Protobuf::FFI.parse memBuf, file_contents.bytesize + temporary_arena = Google::Protobuf::FFI.create_arena + file_descriptor_proto = Google::Protobuf::FFI.parse memBuf, file_contents.bytesize, temporary_arena raise ArgumentError.new("Unable to parse FileDescriptorProto") if file_descriptor_proto.null? status = Google::Protobuf::FFI::Status.new diff --git a/ruby/lib/google/protobuf/ffi/enum_descriptor.rb b/ruby/lib/google/protobuf/ffi/enum_descriptor.rb index 773cf7305eb1..a1f4fefcd520 100644 --- a/ruby/lib/google/protobuf/ffi/enum_descriptor.rb +++ b/ruby/lib/google/protobuf/ffi/enum_descriptor.rb @@ -19,7 +19,7 @@ class << self prepend Google::Protobuf::Internal::TypeSafety include Google::Protobuf::Internal::PointerHelper - # @param value [Arena] Arena to convert to an FFI native type + # @param value [EnumDescriptor] EnumDescriptor to convert to an FFI native type # @param _ [Object] Unused def to_native(value, _) value.instance_variable_get(:@enum_def) || ::FFI::Pointer::NULL @@ -79,6 +79,15 @@ def enummodule @module end + def options + @options ||= begin + size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) + temporary_arena = Google::Protobuf::FFI.create_arena + buffer = Google::Protobuf::FFI.enum_options(self, size_ptr, temporary_arena) + Google::Protobuf::EnumOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze).send(:internal_deep_freeze) + end + end + private def initialize(enum_def, descriptor_pool) @@ -152,6 +161,7 @@ class FFI attach_function :enum_value_by_name, :upb_EnumDef_FindValueByNameWithSize,[EnumDescriptor, :string, :size_t], :EnumValueDef attach_function :enum_value_by_number, :upb_EnumDef_FindValueByNumber, [EnumDescriptor, :int], :EnumValueDef attach_function :get_enum_fullname, :upb_EnumDef_FullName, [EnumDescriptor], :string + attach_function :enum_options, :EnumDescriptor_serialized_options, [EnumDescriptor, :pointer, Internal::Arena], :pointer attach_function :enum_value_by_index, :upb_EnumDef_Value, [EnumDescriptor, :int], :EnumValueDef attach_function :enum_value_count, :upb_EnumDef_ValueCount, [EnumDescriptor], :int attach_function :enum_name, :upb_EnumValueDef_Name, [:EnumValueDef], :string diff --git a/ruby/lib/google/protobuf/ffi/field_descriptor.rb b/ruby/lib/google/protobuf/ffi/field_descriptor.rb index 800661bbd80c..d7c45da193d3 100644 --- a/ruby/lib/google/protobuf/ffi/field_descriptor.rb +++ b/ruby/lib/google/protobuf/ffi/field_descriptor.rb @@ -206,6 +206,15 @@ def wrapper? end end + def options + @options ||= begin + size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) + temporary_arena = Google::Protobuf::FFI.create_arena + buffer = Google::Protobuf::FFI.field_options(self, size_ptr, temporary_arena) + Google::Protobuf::FieldOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze).send(:internal_deep_freeze) + end + end + private def initialize(field_def, descriptor_pool) @@ -289,21 +298,22 @@ class FFI attach_function :get_field_by_number, :upb_MessageDef_FindFieldByNumber, [Descriptor, :uint32_t], FieldDescriptor # FieldDescriptor - attach_function :get_containing_message_def, :upb_FieldDef_ContainingType, [FieldDescriptor], Descriptor - attach_function :get_c_type, :upb_FieldDef_CType, [FieldDescriptor], CType - attach_function :get_default, :upb_FieldDef_Default, [FieldDescriptor], MessageValue.by_value - attach_function :get_subtype_as_enum, :upb_FieldDef_EnumSubDef, [FieldDescriptor], EnumDescriptor - attach_function :get_has_presence, :upb_FieldDef_HasPresence, [FieldDescriptor], :bool - attach_function :is_map, :upb_FieldDef_IsMap, [FieldDescriptor], :bool - attach_function :is_repeated, :upb_FieldDef_IsRepeated, [FieldDescriptor], :bool - attach_function :is_sub_message, :upb_FieldDef_IsSubMessage, [FieldDescriptor], :bool - attach_function :get_json_name, :upb_FieldDef_JsonName, [FieldDescriptor], :string - attach_function :get_label, :upb_FieldDef_Label, [FieldDescriptor], Label - attach_function :get_subtype_as_message, :upb_FieldDef_MessageSubDef, [FieldDescriptor], Descriptor - attach_function :get_full_name, :upb_FieldDef_Name, [FieldDescriptor], :string - attach_function :get_number, :upb_FieldDef_Number, [FieldDescriptor], :uint32_t - attach_function :get_type, :upb_FieldDef_Type, [FieldDescriptor], FieldType - attach_function :file_def_by_raw_field_def, :upb_FieldDef_File, [:pointer], :FileDef + attach_function :field_options, :FieldDescriptor_serialized_options, [FieldDescriptor, :pointer, Internal::Arena], :pointer + attach_function :get_containing_message_def, :upb_FieldDef_ContainingType, [FieldDescriptor], Descriptor + attach_function :get_c_type, :upb_FieldDef_CType, [FieldDescriptor], CType + attach_function :get_default, :upb_FieldDef_Default, [FieldDescriptor], MessageValue.by_value + attach_function :get_subtype_as_enum, :upb_FieldDef_EnumSubDef, [FieldDescriptor], EnumDescriptor + attach_function :get_has_presence, :upb_FieldDef_HasPresence, [FieldDescriptor], :bool + attach_function :is_map, :upb_FieldDef_IsMap, [FieldDescriptor], :bool + attach_function :is_repeated, :upb_FieldDef_IsRepeated, [FieldDescriptor], :bool + attach_function :is_sub_message, :upb_FieldDef_IsSubMessage, [FieldDescriptor], :bool + attach_function :get_json_name, :upb_FieldDef_JsonName, [FieldDescriptor], :string + attach_function :get_label, :upb_FieldDef_Label, [FieldDescriptor], Label + attach_function :get_subtype_as_message, :upb_FieldDef_MessageSubDef, [FieldDescriptor], Descriptor + attach_function :get_full_name, :upb_FieldDef_Name, [FieldDescriptor], :string + attach_function :get_number, :upb_FieldDef_Number, [FieldDescriptor], :uint32_t + attach_function :get_type, :upb_FieldDef_Type, [FieldDescriptor], FieldType + attach_function :file_def_by_raw_field_def, :upb_FieldDef_File, [:pointer], :FileDef end end end diff --git a/ruby/lib/google/protobuf/ffi/file_descriptor.rb b/ruby/lib/google/protobuf/ffi/file_descriptor.rb index 0a465ecdbf54..291ac4f3e41a 100644 --- a/ruby/lib/google/protobuf/ffi/file_descriptor.rb +++ b/ruby/lib/google/protobuf/ffi/file_descriptor.rb @@ -12,7 +12,9 @@ class FFI attach_function :file_def_name, :upb_FileDef_Name, [:FileDef], :string attach_function :file_def_syntax, :upb_FileDef_Syntax, [:FileDef], Syntax attach_function :file_def_pool, :upb_FileDef_Pool, [:FileDef], :DefPool + attach_function :file_options, :FileDescriptor_serialized_options, [:FileDef, :pointer, Internal::Arena], :pointer end + class FileDescriptor attr :descriptor_pool, :file_def @@ -43,6 +45,15 @@ def syntax def name Google::Protobuf::FFI.file_def_name(@file_def) end + + def options + @options ||= begin + size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) + temporary_arena = Google::Protobuf::FFI.create_arena + buffer = Google::Protobuf::FFI.file_options(@file_def, size_ptr, temporary_arena) + Google::Protobuf::FileOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze).send(:internal_deep_freeze) + end + end end end end diff --git a/ruby/lib/google/protobuf/ffi/map.rb b/ruby/lib/google/protobuf/ffi/map.rb index 50c917e63fc5..61abbe53b0bc 100644 --- a/ruby/lib/google/protobuf/ffi/map.rb +++ b/ruby/lib/google/protobuf/ffi/map.rb @@ -269,6 +269,17 @@ def each &block include Google::Protobuf::Internal::Convert + def internal_deep_freeze + freeze + if value_type == :message + internal_iterator do |iterator| + value_message_value = Google::Protobuf::FFI.map_value(@map_ptr, iterator) + convert_upb_to_ruby(value_message_value, value_type, descriptor, arena).send :internal_deep_freeze + end + end + self + end + def internal_iterator iter = ::FFI::MemoryPointer.new(:size_t, 1) iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin) diff --git a/ruby/lib/google/protobuf/ffi/message.rb b/ruby/lib/google/protobuf/ffi/message.rb index d53d43781926..045f67f9083b 100644 --- a/ruby/lib/google/protobuf/ffi/message.rb +++ b/ruby/lib/google/protobuf/ffi/message.rb @@ -19,7 +19,7 @@ class FFI attach_function :encode_message, :upb_Encode, [:Message, MiniTable.by_ref, :size_t, Internal::Arena, :pointer, :pointer], EncodeStatus attach_function :json_decode_message, :upb_JsonDecode, [:binary_string, :size_t, :Message, Descriptor, :DefPool, :int, Internal::Arena, Status.by_ref], :bool attach_function :json_encode_message, :upb_JsonEncode, [:Message, Descriptor, :DefPool, :int, :binary_string, :size_t, Status.by_ref], :size_t - attach_function :decode_message, :upb_Decode, [:binary_string, :size_t, :Message, MiniTable.by_ref, :ExtensionRegistry, :int, Internal::Arena], DecodeStatus + attach_function :decode_message, :upb_Decode, [:binary_string, :size_t, :Message, MiniTable.by_ref, :ExtensionRegistry, :int, Internal::Arena], DecodeStatus attach_function :get_mutable_message, :upb_Message_Mutable, [:Message, FieldDescriptor, Internal::Arena], MutableMessageValue.by_value attach_function :get_message_which_oneof, :upb_Message_WhichOneof, [:Message, OneofDescriptor], FieldDescriptor attach_function :message_discard_unknown, :upb_Message_DiscardUnknown, [:Message, Descriptor, :int], :bool @@ -293,6 +293,17 @@ def self.encode_json(message, options = {}) include Google::Protobuf::Internal::Convert + def internal_deep_freeze + freeze + self.class.descriptor.each do |field_descriptor| + next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(@msg, field_descriptor) + if field_descriptor.map? or field_descriptor.repeated? or field_descriptor.sub_message? + get_field(field_descriptor).send :internal_deep_freeze + end + end + self + end + def self.setup_accessors! @descriptor.each do |field_descriptor| field_name = field_descriptor.name @@ -619,6 +630,7 @@ def get_repeated_field(array, field) repeated_field = OBJECT_CACHE.get(array.address) if repeated_field.nil? repeated_field = RepeatedField.send(:construct_for_field, field, @arena, array: array) + repeated_field.send :internal_deep_freeze if frozen? end repeated_field end @@ -631,6 +643,7 @@ def get_map_field(map, field) map_field = OBJECT_CACHE.get(map.address) if map_field.nil? map_field = Google::Protobuf::Map.send(:construct_for_field, field, @arena, map: map) + map_field.send :internal_deep_freeze if frozen? end map_field end diff --git a/ruby/lib/google/protobuf/ffi/oneof_descriptor.rb b/ruby/lib/google/protobuf/ffi/oneof_descriptor.rb index 00d1d04ea3db..00acc995c18d 100644 --- a/ruby/lib/google/protobuf/ffi/oneof_descriptor.rb +++ b/ruby/lib/google/protobuf/ffi/oneof_descriptor.rb @@ -22,10 +22,7 @@ class << self # @param value [OneofDescriptor] FieldDescriptor to convert to an FFI native type # @param _ [Object] Unused def to_native(value, _ = nil) - oneof_def_ptr = value.instance_variable_get(:@oneof_def) - warn "Underlying oneof_def was nil!" if oneof_def_ptr.nil? - raise "Underlying oneof_def was null!" if !oneof_def_ptr.nil? and oneof_def_ptr.null? - oneof_def_ptr + value.instance_variable_get(:@oneof_def) || ::FFI::Pointer::NULL end ## @@ -56,6 +53,15 @@ def each &block nil end + def options + @options ||= begin + size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) + temporary_arena = Google::Protobuf::FFI.create_arena + buffer = Google::Protobuf::FFI.oneof_options(self, size_ptr, temporary_arena) + Google::Protobuf::OneofOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze).send(:internal_deep_freeze) + end + end + private def initialize(oneof_def, descriptor_pool) @@ -72,17 +78,18 @@ def self.private_constructor(oneof_def, descriptor_pool) class FFI # MessageDef - attach_function :get_oneof_by_name, :upb_MessageDef_FindOneofByNameWithSize, [Descriptor, :string, :size_t], OneofDescriptor - attach_function :get_oneof_by_index, :upb_MessageDef_Oneof, [Descriptor, :int], OneofDescriptor + attach_function :get_oneof_by_name, :upb_MessageDef_FindOneofByNameWithSize, [Descriptor, :string, :size_t], OneofDescriptor + attach_function :get_oneof_by_index, :upb_MessageDef_Oneof, [Descriptor, :int], OneofDescriptor # OneofDescriptor - attach_function :get_oneof_name, :upb_OneofDef_Name, [OneofDescriptor], :string - attach_function :get_oneof_field_count, :upb_OneofDef_FieldCount, [OneofDescriptor], :int - attach_function :get_oneof_field_by_index, :upb_OneofDef_Field, [OneofDescriptor, :int], FieldDescriptor - attach_function :get_oneof_containing_type,:upb_OneofDef_ContainingType,[:pointer], Descriptor + attach_function :get_oneof_name, :upb_OneofDef_Name, [OneofDescriptor], :string + attach_function :get_oneof_field_count, :upb_OneofDef_FieldCount, [OneofDescriptor], :int + attach_function :get_oneof_field_by_index, :upb_OneofDef_Field, [OneofDescriptor, :int], FieldDescriptor + attach_function :get_oneof_containing_type,:upb_OneofDef_ContainingType, [:pointer], Descriptor + attach_function :oneof_options, :OneOfDescriptor_serialized_options, [OneofDescriptor, :pointer, Internal::Arena], :pointer # FieldDescriptor - attach_function :real_containing_oneof, :upb_FieldDef_RealContainingOneof,[FieldDescriptor], OneofDescriptor + attach_function :real_containing_oneof, :upb_FieldDef_RealContainingOneof, [FieldDescriptor], OneofDescriptor end end end diff --git a/ruby/lib/google/protobuf/ffi/repeated_field.rb b/ruby/lib/google/protobuf/ffi/repeated_field.rb index c75ea1728493..ccc95ad6f425 100644 --- a/ruby/lib/google/protobuf/ffi/repeated_field.rb +++ b/ruby/lib/google/protobuf/ffi/repeated_field.rb @@ -5,8 +5,6 @@ # license that can be found in the LICENSE file or at # https://developers.google.com/open-source/licenses/bsd -require 'forwardable' - # # This class makes RepeatedField act (almost-) like a Ruby Array. # It has convenience methods that extend the core C or Java based @@ -15,7 +13,7 @@ # This is a best-effort to mirror Array behavior. Two comments: # 1) patches always welcome :) # 2) if performance is an issue, feel free to rewrite the method -# in jruby and C. The source code has plenty of examples +# in C. The source code has plenty of examples # # KNOWN ISSUES # - #[]= doesn't allow less used approaches such as `arr[1, 2] = 'fizz'` @@ -35,19 +33,6 @@ class FFI end class RepeatedField - extend Forwardable - # NOTE: using delegators rather than method_missing to make the - # relationship explicit instead of implicit - def_delegators :to_ary, - :&, :*, :-, :'<=>', - :assoc, :bsearch, :bsearch_index, :combination, :compact, :count, - :cycle, :dig, :drop, :drop_while, :eql?, :fetch, :find_index, :flatten, - :include?, :index, :inspect, :join, - :pack, :permutation, :product, :pretty_print, :pretty_print_cycle, - :rassoc, :repeated_combination, :repeated_permutation, :reverse, - :rindex, :rotate, :sample, :shuffle, :shelljoin, - :to_s, :transpose, :uniq, :| - include Enumerable ## @@ -262,128 +247,21 @@ def concat(other) push(*other.to_a) end - def first(n=nil) - if n.nil? - return self[0] - elsif n < 0 - raise ArgumentError, "negative array size" - else - return self[0...n] - end - end - - - def last(n=nil) - if n.nil? - return self[-1] - elsif n < 0 - raise ArgumentError, "negative array size" - else - start = [self.size-n, 0].max - return self[start...self.size] - end - end - - - def pop(n=nil) - if n - results = [] - n.times{ results << pop_one } - return results - else - return pop_one - end - end - - - def empty? - self.size == 0 - end - - # array aliases into enumerable - alias_method :each_index, :each_with_index - alias_method :slice, :[] - alias_method :values_at, :select - alias_method :map, :collect - - - class << self - def define_array_wrapper_method(method_name) - define_method(method_name) do |*args, &block| - arr = self.to_a - result = arr.send(method_name, *args) - self.replace(arr) - return result if result - return block ? block.call : result - end - end - private :define_array_wrapper_method - - - def define_array_wrapper_with_result_method(method_name) - define_method(method_name) do |*args, &block| - # result can be an Enumerator, Array, or nil - # Enumerator can sometimes be returned if a block is an optional argument and it is not passed in - # nil usually specifies that no change was made - result = self.to_a.send(method_name, *args, &block) - if result - new_arr = result.to_a - self.replace(new_arr) - if result.is_a?(Enumerator) - # generate a fresh enum; rewinding the exiting one, in Ruby 2.2, will - # reset the enum with the same length, but all the #next calls will - # return nil - result = new_arr.to_enum - # generate a wrapper enum so any changes which occur by a chained - # enum can be captured - ie = ProxyingEnumerator.new(self, result) - result = ie.to_enum - end - end - result - end - end - private :define_array_wrapper_with_result_method - end - - - %w(delete delete_at shift slice! unshift).each do |method_name| - define_array_wrapper_method(method_name) - end + private + include Google::Protobuf::Internal::Convert + attr :name, :arena, :array, :type, :descriptor - %w(collect! compact! delete_if fill flatten! insert reverse! - rotate! select! shuffle! sort! sort_by! uniq!).each do |method_name| - define_array_wrapper_with_result_method(method_name) - end - alias_method :keep_if, :select! - alias_method :map!, :collect! - alias_method :reject!, :delete_if - - - # propagates changes made by user of enumerator back to the original repeated field. - # This only applies in cases where the calling function which created the enumerator, - # such as #sort!, modifies itself rather than a new array, such as #sort - class ProxyingEnumerator < Struct.new(:repeated_field, :external_enumerator) - def each(*args, &block) - results = [] - external_enumerator.each_with_index do |val, i| - result = yield(val) - results << result - #nil means no change occurred from yield; usually occurs when #to_a is called - if result - repeated_field[i] = result if result != val - end + def internal_deep_freeze + freeze + if type == :message + each do |element| + element.send :internal_deep_freeze end - results end + self end - private - include Google::Protobuf::Internal::Convert - - attr :name, :arena, :array, :type, :descriptor - def internal_push(*elements) elements.each do |element| append_msg_val convert_ruby_to_upb(element, arena, type, descriptor) @@ -501,3 +379,5 @@ def self.deep_copy(repeated_field) end end end + +require 'google/protobuf/repeated_field' diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java index b80925331525..aa64d1a5e74f 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java @@ -32,6 +32,7 @@ package com.google.protobuf.jruby; +import com.google.protobuf.CodedInputStream; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; @@ -158,6 +159,22 @@ public IRubyObject lookupOneof(ThreadContext context, IRubyObject name) { return Helpers.nullToNil(oneofDescriptors.get(Utils.symToString(name)), context.nil); } + @JRubyMethod + public IRubyObject options(ThreadContext context) { + RubyDescriptorPool pool = (RubyDescriptorPool) RubyDescriptorPool.generatedPool(null, null); + RubyDescriptor messageOptionsDescriptor = + (RubyDescriptor) + pool.lookup(context, context.runtime.newString("google.protobuf.MessageOptions")); + RubyClass messageOptionsClass = (RubyClass) messageOptionsDescriptor.msgclass(context); + RubyMessage msg = (RubyMessage) messageOptionsClass.newInstance(context, Block.NULL_BLOCK); + return msg.decodeBytes( + context, + msg, + CodedInputStream.newInstance( + descriptor.getOptions().toByteString().toByteArray()), /*freeze*/ + true); + } + protected FieldDescriptor getField(String name) { return descriptor.findFieldByName(name); } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java index 24c03cd4d9c7..7e8247c014bd 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java @@ -32,6 +32,7 @@ package com.google.protobuf.jruby; +import com.google.protobuf.CodedInputStream; import com.google.protobuf.DescriptorProtos.EnumDescriptorProto; import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; @@ -124,6 +125,22 @@ public IRubyObject getFileDescriptor(ThreadContext context) { return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor); } + @JRubyMethod + public IRubyObject options(ThreadContext context) { + RubyDescriptorPool pool = (RubyDescriptorPool) RubyDescriptorPool.generatedPool(null, null); + RubyDescriptor enumOptionsDescriptor = + (RubyDescriptor) + pool.lookup(context, context.runtime.newString("google.protobuf.EnumOptions")); + RubyClass enumOptionsClass = (RubyClass) enumOptionsDescriptor.msgclass(context); + RubyMessage msg = (RubyMessage) enumOptionsClass.newInstance(context, Block.NULL_BLOCK); + return msg.decodeBytes( + context, + msg, + CodedInputStream.newInstance( + descriptor.getOptions().toByteString().toByteArray()), /*freeze*/ + true); + } + public boolean isValidValue(ThreadContext context, IRubyObject value) { EnumValueDescriptor enumValue; @@ -198,9 +215,9 @@ private static String fixEnumConstantName(String name) { // always start with uppercase letters. We tolerate this case by capitalizing // the first character if possible. return new StringBuilder() - .appendCodePoint(Character.toUpperCase(ch)) - .append(name.substring(1)) - .toString(); + .appendCodePoint(Character.toUpperCase(ch)) + .append(name.substring(1)) + .toString(); } } return name; diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java index 50f42cfa7056..5ba86ef2a74a 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java @@ -32,11 +32,13 @@ package com.google.protobuf.jruby; +import com.google.protobuf.CodedInputStream; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.LegacyDescriptorsUtil.LegacyFileDescriptor; import org.jruby.*; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; @@ -231,6 +233,21 @@ public IRubyObject setValue(ThreadContext context, IRubyObject message, IRubyObj return context.nil; } + @JRubyMethod + public IRubyObject options(ThreadContext context) { + RubyDescriptor fieldOptionsDescriptor = + (RubyDescriptor) + pool.lookup(context, context.runtime.newString("google.protobuf.FieldOptions")); + RubyClass fieldOptionsClass = (RubyClass) fieldOptionsDescriptor.msgclass(context); + RubyMessage msg = (RubyMessage) fieldOptionsClass.newInstance(context, Block.NULL_BLOCK); + return msg.decodeBytes( + context, + msg, + CodedInputStream.newInstance( + descriptor.getOptions().toByteString().toByteArray()), /*freeze*/ + true); + } + protected void setDescriptor( ThreadContext context, FieldDescriptor descriptor, RubyDescriptorPool pool) { if (descriptor.isRequired() diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java index 650480684f7b..db9580de984e 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java @@ -32,6 +32,7 @@ package com.google.protobuf.jruby; +import com.google.protobuf.CodedInputStream; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.GenericDescriptor; import com.google.protobuf.LegacyDescriptorsUtil.LegacyFileDescriptor; @@ -106,6 +107,22 @@ public IRubyObject getSyntax(ThreadContext context) { } } + @JRubyMethod + public IRubyObject options(ThreadContext context) { + RubyDescriptorPool pool = (RubyDescriptorPool) RubyDescriptorPool.generatedPool(null, null); + RubyDescriptor fileOptionsDescriptor = + (RubyDescriptor) + pool.lookup(context, context.runtime.newString("google.protobuf.FileOptions")); + RubyClass fileOptionsClass = (RubyClass) fileOptionsDescriptor.msgclass(context); + RubyMessage msg = (RubyMessage) fileOptionsClass.newInstance(context, Block.NULL_BLOCK); + return msg.decodeBytes( + context, + msg, + CodedInputStream.newInstance( + fileDescriptor.getOptions().toByteString().toByteArray()), /*freeze*/ + true); + } + private static RubyClass cFileDescriptor; private FileDescriptor fileDescriptor; diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java index 8727b13cf7dc..f3849b1b2a35 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java @@ -372,6 +372,16 @@ public RubyHash toHash(ThreadContext context) { return RubyHash.newHash(context.runtime, mapForHash, context.nil); } + protected IRubyObject deepFreeze(ThreadContext context) { + setFrozen(true); + if (valueType == FieldDescriptor.Type.MESSAGE) { + for (IRubyObject key : table.keySet()) { + ((RubyMessage) table.get(key)).deepFreeze(context); + } + } + return this; + } + // Used by Google::Protobuf.deep_copy but not exposed directly. protected IRubyObject deepCopy(ThreadContext context) { RubyMap newMap = newThisType(context); diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java index 209f35eeb35c..25f9dca589ad 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java @@ -628,7 +628,11 @@ public static IRubyObject decode(ThreadContext context, IRubyObject recv, IRubyO input.setRecursionLimit(((RubyNumeric) recursionLimit).getIntValue()); } } + return decodeBytes(context, ret, input, /*freeze*/ false); + } + public static IRubyObject decodeBytes( + ThreadContext context, RubyMessage ret, CodedInputStream input, boolean freeze) { try { ret.builder.mergeFrom(input); } catch (Exception e) { @@ -658,7 +662,9 @@ public static IRubyObject decode(ThreadContext context, IRubyObject recv, IRubyO } }); } - + if (freeze) { + ret.deepFreeze(context); + } return ret; } @@ -811,6 +817,22 @@ public IRubyObject toHash(ThreadContext context) { return ret; } + protected IRubyObject deepFreeze(ThreadContext context) { + setFrozen(true); + for (FieldDescriptor fdef : descriptor.getFields()) { + if (fdef.isMapField()) { + ((RubyMap) fields.get(fdef)).deepFreeze(context); + } else if (fdef.isRepeated()) { + this.getRepeatedField(context, fdef).deepFreeze(context); + } else if (fields.containsKey(fdef)) { + if (fdef.getType() == FieldDescriptor.Type.MESSAGE) { + ((RubyMessage) fields.get(fdef)).deepFreeze(context); + } + } + } + return this; + } + protected DynamicMessage build(ThreadContext context, int depth, int recursionLimit) { if (depth >= recursionLimit) { throw context.runtime.newRuntimeError("Recursion limit exceeded during encoding."); diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java index 5ade98b7f32b..36fe2d0e8a5f 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java @@ -1,5 +1,6 @@ package com.google.protobuf.jruby; +import com.google.protobuf.CodedInputStream; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; import java.util.ArrayList; @@ -66,6 +67,22 @@ public IRubyObject each(ThreadContext context, Block block) { return context.nil; } + @JRubyMethod + public IRubyObject options(ThreadContext context) { + RubyDescriptorPool pool = (RubyDescriptorPool) RubyDescriptorPool.generatedPool(null, null); + RubyDescriptor oneofOptionsDescriptor = + (RubyDescriptor) + pool.lookup(context, context.runtime.newString("google.protobuf.OneofOptions")); + RubyClass oneofOptionsClass = (RubyClass) oneofOptionsDescriptor.msgclass(context); + RubyMessage msg = (RubyMessage) oneofOptionsClass.newInstance(context, Block.NULL_BLOCK); + return msg.decodeBytes( + context, + msg, + CodedInputStream.newInstance( + descriptor.getOptions().toByteString().toByteArray()), /*freeze*/ + true); + } + protected Collection getFields() { return fields; } diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java index 90ab5415187b..085dd1cc0f53 100644 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java +++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java @@ -111,6 +111,7 @@ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { */ @JRubyMethod(name = "[]=") public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) { + testFrozen("Can't set index in frozen repeated field"); int arrIndex = normalizeArrayIndex(index); value = Utils.checkType(context, fieldType, name, value, (RubyModule) typeClass); IRubyObject defaultValue = defaultValue(context); @@ -183,6 +184,7 @@ public IRubyObject index(ThreadContext context, IRubyObject[] args) { required = 1, rest = true) public IRubyObject push(ThreadContext context, IRubyObject[] args) { + testFrozen("Can't push frozen repeated field"); for (int i = 0; i < args.length; i++) { IRubyObject val = args[i]; if (fieldType != FieldDescriptor.Type.MESSAGE || !val.isNil()) { @@ -199,6 +201,7 @@ public IRubyObject push(ThreadContext context, IRubyObject[] args) { */ @JRubyMethod(visibility = org.jruby.runtime.Visibility.PRIVATE) public IRubyObject pop_one(ThreadContext context) { + testFrozen("Can't pop frozen repeated field"); IRubyObject ret = this.storage.last(); this.storage.remove(ret); return ret; @@ -212,6 +215,7 @@ public IRubyObject pop_one(ThreadContext context) { */ @JRubyMethod public IRubyObject replace(ThreadContext context, IRubyObject list) { + testFrozen("Can't replace frozen repeated field"); RubyArray arr = (RubyArray) list; checkArrayElementType(context, arr); this.storage = arr; @@ -226,6 +230,7 @@ public IRubyObject replace(ThreadContext context, IRubyObject list) { */ @JRubyMethod public IRubyObject clear(ThreadContext context) { + testFrozen("Can't clear frozen repeated field"); this.storage.clear(); return this; } @@ -274,6 +279,7 @@ public IRubyObject plus(ThreadContext context, IRubyObject list) { */ @JRubyMethod public IRubyObject concat(ThreadContext context, IRubyObject list) { + testFrozen("Can't concat frozen repeated field"); if (list instanceof RubyArray) { checkArrayElementType(context, (RubyArray) list); this.storage.addAll((RubyArray) list); @@ -352,6 +358,16 @@ public IRubyObject inspect() { return storage.inspect(); } + protected IRubyObject deepFreeze(ThreadContext context) { + setFrozen(true); + if (fieldType == FieldDescriptor.Type.MESSAGE) { + for (int i = 0; i < size(); i++) { + ((RubyMessage) storage.eltInternal(i)).deepFreeze(context); + } + } + return this; + } + // Java API protected IRubyObject get(int index) { return this.storage.eltInternal(index); diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb index b70f6304a883..9cd2d705ecca 100755 --- a/ruby/tests/basic.rb +++ b/ruby/tests/basic.rb @@ -695,6 +695,50 @@ def test_map_fields_respond_to? # regression test for issue 9202 msg.map_string_int32_as_value = :boom end end + + def test_file_descriptor_options + file_descriptor = TestMessage.descriptor.file_descriptor + + assert_instance_of Google::Protobuf::FileOptions, file_descriptor.options + assert file_descriptor.options.deprecated + end + + def test_field_descriptor_options + field_descriptor = TestDeprecatedMessage.descriptor.lookup("foo") + + assert_instance_of Google::Protobuf::FieldOptions, field_descriptor.options + assert field_descriptor.options.deprecated + end + + def test_descriptor_options + descriptor = TestDeprecatedMessage.descriptor + + assert_instance_of Google::Protobuf::MessageOptions, descriptor.options + assert descriptor.options.deprecated + end + + def test_enum_descriptor_options + enum_descriptor = TestDeprecatedEnum.descriptor + + assert_instance_of Google::Protobuf::EnumOptions, enum_descriptor.options + assert enum_descriptor.options.deprecated + end + + def test_oneof_descriptor_options + descriptor = TestDeprecatedMessage.descriptor + oneof_descriptor = descriptor.lookup_oneof("test_deprecated_message_oneof") + + assert_instance_of Google::Protobuf::OneofOptions, oneof_descriptor.options + end + + def test_options_deep_freeze + descriptor = TestDeprecatedMessage.descriptor + + assert_raise FrozenError do + descriptor.options.uninterpreted_option.push \ + Google::Protobuf::UninterpretedOption.new + end + end end def test_oneof_fields_respond_to? # regression test for issue 9202 diff --git a/ruby/tests/basic_test.proto b/ruby/tests/basic_test.proto index d480d48e548b..89eb9cc3cf6c 100644 --- a/ruby/tests/basic_test.proto +++ b/ruby/tests/basic_test.proto @@ -2,12 +2,14 @@ syntax = "proto3"; package basic_test; -import "google/protobuf/wrappers.proto"; -import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; import "test_import_proto2.proto"; +option deprecated = true; + message Foo { Bar bar = 1; repeated Baz baz = 2; @@ -68,6 +70,20 @@ message TestMessage2 { optional int32 foo = 1; } +message TestDeprecatedMessage { + option deprecated = true; + + optional int32 foo = 1 [deprecated = true]; + + oneof test_deprecated_message_oneof { + string a = 2; + int32 b = 3; + } + + map map_string_msg = 4; + repeated TestMessage2 repeated_msg = 5; +} + enum TestEnum { Default = 0; A = 1; @@ -76,6 +92,13 @@ enum TestEnum { v0 = 4; } +enum TestDeprecatedEnum { + option deprecated = true; + + DefaultA = 0; + AA = 1 [deprecated = true]; +} + message TestEmbeddedMessageParent { TestEmbeddedMessageChild child_msg = 1; int32 number = 2; @@ -130,8 +153,7 @@ message Outer { map items = 1; } -message Inner { -} +message Inner {} message Wrapper { google.protobuf.DoubleValue double = 1; @@ -213,8 +235,8 @@ message MyStruct { } message WithJsonName { - optional int32 foo_bar = 1 [json_name="jsonFooBar"]; - repeated WithJsonName baz = 2 [json_name="jsonBaz"]; + optional int32 foo_bar = 1 [json_name = "jsonFooBar"]; + repeated WithJsonName baz = 2 [json_name = "jsonBaz"]; } message HelloRequest {