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

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=https://github.com/protocolbuffers/protobuf/pull/14594 from protocolbuffers:add-support-for-options-in-ruby 16cc9e35b8
PiperOrigin-RevId: 580848874
pull/14702/head
Jason Lunn 1 year ago committed by Copybara-Service
parent 92f6153279
commit ae1f2b7e1b
  1. 133
      ruby/ext/google/protobuf_c/defs.c
  2. 39
      ruby/ext/google/protobuf_c/glue.c
  3. 20
      ruby/ext/google/protobuf_c/map.c
  4. 3
      ruby/ext/google/protobuf_c/map.h
  5. 46
      ruby/ext/google/protobuf_c/message.c
  6. 7
      ruby/ext/google/protobuf_c/message.h
  7. 19
      ruby/ext/google/protobuf_c/repeated_field.c
  8. 3
      ruby/ext/google/protobuf_c/repeated_field.h
  9. 11
      ruby/lib/google/protobuf/ffi/descriptor.rb
  10. 5
      ruby/lib/google/protobuf/ffi/descriptor_pool.rb
  11. 12
      ruby/lib/google/protobuf/ffi/enum_descriptor.rb
  12. 40
      ruby/lib/google/protobuf/ffi/field_descriptor.rb
  13. 11
      ruby/lib/google/protobuf/ffi/file_descriptor.rb
  14. 11
      ruby/lib/google/protobuf/ffi/map.rb
  15. 15
      ruby/lib/google/protobuf/ffi/message.rb
  16. 29
      ruby/lib/google/protobuf/ffi/oneof_descriptor.rb
  17. 144
      ruby/lib/google/protobuf/ffi/repeated_field.rb
  18. 17
      ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java
  19. 23
      ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java
  20. 17
      ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java
  21. 17
      ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java
  22. 10
      ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java
  23. 24
      ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
  24. 17
      ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java
  25. 16
      ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java
  26. 44
      ruby/tests/basic.rb
  27. 34
      ruby/tests/basic_test.proto

@ -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;

@ -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;
}

@ -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

@ -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_

@ -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;
}

@ -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);

@ -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

@ -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_

@ -95,6 +95,15 @@ module Google
@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 @@ module Google
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 @@ module Google
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

@ -15,7 +15,7 @@ module Google
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 @@ module Google
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

@ -19,7 +19,7 @@ module Google
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 @@ module Google
@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 @@ module Google
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

@ -206,6 +206,15 @@ module Google
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 @@ module Google
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

@ -12,7 +12,9 @@ module Google
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 @@ module Google
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

@ -269,6 +269,17 @@ module Google
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)

@ -19,7 +19,7 @@ module Google
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 @@ module Google
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 @@ module Google
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 @@ module Google
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

@ -22,10 +22,7 @@ module Google
# @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 @@ module Google
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 @@ module Google
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

@ -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 @@ require 'forwardable'
# 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 @@ module Google
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 @@ module Google
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 @@ module Google
end
end
end
require 'google/protobuf/repeated_field'

@ -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 class RubyDescriptor extends RubyObject {
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);
}

@ -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 class RubyEnumDescriptor extends RubyObject {
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 @@ public class RubyEnumDescriptor extends RubyObject {
// 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;

@ -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 class RubyFieldDescriptor extends RubyObject {
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()

@ -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 class RubyFileDescriptor extends RubyObject {
}
}
@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;

@ -372,6 +372,16 @@ public class RubyMap extends RubyObject {
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);

@ -628,7 +628,11 @@ public class RubyMessage extends RubyObject {
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 class RubyMessage extends RubyObject {
}
});
}
if (freeze) {
ret.deepFreeze(context);
}
return ret;
}
@ -811,6 +817,22 @@ public class RubyMessage extends RubyObject {
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.");

@ -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 class RubyOneofDescriptor extends RubyObject {
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<RubyFieldDescriptor> getFields() {
return fields;
}

@ -111,6 +111,7 @@ public class RubyRepeatedField extends RubyObject {
*/
@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 class RubyRepeatedField extends RubyObject {
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 class RubyRepeatedField extends RubyObject {
*/
@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 class RubyRepeatedField extends RubyObject {
*/
@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 class RubyRepeatedField extends RubyObject {
*/
@JRubyMethod
public IRubyObject clear(ThreadContext context) {
testFrozen("Can't clear frozen repeated field");
this.storage.clear();
return this;
}
@ -274,6 +279,7 @@ public class RubyRepeatedField extends RubyObject {
*/
@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 class RubyRepeatedField extends RubyObject {
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);

@ -695,6 +695,50 @@ module BasicTest
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

@ -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<string, TestMessage2> 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<int32, Inner> 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 {

Loading…
Cancel
Save