From d5ed3dd27eb543cde94e0974502aeadb37636e79 Mon Sep 17 00:00:00 2001 From: Jeremy Steinberg Date: Tue, 16 May 2023 09:25:47 -0500 Subject: [PATCH 1/6] Add support for descriptor options in ruby interface --- ruby/ext/google/protobuf_c/defs.c | 122 ++++++++++++++++++++- ruby/lib/google/protobuf/descriptor_dsl.rb | 54 +++++++++ ruby/tests/basic.rb | 28 +++++ ruby/tests/basic_test.proto | 20 ++++ 4 files changed, 223 insertions(+), 1 deletion(-) diff --git a/ruby/ext/google/protobuf_c/defs.c b/ruby/ext/google/protobuf_c/defs.c index 75555fcb236d1..f2ce5bb406bc4 100644 --- a/ruby/ext/google/protobuf_c/defs.c +++ b/ruby/ext/google/protobuf_c/defs.c @@ -397,6 +397,29 @@ static VALUE Descriptor_msgclass(VALUE _self) { return self->klass; } +/* + * call-seq: + * Descriptor.serialized_options => options + * + * Returns a binary string containing the serialized options for this message. + */ +static VALUE Descriptor_serialized_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); + if (serialized) { + VALUE ret = rb_str_new(serialized, size); + rb_enc_associate(ret, rb_ascii8bit_encoding()); + upb_Arena_Free(arena); + return ret; + } else { + upb_Arena_Free(arena); + rb_raise(rb_eRuntimeError, "Error encoding"); + } +} + static void Descriptor_register(VALUE module) { VALUE klass = rb_define_class_under(module, "Descriptor", rb_cObject); rb_define_alloc_func(klass, Descriptor_alloc); @@ -408,6 +431,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, "serialized_options", Descriptor_serialized_options, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cDescriptor); cDescriptor = klass; @@ -507,12 +531,36 @@ static VALUE FileDescriptor_syntax(VALUE _self) { } } +/* + * call-seq: + * FileDescriptor.serialized_options => options + * + * Returns a binary string containing the serialized options for this message. + */ +static VALUE FileDescriptor_serialized_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); + if (serialized) { + VALUE ret = rb_str_new(serialized, size); + rb_enc_associate(ret, rb_ascii8bit_encoding()); + upb_Arena_Free(arena); + return ret; + } else { + upb_Arena_Free(arena); + rb_raise(rb_eRuntimeError, "Error encoding"); + } +} + 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, "serialized_options", FileDescriptor_serialized_options, 0); rb_gc_register_address(&cFileDescriptor); cFileDescriptor = klass; } @@ -563,7 +611,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. */ @@ -864,6 +912,29 @@ static VALUE FieldDescriptor_set(VALUE _self, VALUE msg_rb, VALUE value) { return Qnil; } +/* + * call-seq: + * FieldDescriptor.serialized_options => options + * + * Returns a binary string containing the serialized options for this message. + */ +static VALUE FieldDescriptor_serialized_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); + if (serialized) { + VALUE ret = rb_str_new(serialized, size); + rb_enc_associate(ret, rb_ascii8bit_encoding()); + upb_Arena_Free(arena); + return ret; + } else { + upb_Arena_Free(arena); + rb_raise(rb_eRuntimeError, "Error encoding"); + } +} + static void FieldDescriptor_register(VALUE module) { VALUE klass = rb_define_class_under(module, "FieldDescriptor", rb_cObject); rb_define_alloc_func(klass, FieldDescriptor_alloc); @@ -880,6 +951,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, "serialized_options", FieldDescriptor_serialized_options, 0); rb_gc_register_address(&cFieldDescriptor); cFieldDescriptor = klass; } @@ -979,12 +1051,36 @@ static VALUE OneofDescriptor_each(VALUE _self) { return Qnil; } +/* + * call-seq: + * OneofDescriptor.serialized_options => options + * + * Returns a binary string containing the serialized options for this message. + */ +static VALUE OneOfDescriptor_serialized_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); + if (serialized) { + VALUE ret = rb_str_new(serialized, size); + rb_enc_associate(ret, rb_ascii8bit_encoding()); + upb_Arena_Free(arena); + return ret; + } else { + upb_Arena_Free(arena); + rb_raise(rb_eRuntimeError, "Error encoding"); + } +} + 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, "serialized_options", OneOfDescriptor_serialized_options, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cOneofDescriptor); cOneofDescriptor = klass; @@ -1153,6 +1249,29 @@ static VALUE EnumDescriptor_enummodule(VALUE _self) { return self->module; } +/* + * call-seq: + * EnumDescriptor.serialized_options => options + * + * Returns a binary string containing the serialized options for this message. + */ +static VALUE EnumDescriptor_serialized_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); + if (serialized) { + VALUE ret = rb_str_new(serialized, size); + rb_enc_associate(ret, rb_ascii8bit_encoding()); + upb_Arena_Free(arena); + return ret; + } else { + upb_Arena_Free(arena); + rb_raise(rb_eRuntimeError, "Error encoding"); + } +} + static void EnumDescriptor_register(VALUE module) { VALUE klass = rb_define_class_under(module, "EnumDescriptor", rb_cObject); rb_define_alloc_func(klass, EnumDescriptor_alloc); @@ -1163,6 +1282,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, "serialized_options", EnumDescriptor_serialized_options, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cEnumDescriptor); cEnumDescriptor = klass; diff --git a/ruby/lib/google/protobuf/descriptor_dsl.rb b/ruby/lib/google/protobuf/descriptor_dsl.rb index 7349b6df15bd9..56341e1a51f15 100644 --- a/ruby/lib/google/protobuf/descriptor_dsl.rb +++ b/ruby/lib/google/protobuf/descriptor_dsl.rb @@ -451,6 +451,10 @@ def value(name, number) end end + def self.decode_options(klass, serialized_options) + options = klass.decode(serialized_options) + options.freeze + end end # Re-open the class (the rest of the class is implemented in C) @@ -461,5 +465,55 @@ def build(&block) builder.build end end + + # Re-open the class (the rest of the class is implemented in C) + class Descriptor + def options + @options ||= Google::Protobuf::Internal.decode_options( + Google::Protobuf::MessageOptions, + serialized_options + ) + end + end + + # Re-open the class (the rest of the class is implemented in C) + class FileDescriptor + def options + @options ||= Google::Protobuf::Internal.decode_options( + Google::Protobuf::FileOptions, + serialized_options + ) + end + end + + # Re-open the class (the rest of the class is implemented in C) + class FieldDescriptor + def options + @options ||= Google::Protobuf::Internal.decode_options( + Google::Protobuf::FieldOptions, + serialized_options + ) + end + end + + # Re-open the class (the rest of the class is implemented in C) + class EnumDescriptor + def options + @options ||= Google::Protobuf::Internal.decode_options( + Google::Protobuf::EnumOptions, + serialized_options + ) + end + end + + # Re-open the class (the rest of the class is implemented in C) + class OneofDescriptor + def options + @options ||= Google::Protobuf::Internal.decode_options( + Google::Protobuf::OneofOptions, + serialized_options + ) + end + end end end diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb index 7351b9cd33e10..cf347a78fc9fe 100755 --- a/ruby/tests/basic.rb +++ b/ruby/tests/basic.rb @@ -711,6 +711,34 @@ 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_equal file_descriptor.options.class, Google::Protobuf::FileOptions + assert file_descriptor.options.deprecated + end + + def test_descriptor_options + descriptor = TestDeprecatedMessage.descriptor + + assert_equal descriptor.options.class, Google::Protobuf::MessageOptions + assert descriptor.options.deprecated + end + + def test_enum_descriptor_options + enum_descriptor = TestDeprecatedEnum.descriptor + + assert_equal enum_descriptor.options.class, Google::Protobuf::EnumOptions + assert enum_descriptor.options.deprecated + end + + def test_oneof_descriptor_options + descriptor = TestDeprecatedMessage.descriptor + oneof_descriptor = descriptor.lookup_oneof("test_deprecated_message_oneof") + + assert_equal oneof_descriptor.options.class, Google::Protobuf::OneofOptions + 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 d480d48e548b7..1c7b32989d62f 100644 --- a/ruby/tests/basic_test.proto +++ b/ruby/tests/basic_test.proto @@ -8,6 +8,8 @@ import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "test_import_proto2.proto"; +option deprecated = true; + message Foo { Bar bar = 1; repeated Baz baz = 2; @@ -68,6 +70,17 @@ message TestMessage2 { optional int32 foo = 1; } +message TestDeprecatedMessage { + option deprecated = true; + + optional int32 foo = 1; + + oneof test_deprecated_message_oneof { + string a = 2; + int32 b = 3; + } +} + enum TestEnum { Default = 0; A = 1; @@ -76,6 +89,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; From 71f7226c4019ec041bbca76e39a1ad28d5fa8023 Mon Sep 17 00:00:00 2001 From: Jeremy Steinberg Date: Wed, 17 May 2023 13:57:59 -0500 Subject: [PATCH 2/6] DRY converting serialized options into ruby object --- ruby/ext/google/protobuf_c/defs.c | 68 +++++++++++-------------------- 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/ruby/ext/google/protobuf_c/defs.c b/ruby/ext/google/protobuf_c/defs.c index f2ce5bb406bc4..abe8c1ae4c81a 100644 --- a/ruby/ext/google/protobuf_c/defs.c +++ b/ruby/ext/google/protobuf_c/defs.c @@ -46,6 +46,7 @@ static VALUE get_enumdef_obj(VALUE descriptor_pool, const upb_EnumDef* def); static VALUE get_fielddef_obj(VALUE descriptor_pool, const upb_FieldDef* def); static VALUE get_filedef_obj(VALUE descriptor_pool, const upb_FileDef* def); static VALUE get_oneofdef_obj(VALUE descriptor_pool, const upb_OneofDef* def); +static VALUE get_serialized_options_obj(const char* serialized, size_t size, upb_Arena* arena); // A distinct object that is not accessible from Ruby. We use this as a // constructor argument to enforce that certain objects cannot be created from @@ -409,15 +410,8 @@ static VALUE Descriptor_serialized_options(VALUE _self) { upb_Arena* arena = upb_Arena_New(); size_t size; char* serialized = google_protobuf_MessageOptions_serialize(opts, arena, &size); - if (serialized) { - VALUE ret = rb_str_new(serialized, size); - rb_enc_associate(ret, rb_ascii8bit_encoding()); - upb_Arena_Free(arena); - return ret; - } else { - upb_Arena_Free(arena); - rb_raise(rb_eRuntimeError, "Error encoding"); - } + + return get_serialized_options_obj(serialized, size, arena); } static void Descriptor_register(VALUE module) { @@ -543,15 +537,8 @@ static VALUE FileDescriptor_serialized_options(VALUE _self) { upb_Arena* arena = upb_Arena_New(); size_t size; char* serialized = google_protobuf_FileOptions_serialize(opts, arena, &size); - if (serialized) { - VALUE ret = rb_str_new(serialized, size); - rb_enc_associate(ret, rb_ascii8bit_encoding()); - upb_Arena_Free(arena); - return ret; - } else { - upb_Arena_Free(arena); - rb_raise(rb_eRuntimeError, "Error encoding"); - } + + return get_serialized_options_obj(serialized, size, arena); } static void FileDescriptor_register(VALUE module) { @@ -924,15 +911,8 @@ static VALUE FieldDescriptor_serialized_options(VALUE _self) { upb_Arena* arena = upb_Arena_New(); size_t size; char* serialized = google_protobuf_FieldOptions_serialize(opts, arena, &size); - if (serialized) { - VALUE ret = rb_str_new(serialized, size); - rb_enc_associate(ret, rb_ascii8bit_encoding()); - upb_Arena_Free(arena); - return ret; - } else { - upb_Arena_Free(arena); - rb_raise(rb_eRuntimeError, "Error encoding"); - } + + return get_serialized_options_obj(serialized, size, arena); } static void FieldDescriptor_register(VALUE module) { @@ -1063,15 +1043,8 @@ static VALUE OneOfDescriptor_serialized_options(VALUE _self) { upb_Arena* arena = upb_Arena_New(); size_t size; char* serialized = google_protobuf_OneofOptions_serialize(opts, arena, &size); - if (serialized) { - VALUE ret = rb_str_new(serialized, size); - rb_enc_associate(ret, rb_ascii8bit_encoding()); - upb_Arena_Free(arena); - return ret; - } else { - upb_Arena_Free(arena); - rb_raise(rb_eRuntimeError, "Error encoding"); - } + + return get_serialized_options_obj(serialized, size, arena); } static void OneofDescriptor_register(VALUE module) { @@ -1261,15 +1234,8 @@ static VALUE EnumDescriptor_serialized_options(VALUE _self) { upb_Arena* arena = upb_Arena_New(); size_t size; char* serialized = google_protobuf_EnumOptions_serialize(opts, arena, &size); - if (serialized) { - VALUE ret = rb_str_new(serialized, size); - rb_enc_associate(ret, rb_ascii8bit_encoding()); - upb_Arena_Free(arena); - return ret; - } else { - upb_Arena_Free(arena); - rb_raise(rb_eRuntimeError, "Error encoding"); - } + + return get_serialized_options_obj(serialized, size, arena); } static void EnumDescriptor_register(VALUE module) { @@ -1329,6 +1295,18 @@ static VALUE get_oneofdef_obj(VALUE descriptor_pool, const upb_OneofDef* def) { return get_def_obj(descriptor_pool, def, cOneofDescriptor); } +static VALUE get_serialized_options_obj(const char* serialized, size_t size, upb_Arena* arena) { + if (serialized) { + VALUE ret = rb_str_new(serialized, size); + rb_enc_associate(ret, rb_ascii8bit_encoding()); + upb_Arena_Free(arena); + return ret; + } else { + upb_Arena_Free(arena); + rb_raise(rb_eRuntimeError, "Error encoding"); + } +} + // ----------------------------------------------------------------------------- // Shared functions // ----------------------------------------------------------------------------- From 04758edbc5435f930a963eab156977a36e43c820 Mon Sep 17 00:00:00 2001 From: Jeremy Steinberg Date: Wed, 17 May 2023 14:02:03 -0500 Subject: [PATCH 3/6] make serialized_options methods private --- ruby/ext/google/protobuf_c/defs.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ruby/ext/google/protobuf_c/defs.c b/ruby/ext/google/protobuf_c/defs.c index abe8c1ae4c81a..fb925acc667e5 100644 --- a/ruby/ext/google/protobuf_c/defs.c +++ b/ruby/ext/google/protobuf_c/defs.c @@ -425,7 +425,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, "serialized_options", Descriptor_serialized_options, 0); + rb_define_private_method(klass, "serialized_options", Descriptor_serialized_options, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cDescriptor); cDescriptor = klass; @@ -547,7 +547,7 @@ static void FileDescriptor_register(VALUE module) { 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, "serialized_options", FileDescriptor_serialized_options, 0); + rb_define_private_method(klass, "serialized_options", FileDescriptor_serialized_options, 0); rb_gc_register_address(&cFileDescriptor); cFileDescriptor = klass; } @@ -931,7 +931,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, "serialized_options", FieldDescriptor_serialized_options, 0); + rb_define_private_method(klass, "serialized_options", FieldDescriptor_serialized_options, 0); rb_gc_register_address(&cFieldDescriptor); cFieldDescriptor = klass; } @@ -1053,7 +1053,7 @@ static void OneofDescriptor_register(VALUE module) { 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, "serialized_options", OneOfDescriptor_serialized_options, 0); + rb_define_private_method(klass, "serialized_options", OneOfDescriptor_serialized_options, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cOneofDescriptor); cOneofDescriptor = klass; @@ -1248,7 +1248,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, "serialized_options", EnumDescriptor_serialized_options, 0); + rb_define_private_method(klass, "serialized_options", EnumDescriptor_serialized_options, 0); rb_include_module(klass, rb_mEnumerable); rb_gc_register_address(&cEnumDescriptor); cEnumDescriptor = klass; From 188f45683ee1b2e3f286fd286240d7d074a58ceb Mon Sep 17 00:00:00 2001 From: Jeremy Steinberg Date: Thu, 18 May 2023 11:59:17 -0500 Subject: [PATCH 4/6] Options messages should recursively freeze (deep_freeze) --- ruby/ext/google/protobuf_c/message.c | 28 ++++++++++++++++++++++ ruby/lib/google/protobuf/descriptor_dsl.rb | 2 +- ruby/tests/basic.rb | 9 +++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c index 8208cb650cb5b..e54c5897b4453 100644 --- a/ruby/ext/google/protobuf_c/message.c +++ b/ruby/ext/google/protobuf_c/message.c @@ -905,6 +905,33 @@ static VALUE Message_freeze(VALUE _self) { return _self; } +/* + * call-seq: + * Message.deep_freeze => self + * + * Deeep freezes the message object recursively. + */ +static VALUE Message_deep_freeze(VALUE _self) { + Message* self = ruby_to_Message(_self); + if (!RB_OBJ_FROZEN(_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 (upb_FieldDef_IsMap(f) || upb_FieldDef_IsRepeated(f)) { + rb_funcall(field, rb_intern("freeze"), 0); + } else if (upb_FieldDef_IsSubMessage(f)) { + Message_deep_freeze(field); + } + } + } + + return _self; +} + /* * call-seq: * Message.[](index) => value @@ -1407,6 +1434,7 @@ static void Message_define_class(VALUE klass) { rb_define_method(klass, "==", Message_eq, 1); rb_define_method(klass, "eql?", Message_eq, 1); rb_define_method(klass, "freeze", Message_freeze, 0); + rb_define_private_method(klass, "deep_freeze", Message_deep_freeze, 0); rb_define_method(klass, "hash", Message_hash, 0); rb_define_method(klass, "to_h", Message_to_h, 0); rb_define_method(klass, "inspect", Message_inspect, 0); diff --git a/ruby/lib/google/protobuf/descriptor_dsl.rb b/ruby/lib/google/protobuf/descriptor_dsl.rb index 56341e1a51f15..c3a08ec164a8f 100644 --- a/ruby/lib/google/protobuf/descriptor_dsl.rb +++ b/ruby/lib/google/protobuf/descriptor_dsl.rb @@ -453,7 +453,7 @@ def value(name, number) def self.decode_options(klass, serialized_options) options = klass.decode(serialized_options) - options.freeze + options.send(:deep_freeze) end end diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb index cf347a78fc9fe..5d2fa7bc7e13a 100755 --- a/ruby/tests/basic.rb +++ b/ruby/tests/basic.rb @@ -739,6 +739,15 @@ def test_oneof_descriptor_options assert_equal oneof_descriptor.options.class, Google::Protobuf::OneofOptions 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 From 730fb064f5914fc12b13a22c1657419f4eb8228d Mon Sep 17 00:00:00 2001 From: Jeremy Steinberg Date: Thu, 18 May 2023 15:24:31 -0500 Subject: [PATCH 5/6] Message internal_deep_freeze now recurses over repeated/map value fields --- ruby/ext/google/protobuf_c/map.c | 29 ++++++++++++++++++++ ruby/ext/google/protobuf_c/message.c | 18 ++++++++----- ruby/ext/google/protobuf_c/repeated_field.c | 30 +++++++++++++++++++++ ruby/lib/google/protobuf/descriptor_dsl.rb | 2 +- ruby/tests/basic.rb | 19 +++++++++++++ ruby/tests/basic_test.proto | 3 +++ 6 files changed, 93 insertions(+), 8 deletions(-) diff --git a/ruby/ext/google/protobuf_c/map.c b/ruby/ext/google/protobuf_c/map.c index 18617ed1c2d61..c75fd58e5db11 100644 --- a/ruby/ext/google/protobuf_c/map.c +++ b/ruby/ext/google/protobuf_c/map.c @@ -593,6 +593,33 @@ static VALUE Map_freeze(VALUE _self) { return _self; } +/* + * call-seq: + * Map.internal_deep_freeze => self + * + * Deeep freezes the map and values recursively. + * Internal use only. + */ +static VALUE Map_internal_deep_freeze(VALUE _self) { + Map* self = ruby_to_Map(_self); + + if (!RB_OBJ_FROZEN(_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); + rb_funcall(val_val, rb_intern("internal_deep_freeze"), 0); + } + } + } + + return _self; +} + /* * call-seq: * Map.hash => hash_value @@ -679,6 +706,8 @@ void Map_register(VALUE module) { rb_define_method(klass, "clone", Map_dup, 0); rb_define_method(klass, "==", Map_eq, 1); rb_define_method(klass, "freeze", Map_freeze, 0); + rb_define_private_method(klass, "internal_deep_freeze", + Map_internal_deep_freeze, 0); rb_define_method(klass, "hash", Map_hash, 0); rb_define_method(klass, "to_h", Map_to_h, 0); rb_define_method(klass, "inspect", Map_inspect, 0); diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c index e54c5897b4453..d259ba7795be7 100644 --- a/ruby/ext/google/protobuf_c/message.c +++ b/ruby/ext/google/protobuf_c/message.c @@ -907,11 +907,12 @@ static VALUE Message_freeze(VALUE _self) { /* * call-seq: - * Message.deep_freeze => self + * Message.internal_deep_freeze => self * * Deeep freezes the message object recursively. + * Internal use only. */ -static VALUE Message_deep_freeze(VALUE _self) { +static VALUE Message_internal_deep_freeze(VALUE _self) { Message* self = ruby_to_Message(_self); if (!RB_OBJ_FROZEN(_self)) { Message_freeze(_self); @@ -921,10 +922,12 @@ static VALUE Message_deep_freeze(VALUE _self) { const upb_FieldDef* f = upb_MessageDef_Field(self->msgdef, i); VALUE field = Message_getfield(_self, f); - if (upb_FieldDef_IsMap(f) || upb_FieldDef_IsRepeated(f)) { - rb_funcall(field, rb_intern("freeze"), 0); - } else if (upb_FieldDef_IsSubMessage(f)) { - Message_deep_freeze(field); + if (field != Qnil) { + if (upb_FieldDef_IsMap(f) || upb_FieldDef_IsRepeated(f)) { + rb_funcall(field, rb_intern("internal_deep_freeze"), 0); + } else if (upb_FieldDef_IsSubMessage(f)) { + Message_internal_deep_freeze(field); + } } } } @@ -1434,7 +1437,8 @@ static void Message_define_class(VALUE klass) { rb_define_method(klass, "==", Message_eq, 1); rb_define_method(klass, "eql?", Message_eq, 1); rb_define_method(klass, "freeze", Message_freeze, 0); - rb_define_private_method(klass, "deep_freeze", Message_deep_freeze, 0); + rb_define_private_method(klass, "internal_deep_freeze", + Message_internal_deep_freeze, 0); rb_define_method(klass, "hash", Message_hash, 0); rb_define_method(klass, "to_h", Message_to_h, 0); rb_define_method(klass, "inspect", Message_inspect, 0); diff --git a/ruby/ext/google/protobuf_c/repeated_field.c b/ruby/ext/google/protobuf_c/repeated_field.c index 700ca16f38a65..5a98388d95a40 100644 --- a/ruby/ext/google/protobuf_c/repeated_field.c +++ b/ruby/ext/google/protobuf_c/repeated_field.c @@ -508,6 +508,34 @@ static VALUE RepeatedField_freeze(VALUE _self) { return _self; } +/* + * call-seq: + * RepeatedField.internal_deep_freeze => self + * + * Deeep freezes the repeated field and values recursively. + * Internal use only. + */ +static VALUE RepeatedField_internal_deep_freeze(VALUE _self) { + RepeatedField* self = ruby_to_RepeatedField(_self); + + if (!RB_OBJ_FROZEN(_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); + rb_funcall(val, rb_intern("internal_deep_freeze"), 0); + } + } + } + + return _self; +} + /* * call-seq: * RepeatedField.hash => hash_value @@ -650,6 +678,8 @@ void RepeatedField_register(VALUE module) { rb_define_method(klass, "==", RepeatedField_eq, 1); rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0); rb_define_method(klass, "freeze", RepeatedField_freeze, 0); + rb_define_private_method(klass, "internal_deep_freeze", + RepeatedField_internal_deep_freeze, 0); rb_define_method(klass, "hash", RepeatedField_hash, 0); rb_define_method(klass, "+", RepeatedField_plus, 1); rb_define_method(klass, "concat", RepeatedField_concat, 1); diff --git a/ruby/lib/google/protobuf/descriptor_dsl.rb b/ruby/lib/google/protobuf/descriptor_dsl.rb index c3a08ec164a8f..f39331c99235d 100644 --- a/ruby/lib/google/protobuf/descriptor_dsl.rb +++ b/ruby/lib/google/protobuf/descriptor_dsl.rb @@ -453,7 +453,7 @@ def value(name, number) def self.decode_options(klass, serialized_options) options = klass.decode(serialized_options) - options.send(:deep_freeze) + options.send(:internal_deep_freeze) end end diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb index 5d2fa7bc7e13a..ee361d9f2acd6 100755 --- a/ruby/tests/basic.rb +++ b/ruby/tests/basic.rb @@ -748,6 +748,25 @@ def test_options_deep_freeze Google::Protobuf::UninterpretedOption.new end end + + def test_message_deep_freeze + message = TestDeprecatedMessage.new + + nested_message_2 = TestMessage2.new + + message.map_string_msg["message"] = TestMessage2.new + message.repeated_msg.push(TestMessage2.new) + + message.send(:internal_deep_freeze) + + assert_raise FrozenError do + message.map_string_msg["message"].foo = "bar" + end + + assert_raise FrozenError do + message.repeated_msg[0].foo = "bar" + 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 1c7b32989d62f..ddae1d5f9575d 100644 --- a/ruby/tests/basic_test.proto +++ b/ruby/tests/basic_test.proto @@ -79,6 +79,9 @@ message TestDeprecatedMessage { string a = 2; int32 b = 3; } + + map map_string_msg = 4; + repeated TestMessage2 repeated_msg = 5; } enum TestEnum { From 10b3e2babaadeab8fad8c9fd8f22ade3469a3f86 Mon Sep 17 00:00:00 2001 From: Jeremy Steinberg Date: Mon, 22 May 2023 09:03:19 -0500 Subject: [PATCH 6/6] Fix spelling issue (Deeep -> Deep) --- ruby/ext/google/protobuf_c/map.c | 2 +- ruby/ext/google/protobuf_c/message.c | 2 +- ruby/ext/google/protobuf_c/repeated_field.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ruby/ext/google/protobuf_c/map.c b/ruby/ext/google/protobuf_c/map.c index c75fd58e5db11..da797bca6ef6b 100644 --- a/ruby/ext/google/protobuf_c/map.c +++ b/ruby/ext/google/protobuf_c/map.c @@ -597,7 +597,7 @@ static VALUE Map_freeze(VALUE _self) { * call-seq: * Map.internal_deep_freeze => self * - * Deeep freezes the map and values recursively. + * Deep freezes the map and values recursively. * Internal use only. */ static VALUE Map_internal_deep_freeze(VALUE _self) { diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c index d259ba7795be7..3966a9c009476 100644 --- a/ruby/ext/google/protobuf_c/message.c +++ b/ruby/ext/google/protobuf_c/message.c @@ -909,7 +909,7 @@ static VALUE Message_freeze(VALUE _self) { * call-seq: * Message.internal_deep_freeze => self * - * Deeep freezes the message object recursively. + * Deep freezes the message object recursively. * Internal use only. */ static VALUE Message_internal_deep_freeze(VALUE _self) { diff --git a/ruby/ext/google/protobuf_c/repeated_field.c b/ruby/ext/google/protobuf_c/repeated_field.c index 5a98388d95a40..e963ad7d59e76 100644 --- a/ruby/ext/google/protobuf_c/repeated_field.c +++ b/ruby/ext/google/protobuf_c/repeated_field.c @@ -512,7 +512,7 @@ static VALUE RepeatedField_freeze(VALUE _self) { * call-seq: * RepeatedField.internal_deep_freeze => self * - * Deeep freezes the repeated field and values recursively. + * Deep freezes the repeated field and values recursively. * Internal use only. */ static VALUE RepeatedField_internal_deep_freeze(VALUE _self) {