Remove `Arena_pin` in favor of adopting the UPB freezing API.

PiperOrigin-RevId: 639960700
pull/16949/head
Protobuf Team Bot 10 months ago committed by Copybara-Service
parent 5d40c4f434
commit 1194440c24
  1. 91
      ruby/ext/google/protobuf_c/map.c
  2. 8
      ruby/ext/google/protobuf_c/map.h
  3. 106
      ruby/ext/google/protobuf_c/message.c
  4. 2
      ruby/ext/google/protobuf_c/message.h
  5. 20
      ruby/ext/google/protobuf_c/protobuf.c
  6. 10
      ruby/ext/google/protobuf_c/protobuf.h
  7. 77
      ruby/ext/google/protobuf_c/repeated_field.c
  8. 6
      ruby/ext/google/protobuf_c/repeated_field.h
  9. 1
      ruby/lib/google/protobuf/ffi/descriptor.rb
  10. 6
      ruby/lib/google/protobuf/ffi/internal/arena.rb
  11. 66
      ruby/lib/google/protobuf/ffi/map.rb
  12. 238
      ruby/lib/google/protobuf/ffi/message.rb
  13. 58
      ruby/lib/google/protobuf/ffi/repeated_field.rb

@ -63,8 +63,8 @@ static VALUE Map_alloc(VALUE klass) {
return TypedData_Wrap_Struct(klass, &Map_type, self); return TypedData_Wrap_Struct(klass, &Map_type, self);
} }
VALUE Map_GetRubyWrapper(upb_Map* map, upb_CType key_type, TypeInfo value_type, VALUE Map_GetRubyWrapper(const upb_Map* map, upb_CType key_type,
VALUE arena) { TypeInfo value_type, VALUE arena) {
PBRUBY_ASSERT(map); PBRUBY_ASSERT(map);
VALUE val = ObjectCache_Get(map); VALUE val = ObjectCache_Get(map);
@ -83,7 +83,6 @@ VALUE Map_GetRubyWrapper(upb_Map* map, upb_CType key_type, TypeInfo value_type,
} }
return ObjectCache_TryAdd(map, val); return ObjectCache_TryAdd(map, val);
} }
return val; return val;
} }
@ -105,8 +104,9 @@ static TypeInfo Map_keyinfo(Map* self) {
} }
static upb_Map* Map_GetMutable(VALUE _self) { static upb_Map* Map_GetMutable(VALUE _self) {
rb_check_frozen(_self); const upb_Map* map = ruby_to_Map(_self)->map;
return (upb_Map*)ruby_to_Map(_self)->map; Protobuf_CheckNotFrozen(_self, upb_Map_IsFrozen(map));
return (upb_Map*)map;
} }
VALUE Map_CreateHash(const upb_Map* map, upb_CType key_type, VALUE Map_CreateHash(const upb_Map* map, upb_CType key_type,
@ -439,14 +439,14 @@ static VALUE Map_has_key(VALUE _self, VALUE key) {
* nil if none was present. Throws an exception if the key is of the wrong type. * nil if none was present. Throws an exception if the key is of the wrong type.
*/ */
static VALUE Map_delete(VALUE _self, VALUE key) { static VALUE Map_delete(VALUE _self, VALUE key) {
upb_Map* map = Map_GetMutable(_self);
Map* self = ruby_to_Map(_self); Map* self = ruby_to_Map(_self);
rb_check_frozen(_self);
upb_MessageValue key_upb = upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL); Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
upb_MessageValue val_upb; upb_MessageValue val_upb;
if (upb_Map_Delete(Map_GetMutable(_self), key_upb, &val_upb)) { if (upb_Map_Delete(map, key_upb, &val_upb)) {
return Convert_UpbToRuby(val_upb, self->value_type_info, self->arena); return Convert_UpbToRuby(val_upb, self->value_type_info, self->arena);
} else { } else {
return Qnil; return Qnil;
@ -560,29 +560,79 @@ VALUE Map_eq(VALUE _self, VALUE _other) {
/* /*
* call-seq: * call-seq:
* Message.freeze => self * Map.frozen? => bool
*
* Returns true if the map is frozen in either Ruby or the underlying
* representation. Freezes the Ruby map object if it is not already frozen in
* Ruby but it is frozen in the underlying representation.
*/
VALUE Map_frozen(VALUE _self) {
Map* self = ruby_to_Map(_self);
if (!upb_Map_IsFrozen(self->map)) {
PBRUBY_ASSERT(!RB_OBJ_FROZEN(_self));
return Qfalse;
}
// Lazily freeze the Ruby wrapper.
if (!RB_OBJ_FROZEN(_self)) RB_OBJ_FREEZE(_self);
return Qtrue;
}
/*
* call-seq:
* Map.freeze => self
* *
* Freezes the message object. We have to intercept this so we can pin the * Freezes the map object. We have to intercept this so we can freeze the
* Ruby object into memory so we don't forget it's frozen. * underlying representation, not just the Ruby wrapper.
*/ */
VALUE Map_freeze(VALUE _self) { VALUE Map_freeze(VALUE _self) {
Map* self = ruby_to_Map(_self); Map* self = ruby_to_Map(_self);
if (RB_OBJ_FROZEN(_self)) {
PBRUBY_ASSERT(upb_Map_IsFrozen(self->map));
return _self;
}
if (!upb_Map_IsFrozen(self->map)) {
if (self->value_type_info.type == kUpb_CType_Message) {
upb_Map_Freeze(
Map_GetMutable(_self),
upb_MessageDef_MiniTable(self->value_type_info.def.msgdef));
} else {
upb_Map_Freeze(Map_GetMutable(_self), NULL);
}
}
if (RB_OBJ_FROZEN(_self)) return _self;
Arena_Pin(self->arena, _self);
RB_OBJ_FREEZE(_self); RB_OBJ_FREEZE(_self);
if (self->value_type_info.type == kUpb_CType_Message) { return _self;
size_t iter = kUpb_Map_Begin; }
upb_MessageValue key, val;
VALUE Map_EmptyFrozen(const upb_FieldDef* f) {
PBRUBY_ASSERT(upb_FieldDef_IsMap(f));
VALUE val = ObjectCache_Get(f);
while (upb_Map_Next(self->map, &key, &val, &iter)) { if (val == Qnil) {
VALUE val_val = const upb_FieldDef* key_f = map_field_key(f);
Convert_UpbToRuby(val, self->value_type_info, self->arena); const upb_FieldDef* val_f = map_field_value(f);
Message_freeze(val_val); upb_CType key_type = upb_FieldDef_CType(key_f);
TypeInfo value_type_info = TypeInfo_get(val_f);
val = Map_alloc(cMap);
Map* self;
TypedData_Get_Struct(val, Map, &Map_type, self);
self->arena = Arena_new();
self->map =
upb_Map_New(Arena_get(self->arena), key_type, value_type_info.type);
self->key_type = key_type;
self->value_type_info = value_type_info;
if (self->value_type_info.type == kUpb_CType_Message) {
const upb_MessageDef* val_m = value_type_info.def.msgdef;
self->value_type_class = Descriptor_DefToClass(val_m);
} }
return ObjectCache_TryAdd(f, Map_freeze(val));
} }
return _self; PBRUBY_ASSERT(RB_OBJ_FROZEN(val));
PBRUBY_ASSERT(upb_Map_IsFrozen(ruby_to_Map(val)->map));
return val;
} }
/* /*
@ -671,6 +721,7 @@ void Map_register(VALUE module) {
rb_define_method(klass, "clone", Map_dup, 0); rb_define_method(klass, "clone", Map_dup, 0);
rb_define_method(klass, "==", Map_eq, 1); rb_define_method(klass, "==", Map_eq, 1);
rb_define_method(klass, "freeze", Map_freeze, 0); rb_define_method(klass, "freeze", Map_freeze, 0);
rb_define_method(klass, "frozen?", Map_frozen, 0);
rb_define_method(klass, "hash", Map_hash, 0); rb_define_method(klass, "hash", Map_hash, 0);
rb_define_method(klass, "to_h", Map_to_h, 0); rb_define_method(klass, "to_h", Map_to_h, 0);
rb_define_method(klass, "inspect", Map_inspect, 0); rb_define_method(klass, "inspect", Map_inspect, 0);

@ -11,10 +11,14 @@
#include "protobuf.h" #include "protobuf.h"
#include "ruby-upb.h" #include "ruby-upb.h"
// Returns a frozen sentinel Ruby wrapper object for an empty upb_Map with the
// key and value types specified by the field. Creates one if it doesn't exist.
VALUE Map_EmptyFrozen(const upb_FieldDef* f);
// Returns a Ruby wrapper object for the given map, which will be created if // Returns a Ruby wrapper object for the given map, which will be created if
// one does not exist already. // one does not exist already.
VALUE Map_GetRubyWrapper(upb_Map *map, upb_CType key_type, TypeInfo value_type, VALUE Map_GetRubyWrapper(const upb_Map *map, upb_CType key_type,
VALUE arena); TypeInfo value_type, VALUE arena);
// Gets the underlying upb_Map for this Ruby map object, which must have // Gets the underlying upb_Map for this Ruby map object, which must have
// key/value type that match |field|. If this is not a map or the type doesn't // key/value type that match |field|. If this is not a map or the type doesn't

@ -80,11 +80,12 @@ const upb_Message* Message_Get(VALUE msg_rb, const upb_MessageDef** m) {
} }
upb_Message* Message_GetMutable(VALUE msg_rb, const upb_MessageDef** m) { upb_Message* Message_GetMutable(VALUE msg_rb, const upb_MessageDef** m) {
rb_check_frozen(msg_rb); const upb_Message* upb_msg = Message_Get(msg_rb, m);
return (upb_Message*)Message_Get(msg_rb, m); Protobuf_CheckNotFrozen(msg_rb, upb_Message_IsFrozen(upb_msg));
return (upb_Message*)upb_msg;
} }
void Message_InitPtr(VALUE self_, upb_Message* msg, VALUE arena) { void Message_InitPtr(VALUE self_, const upb_Message* msg, VALUE arena) {
Message* self = ruby_to_Message(self_); Message* self = ruby_to_Message(self_);
self->msg = msg; self->msg = msg;
RB_OBJ_WRITE(self_, &self->arena, arena); RB_OBJ_WRITE(self_, &self->arena, arena);
@ -105,7 +106,7 @@ void Message_CheckClass(VALUE klass) {
} }
} }
VALUE Message_GetRubyWrapper(upb_Message* msg, const upb_MessageDef* m, VALUE Message_GetRubyWrapper(const upb_Message* msg, const upb_MessageDef* m,
VALUE arena) { VALUE arena) {
if (msg == NULL) return Qnil; if (msg == NULL) return Qnil;
@ -116,7 +117,6 @@ VALUE Message_GetRubyWrapper(upb_Message* msg, const upb_MessageDef* m,
val = Message_alloc(klass); val = Message_alloc(klass);
Message_InitPtr(val, msg, arena); Message_InitPtr(val, msg, arena);
} }
return val; return val;
} }
@ -288,13 +288,42 @@ static void Message_setfield(upb_Message* msg, const upb_FieldDef* f, VALUE val,
upb_Message_SetFieldByDef(msg, f, msgval, arena); upb_Message_SetFieldByDef(msg, f, msgval, arena);
} }
VALUE Message_getfield_frozen(const upb_Message* msg, const upb_FieldDef* f,
VALUE arena) {
upb_MessageValue msgval = upb_Message_GetFieldByDef(msg, f);
if (upb_FieldDef_IsMap(f)) {
if (msgval.map_val == NULL) {
return Map_EmptyFrozen(f);
}
const upb_FieldDef* key_f = map_field_key(f);
const upb_FieldDef* val_f = map_field_value(f);
upb_CType key_type = upb_FieldDef_CType(key_f);
TypeInfo value_type_info = TypeInfo_get(val_f);
return Map_GetRubyWrapper(msgval.map_val, key_type, value_type_info, Qnil);
}
if (upb_FieldDef_IsRepeated(f)) {
if (msgval.array_val == NULL) {
return RepeatedField_EmptyFrozen(f);
}
return RepeatedField_GetRubyWrapper(msgval.array_val, TypeInfo_get(f),
Qnil);
}
VALUE ret;
if (upb_FieldDef_IsSubMessage(f)) {
const upb_MessageDef* m = upb_FieldDef_MessageSubDef(f);
ret = Message_GetRubyWrapper(msgval.msg_val, m, arena);
} else {
ret = Convert_UpbToRuby(msgval, TypeInfo_get(f), Qnil);
}
return ret;
}
VALUE Message_getfield(VALUE _self, const upb_FieldDef* f) { VALUE Message_getfield(VALUE _self, const upb_FieldDef* f) {
Message* self = ruby_to_Message(_self); Message* self = ruby_to_Message(_self);
// This is a special-case: upb_Message_Mutable() for map & array are logically if (upb_Message_IsFrozen(self->msg)) {
// const (they will not change what is serialized) but physically return Message_getfield_frozen(self->msg, f, self->arena);
// non-const, as they do allocate a repeated field or map. The logical }
// constness means it's ok to do even if the message is frozen. upb_Message* msg = Message_GetMutable(_self, NULL);
upb_Message* msg = (upb_Message*)self->msg;
upb_Arena* arena = Arena_get(self->arena); upb_Arena* arena = Arena_get(self->arena);
if (upb_FieldDef_IsMap(f)) { if (upb_FieldDef_IsMap(f)) {
upb_Map* map = upb_Message_Mutable(msg, f, arena).map; upb_Map* map = upb_Message_Mutable(msg, f, arena).map;
@ -307,12 +336,12 @@ VALUE Message_getfield(VALUE _self, const upb_FieldDef* f) {
upb_Array* arr = upb_Message_Mutable(msg, f, arena).array; upb_Array* arr = upb_Message_Mutable(msg, f, arena).array;
return RepeatedField_GetRubyWrapper(arr, TypeInfo_get(f), self->arena); return RepeatedField_GetRubyWrapper(arr, TypeInfo_get(f), self->arena);
} else if (upb_FieldDef_IsSubMessage(f)) { } else if (upb_FieldDef_IsSubMessage(f)) {
if (!upb_Message_HasFieldByDef(self->msg, f)) return Qnil; if (!upb_Message_HasFieldByDef(msg, f)) return Qnil;
upb_Message* submsg = upb_Message_Mutable(msg, f, arena).msg; upb_Message* submsg = upb_Message_Mutable(msg, f, arena).msg;
const upb_MessageDef* m = upb_FieldDef_MessageSubDef(f); const upb_MessageDef* m = upb_FieldDef_MessageSubDef(f);
return Message_GetRubyWrapper(submsg, m, self->arena); return Message_GetRubyWrapper(submsg, m, self->arena);
} else { } else {
upb_MessageValue msgval = upb_Message_GetFieldByDef(self->msg, f); upb_MessageValue msgval = upb_Message_GetFieldByDef(msg, f);
return Convert_UpbToRuby(msgval, TypeInfo_get(f), self->arena); return Convert_UpbToRuby(msgval, TypeInfo_get(f), self->arena);
} }
} }
@ -436,7 +465,6 @@ static VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
if (argc != 2) { if (argc != 2) {
rb_raise(rb_eArgError, "Expected 2 arguments, received %d", argc); rb_raise(rb_eArgError, "Expected 2 arguments, received %d", argc);
} }
rb_check_frozen(_self);
break; break;
default: default:
if (argc != 1) { if (argc != 1) {
@ -812,33 +840,42 @@ static VALUE Message_to_h(VALUE _self) {
/* /*
* call-seq: * call-seq:
* Message.freeze => self * Message.frozen? => bool
* *
* Freezes the message object. We have to intercept this so we can pin the * Returns true if the message is frozen in either Ruby or the underlying
* Ruby object into memory so we don't forget it's frozen. * representation. Freezes the Ruby message object if it is not already frozen
* in Ruby but it is frozen in the underlying representation.
*/ */
VALUE Message_freeze(VALUE _self) { VALUE Message_frozen(VALUE _self) {
Message* self = ruby_to_Message(_self); Message* self = ruby_to_Message(_self);
if (!upb_Message_IsFrozen(self->msg)) {
PBRUBY_ASSERT(!RB_OBJ_FROZEN(_self));
return Qfalse;
}
if (RB_OBJ_FROZEN(_self)) return _self; // Lazily freeze the Ruby wrapper.
Arena_Pin(self->arena, _self); if (!RB_OBJ_FROZEN(_self)) RB_OBJ_FREEZE(_self);
RB_OBJ_FREEZE(_self); return Qtrue;
}
int n = upb_MessageDef_FieldCount(self->msgdef); /*
for (int i = 0; i < n; i++) { * call-seq:
const upb_FieldDef* f = upb_MessageDef_Field(self->msgdef, i); * Message.freeze => self
VALUE field = Message_getfield(_self, f); *
* Freezes the message object. We have to intercept this so we can freeze the
if (field != Qnil) { * underlying representation, not just the Ruby wrapper.
if (upb_FieldDef_IsMap(f)) { */
Map_freeze(field); VALUE Message_freeze(VALUE _self) {
} else if (upb_FieldDef_IsRepeated(f)) { Message* self = ruby_to_Message(_self);
RepeatedField_freeze(field); if (RB_OBJ_FROZEN(_self)) {
} else if (upb_FieldDef_IsSubMessage(f)) { PBRUBY_ASSERT(upb_Message_IsFrozen(self->msg));
Message_freeze(field); return _self;
}
}
} }
if (!upb_Message_IsFrozen(self->msg)) {
upb_Message_Freeze(Message_GetMutable(_self, NULL),
upb_MessageDef_MiniTable(self->msgdef));
}
RB_OBJ_FREEZE(_self);
return _self; return _self;
} }
@ -1352,6 +1389,7 @@ static void Message_define_class(VALUE klass) {
rb_define_method(klass, "==", Message_eq, 1); rb_define_method(klass, "==", Message_eq, 1);
rb_define_method(klass, "eql?", Message_eq, 1); rb_define_method(klass, "eql?", Message_eq, 1);
rb_define_method(klass, "freeze", Message_freeze, 0); rb_define_method(klass, "freeze", Message_freeze, 0);
rb_define_method(klass, "frozen?", Message_frozen, 0);
rb_define_method(klass, "hash", Message_hash, 0); rb_define_method(klass, "hash", Message_hash, 0);
rb_define_method(klass, "to_h", Message_to_h, 0); rb_define_method(klass, "to_h", Message_to_h, 0);
rb_define_method(klass, "inspect", Message_inspect, 0); rb_define_method(klass, "inspect", Message_inspect, 0);

@ -36,7 +36,7 @@ const upb_Message* Message_GetUpbMessage(VALUE value, const upb_MessageDef* m,
// Gets or constructs a Ruby wrapper object for the given message. The wrapper // Gets or constructs a Ruby wrapper object for the given message. The wrapper
// object will reference |arena| and ensure that it outlives this object. // object will reference |arena| and ensure that it outlives this object.
VALUE Message_GetRubyWrapper(upb_Message* msg, const upb_MessageDef* m, VALUE Message_GetRubyWrapper(const upb_Message* msg, const upb_MessageDef* m,
VALUE arena); VALUE arena);
// Gets the given field from this message. // Gets the given field from this message.

@ -221,15 +221,6 @@ void Arena_fuse(VALUE _arena, upb_Arena *other) {
VALUE Arena_new() { return Arena_alloc(cArena); } VALUE Arena_new() { return Arena_alloc(cArena); }
void Arena_Pin(VALUE _arena, VALUE obj) {
Arena *arena;
TypedData_Get_Struct(_arena, Arena, &Arena_type, arena);
if (arena->pinned_objs == Qnil) {
RB_OBJ_WRITE(_arena, &arena->pinned_objs, rb_ary_new());
}
rb_ary_push(arena->pinned_objs, obj);
}
void Arena_register(VALUE module) { void Arena_register(VALUE module) {
VALUE internal = rb_define_module_under(module, "Internal"); VALUE internal = rb_define_module_under(module, "Internal");
VALUE klass = rb_define_class_under(internal, "Arena", rb_cObject); VALUE klass = rb_define_class_under(internal, "Arena", rb_cObject);
@ -354,3 +345,14 @@ __attribute__((visibility("default"))) void Init_protobuf_c() {
rb_define_singleton_method(protobuf, "deep_copy", Google_Protobuf_deep_copy, rb_define_singleton_method(protobuf, "deep_copy", Google_Protobuf_deep_copy,
1); 1);
} }
// -----------------------------------------------------------------------------
// Utilities
// -----------------------------------------------------------------------------
// Raises a Ruby error if val is frozen in Ruby or UPB.
void Protobuf_CheckNotFrozen(VALUE val, bool upb_frozen) {
if (RB_UNLIKELY(rb_obj_frozen_p(val)||upb_frozen)) {
rb_error_frozen_object(val);
}
}

@ -50,13 +50,6 @@ upb_Arena* Arena_get(VALUE arena);
// possible. // possible.
void Arena_fuse(VALUE arena, upb_Arena* other); void Arena_fuse(VALUE arena, upb_Arena* other);
// Pins this Ruby object to the lifetime of this arena, so that as long as the
// arena is alive this object will not be collected.
//
// We use this to guarantee that the "frozen" bit on the object will be
// remembered, even if the user drops their reference to this precise object.
void Arena_Pin(VALUE arena, VALUE obj);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ObjectCache // ObjectCache
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -105,6 +98,9 @@ extern VALUE cTypeError;
rb_bug("Assertion failed at %s:%d, expr: %s", __FILE__, __LINE__, #expr) rb_bug("Assertion failed at %s:%d, expr: %s", __FILE__, __LINE__, #expr)
#endif #endif
// Raises a Ruby error if val is frozen in Ruby or upb_frozen is true.
void Protobuf_CheckNotFrozen(VALUE val, bool upb_frozen);
#define PBRUBY_MAX(x, y) (((x) > (y)) ? (x) : (y)) #define PBRUBY_MAX(x, y) (((x) > (y)) ? (x) : (y))
#define UPB_UNUSED(var) (void)var #define UPB_UNUSED(var) (void)var

@ -44,8 +44,9 @@ static RepeatedField* ruby_to_RepeatedField(VALUE _self) {
} }
static upb_Array* RepeatedField_GetMutable(VALUE _self) { static upb_Array* RepeatedField_GetMutable(VALUE _self) {
rb_check_frozen(_self); const upb_Array* array = ruby_to_RepeatedField(_self)->array;
return (upb_Array*)ruby_to_RepeatedField(_self)->array; Protobuf_CheckNotFrozen(_self, upb_Array_IsFrozen(array));
return (upb_Array*)array;
} }
VALUE RepeatedField_alloc(VALUE klass) { VALUE RepeatedField_alloc(VALUE klass) {
@ -56,7 +57,29 @@ VALUE RepeatedField_alloc(VALUE klass) {
return TypedData_Wrap_Struct(klass, &RepeatedField_type, self); return TypedData_Wrap_Struct(klass, &RepeatedField_type, self);
} }
VALUE RepeatedField_GetRubyWrapper(upb_Array* array, TypeInfo type_info, VALUE RepeatedField_EmptyFrozen(const upb_FieldDef* f) {
PBRUBY_ASSERT(upb_FieldDef_IsRepeated(f));
VALUE val = ObjectCache_Get(f);
if (val == Qnil) {
val = RepeatedField_alloc(cRepeatedField);
RepeatedField* self;
TypedData_Get_Struct(val, RepeatedField, &RepeatedField_type, self);
self->arena = Arena_new();
TypeInfo type_info = TypeInfo_get(f);
self->array = upb_Array_New(Arena_get(self->arena), type_info.type);
self->type_info = type_info;
if (self->type_info.type == kUpb_CType_Message) {
self->type_class = Descriptor_DefToClass(type_info.def.msgdef);
}
val = ObjectCache_TryAdd(f, RepeatedField_freeze(val));
}
PBRUBY_ASSERT(RB_OBJ_FROZEN(val));
PBRUBY_ASSERT(upb_Array_IsFrozen(ruby_to_RepeatedField(val)->array));
return val;
}
VALUE RepeatedField_GetRubyWrapper(const upb_Array* array, TypeInfo type_info,
VALUE arena) { VALUE arena) {
PBRUBY_ASSERT(array); PBRUBY_ASSERT(array);
VALUE val = ObjectCache_Get(array); VALUE val = ObjectCache_Get(array);
@ -78,7 +101,6 @@ VALUE RepeatedField_GetRubyWrapper(upb_Array* array, TypeInfo type_info,
PBRUBY_ASSERT(ruby_to_RepeatedField(val)->type_info.def.msgdef == PBRUBY_ASSERT(ruby_to_RepeatedField(val)->type_info.def.msgdef ==
type_info.def.msgdef); type_info.def.msgdef);
PBRUBY_ASSERT(ruby_to_RepeatedField(val)->array == array); PBRUBY_ASSERT(ruby_to_RepeatedField(val)->array == array);
return val; return val;
} }
@ -471,29 +493,49 @@ VALUE RepeatedField_eq(VALUE _self, VALUE _other) {
return Qtrue; return Qtrue;
} }
/*
* call-seq:
* RepeatedField.frozen? => bool
*
* Returns true if the repeated field is frozen in either Ruby or the underlying
* representation. Freezes the Ruby repeated field object if it is not already
* frozen in Ruby but it is frozen in the underlying representation.
*/
VALUE RepeatedField_frozen(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
if (!upb_Array_IsFrozen(self->array)) {
PBRUBY_ASSERT(!RB_OBJ_FROZEN(_self));
return Qfalse;
}
// Lazily freeze the Ruby wrapper.
if (!RB_OBJ_FROZEN(_self)) RB_OBJ_FREEZE(_self);
return Qtrue;
}
/* /*
* call-seq: * call-seq:
* RepeatedField.freeze => self * RepeatedField.freeze => self
* *
* Freezes the repeated field. We have to intercept this so we can pin the Ruby * Freezes the repeated field object. We have to intercept this so we can freeze
* object into memory so we don't forget it's frozen. * the underlying representation, not just the Ruby wrapper.
*/ */
VALUE RepeatedField_freeze(VALUE _self) { VALUE RepeatedField_freeze(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self); RepeatedField* self = ruby_to_RepeatedField(_self);
if (RB_OBJ_FROZEN(_self)) {
PBRUBY_ASSERT(upb_Array_IsFrozen(self->array));
return _self;
}
if (RB_OBJ_FROZEN(_self)) return _self; if (!upb_Array_IsFrozen(self->array)) {
Arena_Pin(self->arena, _self); if (self->type_info.type == kUpb_CType_Message) {
RB_OBJ_FREEZE(_self); upb_Array_Freeze(RepeatedField_GetMutable(_self),
upb_MessageDef_MiniTable(self->type_info.def.msgdef));
if (self->type_info.type == kUpb_CType_Message) { } else {
int size = upb_Array_Size(self->array); upb_Array_Freeze(RepeatedField_GetMutable(_self), NULL);
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_freeze(val);
} }
} }
RB_OBJ_FREEZE(_self);
return _self; return _self;
} }
@ -640,6 +682,7 @@ void RepeatedField_register(VALUE module) {
rb_define_method(klass, "==", RepeatedField_eq, 1); rb_define_method(klass, "==", RepeatedField_eq, 1);
rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0); rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0);
rb_define_method(klass, "freeze", RepeatedField_freeze, 0); rb_define_method(klass, "freeze", RepeatedField_freeze, 0);
rb_define_method(klass, "frozen?", RepeatedField_frozen, 0);
rb_define_method(klass, "hash", RepeatedField_hash, 0); rb_define_method(klass, "hash", RepeatedField_hash, 0);
rb_define_method(klass, "+", RepeatedField_plus, 1); rb_define_method(klass, "+", RepeatedField_plus, 1);
rb_define_method(klass, "concat", RepeatedField_concat, 1); rb_define_method(klass, "concat", RepeatedField_concat, 1);

@ -11,9 +11,13 @@
#include "protobuf.h" #include "protobuf.h"
#include "ruby-upb.h" #include "ruby-upb.h"
// Returns a frozen sentinel Ruby wrapper object for an empty upb_Array of the
// type specified by the field. Creates one if it doesn't exist.
VALUE RepeatedField_EmptyFrozen(const upb_FieldDef* f);
// Returns a Ruby wrapper object for the given upb_Array, which will be created // Returns a Ruby wrapper object for the given upb_Array, which will be created
// if one does not exist already. // if one does not exist already.
VALUE RepeatedField_GetRubyWrapper(upb_Array* msg, TypeInfo type_info, VALUE RepeatedField_GetRubyWrapper(const upb_Array* msg, TypeInfo type_info,
VALUE arena); VALUE arena);
// Gets the underlying upb_Array for this Ruby RepeatedField object, which must // Gets the underlying upb_Array for this Ruby RepeatedField object, which must

@ -140,7 +140,6 @@ module Google
message = OBJECT_CACHE.get(msg.address) message = OBJECT_CACHE.get(msg.address)
if message.nil? if message.nil?
message = descriptor.msgclass.send(:private_constructor, arena, msg: msg) message = descriptor.msgclass.send(:private_constructor, arena, msg: msg)
message.freeze if frozen?
end end
message message
end end

@ -12,8 +12,6 @@ module Google
module Protobuf module Protobuf
module Internal module Internal
class Arena class Arena
attr :pinned_messages
# FFI Interface methods and setup # FFI Interface methods and setup
extend ::FFI::DataConverter extend ::FFI::DataConverter
native_type ::FFI::Type::POINTER native_type ::FFI::Type::POINTER
@ -46,10 +44,6 @@ module Google
raise RuntimeError.new "Unable to fuse arenas. This should never happen since Ruby does not use initial blocks" raise RuntimeError.new "Unable to fuse arenas. This should never happen since Ruby does not use initial blocks"
end end
end end
def pin(message)
pinned_messages.push message
end
end end
end end

@ -9,18 +9,20 @@ module Google
module Protobuf module Protobuf
class FFI class FFI
# Map # Map
attach_function :map_clear, :upb_Map_Clear, [:Map], :void attach_function :map_clear, :upb_Map_Clear, [:Map], :void
attach_function :map_delete, :upb_Map_Delete, [:Map, MessageValue.by_value, MessageValue.by_ref], :bool attach_function :map_delete, :upb_Map_Delete, [:Map, MessageValue.by_value, MessageValue.by_ref], :bool
attach_function :map_get, :upb_Map_Get, [:Map, MessageValue.by_value, MessageValue.by_ref], :bool attach_function :map_get, :upb_Map_Get, [:Map, MessageValue.by_value, MessageValue.by_ref], :bool
attach_function :create_map, :upb_Map_New, [Internal::Arena, CType, CType], :Map attach_function :create_map, :upb_Map_New, [Internal::Arena, CType, CType], :Map
attach_function :map_size, :upb_Map_Size, [:Map], :size_t attach_function :map_size, :upb_Map_Size, [:Map], :size_t
attach_function :map_set, :upb_Map_Set, [:Map, MessageValue.by_value, MessageValue.by_value, Internal::Arena], :bool attach_function :map_set, :upb_Map_Set, [:Map, MessageValue.by_value, MessageValue.by_value, Internal::Arena], :bool
attach_function :map_freeze, :upb_Map_Freeze, [:Map, MiniTable.by_ref], :void
attach_function :map_frozen?, :upb_Map_IsFrozen, [:Map], :bool
# MapIterator # MapIterator
attach_function :map_next, :upb_MapIterator_Next, [:Map, :pointer], :bool attach_function :map_next, :upb_MapIterator_Next, [:Map, :pointer], :bool
attach_function :map_done, :upb_MapIterator_Done, [:Map, :size_t], :bool attach_function :map_done, :upb_MapIterator_Done, [:Map, :size_t], :bool
attach_function :map_key, :upb_MapIterator_Key, [:Map, :size_t], MessageValue.by_value attach_function :map_key, :upb_MapIterator_Key, [:Map, :size_t], MessageValue.by_value
attach_function :map_value, :upb_MapIterator_Value, [:Map, :size_t], MessageValue.by_value attach_function :map_value, :upb_MapIterator_Value, [:Map, :size_t], MessageValue.by_value
end end
class Map class Map
include Enumerable include Enumerable
@ -155,17 +157,35 @@ module Google
end end
alias size length alias size length
##
# Is this object frozen?
# Returns true if either this Ruby wrapper or the underlying
# representation are frozen. Freezes the wrapper if the underlying
# representation is already frozen but this wrapper isn't.
def frozen?
unless Google::Protobuf::FFI.map_frozen? @map_ptr
raise RuntimeError.new "Ruby frozen Map with mutable representation" if super
return false
end
method(:freeze).super_method.call unless super
true
end
##
# Freezes the map object. We have to intercept this so we can freeze the
# underlying representation, not just the Ruby wrapper. Returns self.
def freeze def freeze
return self if frozen? if method(:frozen?).super_method.call
super unless Google::Protobuf::FFI.map_frozen? @map_ptr
@arena.pin self raise RuntimeError.new "Underlying representation of map still mutable despite frozen wrapper"
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).freeze
end end
return self
end end
self unless Google::Protobuf::FFI.map_frozen? @map_ptr
mini_table = (value_type == :message) ? Google::Protobuf::FFI.get_mini_table(@descriptor) : nil
Google::Protobuf::FFI.map_freeze(@map_ptr, mini_table)
end
super
end end
## ##
@ -371,10 +391,14 @@ module Google
OBJECT_CACHE.try_add(@map_ptr.address, self) OBJECT_CACHE.try_add(@map_ptr.address, self)
end end
# @param field [FieldDescriptor] Descriptor of the field where the RepeatedField will be assigned ##
# @param values [Hash|Map] Initial value; may be nil or empty # Constructor that uses the type information from the given
# FieldDescriptor to configure the new Map instance.
# @param field [FieldDescriptor] Type information for the new Map
# @param arena [Arena] Owning message's arena # @param arena [Arena] Owning message's arena
def self.construct_for_field(field, arena, value: nil, map: nil) # @param value [Hash|Map] Initial value
# @param map [::FFI::Pointer] Existing upb_Map
def self.construct_for_field(field, arena: nil, value: nil, map: nil)
raise ArgumentError.new "Expected Hash object as initializer value for map field '#{field.name}' (given #{value.class})." unless value.nil? or value.is_a? Hash raise ArgumentError.new "Expected Hash object as initializer value for map field '#{field.name}' (given #{value.class})." unless value.nil? or value.is_a? Hash
instance = allocate instance = allocate
raise ArgumentError.new "Expected field with type :message, instead got #{field.class}" unless field.type == :message raise ArgumentError.new "Expected field with type :message, instead got #{field.class}" unless field.type == :message

@ -24,6 +24,9 @@ module Google
attach_function :get_message_which_oneof, :upb_Message_WhichOneofByDef, [:Message, OneofDescriptor], FieldDescriptor attach_function :get_message_which_oneof, :upb_Message_WhichOneofByDef, [:Message, OneofDescriptor], FieldDescriptor
attach_function :message_discard_unknown, :upb_Message_DiscardUnknown, [:Message, Descriptor, :int], :bool attach_function :message_discard_unknown, :upb_Message_DiscardUnknown, [:Message, Descriptor, :int], :bool
attach_function :message_next, :upb_Message_Next, [:Message, Descriptor, :DefPool, :FieldDefPointer, MessageValue.by_ref, :pointer], :bool attach_function :message_next, :upb_Message_Next, [:Message, Descriptor, :DefPool, :FieldDefPointer, MessageValue.by_ref, :pointer], :bool
attach_function :message_freeze, :upb_Message_Freeze, [:Message, MiniTable.by_ref], :void
attach_function :message_frozen?, :upb_Message_IsFrozen, [:Message], :bool
# MessageValue # MessageValue
attach_function :message_value_equal, :shared_Msgval_IsEqual, [MessageValue.by_value, MessageValue.by_value, CType, Descriptor], :bool attach_function :message_value_equal, :shared_Msgval_IsEqual, [MessageValue.by_value, MessageValue.by_value, CType, Descriptor], :bool
attach_function :message_value_hash, :shared_Msgval_GetHash, [MessageValue.by_value, CType, Descriptor, :uint64_t], :uint64_t attach_function :message_value_hash, :shared_Msgval_GetHash, [MessageValue.by_value, CType, Descriptor, :uint64_t], :uint64_t
@ -58,18 +61,35 @@ module Google
instance instance
end end
##
# Is this object frozen?
# Returns true if either this Ruby wrapper or the underlying
# representation are frozen. Freezes the wrapper if the underlying
# representation is already frozen but this wrapper isn't.
def frozen?
unless Google::Protobuf::FFI.message_frozen? @msg
raise RuntimeError.new "Ruby frozen Message with mutable representation" if super
return false
end
method(:freeze).super_method.call unless super
true
end
##
# Freezes the map object. We have to intercept this so we can freeze the
# underlying representation, not just the Ruby wrapper. Returns self.
def freeze def freeze
return self if frozen? if method(:frozen?).super_method.call
super unless Google::Protobuf::FFI.message_frozen? @msg
@arena.pin self raise RuntimeError.new "Underlying representation of message still mutable despite frozen wrapper"
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).freeze
end end
return self
end end
self unless Google::Protobuf::FFI.message_frozen? @msg
end Google::Protobuf::FFI.message_freeze(@msg, Google::Protobuf::FFI.get_mini_table(self.class.descriptor))
end
super
end
def dup def dup
duplicate = self.class.private_constructor(@arena) duplicate = self.class.private_constructor(@arena)
@ -309,42 +329,136 @@ module Google
include Google::Protobuf::Internal::Convert include Google::Protobuf::Internal::Convert
##
# Checks ObjectCache for a sentinel empty frozen Map of the key and
# value types matching the field descriptor's MessageDef and returns
# the cache entry. If an entry is not found, one is created and added
# to the cache keyed by the MessageDef pointer first.
# @param field_descriptor [FieldDescriptor] Field to retrieve.
def empty_frozen_map(field_descriptor)
sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor
frozen_map = OBJECT_CACHE.get sub_message_def
if frozen_map.nil?
frozen_map = Google::Protobuf::Map.send(:construct_for_field, field_descriptor)
OBJECT_CACHE.try_add(sub_message_def, frozen_map.freeze)
end
raise "Empty Frozen Map is not frozen" unless frozen_map.frozen?
frozen_map
end
##
# Returns a frozen Map instance for the given field. If the message
# already has a value for that field, it is used. If not, a sentinel
# (per FieldDescriptor) empty frozen Map is returned instead.
# @param field_descriptor [FieldDescriptor] Field to retrieve.
def frozen_map_from_field_descriptor(field_descriptor)
message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
return empty_frozen_map field_descriptor if message_value[:map_val].null?
get_map_field(message_value[:map_val], field_descriptor).freeze
end
##
# Returns a Map instance for the given field. If the message is frozen
# the return value is also frozen. If not, a mutable instance is
# returned instead.
# @param field_descriptor [FieldDescriptor] Field to retrieve.
def map_from_field_descriptor(field_descriptor)
return frozen_map_from_field_descriptor field_descriptor if frozen?
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
get_map_field(mutable_message_value[:map], field_descriptor)
end
##
# Checks ObjectCache for a sentinel empty frozen RepeatedField of the
# value type matching the field descriptor's MessageDef and returns
# the cache entry. If an entry is not found, one is created and added
# to the cache keyed by the MessageDef pointer first.
# @param field_descriptor [FieldDescriptor] Field to retrieve.
def empty_frozen_repeated_field(field_descriptor)
sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor
frozen_repeated_field = OBJECT_CACHE.get sub_message_def
if frozen_repeated_field.nil?
frozen_repeated_field = Google::Protobuf::RepeatedField.send(:construct_for_field, field_descriptor)
OBJECT_CACHE.try_add(sub_message_def, frozen_repeated_field.freeze)
end
raise "Empty frozen RepeatedField is not frozen" unless frozen_repeated_field.frozen?
frozen_repeated_field
end
##
# Returns a frozen RepeatedField instance for the given field. If the
# message already has a value for that field, it is used. If not, a
# sentinel (per FieldDescriptor) empty frozen RepeatedField is
# returned instead.
# @param field_descriptor [FieldDescriptor] Field to retrieve.
def frozen_repeated_field_from_field_descriptor(field_descriptor)
message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
return empty_frozen_repeated_field field_descriptor if message_value[:array_val].null?
get_repeated_field(message_value[:array_val], field_descriptor).freeze
end
##
# Returns a RepeatedField instance for the given field. If the message
# is frozen the return value is also frozen. If not, a mutable
# instance is returned instead.
# @param field_descriptor [FieldDescriptor] Field to retrieve.
def repeated_field_from_field_descriptor(field_descriptor)
return frozen_repeated_field_from_field_descriptor field_descriptor if frozen?
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
get_repeated_field(mutable_message_value[:array], field_descriptor)
end
##
# Returns a Message instance for the given field. If the message
# is frozen nil is always returned. Otherwise, a mutable instance is
# returned instead.
# @param field_descriptor [FieldDescriptor] Field to retrieve.
def message_from_field_descriptor(field_descriptor)
return nil if frozen?
return nil unless Google::Protobuf::FFI.get_message_has @msg, field_descriptor
mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
sub_message = mutable_message[:msg]
sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field_descriptor
Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
end
##
# Returns a scalar value for the given field. If the message
# is frozen the return value is also frozen.
# @param field_descriptor [FieldDescriptor] Field to retrieve.
def scalar_from_field_descriptor(field_descriptor)
c_type = field_descriptor.send(:c_type)
message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
msg_or_enum_def = c_type == :enum ? Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor) : nil
return_value = convert_upb_to_ruby message_value, c_type, msg_or_enum_def
frozen? ? return_value.freeze : return_value
end
##
# Dynamically define accessors methods for every field of @descriptor.
# Methods with names that conflict with existing methods are skipped.
def self.setup_accessors! def self.setup_accessors!
@descriptor.each do |field_descriptor| @descriptor.each do |field_descriptor|
field_name = field_descriptor.name field_name = field_descriptor.name
unless instance_methods(true).include?(field_name.to_sym) unless instance_methods(true).include?(field_name.to_sym)
#TODO - at a high level, dispatching to either # Dispatching to either index_internal or get_field is logically
# index_internal or get_field would be logically correct, but slightly slower. # correct, but slightly slower due to having to perform extra
# lookups on each invocation rather than doing it once here.
if field_descriptor.map? if field_descriptor.map?
define_method(field_name) do define_method(field_name) do
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena map_from_field_descriptor field_descriptor
get_map_field(mutable_message_value[:map], field_descriptor)
end end
elsif field_descriptor.repeated? elsif field_descriptor.repeated?
define_method(field_name) do define_method(field_name) do
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena repeated_field_from_field_descriptor field_descriptor
get_repeated_field(mutable_message_value[:array], field_descriptor)
end end
elsif field_descriptor.sub_message? elsif field_descriptor.sub_message?
define_method(field_name) do define_method(field_name) do
return nil unless Google::Protobuf::FFI.get_message_has @msg, field_descriptor message_from_field_descriptor field_descriptor
mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
sub_message = mutable_message[:msg]
sub_message_def = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor)
Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
end end
else else
c_type = field_descriptor.send(:c_type) define_method(field_name) do
if c_type == :enum scalar_from_field_descriptor field_descriptor
define_method(field_name) do
message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor)
end
else
define_method(field_name) do
message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
convert_upb_to_ruby message_value, c_type
end
end end
end end
define_method("#{field_name}=") do |value| define_method("#{field_name}=") do |value|
@ -354,14 +468,16 @@ module Google
clear_internal(field_descriptor) clear_internal(field_descriptor)
end end
if field_descriptor.type == :enum if field_descriptor.type == :enum
define_method("#{field_name}_const") do if field_descriptor.repeated?
if field_descriptor.repeated? define_method("#{field_name}_const") do
return_value = [] return_value = []
get_field(field_descriptor).send(:each_msg_val) do |msg_val| repeated_field_from_field_descriptor(field_descriptor).send(:each_msg_val) do |msg_val|
return_value << msg_val[:int32_val] return_value << msg_val[:int32_val]
end end
return_value return_value
else end
else
define_method("#{field_name}_const") do
message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
message_value[:int32_val] message_value[:int32_val]
end end
@ -388,12 +504,21 @@ module Google
end end
end end
##
# Dynamically define accessors methods for every OneOf field of
# @descriptor.
def self.setup_oneof_accessors! def self.setup_oneof_accessors!
@oneof_field_names = [] @oneof_field_names = []
@descriptor.each_oneof do |oneof_descriptor| @descriptor.each_oneof do |oneof_descriptor|
self.add_oneof_accessors_for! oneof_descriptor self.add_oneof_accessors_for! oneof_descriptor
end end
end end
##
# Dynamically define accessors methods for the given OneOf field.
# Methods with names that conflict with existing methods are skipped.
# @param oneof_descriptor [OneofDescriptor] Field to create accessors
# for.
def self.add_oneof_accessors_for!(oneof_descriptor) def self.add_oneof_accessors_for!(oneof_descriptor)
field_name = oneof_descriptor.name.to_sym field_name = oneof_descriptor.name.to_sym
@oneof_field_names << field_name @oneof_field_names << field_name
@ -524,6 +649,10 @@ module Google
Google::Protobuf::FFI.clear_message_field(@msg, field_def) Google::Protobuf::FFI.clear_message_field(@msg, field_def)
end end
# Accessor for field by name. Does not delegate to methods setup by
# self.setup_accessors! in order to avoid conflicts with bad field
# names e.g. `dup` or `class` which are perfectly valid for proto
# fields.
def index_internal(name) def index_internal(name)
field_descriptor = self.class.descriptor.lookup(name) field_descriptor = self.class.descriptor.lookup(name)
get_field field_descriptor unless field_descriptor.nil? get_field field_descriptor unless field_descriptor.nil?
@ -572,9 +701,9 @@ module Google
next if value.nil? next if value.nil?
if field_descriptor.map? if field_descriptor.map?
index_assign_internal(Google::Protobuf::Map.send(:construct_for_field, field_descriptor, @arena, value: value), name: key.to_s) index_assign_internal(Google::Protobuf::Map.send(:construct_for_field, field_descriptor, arena: @arena, value: value), name: key.to_s)
elsif field_descriptor.repeated? elsif field_descriptor.repeated?
index_assign_internal(RepeatedField.send(:construct_for_field, field_descriptor, @arena, values: value), name: key.to_s) index_assign_internal(RepeatedField.send(:construct_for_field, field_descriptor, arena: @arena, values: value), name: key.to_s)
else else
index_assign_internal(value, name: key.to_s) index_assign_internal(value, name: key.to_s)
end end
@ -587,21 +716,20 @@ module Google
end end
## ##
# Gets a field of this message identified by the argument definition. # Gets a field of this message identified by FieldDescriptor.
# #
# @param field [FieldDescriptor] Descriptor of the field to get # @param field [FieldDescriptor] Field to retrieve.
# @param unwrap [Boolean](false) If true, unwraps wrappers.
def get_field(field, unwrap: false) def get_field(field, unwrap: false)
if field.map? if field.map?
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena map_from_field_descriptor field
get_map_field(mutable_message_value[:map], field)
elsif field.repeated? elsif field.repeated?
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena repeated_field_from_field_descriptor field
get_repeated_field(mutable_message_value[:array], field)
elsif field.sub_message? elsif field.sub_message?
return nil unless Google::Protobuf::FFI.get_message_has @msg, field return nil unless Google::Protobuf::FFI.get_message_has @msg, field
sub_message_def = Google::Protobuf::FFI.get_subtype_as_message(field)
if unwrap if unwrap
if field.has?(self) if field.has?(self)
sub_message_def = Google::Protobuf::FFI.get_subtype_as_message field
wrapper_message_value = Google::Protobuf::FFI.get_message_value @msg, field wrapper_message_value = Google::Protobuf::FFI.get_message_value @msg, field
fields = Google::Protobuf::FFI.field_count(sub_message_def) fields = Google::Protobuf::FFI.field_count(sub_message_def)
raise "Sub message has #{fields} fields! Expected exactly 1." unless fields == 1 raise "Sub message has #{fields} fields! Expected exactly 1." unless fields == 1
@ -612,42 +740,36 @@ module Google
nil nil
end end
else else
mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena message_from_field_descriptor field
sub_message = mutable_message[:msg]
Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
end end
else else
c_type = field.send(:c_type) scalar_from_field_descriptor field
message_value = Google::Protobuf::FFI.get_message_value @msg, field
if c_type == :enum
convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field)
else
convert_upb_to_ruby message_value, c_type
end
end end
end end
## ##
# @param array [::FFI::Pointer] Pointer to the Array # Gets a RepeatedField from the ObjectCache or creates a new one.
# @param array [::FFI::Pointer] Pointer to the upb_Array
# @param field [Google::Protobuf::FieldDescriptor] Type of the repeated field # @param field [Google::Protobuf::FieldDescriptor] Type of the repeated field
def get_repeated_field(array, field) def get_repeated_field(array, field)
return nil if array.nil? or array.null? return nil if array.nil? or array.null?
repeated_field = OBJECT_CACHE.get(array.address) repeated_field = OBJECT_CACHE.get(array.address)
if repeated_field.nil? if repeated_field.nil?
repeated_field = RepeatedField.send(:construct_for_field, field, @arena, array: array) repeated_field = RepeatedField.send(:construct_for_field, field, arena: @arena, array: array)
repeated_field.freeze if frozen? repeated_field.freeze if frozen?
end end
repeated_field repeated_field
end end
## ##
# @param map [::FFI::Pointer] Pointer to the Map # Gets a Map from the ObjectCache or creates a new one.
# @param map [::FFI::Pointer] Pointer to the upb_Map
# @param field [Google::Protobuf::FieldDescriptor] Type of the map field # @param field [Google::Protobuf::FieldDescriptor] Type of the map field
def get_map_field(map, field) def get_map_field(map, field)
return nil if map.nil? or map.null? return nil if map.nil? or map.null?
map_field = OBJECT_CACHE.get(map.address) map_field = OBJECT_CACHE.get(map.address)
if map_field.nil? if map_field.nil?
map_field = Google::Protobuf::Map.send(:construct_for_field, field, @arena, map: map) map_field = Google::Protobuf::Map.send(:construct_for_field, field, arena: @arena, map: map)
map_field.freeze if frozen? map_field.freeze if frozen?
end end
map_field map_field

@ -24,12 +24,14 @@ module Google
module Protobuf module Protobuf
class FFI class FFI
# Array # Array
attach_function :append_array, :upb_Array_Append, [:Array, MessageValue.by_value, Internal::Arena], :bool attach_function :append_array, :upb_Array_Append, [:Array, MessageValue.by_value, Internal::Arena], :bool
attach_function :get_msgval_at,:upb_Array_Get, [:Array, :size_t], MessageValue.by_value attach_function :get_msgval_at, :upb_Array_Get, [:Array, :size_t], MessageValue.by_value
attach_function :create_array, :upb_Array_New, [Internal::Arena, CType], :Array attach_function :create_array, :upb_Array_New, [Internal::Arena, CType], :Array
attach_function :array_resize, :upb_Array_Resize, [:Array, :size_t, Internal::Arena], :bool attach_function :array_resize, :upb_Array_Resize, [:Array, :size_t, Internal::Arena], :bool
attach_function :array_set, :upb_Array_Set, [:Array, :size_t, MessageValue.by_value], :void attach_function :array_set, :upb_Array_Set, [:Array, :size_t, MessageValue.by_value], :void
attach_function :array_size, :upb_Array_Size, [:Array], :size_t attach_function :array_size, :upb_Array_Size, [:Array], :size_t
attach_function :array_freeze, :upb_Array_Freeze, [:Array, MiniTable.by_ref], :void
attach_function :array_frozen?, :upb_Array_IsFrozen, [:Array], :bool
end end
class RepeatedField class RepeatedField
@ -174,16 +176,36 @@ module Google
end end
alias size :length alias size :length
##
# Is this object frozen?
# Returns true if either this Ruby wrapper or the underlying
# representation are frozen. Freezes the wrapper if the underlying
# representation is already frozen but this wrapper isn't.
def frozen?
unless Google::Protobuf::FFI.array_frozen? array
raise RuntimeError.new "Ruby frozen RepeatedField with mutable representation" if super
return false
end
method(:freeze).super_method.call unless super
true
end
##
# Freezes the RepeatedField object. We have to intercept this so we can
# freeze the underlying representation, not just the Ruby wrapper. Returns
# self.
def freeze def freeze
return self if frozen? if method(:frozen?).super_method.call
super unless Google::Protobuf::FFI.array_frozen? array
@arena.pin self raise RuntimeError.new "Underlying representation of repeated field still mutable despite frozen wrapper"
if type == :message
each do |element|
element.freeze
end end
return self
end end
self unless Google::Protobuf::FFI.array_frozen? array
mini_table = (type == :message) ? Google::Protobuf::FFI.get_mini_table(@descriptor) : nil
Google::Protobuf::FFI.array_freeze(array, mini_table)
end
super
end end
def dup def dup
@ -348,10 +370,14 @@ module Google
OBJECT_CACHE.try_add(@array.address, self) OBJECT_CACHE.try_add(@array.address, self)
end end
# @param field [FieldDescriptor] Descriptor of the field where the RepeatedField will be assigned ##
# @param values [Enumerable] Initial values; may be nil or empty # Constructor that uses the type information from the given
# FieldDescriptor to configure the new RepeatedField instance.
# @param field [FieldDescriptor] Type information for the new RepeatedField
# @param arena [Arena] Owning message's arena # @param arena [Arena] Owning message's arena
def self.construct_for_field(field, arena, values: nil, array: nil) # @param values [Enumerable] Initial values
# @param array [::FFI::Pointer] Existing upb_Array
def self.construct_for_field(field, arena: nil, values: nil, array: nil)
instance = allocate instance = allocate
options = {initial_values: values, name: field.name, arena: arena, array: array} options = {initial_values: values, name: field.name, arena: arena, array: array}
if [:enum, :message].include? field.type if [:enum, :message].include? field.type

Loading…
Cancel
Save