Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for options in CRuby, JRuby and FFI #14594

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d5ed3dd
Add support for descriptor options in ruby interface
jsteinberg May 16, 2023
71f7226
DRY converting serialized options into ruby object
jsteinberg May 17, 2023
04758ed
make serialized_options methods private
jsteinberg May 17, 2023
188f456
Options messages should recursively freeze (deep_freeze)
jsteinberg May 18, 2023
730fb06
Message internal_deep_freeze now recurses over repeated/map value fields
jsteinberg May 18, 2023
10b3e2b
Fix spelling issue (Deeep -> Deep)
jsteinberg May 22, 2023
018f8a7
Merge remote-tracking branch 'jsteinberg/add-support-for-options-in-r…
JasonLunn Nov 1, 2023
eb67c03
Implement `serialized_options` and for JRuby Descriptor classes and `…
JasonLunn Nov 2, 2023
8295112
Implement `serialized_options` and for FFI Descriptor classes and `in…
JasonLunn Nov 2, 2023
887ab6b
Move implementation of `options` from Ruby to C.
JasonLunn Nov 7, 2023
7830f55
Fix FFI implementation of `options`
JasonLunn Nov 7, 2023
33f6236
Fix test assertions that swapped `expected` and `actual` by using ass…
JasonLunn Nov 7, 2023
5b52c3a
Refactor JRuby implementation of `serialized_options` into `options`.
JasonLunn Nov 7, 2023
7ce6d7f
Don't expose `internal_deep_freeze` under CRuby, even as private method.
JasonLunn Nov 7, 2023
73dfef7
Remove extraneous import.
JasonLunn Nov 7, 2023
957654f
Fix calls to ``*_internal_deep_freeze`
JasonLunn Nov 7, 2023
1c86a90
Remove extraneous import.
JasonLunn Nov 7, 2023
6a230b1
Implement feedback from PR review:
JasonLunn Nov 8, 2023
1f2c8ba
Improve tests by:
JasonLunn Nov 8, 2023
3beac3d
Add `options` to `FieldDescriptors` under JRuby.
JasonLunn Nov 8, 2023
34b0dea
Fix imports.
JasonLunn Nov 8, 2023
16cc9e3
Simplify deep freeze logic.
JasonLunn Nov 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 110 additions & 1 deletion ruby/ext/google/protobuf_c/defs.c
Expand Up @@ -226,6 +226,25 @@ 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(const char *option_type, int size, const char *bytes, VALUE descriptor_pool) {
static const char * prefix = "google.protobuf.";
char fullname[strlen(prefix) + strlen(option_type) + 1];
JasonLunn marked this conversation as resolved.
Show resolved Hide resolved
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);

return Message_decode_bytes(size, bytes, 0, desc->klass, true);
}

/*
* call-seq:
* Descriptor.new => descriptor
Expand Down Expand Up @@ -374,6 +393,23 @@ 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("MessageOptions", size, serialized, self->descriptor_pool);
JasonLunn marked this conversation as resolved.
Show resolved Hide resolved
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);
Expand All @@ -385,6 +421,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;
Expand Down Expand Up @@ -484,12 +521,30 @@ 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("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;
}
Expand Down Expand Up @@ -540,7 +595,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.
*/
Expand Down Expand Up @@ -841,6 +896,23 @@ 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("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);
Expand All @@ -857,6 +929,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;
}
Expand Down Expand Up @@ -956,12 +1029,30 @@ 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("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;
Expand Down Expand Up @@ -1131,6 +1222,23 @@ 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("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);
Expand All @@ -1141,6 +1249,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;
Expand Down
32 changes: 32 additions & 0 deletions ruby/ext/google/protobuf_c/glue.c
Expand Up @@ -19,3 +19,35 @@ google_protobuf_FileDescriptorProto* FileDescriptorProto_parse(
return google_protobuf_FileDescriptorProto_parse(serialized_file_proto,
length, arena);
}

char* EnumDescriptor_serialized_options(const upb_EnumDef* enumdef, size_t *size) {
const google_protobuf_EnumOptions* opts = upb_EnumDef_Options(enumdef);
JasonLunn marked this conversation as resolved.
Show resolved Hide resolved
upb_Arena* arena = upb_Arena_New();
char* serialized = google_protobuf_EnumOptions_serialize(opts, arena, size);
upb_Arena_Free(arena);
return serialized;
}

char* FileDescriptor_serialized_options(const upb_FileDef* filedef, size_t *size) {
const google_protobuf_FileOptions* opts = upb_FileDef_Options(filedef);
upb_Arena* arena = upb_Arena_New();
char* serialized = google_protobuf_FileOptions_serialize(opts, arena, size);
upb_Arena_Free(arena);
return serialized;
}

char* Descriptor_serialized_options(const upb_MessageDef* msgdef, size_t *size) {
const google_protobuf_MessageOptions* opts = upb_MessageDef_Options(msgdef);
upb_Arena* arena = upb_Arena_New();
char* serialized = google_protobuf_MessageOptions_serialize(opts, arena, size);
upb_Arena_Free(arena);
return serialized;
}

char* OneOfDescriptor_serialized_options(const upb_OneofDef* oneofdef, size_t *size) {
const google_protobuf_OneofOptions* opts = upb_OneofDef_Options(oneofdef);
upb_Arena* arena = upb_Arena_New();
char* serialized = google_protobuf_OneofOptions_serialize(opts, arena, size);
upb_Arena_Free(arena);
return serialized;
}
24 changes: 24 additions & 0 deletions ruby/ext/google/protobuf_c/map.c
Expand Up @@ -572,6 +572,30 @@ 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);

if (!RB_OBJ_FROZEN(_self)) {
JasonLunn marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
3 changes: 3 additions & 0 deletions ruby/ext/google/protobuf_c/map.h
Expand Up @@ -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_
42 changes: 38 additions & 4 deletions ruby/ext/google/protobuf_c/message.c
Expand Up @@ -859,6 +859,35 @@ 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);
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 (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
Expand Down Expand Up @@ -911,7 +940,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)
Expand Down Expand Up @@ -942,18 +971,23 @@ 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_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;
}

Expand Down
6 changes: 6 additions & 0 deletions ruby/ext/google/protobuf_c/message.h
Expand Up @@ -73,6 +73,12 @@ 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);

Expand Down
25 changes: 25 additions & 0 deletions ruby/ext/google/protobuf_c/repeated_field.c
Expand Up @@ -487,6 +487,31 @@ 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);

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);
Message_internal_deep_freeze(val);
}
}
}

return _self;
}

/*
* call-seq:
* RepeatedField.hash => hash_value
Expand Down
3 changes: 3 additions & 0 deletions ruby/ext/google/protobuf_c/repeated_field.h
Expand Up @@ -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_