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);
}
VALUE Map_GetRubyWrapper(upb_Map* map, upb_CType key_type, TypeInfo value_type,
VALUE arena) {
VALUE Map_GetRubyWrapper(const upb_Map* map, upb_CType key_type,
TypeInfo value_type, VALUE arena) {
PBRUBY_ASSERT(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 val;
}
@ -105,8 +104,9 @@ static TypeInfo Map_keyinfo(Map* self) {
}
static upb_Map* Map_GetMutable(VALUE _self) {
rb_check_frozen(_self);
return (upb_Map*)ruby_to_Map(_self)->map;
const upb_Map* 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,
@ -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.
*/
static VALUE Map_delete(VALUE _self, VALUE key) {
upb_Map* map = Map_GetMutable(_self);
Map* self = ruby_to_Map(_self);
rb_check_frozen(_self);
upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
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);
} else {
return Qnil;
@ -560,29 +560,79 @@ VALUE Map_eq(VALUE _self, VALUE _other) {
/*
* 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
* Ruby object into memory so we don't forget it's frozen.
* Freezes the map object. We have to intercept this so we can freeze the
* underlying representation, not just the Ruby wrapper.
*/
VALUE Map_freeze(VALUE _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);
if (self->value_type_info.type == kUpb_CType_Message) {
size_t iter = kUpb_Map_Begin;
upb_MessageValue key, val;
return _self;
}
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)) {
VALUE val_val =
Convert_UpbToRuby(val, self->value_type_info, self->arena);
Message_freeze(val_val);
if (val == Qnil) {
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);
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, "==", Map_eq, 1);
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, "to_h", Map_to_h, 0);
rb_define_method(klass, "inspect", Map_inspect, 0);

@ -11,10 +11,14 @@
#include "protobuf.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
// one does not exist already.
VALUE Map_GetRubyWrapper(upb_Map *map, upb_CType key_type, TypeInfo value_type,
VALUE arena);
VALUE Map_GetRubyWrapper(const upb_Map *map, upb_CType key_type,
TypeInfo value_type, VALUE arena);
// 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

@ -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) {
rb_check_frozen(msg_rb);
return (upb_Message*)Message_Get(msg_rb, m);
const upb_Message* upb_msg = 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_);
self->msg = msg;
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) {
if (msg == NULL) return Qnil;
@ -116,7 +117,6 @@ VALUE Message_GetRubyWrapper(upb_Message* msg, const upb_MessageDef* m,
val = Message_alloc(klass);
Message_InitPtr(val, msg, arena);
}
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);
}
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) {
Message* self = ruby_to_Message(_self);
// This is a special-case: upb_Message_Mutable() for map & array are logically
// const (they will not change what is serialized) but physically
// 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 = (upb_Message*)self->msg;
if (upb_Message_IsFrozen(self->msg)) {
return Message_getfield_frozen(self->msg, f, self->arena);
}
upb_Message* msg = Message_GetMutable(_self, NULL);
upb_Arena* arena = Arena_get(self->arena);
if (upb_FieldDef_IsMap(f)) {
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;
return RepeatedField_GetRubyWrapper(arr, TypeInfo_get(f), self->arena);
} 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;
const upb_MessageDef* m = upb_FieldDef_MessageSubDef(f);
return Message_GetRubyWrapper(submsg, m, self->arena);
} 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);
}
}
@ -436,7 +465,6 @@ static VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
if (argc != 2) {
rb_raise(rb_eArgError, "Expected 2 arguments, received %d", argc);
}
rb_check_frozen(_self);
break;
default:
if (argc != 1) {
@ -812,33 +840,42 @@ static VALUE Message_to_h(VALUE _self) {
/*
* call-seq:
* Message.freeze => self
* Message.frozen? => bool
*
* Freezes the message object. We have to intercept this so we can pin the
* Ruby object into memory so we don't forget it's frozen.
* Returns true if the message is frozen in either Ruby or the underlying
* 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);
if (!upb_Message_IsFrozen(self->msg)) {
PBRUBY_ASSERT(!RB_OBJ_FROZEN(_self));
return Qfalse;
}
if (RB_OBJ_FROZEN(_self)) return _self;
Arena_Pin(self->arena, _self);
RB_OBJ_FREEZE(_self);
// Lazily freeze the Ruby wrapper.
if (!RB_OBJ_FROZEN(_self)) RB_OBJ_FREEZE(_self);
return Qtrue;
}
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_freeze(field);
} else if (upb_FieldDef_IsRepeated(f)) {
RepeatedField_freeze(field);
} else if (upb_FieldDef_IsSubMessage(f)) {
Message_freeze(field);
}
}
/*
* call-seq:
* Message.freeze => self
*
* Freezes the message object. We have to intercept this so we can freeze the
* underlying representation, not just the Ruby wrapper.
*/
VALUE Message_freeze(VALUE _self) {
Message* self = ruby_to_Message(_self);
if (RB_OBJ_FROZEN(_self)) {
PBRUBY_ASSERT(upb_Message_IsFrozen(self->msg));
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;
}
@ -1352,6 +1389,7 @@ static void Message_define_class(VALUE klass) {
rb_define_method(klass, "==", Message_eq, 1);
rb_define_method(klass, "eql?", Message_eq, 1);
rb_define_method(klass, "freeze", Message_freeze, 0);
rb_define_method(klass, "frozen?", Message_frozen, 0);
rb_define_method(klass, "hash", Message_hash, 0);
rb_define_method(klass, "to_h", Message_to_h, 0);
rb_define_method(klass, "inspect", Message_inspect, 0);

@ -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
// 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);
// 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); }
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) {
VALUE internal = rb_define_module_under(module, "Internal");
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,
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.
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
// -----------------------------------------------------------------------------
@ -105,6 +98,9 @@ extern VALUE cTypeError;
rb_bug("Assertion failed at %s:%d, expr: %s", __FILE__, __LINE__, #expr)
#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 UPB_UNUSED(var) (void)var

@ -44,8 +44,9 @@ static RepeatedField* ruby_to_RepeatedField(VALUE _self) {
}
static upb_Array* RepeatedField_GetMutable(VALUE _self) {
rb_check_frozen(_self);
return (upb_Array*)ruby_to_RepeatedField(_self)->array;
const upb_Array* array = ruby_to_RepeatedField(_self)->array;
Protobuf_CheckNotFrozen(_self, upb_Array_IsFrozen(array));
return (upb_Array*)array;
}
VALUE RepeatedField_alloc(VALUE klass) {
@ -56,7 +57,29 @@ VALUE RepeatedField_alloc(VALUE klass) {
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) {
PBRUBY_ASSERT(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 ==
type_info.def.msgdef);
PBRUBY_ASSERT(ruby_to_RepeatedField(val)->array == array);
return val;
}
@ -471,29 +493,49 @@ VALUE RepeatedField_eq(VALUE _self, VALUE _other) {
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:
* RepeatedField.freeze => self
*
* Freezes the repeated field. We have to intercept this so we can pin the Ruby
* object into memory so we don't forget it's frozen.
* Freezes the repeated field object. We have to intercept this so we can freeze
* the underlying representation, not just the Ruby wrapper.
*/
VALUE RepeatedField_freeze(VALUE _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;
Arena_Pin(self->arena, _self);
RB_OBJ_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_freeze(val);
if (!upb_Array_IsFrozen(self->array)) {
if (self->type_info.type == kUpb_CType_Message) {
upb_Array_Freeze(RepeatedField_GetMutable(_self),
upb_MessageDef_MiniTable(self->type_info.def.msgdef));
} else {
upb_Array_Freeze(RepeatedField_GetMutable(_self), NULL);
}
}
RB_OBJ_FREEZE(_self);
return _self;
}
@ -640,6 +682,7 @@ void RepeatedField_register(VALUE module) {
rb_define_method(klass, "==", RepeatedField_eq, 1);
rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0);
rb_define_method(klass, "freeze", RepeatedField_freeze, 0);
rb_define_method(klass, "frozen?", RepeatedField_frozen, 0);
rb_define_method(klass, "hash", RepeatedField_hash, 0);
rb_define_method(klass, "+", RepeatedField_plus, 1);
rb_define_method(klass, "concat", RepeatedField_concat, 1);

@ -11,9 +11,13 @@
#include "protobuf.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
// 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);
// Gets the underlying upb_Array for this Ruby RepeatedField object, which must

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

@ -12,8 +12,6 @@ module Google
module Protobuf
module Internal
class Arena
attr :pinned_messages
# FFI Interface methods and setup
extend ::FFI::DataConverter
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"
end
end
def pin(message)
pinned_messages.push message
end
end
end

@ -9,18 +9,20 @@ module Google
module Protobuf
class FFI
# Map
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_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 :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_clear, :upb_Map_Clear, [:Map], :void
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 :create_map, :upb_Map_New, [Internal::Arena, CType, CType], :Map
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_freeze, :upb_Map_Freeze, [:Map, MiniTable.by_ref], :void
attach_function :map_frozen?, :upb_Map_IsFrozen, [:Map], :bool
# MapIterator
attach_function :map_next, :upb_MapIterator_Next, [:Map, :pointer], :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_value, :upb_MapIterator_Value, [:Map, :size_t], MessageValue.by_value
attach_function :map_next, :upb_MapIterator_Next, [:Map, :pointer], :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_value, :upb_MapIterator_Value, [:Map, :size_t], MessageValue.by_value
end
class Map
include Enumerable
@ -155,17 +157,35 @@ module Google
end
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
return self if frozen?
super
@arena.pin self
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
if method(:frozen?).super_method.call
unless Google::Protobuf::FFI.map_frozen? @map_ptr
raise RuntimeError.new "Underlying representation of map still mutable despite frozen wrapper"
end
return self
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
##
@ -371,10 +391,14 @@ module Google
OBJECT_CACHE.try_add(@map_ptr.address, self)
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
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
instance = allocate
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 :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_freeze, :upb_Message_Freeze, [:Message, MiniTable.by_ref], :void
attach_function :message_frozen?, :upb_Message_IsFrozen, [:Message], :bool
# MessageValue
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
@ -58,18 +61,35 @@ module Google
instance
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
return self if frozen?
super
@arena.pin self
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
if method(:frozen?).super_method.call
unless Google::Protobuf::FFI.message_frozen? @msg
raise RuntimeError.new "Underlying representation of message still mutable despite frozen wrapper"
end
return self
end
self
end
unless Google::Protobuf::FFI.message_frozen? @msg
Google::Protobuf::FFI.message_freeze(@msg, Google::Protobuf::FFI.get_mini_table(self.class.descriptor))
end
super
end
def dup
duplicate = self.class.private_constructor(@arena)
@ -309,42 +329,136 @@ module Google
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!
@descriptor.each do |field_descriptor|
field_name = field_descriptor.name
unless instance_methods(true).include?(field_name.to_sym)
#TODO - at a high level, dispatching to either
# index_internal or get_field would be logically correct, but slightly slower.
# Dispatching to either index_internal or get_field is logically
# correct, but slightly slower due to having to perform extra
# lookups on each invocation rather than doing it once here.
if field_descriptor.map?
define_method(field_name) do
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
get_map_field(mutable_message_value[:map], field_descriptor)
map_from_field_descriptor field_descriptor
end
elsif field_descriptor.repeated?
define_method(field_name) do
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
get_repeated_field(mutable_message_value[:array], field_descriptor)
repeated_field_from_field_descriptor field_descriptor
end
elsif field_descriptor.sub_message?
define_method(field_name) do
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)
message_from_field_descriptor field_descriptor
end
else
c_type = field_descriptor.send(:c_type)
if c_type == :enum
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
define_method(field_name) do
scalar_from_field_descriptor field_descriptor
end
end
define_method("#{field_name}=") do |value|
@ -354,14 +468,16 @@ module Google
clear_internal(field_descriptor)
end
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 = []
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]
end
return_value
else
end
else
define_method("#{field_name}_const") do
message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
message_value[:int32_val]
end
@ -388,12 +504,21 @@ module Google
end
end
##
# Dynamically define accessors methods for every OneOf field of
# @descriptor.
def self.setup_oneof_accessors!
@oneof_field_names = []
@descriptor.each_oneof do |oneof_descriptor|
self.add_oneof_accessors_for! oneof_descriptor
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)
field_name = oneof_descriptor.name.to_sym
@oneof_field_names << field_name
@ -524,6 +649,10 @@ module Google
Google::Protobuf::FFI.clear_message_field(@msg, field_def)
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)
field_descriptor = self.class.descriptor.lookup(name)
get_field field_descriptor unless field_descriptor.nil?
@ -572,9 +701,9 @@ module Google
next if value.nil?
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?
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
index_assign_internal(value, name: key.to_s)
end
@ -587,21 +716,20 @@ module Google
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)
if field.map?
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
get_map_field(mutable_message_value[:map], field)
map_from_field_descriptor field
elsif field.repeated?
mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
get_repeated_field(mutable_message_value[:array], field)
repeated_field_from_field_descriptor field
elsif field.sub_message?
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 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
fields = Google::Protobuf::FFI.field_count(sub_message_def)
raise "Sub message has #{fields} fields! Expected exactly 1." unless fields == 1
@ -612,42 +740,36 @@ module Google
nil
end
else
mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
sub_message = mutable_message[:msg]
Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
message_from_field_descriptor field
end
else
c_type = field.send(:c_type)
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
scalar_from_field_descriptor field
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
def get_repeated_field(array, field)
return nil if array.nil? or array.null?
repeated_field = OBJECT_CACHE.get(array.address)
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?
end
repeated_field
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
def get_map_field(map, field)
return nil if map.nil? or map.null?
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 = Google::Protobuf::Map.send(:construct_for_field, field, arena: @arena, map: map)
map_field.freeze if frozen?
end
map_field

@ -24,12 +24,14 @@ module Google
module Protobuf
class FFI
# Array
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 :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_set, :upb_Array_Set, [:Array, :size_t, MessageValue.by_value], :void
attach_function :array_size, :upb_Array_Size, [:Array], :size_t
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 :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_set, :upb_Array_Set, [:Array, :size_t, MessageValue.by_value], :void
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
class RepeatedField
@ -174,16 +176,36 @@ module Google
end
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
return self if frozen?
super
@arena.pin self
if type == :message
each do |element|
element.freeze
if method(:frozen?).super_method.call
unless Google::Protobuf::FFI.array_frozen? array
raise RuntimeError.new "Underlying representation of repeated field still mutable despite frozen wrapper"
end
return self
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
def dup
@ -348,10 +370,14 @@ module Google
OBJECT_CACHE.try_add(@array.address, self)
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
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
options = {initial_values: values, name: field.name, arena: arena, array: array}
if [:enum, :message].include? field.type

Loading…
Cancel
Save