Skip to content

Commit 2495d4f

Browse files
authored
Add support for options in CRuby, JRuby and FFI (#14594) (#14739)
Rewrrte and extension of #12828, with additional work for JRuby. Partially fixes #1198 by adding support for custom options. Handling of extensions will be handled in a follow up. Also includes these unrelated fixes: * Removes code echo between `google/protobuf/repeated_field.rb` and `google/protobuf/ffi/repeated_field.rb` by `require`'ing the former in the latter. * Adds missing calles to `testFrozen()` from methods of `RepeatedField` under JRuby that mutate. * Various typos in comments. Closes #14594 COPYBARA_INTEGRATE_REVIEW=#14594 from protocolbuffers:add-support-for-options-in-ruby 16cc9e3 PiperOrigin-RevId: 580848874
1 parent a29f47d commit 2495d4f

27 files changed

+584
-183
lines changed

ruby/ext/google/protobuf_c/defs.c

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ static VALUE rb_str_maybe_null(const char* s) {
4444
}
4545
return rb_str_new2(s);
4646
}
47-
47+
static ID options_instancevar_interned;
4848
// -----------------------------------------------------------------------------
4949
// DescriptorPool.
5050
// -----------------------------------------------------------------------------
@@ -192,6 +192,7 @@ static void DescriptorPool_register(VALUE module) {
192192

193193
rb_gc_register_address(&generated_pool);
194194
generated_pool = rb_class_new_instance(0, NULL, klass);
195+
options_instancevar_interned = rb_intern("options");
195196
}
196197

197198
// -----------------------------------------------------------------------------
@@ -226,6 +227,35 @@ static Descriptor* ruby_to_Descriptor(VALUE val) {
226227
return ret;
227228
}
228229

230+
// Decode and return a frozen instance of a Descriptor Option for the given pool
231+
static VALUE decode_options(VALUE self, const char* option_type, int size,
232+
const char* bytes, VALUE descriptor_pool) {
233+
VALUE options_rb = rb_ivar_get(self, options_instancevar_interned);
234+
if (options_rb != Qnil) {
235+
return options_rb;
236+
}
237+
238+
static const char* prefix = "google.protobuf.";
239+
char fullname
240+
[/*strlen(prefix)*/ 16 +
241+
/*strln(longest option type supported e.g. "MessageOptions")*/ 14 +
242+
/*null terminator*/ 1];
243+
244+
snprintf(fullname, sizeof(fullname), "%s%s", prefix, option_type);
245+
const upb_MessageDef* msgdef = upb_DefPool_FindMessageByName(
246+
ruby_to_DescriptorPool(descriptor_pool)->symtab, fullname);
247+
if (!msgdef) {
248+
rb_raise(rb_eRuntimeError, "Cannot find %s in DescriptorPool", option_type);
249+
}
250+
251+
VALUE desc_rb = get_msgdef_obj(descriptor_pool, msgdef);
252+
const Descriptor* desc = ruby_to_Descriptor(desc_rb);
253+
254+
options_rb = Message_decode_bytes(size, bytes, 0, desc->klass, true);
255+
rb_ivar_set(self, options_instancevar_interned, options_rb);
256+
return options_rb;
257+
}
258+
229259
/*
230260
* call-seq:
231261
* Descriptor.new => descriptor
@@ -374,6 +404,26 @@ static VALUE Descriptor_msgclass(VALUE _self) {
374404
return self->klass;
375405
}
376406

407+
/*
408+
* call-seq:
409+
* Descriptor.options => options
410+
*
411+
* Returns the `MessageOptions` for this `Descriptor`.
412+
*/
413+
static VALUE Descriptor_options(VALUE _self) {
414+
Descriptor* self = ruby_to_Descriptor(_self);
415+
const google_protobuf_MessageOptions* opts =
416+
upb_MessageDef_Options(self->msgdef);
417+
upb_Arena* arena = upb_Arena_New();
418+
size_t size;
419+
char* serialized =
420+
google_protobuf_MessageOptions_serialize(opts, arena, &size);
421+
VALUE message_options = decode_options(_self, "MessageOptions", size,
422+
serialized, self->descriptor_pool);
423+
upb_Arena_Free(arena);
424+
return message_options;
425+
}
426+
377427
static void Descriptor_register(VALUE module) {
378428
VALUE klass = rb_define_class_under(module, "Descriptor", rb_cObject);
379429
rb_define_alloc_func(klass, Descriptor_alloc);
@@ -385,6 +435,7 @@ static void Descriptor_register(VALUE module) {
385435
rb_define_method(klass, "msgclass", Descriptor_msgclass, 0);
386436
rb_define_method(klass, "name", Descriptor_name, 0);
387437
rb_define_method(klass, "file_descriptor", Descriptor_file_descriptor, 0);
438+
rb_define_method(klass, "options", Descriptor_options, 0);
388439
rb_include_module(klass, rb_mEnumerable);
389440
rb_gc_register_address(&cDescriptor);
390441
cDescriptor = klass;
@@ -484,12 +535,31 @@ static VALUE FileDescriptor_syntax(VALUE _self) {
484535
}
485536
}
486537

538+
/*
539+
* call-seq:
540+
* FileDescriptor.options => options
541+
*
542+
* Returns the `FileOptions` for this `FileDescriptor`.
543+
*/
544+
static VALUE FileDescriptor_options(VALUE _self) {
545+
FileDescriptor* self = ruby_to_FileDescriptor(_self);
546+
const google_protobuf_FileOptions* opts = upb_FileDef_Options(self->filedef);
547+
upb_Arena* arena = upb_Arena_New();
548+
size_t size;
549+
char* serialized = google_protobuf_FileOptions_serialize(opts, arena, &size);
550+
VALUE file_options = decode_options(_self, "FileOptions", size, serialized,
551+
self->descriptor_pool);
552+
upb_Arena_Free(arena);
553+
return file_options;
554+
}
555+
487556
static void FileDescriptor_register(VALUE module) {
488557
VALUE klass = rb_define_class_under(module, "FileDescriptor", rb_cObject);
489558
rb_define_alloc_func(klass, FileDescriptor_alloc);
490559
rb_define_method(klass, "initialize", FileDescriptor_initialize, 3);
491560
rb_define_method(klass, "name", FileDescriptor_name, 0);
492561
rb_define_method(klass, "syntax", FileDescriptor_syntax, 0);
562+
rb_define_method(klass, "options", FileDescriptor_options, 0);
493563
rb_gc_register_address(&cFileDescriptor);
494564
cFileDescriptor = klass;
495565
}
@@ -540,7 +610,7 @@ static VALUE FieldDescriptor_alloc(VALUE klass) {
540610

541611
/*
542612
* call-seq:
543-
* EnumDescriptor.new(c_only_cookie, pool, ptr) => EnumDescriptor
613+
* FieldDescriptor.new(c_only_cookie, pool, ptr) => FieldDescriptor
544614
*
545615
* Creates a descriptor wrapper object. May only be called from C.
546616
*/
@@ -841,6 +911,25 @@ static VALUE FieldDescriptor_set(VALUE _self, VALUE msg_rb, VALUE value) {
841911
return Qnil;
842912
}
843913

914+
/*
915+
* call-seq:
916+
* FieldDescriptor.options => options
917+
*
918+
* Returns the `FieldOptions` for this `FieldDescriptor`.
919+
*/
920+
static VALUE FieldDescriptor_options(VALUE _self) {
921+
FieldDescriptor* self = ruby_to_FieldDescriptor(_self);
922+
const google_protobuf_FieldOptions* opts =
923+
upb_FieldDef_Options(self->fielddef);
924+
upb_Arena* arena = upb_Arena_New();
925+
size_t size;
926+
char* serialized = google_protobuf_FieldOptions_serialize(opts, arena, &size);
927+
VALUE field_options = decode_options(_self, "FieldOptions", size, serialized,
928+
self->descriptor_pool);
929+
upb_Arena_Free(arena);
930+
return field_options;
931+
}
932+
844933
static void FieldDescriptor_register(VALUE module) {
845934
VALUE klass = rb_define_class_under(module, "FieldDescriptor", rb_cObject);
846935
rb_define_alloc_func(klass, FieldDescriptor_alloc);
@@ -857,6 +946,7 @@ static void FieldDescriptor_register(VALUE module) {
857946
rb_define_method(klass, "clear", FieldDescriptor_clear, 1);
858947
rb_define_method(klass, "get", FieldDescriptor_get, 1);
859948
rb_define_method(klass, "set", FieldDescriptor_set, 2);
949+
rb_define_method(klass, "options", FieldDescriptor_options, 0);
860950
rb_gc_register_address(&cFieldDescriptor);
861951
cFieldDescriptor = klass;
862952
}
@@ -956,12 +1046,32 @@ static VALUE OneofDescriptor_each(VALUE _self) {
9561046
return Qnil;
9571047
}
9581048

1049+
/*
1050+
* call-seq:
1051+
* OneofDescriptor.options => options
1052+
*
1053+
* Returns the `OneofOptions` for this `OneofDescriptor`.
1054+
*/
1055+
static VALUE OneOfDescriptor_options(VALUE _self) {
1056+
OneofDescriptor* self = ruby_to_OneofDescriptor(_self);
1057+
const google_protobuf_OneofOptions* opts =
1058+
upb_OneofDef_Options(self->oneofdef);
1059+
upb_Arena* arena = upb_Arena_New();
1060+
size_t size;
1061+
char* serialized = google_protobuf_OneofOptions_serialize(opts, arena, &size);
1062+
VALUE oneof_options = decode_options(_self, "OneofOptions", size, serialized,
1063+
self->descriptor_pool);
1064+
upb_Arena_Free(arena);
1065+
return oneof_options;
1066+
}
1067+
9591068
static void OneofDescriptor_register(VALUE module) {
9601069
VALUE klass = rb_define_class_under(module, "OneofDescriptor", rb_cObject);
9611070
rb_define_alloc_func(klass, OneofDescriptor_alloc);
9621071
rb_define_method(klass, "initialize", OneofDescriptor_initialize, 3);
9631072
rb_define_method(klass, "name", OneofDescriptor_name, 0);
9641073
rb_define_method(klass, "each", OneofDescriptor_each, 0);
1074+
rb_define_method(klass, "options", OneOfDescriptor_options, 0);
9651075
rb_include_module(klass, rb_mEnumerable);
9661076
rb_gc_register_address(&cOneofDescriptor);
9671077
cOneofDescriptor = klass;
@@ -1131,6 +1241,24 @@ static VALUE EnumDescriptor_enummodule(VALUE _self) {
11311241
return self->module;
11321242
}
11331243

1244+
/*
1245+
* call-seq:
1246+
* EnumDescriptor.options => options
1247+
*
1248+
* Returns the `EnumOptions` for this `EnumDescriptor`.
1249+
*/
1250+
static VALUE EnumDescriptor_options(VALUE _self) {
1251+
EnumDescriptor* self = ruby_to_EnumDescriptor(_self);
1252+
const google_protobuf_EnumOptions* opts = upb_EnumDef_Options(self->enumdef);
1253+
upb_Arena* arena = upb_Arena_New();
1254+
size_t size;
1255+
char* serialized = google_protobuf_EnumOptions_serialize(opts, arena, &size);
1256+
VALUE enum_options = decode_options(_self, "EnumOptions", size, serialized,
1257+
self->descriptor_pool);
1258+
upb_Arena_Free(arena);
1259+
return enum_options;
1260+
}
1261+
11341262
static void EnumDescriptor_register(VALUE module) {
11351263
VALUE klass = rb_define_class_under(module, "EnumDescriptor", rb_cObject);
11361264
rb_define_alloc_func(klass, EnumDescriptor_alloc);
@@ -1141,6 +1269,7 @@ static void EnumDescriptor_register(VALUE module) {
11411269
rb_define_method(klass, "each", EnumDescriptor_each, 0);
11421270
rb_define_method(klass, "enummodule", EnumDescriptor_enummodule, 0);
11431271
rb_define_method(klass, "file_descriptor", EnumDescriptor_file_descriptor, 0);
1272+
rb_define_method(klass, "options", EnumDescriptor_options, 0);
11441273
rb_include_module(klass, rb_mEnumerable);
11451274
rb_gc_register_address(&cEnumDescriptor);
11461275
cEnumDescriptor = klass;

ruby/ext/google/protobuf_c/glue.c

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,43 @@
1414
upb_Arena* Arena_create() { return upb_Arena_Init(NULL, 0, &upb_alloc_global); }
1515

1616
google_protobuf_FileDescriptorProto* FileDescriptorProto_parse(
17-
const char* serialized_file_proto, size_t length) {
18-
upb_Arena* arena = Arena_create();
17+
const char* serialized_file_proto, size_t length, upb_Arena* arena) {
1918
return google_protobuf_FileDescriptorProto_parse(serialized_file_proto,
2019
length, arena);
2120
}
21+
22+
char* EnumDescriptor_serialized_options(const upb_EnumDef* enumdef,
23+
size_t* size, upb_Arena* arena) {
24+
const google_protobuf_EnumOptions* opts = upb_EnumDef_Options(enumdef);
25+
char* serialized = google_protobuf_EnumOptions_serialize(opts, arena, size);
26+
return serialized;
27+
}
28+
29+
char* FileDescriptor_serialized_options(const upb_FileDef* filedef,
30+
size_t* size, upb_Arena* arena) {
31+
const google_protobuf_FileOptions* opts = upb_FileDef_Options(filedef);
32+
char* serialized = google_protobuf_FileOptions_serialize(opts, arena, size);
33+
return serialized;
34+
}
35+
36+
char* Descriptor_serialized_options(const upb_MessageDef* msgdef, size_t* size,
37+
upb_Arena* arena) {
38+
const google_protobuf_MessageOptions* opts = upb_MessageDef_Options(msgdef);
39+
char* serialized =
40+
google_protobuf_MessageOptions_serialize(opts, arena, size);
41+
return serialized;
42+
}
43+
44+
char* OneOfDescriptor_serialized_options(const upb_OneofDef* oneofdef,
45+
size_t* size, upb_Arena* arena) {
46+
const google_protobuf_OneofOptions* opts = upb_OneofDef_Options(oneofdef);
47+
char* serialized = google_protobuf_OneofOptions_serialize(opts, arena, size);
48+
return serialized;
49+
}
50+
51+
char* FieldDescriptor_serialized_options(const upb_FieldDef* fielddef,
52+
size_t* size, upb_Arena* arena) {
53+
const google_protobuf_FieldOptions* opts = upb_FieldDef_Options(fielddef);
54+
char* serialized = google_protobuf_FieldOptions_serialize(opts, arena, size);
55+
return serialized;
56+
}

ruby/ext/google/protobuf_c/map.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,26 @@ static VALUE Map_freeze(VALUE _self) {
572572
return _self;
573573
}
574574

575+
/*
576+
* Deep freezes the map and values recursively.
577+
* Internal use only.
578+
*/
579+
VALUE Map_internal_deep_freeze(VALUE _self) {
580+
Map* self = ruby_to_Map(_self);
581+
Map_freeze(_self);
582+
if (self->value_type_info.type == kUpb_CType_Message) {
583+
size_t iter = kUpb_Map_Begin;
584+
upb_MessageValue key, val;
585+
586+
while (upb_Map_Next(self->map, &key, &val, &iter)) {
587+
VALUE val_val =
588+
Convert_UpbToRuby(val, self->value_type_info, self->arena);
589+
Message_internal_deep_freeze(val_val);
590+
}
591+
}
592+
return _self;
593+
}
594+
575595
/*
576596
* call-seq:
577597
* Map.hash => hash_value

ruby/ext/google/protobuf_c/map.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,7 @@ extern VALUE cMap;
3838
// Call at startup to register all types in this module.
3939
void Map_register(VALUE module);
4040

41+
// Recursively freeze map
42+
VALUE Map_internal_deep_freeze(VALUE _self);
43+
4144
#endif // RUBY_PROTOBUF_MAP_H_

ruby/ext/google/protobuf_c/message.c

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,32 @@ static VALUE Message_freeze(VALUE _self) {
859859
return _self;
860860
}
861861

862+
/*
863+
* Deep freezes the message object recursively.
864+
* Internal use only.
865+
*/
866+
VALUE Message_internal_deep_freeze(VALUE _self) {
867+
Message* self = ruby_to_Message(_self);
868+
Message_freeze(_self);
869+
870+
int n = upb_MessageDef_FieldCount(self->msgdef);
871+
for (int i = 0; i < n; i++) {
872+
const upb_FieldDef* f = upb_MessageDef_Field(self->msgdef, i);
873+
VALUE field = Message_getfield(_self, f);
874+
875+
if (field != Qnil) {
876+
if (upb_FieldDef_IsMap(f)) {
877+
Map_internal_deep_freeze(field);
878+
} else if (upb_FieldDef_IsRepeated(f)) {
879+
RepeatedField_internal_deep_freeze(field);
880+
} else if (upb_FieldDef_IsSubMessage(f)) {
881+
Message_internal_deep_freeze(field);
882+
}
883+
}
884+
}
885+
return _self;
886+
}
887+
862888
/*
863889
* call-seq:
864890
* Message.[](index) => value
@@ -911,7 +937,7 @@ static VALUE Message_index_set(VALUE _self, VALUE field_name, VALUE value) {
911937
* MessageClass.decode(data, options) => message
912938
*
913939
* Decodes the given data (as a string containing bytes in protocol buffers wire
914-
* format) under the interpretration given by this message class's definition
940+
* format) under the interpretation given by this message class's definition
915941
* and returns a message object with the corresponding field values.
916942
* @param options [Hash] options for the decoder
917943
* 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) {
942968
rb_raise(rb_eArgError, "Expected string for binary protobuf data.");
943969
}
944970

971+
return Message_decode_bytes(RSTRING_LEN(data), RSTRING_PTR(data), options,
972+
klass, /*freeze*/ false);
973+
}
974+
975+
VALUE Message_decode_bytes(int size, const char* bytes, int options,
976+
VALUE klass, bool freeze) {
945977
VALUE msg_rb = initialize_rb_class_with_no_args(klass);
946978
Message* msg = ruby_to_Message(msg_rb);
947979

948-
upb_DecodeStatus status =
949-
upb_Decode(RSTRING_PTR(data), RSTRING_LEN(data), (upb_Message*)msg->msg,
950-
upb_MessageDef_MiniTable(msg->msgdef), NULL, options,
951-
Arena_get(msg->arena));
952-
980+
upb_DecodeStatus status = upb_Decode(bytes, size, (upb_Message*)msg->msg,
981+
upb_MessageDef_MiniTable(msg->msgdef),
982+
NULL, options, Arena_get(msg->arena));
953983
if (status != kUpb_DecodeStatus_Ok) {
954984
rb_raise(cParseError, "Error occurred during parsing");
955985
}
956-
986+
if (freeze) {
987+
Message_internal_deep_freeze(msg_rb);
988+
}
957989
return msg_rb;
958990
}
959991

ruby/ext/google/protobuf_c/message.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ VALUE build_module_from_enumdesc(VALUE _enumdesc);
7373
// module.
7474
VALUE MessageOrEnum_GetDescriptor(VALUE klass);
7575

76+
// Decodes a Message from a byte sequence.
77+
VALUE Message_decode_bytes(int size, const char* bytes, int options,
78+
VALUE klass, bool freeze);
79+
80+
// Recursively freeze message
81+
VALUE Message_internal_deep_freeze(VALUE _self);
82+
7683
// Call at startup to register all types in this module.
7784
void Message_register(VALUE protobuf);
7885

0 commit comments

Comments
 (0)