Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
@@ -0,0 +1,3 @@
import '../../../lib/google/tasks/ffi.rake'
task default: ['ffi-protobuf:default']
@@ -0,0 +1,335 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// -----------------------------------------------------------------------------
// Ruby <-> upb data conversion functions.
//
// This file Also contains a few other assorted algorithms on upb_MessageValue.
//
// None of the algorithms in this file require any access to the internal
// representation of Ruby or upb objects.
// -----------------------------------------------------------------------------
#include "convert.h"
#include "message.h"
#include "protobuf.h"
#include "shared_convert.h"
static upb_StringView Convert_StringData(VALUE str, upb_Arena* arena) {
upb_StringView ret;
if (arena) {
char* ptr = upb_Arena_Malloc(arena, RSTRING_LEN(str));
memcpy(ptr, RSTRING_PTR(str), RSTRING_LEN(str));
ret.data = ptr;
} else {
// Data is only needed temporarily (within map lookup).
ret.data = RSTRING_PTR(str);
}
ret.size = RSTRING_LEN(str);
return ret;
}
static bool is_ruby_num(VALUE value) {
return (TYPE(value) == T_FLOAT || TYPE(value) == T_FIXNUM ||
TYPE(value) == T_BIGNUM);
}
static void Convert_CheckInt(const char* name, upb_CType type, VALUE val) {
if (!is_ruby_num(val)) {
rb_raise(cTypeError,
"Expected number type for integral field '%s' (given %s).", name,
rb_class2name(CLASS_OF(val)));
}
// NUM2{INT,UINT,LL,ULL} macros do the appropriate range checks on upper
// bound; we just need to do precision checks (i.e., disallow rounding) and
// check for < 0 on unsigned types.
if (TYPE(val) == T_FLOAT) {
double dbl_val = NUM2DBL(val);
if (floor(dbl_val) != dbl_val) {
rb_raise(rb_eRangeError,
"Non-integral floating point value assigned to integer field "
"'%s' (given %s).",
name, rb_class2name(CLASS_OF(val)));
}
}
if (type == kUpb_CType_UInt32 || type == kUpb_CType_UInt64) {
if (NUM2DBL(val) < 0) {
rb_raise(
rb_eRangeError,
"Assigning negative value to unsigned integer field '%s' (given %s).",
name, rb_class2name(CLASS_OF(val)));
}
}
}
static int32_t Convert_ToEnum(VALUE value, const char* name,
const upb_EnumDef* e) {
int32_t val;
switch (TYPE(value)) {
case T_FLOAT:
case T_FIXNUM:
case T_BIGNUM:
Convert_CheckInt(name, kUpb_CType_Int32, value);
val = NUM2INT(value);
break;
case T_STRING: {
const upb_EnumValueDef* ev = upb_EnumDef_FindValueByNameWithSize(
e, RSTRING_PTR(value), RSTRING_LEN(value));
if (!ev) goto unknownval;
val = upb_EnumValueDef_Number(ev);
break;
}
case T_SYMBOL: {
const upb_EnumValueDef* ev =
upb_EnumDef_FindValueByName(e, rb_id2name(SYM2ID(value)));
if (!ev) goto unknownval;
val = upb_EnumValueDef_Number(ev);
break;
}
default:
rb_raise(cTypeError,
"Expected number or symbol type for enum field '%s'.", name);
}
return val;
unknownval:
rb_raise(rb_eRangeError, "Unknown symbol value for enum field '%s'.", name);
}
VALUE Convert_CheckStringUtf8(VALUE str) {
VALUE utf8 = rb_enc_from_encoding(rb_utf8_encoding());
if (rb_obj_encoding(str) == utf8) {
// Note: Just because a string is marked as having UTF-8 encoding does
// not mean that it is *valid* UTF-8. We have to check separately
// whether it is valid.
if (rb_enc_str_coderange(str) == ENC_CODERANGE_BROKEN) {
VALUE exc =
rb_const_get_at(rb_cEncoding, rb_intern("InvalidByteSequenceError"));
rb_raise(exc, "String is invalid UTF-8");
}
} else {
// Note: this will not duplicate underlying string data unless
// necessary.
//
// This will throw an exception if the conversion cannot be performed:
// - Encoding::UndefinedConversionError if certain characters cannot be
// converted to UTF-8.
// - Encoding::InvalidByteSequenceError if certain characters were invalid
// in the source encoding.
str = rb_str_encode(str, utf8, 0, Qnil);
PBRUBY_ASSERT(rb_enc_str_coderange(str) != ENC_CODERANGE_BROKEN);
}
return str;
}
upb_MessageValue Convert_RubyToUpb(VALUE value, const char* name,
TypeInfo type_info, upb_Arena* arena) {
upb_MessageValue ret;
switch (type_info.type) {
case kUpb_CType_Float:
if (!is_ruby_num(value)) {
rb_raise(cTypeError,
"Expected number type for float field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
ret.float_val = NUM2DBL(value);
break;
case kUpb_CType_Double:
if (!is_ruby_num(value)) {
rb_raise(cTypeError,
"Expected number type for double field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
ret.double_val = NUM2DBL(value);
break;
case kUpb_CType_Bool: {
if (value == Qtrue) {
ret.bool_val = 1;
} else if (value == Qfalse) {
ret.bool_val = 0;
} else {
rb_raise(cTypeError,
"Invalid argument for boolean field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
break;
}
case kUpb_CType_String:
if (rb_obj_class(value) == rb_cSymbol) {
value = rb_funcall(value, rb_intern("to_s"), 0);
} else if (!rb_obj_is_kind_of(value, rb_cString)) {
rb_raise(cTypeError,
"Invalid argument for string field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
value = Convert_CheckStringUtf8(value);
ret.str_val = Convert_StringData(value, arena);
break;
case kUpb_CType_Bytes: {
VALUE bytes = rb_enc_from_encoding(rb_ascii8bit_encoding());
if (rb_obj_class(value) != rb_cString) {
rb_raise(cTypeError,
"Invalid argument for bytes field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
if (rb_obj_encoding(value) != bytes) {
// Note: this will not duplicate underlying string data unless
// necessary.
// TODO: is this really necessary to get raw bytes?
value = rb_str_encode(value, bytes, 0, Qnil);
}
ret.str_val = Convert_StringData(value, arena);
break;
}
case kUpb_CType_Message:
ret.msg_val =
Message_GetUpbMessage(value, type_info.def.msgdef, name, arena);
break;
case kUpb_CType_Enum:
ret.int32_val = Convert_ToEnum(value, name, type_info.def.enumdef);
break;
case kUpb_CType_Int32:
case kUpb_CType_Int64:
case kUpb_CType_UInt32:
case kUpb_CType_UInt64:
Convert_CheckInt(name, type_info.type, value);
switch (type_info.type) {
case kUpb_CType_Int32:
ret.int32_val = NUM2INT(value);
break;
case kUpb_CType_Int64:
ret.int64_val = NUM2LL(value);
break;
case kUpb_CType_UInt32:
ret.uint32_val = NUM2UINT(value);
break;
case kUpb_CType_UInt64:
ret.uint64_val = NUM2ULL(value);
break;
default:
rb_raise(cTypeError, "Convert_RubyToUpb(): Unexpected type %d",
(int)type_info.type);
}
break;
default:
rb_raise(cTypeError, "Convert_RubyToUpb(): Unexpected type %d",
(int)type_info.type);
}
return ret;
}
VALUE Convert_UpbToRuby(upb_MessageValue upb_val, TypeInfo type_info,
VALUE arena) {
switch (type_info.type) {
case kUpb_CType_Float:
return DBL2NUM(upb_val.float_val);
case kUpb_CType_Double:
return DBL2NUM(upb_val.double_val);
case kUpb_CType_Bool:
return upb_val.bool_val ? Qtrue : Qfalse;
case kUpb_CType_Int32:
return INT2NUM(upb_val.int32_val);
case kUpb_CType_Int64:
return LL2NUM(upb_val.int64_val);
case kUpb_CType_UInt32:
return UINT2NUM(upb_val.uint32_val);
case kUpb_CType_UInt64:
return ULL2NUM(upb_val.int64_val);
case kUpb_CType_Enum: {
const upb_EnumValueDef* ev = upb_EnumDef_FindValueByNumber(
type_info.def.enumdef, upb_val.int32_val);
if (ev) {
return ID2SYM(rb_intern(upb_EnumValueDef_Name(ev)));
} else {
return INT2NUM(upb_val.int32_val);
}
}
case kUpb_CType_String: {
VALUE str_rb = rb_str_new(upb_val.str_val.data, upb_val.str_val.size);
rb_enc_associate(str_rb, rb_utf8_encoding());
rb_obj_freeze(str_rb);
return str_rb;
}
case kUpb_CType_Bytes: {
VALUE str_rb = rb_str_new(upb_val.str_val.data, upb_val.str_val.size);
rb_enc_associate(str_rb, rb_ascii8bit_encoding());
rb_obj_freeze(str_rb);
return str_rb;
}
case kUpb_CType_Message:
return Message_GetRubyWrapper((upb_Message*)upb_val.msg_val,
type_info.def.msgdef, arena);
default:
rb_raise(rb_eRuntimeError, "Convert_UpbToRuby(): Unexpected type %d",
(int)type_info.type);
}
}
upb_MessageValue Msgval_DeepCopy(upb_MessageValue msgval, TypeInfo type_info,
upb_Arena* arena) {
upb_MessageValue new_msgval;
switch (type_info.type) {
default:
memcpy(&new_msgval, &msgval, sizeof(msgval));
break;
case kUpb_CType_String:
case kUpb_CType_Bytes: {
size_t n = msgval.str_val.size;
char* mem = upb_Arena_Malloc(arena, n);
new_msgval.str_val.data = mem;
new_msgval.str_val.size = n;
memcpy(mem, msgval.str_val.data, n);
break;
}
case kUpb_CType_Message:
new_msgval.msg_val =
Message_deep_copy(msgval.msg_val, type_info.def.msgdef, arena);
break;
}
return new_msgval;
}
bool Msgval_IsEqual(upb_MessageValue val1, upb_MessageValue val2,
TypeInfo type_info) {
upb_Status status;
upb_Status_Clear(&status);
bool return_value = shared_Msgval_IsEqual(val1, val2, type_info.type,
type_info.def.msgdef, &status);
if (upb_Status_IsOk(&status)) {
return return_value;
} else {
rb_raise(rb_eRuntimeError, "Msgval_IsEqual(): %s",
upb_Status_ErrorMessage(&status));
}
}
uint64_t Msgval_GetHash(upb_MessageValue val, TypeInfo type_info,
uint64_t seed) {
upb_Status status;
upb_Status_Clear(&status);
uint64_t return_value = shared_Msgval_GetHash(
val, type_info.type, type_info.def.msgdef, seed, &status);
if (upb_Status_IsOk(&status)) {
return return_value;
} else {
rb_raise(rb_eRuntimeError, "Msgval_GetHash(): %s",
upb_Status_ErrorMessage(&status));
}
}
@@ -0,0 +1,50 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef RUBY_PROTOBUF_CONVERT_H_
#define RUBY_PROTOBUF_CONVERT_H_
#include "protobuf.h"
#include "ruby-upb.h"
// Converts |ruby_val| to a upb_MessageValue according to |type_info|.
//
// The |arena| parameter indicates the lifetime of the container where this
// value will be assigned. It is used as follows:
// - If type is string or bytes, the string data will be copied into |arena|.
// - If type is message, and we need to auto-construct a message due to implicit
// conversions (eg. Time -> Google::Protobuf::Timestamp), the new message
// will be created in |arena|.
// - If type is message and the Ruby value is a message instance, we will fuse
// the message's arena into |arena|, to ensure that this message outlives the
// container.
upb_MessageValue Convert_RubyToUpb(VALUE ruby_val, const char* name,
TypeInfo type_info, upb_Arena* arena);
// Converts |upb_val| to a Ruby VALUE according to |type_info|. This may involve
// creating a Ruby wrapper object.
//
// The |arena| parameter indicates the arena that owns the lifetime of
// |upb_val|. Any Ruby wrapper object that is created will reference |arena|
// and ensure it outlives the wrapper.
VALUE Convert_UpbToRuby(upb_MessageValue upb_val, TypeInfo type_info,
VALUE arena);
// Creates a deep copy of |msgval| in |arena|.
upb_MessageValue Msgval_DeepCopy(upb_MessageValue msgval, TypeInfo type_info,
upb_Arena* arena);
// Returns true if |val1| and |val2| are equal. Their type is given by
// |type_info|.
bool Msgval_IsEqual(upb_MessageValue val1, upb_MessageValue val2,
TypeInfo type_info);
// Returns a hash value for the given upb_MessageValue.
uint64_t Msgval_GetHash(upb_MessageValue val, TypeInfo type_info,
uint64_t seed);
#endif // RUBY_PROTOBUF_CONVERT_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,82 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef RUBY_PROTOBUF_DEFS_H_
#define RUBY_PROTOBUF_DEFS_H_
#include "protobuf.h"
#include "ruby-upb.h"
// -----------------------------------------------------------------------------
// TypeInfo
// -----------------------------------------------------------------------------
// This bundles a upb_CType and msgdef/enumdef when appropriate. This is
// convenient for functions that need type information but cannot necessarily
// assume a upb_FieldDef will be available.
//
// For example, Google::Protobuf::Map and Google::Protobuf::RepeatedField can
// be constructed with type information alone:
//
// # RepeatedField will internally store the type information in a TypeInfo.
// Google::Protobuf::RepeatedField.new(:message, FooMessage)
typedef struct {
upb_CType type;
union {
const upb_MessageDef* msgdef; // When type == kUpb_CType_Message
const upb_EnumDef* enumdef; // When type == kUpb_CType_Enum
} def;
} TypeInfo;
static inline TypeInfo TypeInfo_get(const upb_FieldDef* f) {
TypeInfo ret = {upb_FieldDef_CType(f), {NULL}};
switch (ret.type) {
case kUpb_CType_Message:
ret.def.msgdef = upb_FieldDef_MessageSubDef(f);
break;
case kUpb_CType_Enum:
ret.def.enumdef = upb_FieldDef_EnumSubDef(f);
break;
default:
break;
}
return ret;
}
TypeInfo TypeInfo_FromClass(int argc, VALUE* argv, int skip_arg,
VALUE* type_class, VALUE* init_arg);
static inline TypeInfo TypeInfo_from_type(upb_CType type) {
TypeInfo ret = {type};
assert(type != kUpb_CType_Message && type != kUpb_CType_Enum);
return ret;
}
// -----------------------------------------------------------------------------
// Other utilities
// -----------------------------------------------------------------------------
VALUE Descriptor_DefToClass(const upb_MessageDef* m);
// Returns the underlying msgdef, enumdef, or symtab (respectively) for the
// given Descriptor, EnumDescriptor, or DescriptorPool Ruby object.
const upb_EnumDef* EnumDescriptor_GetEnumDef(VALUE enum_desc_rb);
const upb_DefPool* DescriptorPool_GetSymtab(VALUE desc_pool_rb);
const upb_MessageDef* Descriptor_GetMsgDef(VALUE desc_rb);
// Returns a upb field type for the given Ruby symbol
// (eg. :float => kUpb_CType_Float).
upb_CType ruby_to_fieldtype(VALUE type);
// The singleton generated pool (a DescriptorPool object).
extern VALUE generated_pool;
// Call at startup to register all types in this module.
void Defs_register(VALUE module);
#endif // RUBY_PROTOBUF_DEFS_H_
@@ -0,0 +1,38 @@
#!/usr/bin/ruby
require 'mkmf'
ext_name = "google/protobuf_c"
dir_config(ext_name)
if ENV["CC"]
RbConfig::CONFIG["CC"] = RbConfig::MAKEFILE_CONFIG["CC"] = ENV["CC"]
end
if ENV["CXX"]
RbConfig::CONFIG["CXX"] = RbConfig::MAKEFILE_CONFIG["CXX"] = ENV["CXX"]
end
if ENV["LD"]
RbConfig::CONFIG["LD"] = RbConfig::MAKEFILE_CONFIG["LD"] = ENV["LD"]
end
debug_enabled = ENV["PROTOBUF_CONFIG"] == "dbg"
additional_c_flags = debug_enabled ? "-O0 -fno-omit-frame-pointer -fvisibility=default -g" : "-O3 -DNDEBUG -fvisibility=hidden"
if RUBY_PLATFORM =~ /darwin/ || RUBY_PLATFORM =~ /linux/ || RUBY_PLATFORM =~ /freebsd/
$CFLAGS += " -std=gnu99 -Wall -Wsign-compare -Wno-declaration-after-statement #{additional_c_flags}"
else
$CFLAGS += " -std=gnu99 #{additional_c_flags}"
end
$VPATH << "$(srcdir)/third_party/utf8_range"
$INCFLAGS += " -I$(srcdir)/third_party/utf8_range"
$srcs = ["protobuf.c", "convert.c", "defs.c", "message.c", "repeated_field.c",
"map.c", "ruby-upb.c", "utf8_range.c", "shared_convert.c",
"shared_message.c"]
create_makefile(ext_name)
@@ -0,0 +1,135 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// -----------------------------------------------------------------------------
// Exposing inlined UPB functions. Strictly free of dependencies on
// Ruby interpreter internals.
#include "ruby-upb.h"
upb_Arena* Arena_create() { return upb_Arena_Init(NULL, 0, &upb_alloc_global); }
google_protobuf_FileDescriptorProto* FileDescriptorProto_parse(
const char* serialized_file_proto, size_t length, upb_Arena* arena) {
return google_protobuf_FileDescriptorProto_parse(serialized_file_proto,
length, arena);
}
char* EnumDescriptor_serialized_options(const upb_EnumDef* enumdef,
size_t* size, upb_Arena* arena) {
const google_protobuf_EnumOptions* opts = upb_EnumDef_Options(enumdef);
char* serialized = google_protobuf_EnumOptions_serialize(opts, arena, size);
return serialized;
}
char* EnumDescriptor_serialized_to_proto(const upb_EnumDef* enumdef,
size_t* size, upb_Arena* arena) {
const google_protobuf_EnumDescriptorProto* file_proto =
upb_EnumDef_ToProto(enumdef, arena);
char* serialized =
google_protobuf_EnumDescriptorProto_serialize(file_proto, arena, size);
return serialized;
}
char* FileDescriptor_serialized_options(const upb_FileDef* filedef,
size_t* size, upb_Arena* arena) {
const google_protobuf_FileOptions* opts = upb_FileDef_Options(filedef);
char* serialized = google_protobuf_FileOptions_serialize(opts, arena, size);
return serialized;
}
char* FileDescriptor_serialized_to_proto(const upb_FileDef* filedef,
size_t* size, upb_Arena* arena) {
const google_protobuf_FileDescriptorProto* file_proto =
upb_FileDef_ToProto(filedef, arena);
char* serialized =
google_protobuf_FileDescriptorProto_serialize(file_proto, arena, size);
return serialized;
}
char* Descriptor_serialized_options(const upb_MessageDef* msgdef, size_t* size,
upb_Arena* arena) {
const google_protobuf_MessageOptions* opts = upb_MessageDef_Options(msgdef);
char* serialized =
google_protobuf_MessageOptions_serialize(opts, arena, size);
return serialized;
}
char* Descriptor_serialized_to_proto(const upb_MessageDef* msgdef, size_t* size,
upb_Arena* arena) {
const google_protobuf_DescriptorProto* proto =
upb_MessageDef_ToProto(msgdef, arena);
char* serialized =
google_protobuf_DescriptorProto_serialize(proto, arena, size);
return serialized;
}
char* OneOfDescriptor_serialized_options(const upb_OneofDef* oneofdef,
size_t* size, upb_Arena* arena) {
const google_protobuf_OneofOptions* opts = upb_OneofDef_Options(oneofdef);
char* serialized = google_protobuf_OneofOptions_serialize(opts, arena, size);
return serialized;
}
char* OneOfDescriptor_serialized_to_proto(const upb_OneofDef* oneofdef,
size_t* size, upb_Arena* arena) {
const google_protobuf_OneofDescriptorProto* proto =
upb_OneofDef_ToProto(oneofdef, arena);
char* serialized =
google_protobuf_OneofDescriptorProto_serialize(proto, arena, size);
return serialized;
}
char* FieldDescriptor_serialized_options(const upb_FieldDef* fielddef,
size_t* size, upb_Arena* arena) {
const google_protobuf_FieldOptions* opts = upb_FieldDef_Options(fielddef);
char* serialized = google_protobuf_FieldOptions_serialize(opts, arena, size);
return serialized;
}
char* FieldDescriptor_serialized_to_proto(const upb_FieldDef* fieldef,
size_t* size, upb_Arena* arena) {
const google_protobuf_FieldDescriptorProto* proto =
upb_FieldDef_ToProto(fieldef, arena);
char* serialized =
google_protobuf_FieldDescriptorProto_serialize(proto, arena, size);
return serialized;
}
char* ServiceDescriptor_serialized_options(const upb_ServiceDef* servicedef,
size_t* size, upb_Arena* arena) {
const google_protobuf_ServiceOptions* opts =
upb_ServiceDef_Options(servicedef);
char* serialized =
google_protobuf_ServiceOptions_serialize(opts, arena, size);
return serialized;
}
char* ServiceDescriptor_serialized_to_proto(const upb_ServiceDef* servicedef,
size_t* size, upb_Arena* arena) {
const google_protobuf_ServiceDescriptorProto* proto =
upb_ServiceDef_ToProto(servicedef, arena);
char* serialized =
google_protobuf_ServiceDescriptorProto_serialize(proto, arena, size);
return serialized;
}
char* MethodDescriptor_serialized_options(const upb_MethodDef* methoddef,
size_t* size, upb_Arena* arena) {
const google_protobuf_MethodOptions* opts = upb_MethodDef_Options(methoddef);
char* serialized = google_protobuf_MethodOptions_serialize(opts, arena, size);
return serialized;
}
char* MethodDescriptor_serialized_to_proto(const upb_MethodDef* methodef,
size_t* size, upb_Arena* arena) {
const google_protobuf_MethodDescriptorProto* proto =
upb_MethodDef_ToProto(methodef, arena);
char* serialized =
google_protobuf_MethodDescriptorProto_serialize(proto, arena, size);
return serialized;
}
@@ -0,0 +1,768 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "convert.h"
#include "defs.h"
#include "message.h"
#include "protobuf.h"
// -----------------------------------------------------------------------------
// Basic map operations on top of upb_Map.
//
// Note that we roll our own `Map` container here because, as for
// `RepeatedField`, we want a strongly-typed container. This is so that any user
// errors due to incorrect map key or value types are raised as close as
// possible to the error site, rather than at some deferred point (e.g.,
// serialization).
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Map container type.
// -----------------------------------------------------------------------------
typedef struct {
const upb_Map* map; // Can convert to mutable when non-frozen.
upb_CType key_type;
TypeInfo value_type_info;
VALUE value_type_class;
VALUE arena;
} Map;
static void Map_mark(void* _self) {
Map* self = _self;
rb_gc_mark(self->value_type_class);
rb_gc_mark(self->arena);
}
static size_t Map_memsize(const void* _self) { return sizeof(Map); }
const rb_data_type_t Map_type = {
"Google::Protobuf::Map",
{Map_mark, RUBY_DEFAULT_FREE, Map_memsize},
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
};
VALUE cMap;
static Map* ruby_to_Map(VALUE _self) {
Map* self;
TypedData_Get_Struct(_self, Map, &Map_type, self);
return self;
}
static VALUE Map_alloc(VALUE klass) {
Map* self = ALLOC(Map);
self->map = NULL;
self->value_type_class = Qnil;
self->value_type_info.def.msgdef = NULL;
self->arena = Qnil;
return TypedData_Wrap_Struct(klass, &Map_type, self);
}
VALUE Map_GetRubyWrapper(const upb_Map* map, upb_CType key_type,
TypeInfo value_type, VALUE arena) {
PBRUBY_ASSERT(map);
PBRUBY_ASSERT(arena != Qnil);
VALUE val = ObjectCache_Get(map);
if (val == Qnil) {
val = Map_alloc(cMap);
Map* self;
TypedData_Get_Struct(val, Map, &Map_type, self);
self->map = map;
self->arena = arena;
self->key_type = key_type;
self->value_type_info = value_type;
if (self->value_type_info.type == kUpb_CType_Message) {
const upb_MessageDef* val_m = self->value_type_info.def.msgdef;
self->value_type_class = Descriptor_DefToClass(val_m);
}
return ObjectCache_TryAdd(map, val);
}
return val;
}
static VALUE Map_new_this_type(Map* from) {
VALUE arena_rb = Arena_new();
upb_Map* map = upb_Map_New(Arena_get(arena_rb), from->key_type,
from->value_type_info.type);
VALUE ret =
Map_GetRubyWrapper(map, from->key_type, from->value_type_info, arena_rb);
PBRUBY_ASSERT(ruby_to_Map(ret)->value_type_class == from->value_type_class);
return ret;
}
static TypeInfo Map_keyinfo(Map* self) {
TypeInfo ret;
ret.type = self->key_type;
ret.def.msgdef = NULL;
return ret;
}
static upb_Map* Map_GetMutable(VALUE _self) {
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,
TypeInfo val_info) {
VALUE hash = rb_hash_new();
TypeInfo key_info = TypeInfo_from_type(key_type);
if (!map) return hash;
size_t iter = kUpb_Map_Begin;
upb_MessageValue key, val;
while (upb_Map_Next(map, &key, &val, &iter)) {
VALUE key_val = Convert_UpbToRuby(key, key_info, Qnil);
VALUE val_val = Scalar_CreateHash(val, val_info);
rb_hash_aset(hash, key_val, val_val);
}
return hash;
}
VALUE Map_deep_copy(VALUE obj) {
Map* self = ruby_to_Map(obj);
VALUE new_arena_rb = Arena_new();
upb_Arena* arena = Arena_get(new_arena_rb);
upb_Map* new_map =
upb_Map_New(arena, self->key_type, self->value_type_info.type);
size_t iter = kUpb_Map_Begin;
upb_MessageValue key, val;
while (upb_Map_Next(self->map, &key, &val, &iter)) {
upb_MessageValue val_copy =
Msgval_DeepCopy(val, self->value_type_info, arena);
upb_Map_Set(new_map, key, val_copy, arena);
}
return Map_GetRubyWrapper(new_map, self->key_type, self->value_type_info,
new_arena_rb);
}
const upb_Map* Map_GetUpbMap(VALUE val, const upb_FieldDef* field,
upb_Arena* arena) {
const upb_FieldDef* key_field = map_field_key(field);
const upb_FieldDef* value_field = map_field_value(field);
TypeInfo value_type_info = TypeInfo_get(value_field);
Map* self;
if (!RB_TYPE_P(val, T_DATA) || !RTYPEDDATA_P(val) ||
RTYPEDDATA_TYPE(val) != &Map_type) {
rb_raise(cTypeError, "Expected Map instance");
}
self = ruby_to_Map(val);
if (self->key_type != upb_FieldDef_CType(key_field)) {
rb_raise(cTypeError, "Map key type does not match field's key type");
}
if (self->value_type_info.type != value_type_info.type) {
rb_raise(cTypeError, "Map value type does not match field's value type");
}
if (self->value_type_info.def.msgdef != value_type_info.def.msgdef) {
rb_raise(cTypeError, "Map value type has wrong message/enum class");
}
Arena_fuse(self->arena, arena);
return self->map;
}
void Map_Inspect(StringBuilder* b, const upb_Map* map, upb_CType key_type,
TypeInfo val_type) {
bool first = true;
TypeInfo key_type_info = {key_type};
StringBuilder_Printf(b, "{");
if (map) {
size_t iter = kUpb_Map_Begin;
upb_MessageValue key, val;
while (upb_Map_Next(map, &key, &val, &iter)) {
if (first) {
first = false;
} else {
StringBuilder_Printf(b, ", ");
}
StringBuilder_PrintMsgval(b, key, key_type_info);
StringBuilder_Printf(b, "=>");
StringBuilder_PrintMsgval(b, val, val_type);
}
}
StringBuilder_Printf(b, "}");
}
static int merge_into_self_callback(VALUE key, VALUE val, VALUE _self) {
Map* self = ruby_to_Map(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_MessageValue key_val =
Convert_RubyToUpb(key, "", Map_keyinfo(self), arena);
upb_MessageValue val_val =
Convert_RubyToUpb(val, "", self->value_type_info, arena);
upb_Map_Set(Map_GetMutable(_self), key_val, val_val, arena);
return ST_CONTINUE;
}
// Used only internally -- shared by #merge and #initialize.
static VALUE Map_merge_into_self(VALUE _self, VALUE hashmap) {
if (TYPE(hashmap) == T_HASH) {
rb_hash_foreach(hashmap, merge_into_self_callback, _self);
} else if (RB_TYPE_P(hashmap, T_DATA) && RTYPEDDATA_P(hashmap) &&
RTYPEDDATA_TYPE(hashmap) == &Map_type) {
Map* self = ruby_to_Map(_self);
Map* other = ruby_to_Map(hashmap);
upb_Arena* arena = Arena_get(self->arena);
upb_Map* self_map = Map_GetMutable(_self);
Arena_fuse(other->arena, arena);
if (self->key_type != other->key_type ||
self->value_type_info.type != other->value_type_info.type ||
self->value_type_class != other->value_type_class) {
rb_raise(rb_eArgError, "Attempt to merge Map with mismatching types");
}
size_t iter = kUpb_Map_Begin;
upb_MessageValue key, val;
while (upb_Map_Next(other->map, &key, &val, &iter)) {
upb_Map_Set(self_map, key, val, arena);
}
} else {
rb_raise(rb_eArgError, "Unknown type merging into Map");
}
return _self;
}
/**
* ruby-doc: Map
*
* This class represents a Protobuf Map. It is largely automatically transformed
* to and from a Ruby hash.
*/
/*
* ruby-doc: Map#initialize
*
* Allocates a new Map container. This constructor may be called with 2, 3, or 4
* arguments. The first two arguments are always present and are symbols (taking
* on the same values as field-type symbols in message descriptors) that
* indicate the type of the map key and value fields.
*
* The supported key types are: :int32, :int64, :uint32, :uint64, :bool,
* :string, :bytes.
*
* The supported value types are: :int32, :int64, :uint32, :uint64, :bool,
* :string, :bytes, :enum, :message.
*
* The third argument, value_typeclass, must be present if value_type is :enum
* or :message. As in RepeatedField#new, this argument must be a message class
* (for :message) or enum module (for :enum).
*
* The last argument, if present, provides initial content for map. Note that
* this may be an ordinary Ruby hashmap or another Map instance with identical
* key and value types. Also note that this argument may be present whether or
* not value_typeclass is present (and it is unambiguously separate from
* value_typeclass because value_typeclass's presence is strictly determined by
* value_type). The contents of this initial hashmap or Map instance are
* shallow-copied into the new Map: the original map is unmodified, but
* references to underlying objects will be shared if the value type is a
* message type.
*
* @param key_type [Symbol]
* @param value_type [Symbol]
* @param value_typeclass [Class<AbstractMessage>,Module]
* @paramdefault value_typeclass nil
* @param init_hashmap [Hash,Map]
* @paramdefault init_hashmap {}
*/
static VALUE Map_init(int argc, VALUE* argv, VALUE _self) {
Map* self = ruby_to_Map(_self);
VALUE init_arg;
// We take either two args (:key_type, :value_type), three args (:key_type,
// :value_type, "ValueMessageType"), or four args (the above plus an initial
// hashmap).
if (argc < 2 || argc > 4) {
rb_raise(rb_eArgError, "Map constructor expects 2, 3 or 4 arguments.");
}
self->key_type = ruby_to_fieldtype(argv[0]);
self->value_type_info =
TypeInfo_FromClass(argc, argv, 1, &self->value_type_class, &init_arg);
self->arena = Arena_new();
// Check that the key type is an allowed type.
switch (self->key_type) {
case kUpb_CType_Int32:
case kUpb_CType_Int64:
case kUpb_CType_UInt32:
case kUpb_CType_UInt64:
case kUpb_CType_Bool:
case kUpb_CType_String:
case kUpb_CType_Bytes:
// These are OK.
break;
default:
rb_raise(rb_eArgError, "Invalid key type for map.");
}
self->map = upb_Map_New(Arena_get(self->arena), self->key_type,
self->value_type_info.type);
VALUE stored = ObjectCache_TryAdd(self->map, _self);
(void)stored;
PBRUBY_ASSERT(stored == _self);
if (init_arg != Qnil) {
Map_merge_into_self(_self, init_arg);
}
return Qnil;
}
/*
* ruby-doc: Map#each
*
* Invokes &block on each |key, value| pair in the map, in unspecified order.
* Note that Map also includes Enumerable; map thus acts like a normal Ruby
* sequence.
*
* @yield [Object, Object]
* @return [nil]
*/
static VALUE Map_each(VALUE _self) {
Map* self = ruby_to_Map(_self);
size_t iter = kUpb_Map_Begin;
upb_MessageValue key, val;
while (upb_Map_Next(self->map, &key, &val, &iter)) {
VALUE key_val = Convert_UpbToRuby(key, Map_keyinfo(self), self->arena);
VALUE val_val = Convert_UpbToRuby(val, self->value_type_info, self->arena);
rb_yield_values(2, key_val, val_val);
}
return Qnil;
}
/*
* ruby-doc: Map#keys
*
* Returns the list of keys contained in the map, in unspecified order.
*
* @return [Array<Object>]
*/
static VALUE Map_keys(VALUE _self) {
Map* self = ruby_to_Map(_self);
size_t iter = kUpb_Map_Begin;
VALUE ret = rb_ary_new();
upb_MessageValue key, val;
while (upb_Map_Next(self->map, &key, &val, &iter)) {
VALUE key_val = Convert_UpbToRuby(key, Map_keyinfo(self), self->arena);
rb_ary_push(ret, key_val);
}
return ret;
}
/*
* ruby-doc: Map#values
*
* Returns the list of values contained in the map, in unspecified order.
*
* @return [Array<Object>]
*/
static VALUE Map_values(VALUE _self) {
Map* self = ruby_to_Map(_self);
size_t iter = kUpb_Map_Begin;
VALUE ret = rb_ary_new();
upb_MessageValue key, val;
while (upb_Map_Next(self->map, &key, &val, &iter)) {
VALUE val_val = Convert_UpbToRuby(val, self->value_type_info, self->arena);
rb_ary_push(ret, val_val);
}
return ret;
}
/*
* ruby-doc: Map#[]
*
* Accesses the element at the given key. Throws an exception if the key type is
* incorrect. Returns nil when the key is not present in the map.
*
* @param key [Object]
* @return [Object]
*/
static VALUE Map_index(VALUE _self, VALUE key) {
Map* self = ruby_to_Map(_self);
upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
upb_MessageValue val;
if (upb_Map_Get(self->map, key_upb, &val)) {
return Convert_UpbToRuby(val, self->value_type_info, self->arena);
} else {
return Qnil;
}
}
/*
* ruby-doc: Map#[]=
*
* Inserts or overwrites the value at the given key with the given new value.
* Throws an exception if the key type is incorrect. Returns the new value that
* was just inserted.
*
* @param key [Object]
* @param value [Object]
* @return [Object]
*/
static VALUE Map_index_set(VALUE _self, VALUE key, VALUE val) {
Map* self = ruby_to_Map(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
upb_MessageValue val_upb =
Convert_RubyToUpb(val, "", self->value_type_info, arena);
upb_Map_Set(Map_GetMutable(_self), key_upb, val_upb, arena);
return val;
}
/*
* ruby-doc: Map#has_key?
*
* Returns true if the given key is present in the map. Throws an exception if
* the key has the wrong type.
*
* @param key [Object]
* @return [Boolean]
*/
static VALUE Map_has_key(VALUE _self, VALUE key) {
Map* self = ruby_to_Map(_self);
upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
if (upb_Map_Get(self->map, key_upb, NULL)) {
return Qtrue;
} else {
return Qfalse;
}
}
/*
* ruby-doc: Map#delete
*
* Deletes the value at the given key, if any, returning either the old value or
* nil if none was present. Throws an exception if the key is of the wrong type.
*
* @param key [Object]
* @return [Object]
*/
static VALUE Map_delete(VALUE _self, VALUE key) {
upb_Map* map = Map_GetMutable(_self);
Map* self = ruby_to_Map(_self);
upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
upb_MessageValue 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;
}
}
/*
* ruby-doc: Map#clear
*
* Removes all entries from the map.
*
* @return [nil]
*/
static VALUE Map_clear(VALUE _self) {
upb_Map_Clear(Map_GetMutable(_self));
return Qnil;
}
/*
* ruby-doc: Map#length
*
* Returns the number of entries (key-value pairs) in the map.
*
* @return [Integer]
*/
static VALUE Map_length(VALUE _self) {
Map* self = ruby_to_Map(_self);
return ULL2NUM(upb_Map_Size(self->map));
}
/*
* ruby-doc: Map#dup
*
* Duplicates this map with a shallow copy. References to all non-primitive
* element objects (e.g., submessages) are shared.
*
* @return [Map]
*/
static VALUE Map_dup(VALUE _self) {
Map* self = ruby_to_Map(_self);
VALUE new_map_rb = Map_new_this_type(self);
Map* new_self = ruby_to_Map(new_map_rb);
size_t iter = kUpb_Map_Begin;
upb_Arena* arena = Arena_get(new_self->arena);
upb_Map* new_map = Map_GetMutable(new_map_rb);
Arena_fuse(self->arena, arena);
upb_MessageValue key, val;
while (upb_Map_Next(self->map, &key, &val, &iter)) {
upb_Map_Set(new_map, key, val, arena);
}
return new_map_rb;
}
/*
* ruby-doc: Map#==
*
* Compares this map to another. Maps are equal if they have identical key sets,
* and for each key, the values in both maps compare equal. Elements are
* compared as per normal Ruby semantics, by calling their :== methods (or
* performing a more efficient comparison for primitive types).
*
* Maps with dissimilar key types or value types/typeclasses are never equal,
* even if value comparison (for example, between integers and floats) would
* have otherwise indicated that every element has equal value.
*
* @param other [Map]
* @return [Boolean]
*/
VALUE Map_eq(VALUE _self, VALUE _other) {
Map* self = ruby_to_Map(_self);
Map* other;
// Allow comparisons to Ruby hashmaps by converting to a temporary Map
// instance. Slow, but workable.
if (TYPE(_other) == T_HASH) {
VALUE other_map = Map_new_this_type(self);
Map_merge_into_self(other_map, _other);
_other = other_map;
}
other = ruby_to_Map(_other);
if (self == other) {
return Qtrue;
}
if (self->key_type != other->key_type ||
self->value_type_info.type != other->value_type_info.type ||
self->value_type_class != other->value_type_class) {
return Qfalse;
}
if (upb_Map_Size(self->map) != upb_Map_Size(other->map)) {
return Qfalse;
}
// For each member of self, check that an equal member exists at the same key
// in other.
size_t iter = kUpb_Map_Begin;
upb_MessageValue key, val;
while (upb_Map_Next(self->map, &key, &val, &iter)) {
upb_MessageValue other_val;
if (!upb_Map_Get(other->map, key, &other_val)) {
// Not present in other map.
return Qfalse;
}
if (!Msgval_IsEqual(val, other_val, self->value_type_info)) {
// Present but different value.
return Qfalse;
}
}
return Qtrue;
}
/*
* ruby-doc: Map#frozen?
*
* 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.
*
* @return [Boolean]
*/
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;
}
/*
* ruby-doc: Map#freeze
*
* Freezes the map object. We have to intercept this so we can freeze the
* underlying representation, not just the Ruby wrapper.
*
* @return [self]
*/
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);
}
}
RB_OBJ_FREEZE(_self);
return _self;
}
VALUE Map_EmptyFrozen(const upb_FieldDef* f) {
PBRUBY_ASSERT(upb_FieldDef_IsMap(f));
VALUE val = ObjectCache_Get(f);
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));
}
PBRUBY_ASSERT(RB_OBJ_FROZEN(val));
PBRUBY_ASSERT(upb_Map_IsFrozen(ruby_to_Map(val)->map));
return val;
}
/*
* ruby-doc: Map#hash
*
* Returns a hash value based on this map's contents.
*
* @return [Integer]
*/
VALUE Map_hash(VALUE _self) {
Map* self = ruby_to_Map(_self);
uint64_t hash = 0;
size_t iter = kUpb_Map_Begin;
TypeInfo key_info = {self->key_type};
upb_MessageValue key, val;
while (upb_Map_Next(self->map, &key, &val, &iter)) {
hash += Msgval_GetHash(key, key_info, 0);
hash += Msgval_GetHash(val, self->value_type_info, 0);
}
return LL2NUM(hash);
}
/*
* ruby-doc: Map#to_h
*
* Returns a Ruby Hash object containing all the values within the map
*
* @return [Hash]
*/
VALUE Map_to_h(VALUE _self) {
Map* self = ruby_to_Map(_self);
return Map_CreateHash(self->map, self->key_type, self->value_type_info);
}
/*
* ruby-doc: Map#inspect
*
* Returns a string representing this map's elements. It will be formatted as
* "{key => value, key => value, ...}", with each key and value string
* representation computed by its own #inspect method.
*
* @return [String]
*/
VALUE Map_inspect(VALUE _self) {
Map* self = ruby_to_Map(_self);
StringBuilder* builder = StringBuilder_New();
Map_Inspect(builder, self->map, self->key_type, self->value_type_info);
VALUE ret = StringBuilder_ToRubyString(builder);
StringBuilder_Free(builder);
return ret;
}
/*
* ruby-doc: Map#merge
*
* Copies key/value pairs from other_map into a copy of this map. If a key is
* set in other_map and this map, the value from other_map overwrites the value
* in the new copy of this map. Returns the new copy of this map with merged
* contents.
*
* @param other_map [Map]
* @return [Map]
*/
static VALUE Map_merge(VALUE _self, VALUE hashmap) {
VALUE dupped = Map_dup(_self);
return Map_merge_into_self(dupped, hashmap);
}
void Map_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "Map", rb_cObject);
rb_define_alloc_func(klass, Map_alloc);
rb_gc_register_address(&cMap);
cMap = klass;
rb_define_method(klass, "initialize", Map_init, -1);
rb_define_method(klass, "each", Map_each, 0);
rb_define_method(klass, "keys", Map_keys, 0);
rb_define_method(klass, "values", Map_values, 0);
rb_define_method(klass, "[]", Map_index, 1);
rb_define_method(klass, "[]=", Map_index_set, 2);
rb_define_method(klass, "has_key?", Map_has_key, 1);
rb_define_method(klass, "delete", Map_delete, 1);
rb_define_method(klass, "clear", Map_clear, 0);
rb_define_method(klass, "length", Map_length, 0);
rb_define_method(klass, "size", Map_length, 0);
rb_define_method(klass, "dup", Map_dup, 0);
// Also define #clone so that we don't inherit Object#clone.
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);
rb_define_method(klass, "merge", Map_merge, 1);
rb_include_module(klass, rb_mEnumerable);
}
@@ -0,0 +1,48 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef RUBY_PROTOBUF_MAP_H_
#define RUBY_PROTOBUF_MAP_H_
#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(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
// match, raises an exception.
const upb_Map* Map_GetUpbMap(VALUE val, const upb_FieldDef* field,
upb_Arena* arena);
// Implements #inspect for this map by appending its contents to |b|.
void Map_Inspect(StringBuilder* b, const upb_Map* map, upb_CType key_type,
TypeInfo val_type);
// Returns a new Hash object containing the contents of this Map.
VALUE Map_CreateHash(const upb_Map* map, upb_CType key_type, TypeInfo val_info);
// Returns a deep copy of this Map object.
VALUE Map_deep_copy(VALUE obj);
// Ruby class of Google::Protobuf::Map.
extern VALUE cMap;
// Call at startup to register all types in this module.
void Map_register(VALUE module);
// Recursively freeze map
VALUE Map_freeze(VALUE _self);
#endif // RUBY_PROTOBUF_MAP_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,82 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef RUBY_PROTOBUF_MESSAGE_H_
#define RUBY_PROTOBUF_MESSAGE_H_
#include "protobuf.h"
#include "ruby-upb.h"
// Gets the underlying upb_Message* and upb_MessageDef for the given Ruby
// message wrapper. Requires that |value| is indeed a message object.
const upb_Message* Message_Get(VALUE value, const upb_MessageDef** m);
// Like Message_Get(), but checks that the object is not frozen and returns a
// mutable pointer.
upb_Message* Message_GetMutable(VALUE value, const upb_MessageDef** m);
// Returns the Arena object for this message.
VALUE Message_GetArena(VALUE value);
// Converts |value| into a upb_Message value of the expected upb_MessageDef
// type, raising an error if this is not possible. Used when assigning |value|
// to a field of another message, which means the message must be of a
// particular type.
//
// This will perform automatic conversions in some cases (for example, Time ->
// Google::Protobuf::Timestamp). If any new message is created, it will be
// created on |arena|, and any existing message will have its arena fused with
// |arena|.
const upb_Message* Message_GetUpbMessage(VALUE value, const upb_MessageDef* m,
const char* name, upb_Arena* arena);
// 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(const upb_Message* msg, const upb_MessageDef* m,
VALUE arena);
// Gets the given field from this message.
VALUE Message_getfield(VALUE _self, const upb_FieldDef* f);
// Implements #inspect for this message, printing the text to |b|.
void Message_PrintMessage(StringBuilder* b, const upb_Message* msg,
const upb_MessageDef* m);
// Returns a hash value for the given message.
uint64_t Message_Hash(const upb_Message* msg, const upb_MessageDef* m,
uint64_t seed);
// Returns a deep copy of the given message.
upb_Message* Message_deep_copy(const upb_Message* msg, const upb_MessageDef* m,
upb_Arena* arena);
// Checks that this Ruby object is a message, and raises an exception if not.
void Message_CheckClass(VALUE klass);
// Returns a new Hash object containing the contents of this message.
VALUE Scalar_CreateHash(upb_MessageValue val, TypeInfo type_info);
// Creates a message class or enum module for this descriptor, respectively.
VALUE build_class_from_descriptor(VALUE descriptor);
VALUE build_module_from_enumdesc(VALUE _enumdesc);
// Returns the Descriptor/EnumDescriptor for the given message class or enum
// module, respectively. Returns nil if this is not a message class or enum
// module.
VALUE MessageOrEnum_GetDescriptor(VALUE klass);
// Decodes a Message from a byte sequence.
VALUE Message_decode_bytes(size_t size, const char* bytes, int options,
VALUE klass, bool freeze);
// Recursively freeze message
VALUE Message_freeze(VALUE _self);
// Call at startup to register all types in this module.
void Message_register(VALUE protobuf);
#endif // RUBY_PROTOBUF_MESSAGE_H_
@@ -0,0 +1,357 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "protobuf.h"
#include "defs.h"
#include "map.h"
#include "message.h"
#include "repeated_field.h"
VALUE cParseError;
VALUE cTypeError;
const upb_FieldDef* map_field_key(const upb_FieldDef* field) {
const upb_MessageDef* entry = upb_FieldDef_MessageSubDef(field);
return upb_MessageDef_FindFieldByNumber(entry, 1);
}
const upb_FieldDef* map_field_value(const upb_FieldDef* field) {
const upb_MessageDef* entry = upb_FieldDef_MessageSubDef(field);
return upb_MessageDef_FindFieldByNumber(entry, 2);
}
// -----------------------------------------------------------------------------
// StringBuilder, for inspect
// -----------------------------------------------------------------------------
struct StringBuilder {
size_t size;
size_t cap;
char* data;
};
typedef struct StringBuilder StringBuilder;
static size_t StringBuilder_SizeOf(size_t cap) {
return sizeof(StringBuilder) + cap;
}
StringBuilder* StringBuilder_New() {
const size_t cap = 128;
StringBuilder* builder = malloc(sizeof(*builder));
builder->size = 0;
builder->cap = cap;
builder->data = malloc(builder->cap);
return builder;
}
void StringBuilder_Free(StringBuilder* b) {
free(b->data);
free(b);
}
void StringBuilder_Printf(StringBuilder* b, const char* fmt, ...) {
size_t have = b->cap - b->size;
size_t n;
va_list args;
va_start(args, fmt);
n = vsnprintf(&b->data[b->size], have, fmt, args);
va_end(args);
if (have <= n) {
while (have <= n) {
b->cap *= 2;
have = b->cap - b->size;
}
b->data = realloc(b->data, StringBuilder_SizeOf(b->cap));
va_start(args, fmt);
n = vsnprintf(&b->data[b->size], have, fmt, args);
va_end(args);
PBRUBY_ASSERT(n < have);
}
b->size += n;
}
VALUE StringBuilder_ToRubyString(StringBuilder* b) {
VALUE ret = rb_str_new(b->data, b->size);
rb_enc_associate(ret, rb_utf8_encoding());
return ret;
}
static void StringBuilder_PrintEnum(StringBuilder* b, int32_t val,
const upb_EnumDef* e) {
const upb_EnumValueDef* ev = upb_EnumDef_FindValueByNumber(e, val);
if (ev) {
StringBuilder_Printf(b, ":%s", upb_EnumValueDef_Name(ev));
} else {
StringBuilder_Printf(b, "%" PRId32, val);
}
}
void StringBuilder_PrintMsgval(StringBuilder* b, upb_MessageValue val,
TypeInfo info) {
switch (info.type) {
case kUpb_CType_Bool:
StringBuilder_Printf(b, "%s", val.bool_val ? "true" : "false");
break;
case kUpb_CType_Float: {
VALUE str = rb_inspect(DBL2NUM(val.float_val));
StringBuilder_Printf(b, "%s", RSTRING_PTR(str));
break;
}
case kUpb_CType_Double: {
VALUE str = rb_inspect(DBL2NUM(val.double_val));
StringBuilder_Printf(b, "%s", RSTRING_PTR(str));
break;
}
case kUpb_CType_Int32:
StringBuilder_Printf(b, "%" PRId32, val.int32_val);
break;
case kUpb_CType_UInt32:
StringBuilder_Printf(b, "%" PRIu32, val.uint32_val);
break;
case kUpb_CType_Int64:
StringBuilder_Printf(b, "%" PRId64, val.int64_val);
break;
case kUpb_CType_UInt64:
StringBuilder_Printf(b, "%" PRIu64, val.uint64_val);
break;
case kUpb_CType_String:
StringBuilder_Printf(b, "\"%.*s\"", (int)val.str_val.size,
val.str_val.data);
break;
case kUpb_CType_Bytes:
StringBuilder_Printf(b, "\"%.*s\"", (int)val.str_val.size,
val.str_val.data);
break;
case kUpb_CType_Enum:
StringBuilder_PrintEnum(b, val.int32_val, info.def.enumdef);
break;
case kUpb_CType_Message:
Message_PrintMessage(b, val.msg_val, info.def.msgdef);
break;
}
}
// -----------------------------------------------------------------------------
// Arena
// -----------------------------------------------------------------------------
typedef struct {
upb_Arena* arena;
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
VALUE pinned_objs;
} Arena;
static void Arena_mark(void* data) {
Arena* arena = data;
rb_gc_mark(arena->pinned_objs);
}
static void Arena_free(void* data) {
Arena* arena = data;
upb_Arena_Free(arena->arena);
xfree(arena);
}
static size_t Arena_memsize(const void* data) {
const Arena* arena = data;
size_t fused_count;
size_t memsize = upb_Arena_SpaceAllocated(arena->arena, &fused_count);
if (fused_count > 1) {
// If other arena were fused we attribute an equal
// share of memory usage to each one.
memsize /= fused_count;
}
return memsize + sizeof(Arena);
}
static VALUE cArena;
const rb_data_type_t Arena_type = {
"Google::Protobuf::Internal::Arena",
{Arena_mark, Arena_free, Arena_memsize},
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
};
static void* ruby_upb_allocfunc(upb_alloc* alloc, void* ptr, size_t oldsize,
size_t size, size_t* actual_size) {
if (size == 0) {
xfree(ptr);
return NULL;
} else {
return xrealloc(ptr, size);
}
}
upb_alloc ruby_upb_alloc = {&ruby_upb_allocfunc};
static VALUE Arena_alloc(VALUE klass) {
Arena* arena = ALLOC(Arena);
arena->arena = upb_Arena_Init(NULL, 0, &ruby_upb_alloc);
arena->pinned_objs = Qnil;
return TypedData_Wrap_Struct(klass, &Arena_type, arena);
}
upb_Arena* Arena_get(VALUE _arena) {
Arena* arena;
TypedData_Get_Struct(_arena, Arena, &Arena_type, arena);
return arena->arena;
}
void Arena_fuse(VALUE _arena, upb_Arena* other) {
Arena* arena;
TypedData_Get_Struct(_arena, Arena, &Arena_type, arena);
if (!upb_Arena_Fuse(arena->arena, other)) {
rb_raise(rb_eRuntimeError,
"Unable to fuse arenas. This should never happen since Ruby does "
"not use initial blocks");
}
}
VALUE Arena_new() { return Arena_alloc(cArena); }
void Arena_register(VALUE module) {
VALUE internal = rb_define_module_under(module, "Internal");
VALUE klass = rb_define_class_under(internal, "Arena", rb_cObject);
rb_define_alloc_func(klass, Arena_alloc);
rb_gc_register_address(&cArena);
cArena = klass;
}
// -----------------------------------------------------------------------------
// Object Cache
// -----------------------------------------------------------------------------
// Public ObjectCache API.
VALUE weak_obj_cache = Qnil;
ID item_get;
ID item_try_add;
static void ObjectCache_Init(VALUE protobuf) {
item_get = rb_intern("get");
item_try_add = rb_intern("try_add");
rb_gc_register_address(&weak_obj_cache);
VALUE internal = rb_const_get(protobuf, rb_intern("Internal"));
#if SIZEOF_LONG >= SIZEOF_VALUE
VALUE cache_class = rb_const_get(internal, rb_intern("ObjectCache"));
#else
VALUE cache_class = rb_const_get(internal, rb_intern("LegacyObjectCache"));
#endif
weak_obj_cache = rb_class_new_instance(0, NULL, cache_class);
rb_const_set(internal, rb_intern("OBJECT_CACHE"), weak_obj_cache);
rb_const_set(internal, rb_intern("SIZEOF_LONG"), INT2NUM(SIZEOF_LONG));
rb_const_set(internal, rb_intern("SIZEOF_VALUE"), INT2NUM(SIZEOF_VALUE));
}
static VALUE ObjectCache_GetKey(const void* key) {
VALUE key_val = (VALUE)key;
PBRUBY_ASSERT((key_val & 3) == 0);
// Ensure the key can be stored as a Fixnum since 1 bit is needed for
// FIXNUM_FLAG and 1 bit is needed for the sign bit.
VALUE new_key = LL2NUM(key_val >> 2);
PBRUBY_ASSERT(FIXNUM_P(new_key));
return new_key;
}
VALUE ObjectCache_TryAdd(const void* key, VALUE val) {
VALUE key_val = ObjectCache_GetKey(key);
return rb_funcall(weak_obj_cache, item_try_add, 2, key_val, val);
}
// Returns the cached object for this key, if any. Otherwise returns Qnil.
VALUE ObjectCache_Get(const void* key) {
VALUE key_val = ObjectCache_GetKey(key);
return rb_funcall(weak_obj_cache, item_get, 1, key_val);
}
/*
* call-seq:
* Google::Protobuf.discard_unknown(msg)
*
* Discard unknown fields in the given message object and recursively discard
* unknown fields in submessages.
*/
static VALUE Google_Protobuf_discard_unknown(VALUE self, VALUE msg_rb) {
const upb_MessageDef* m;
upb_Message* msg = Message_GetMutable(msg_rb, &m);
const upb_DefPool* ext_pool = upb_FileDef_Pool(upb_MessageDef_File(m));
if (!upb_Message_DiscardUnknown(msg, m, ext_pool, 128)) {
rb_raise(rb_eRuntimeError, "Messages nested too deeply.");
}
return Qnil;
}
/*
* call-seq:
* Google::Protobuf.deep_copy(obj) => copy_of_obj
*
* Performs a deep copy of a RepeatedField instance, a Map instance, or a
* message object, recursively copying its members.
*/
VALUE Google_Protobuf_deep_copy(VALUE self, VALUE obj) {
VALUE klass = CLASS_OF(obj);
if (klass == cRepeatedField) {
return RepeatedField_deep_copy(obj);
} else if (klass == cMap) {
return Map_deep_copy(obj);
} else {
VALUE new_arena_rb = Arena_new();
upb_Arena* new_arena = Arena_get(new_arena_rb);
const upb_MessageDef* m;
const upb_Message* msg = Message_Get(obj, &m);
upb_Message* new_msg = Message_deep_copy(msg, m, new_arena);
return Message_GetRubyWrapper(new_msg, m, new_arena_rb);
}
}
// -----------------------------------------------------------------------------
// Initialization/entry point.
// -----------------------------------------------------------------------------
// This must be named "Init_protobuf_c" because the Ruby module is named
// "protobuf_c" -- the VM looks for this symbol in our .so.
__attribute__((visibility("default"))) void Init_protobuf_c() {
VALUE google = rb_define_module("Google");
VALUE protobuf = rb_define_module_under(google, "Protobuf");
ObjectCache_Init(protobuf);
Arena_register(protobuf);
Defs_register(protobuf);
RepeatedField_register(protobuf);
Map_register(protobuf);
Message_register(protobuf);
cParseError = rb_const_get(protobuf, rb_intern("ParseError"));
rb_gc_register_mark_object(cParseError);
cTypeError = rb_const_get(protobuf, rb_intern("TypeError"));
rb_gc_register_mark_object(cTypeError);
rb_define_singleton_method(protobuf, "discard_unknown",
Google_Protobuf_discard_unknown, 1);
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);
}
}
@@ -0,0 +1,100 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef __GOOGLE_PROTOBUF_RUBY_PROTOBUF_H__
#define __GOOGLE_PROTOBUF_RUBY_PROTOBUF_H__
// Ruby 3+ defines NDEBUG itself, see: https://bugs.ruby-lang.org/issues/18777
#ifdef NDEBUG
#include <ruby.h>
#else
#include <ruby.h>
#undef NDEBUG
#endif
#include <assert.h> // Must be included after the NDEBUG logic above.
#include <ruby/encoding.h>
#include <ruby/vm.h>
#include "defs.h"
#include "ruby-upb.h"
// These operate on a map field (i.e., a repeated field of submessages whose
// submessage type is a map-entry msgdef).
const upb_FieldDef* map_field_key(const upb_FieldDef* field);
const upb_FieldDef* map_field_value(const upb_FieldDef* field);
// -----------------------------------------------------------------------------
// Arena
// -----------------------------------------------------------------------------
// A Ruby object that wraps an underlying upb_Arena. Any objects that are
// allocated from this arena should reference the Arena in rb_gc_mark(), to
// ensure that the object's underlying memory outlives any Ruby object that can
// reach it.
VALUE Arena_new();
upb_Arena* Arena_get(VALUE arena);
// Fuses this arena to another, throwing a Ruby exception if this is not
// possible.
void Arena_fuse(VALUE arena, upb_Arena* other);
// -----------------------------------------------------------------------------
// ObjectCache
// -----------------------------------------------------------------------------
// Global object cache from upb array/map/message/symtab to wrapper object.
//
// This is a conceptually "weak" cache, in that it does not prevent "val" from
// being collected (though in Ruby <2.7 is it effectively strong, due to
// implementation limitations).
// Tries to add a new entry to the cache, returning the newly installed value or
// the pre-existing entry.
VALUE ObjectCache_TryAdd(const void* key, VALUE val);
// Returns the cached object for this key, if any. Otherwise returns Qnil.
VALUE ObjectCache_Get(const void* key);
// -----------------------------------------------------------------------------
// StringBuilder, for inspect
// -----------------------------------------------------------------------------
struct StringBuilder;
typedef struct StringBuilder StringBuilder;
StringBuilder* StringBuilder_New();
void StringBuilder_Free(StringBuilder* b);
void StringBuilder_Printf(StringBuilder* b, const char* fmt, ...);
VALUE StringBuilder_ToRubyString(StringBuilder* b);
void StringBuilder_PrintMsgval(StringBuilder* b, upb_MessageValue val,
TypeInfo info);
// -----------------------------------------------------------------------------
// Utilities.
// -----------------------------------------------------------------------------
extern VALUE cTypeError;
#ifdef NDEBUG
#define PBRUBY_ASSERT(expr) \
do { \
} while (false && (expr))
#else
#define PBRUBY_ASSERT(expr) \
if (!(expr)) \
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))
#endif // __GOOGLE_PROTOBUF_RUBY_PROTOBUF_H__
@@ -0,0 +1,735 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "repeated_field.h"
#include "convert.h"
#include "defs.h"
#include "message.h"
#include "protobuf.h"
// -----------------------------------------------------------------------------
// Repeated field container type.
// -----------------------------------------------------------------------------
typedef struct {
const upb_Array* array; // Can get as mutable when non-frozen.
TypeInfo type_info;
VALUE type_class; // To GC-root the msgdef/enumdef in type_info.
VALUE arena; // To GC-root the upb_Array.
} RepeatedField;
VALUE cRepeatedField;
static void RepeatedField_mark(void* _self) {
RepeatedField* self = (RepeatedField*)_self;
rb_gc_mark(self->type_class);
rb_gc_mark(self->arena);
}
const rb_data_type_t RepeatedField_type = {
"Google::Protobuf::RepeatedField",
{RepeatedField_mark, RUBY_DEFAULT_FREE, NULL},
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
};
static RepeatedField* ruby_to_RepeatedField(VALUE _self) {
RepeatedField* self;
TypedData_Get_Struct(_self, RepeatedField, &RepeatedField_type, self);
return self;
}
static upb_Array* RepeatedField_GetMutable(VALUE _self) {
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) {
RepeatedField* self = ALLOC(RepeatedField);
self->arena = Qnil;
self->type_class = Qnil;
self->array = NULL;
return TypedData_Wrap_Struct(klass, &RepeatedField_type, self);
}
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);
PBRUBY_ASSERT(arena != Qnil);
VALUE val = ObjectCache_Get(array);
if (val == Qnil) {
val = RepeatedField_alloc(cRepeatedField);
RepeatedField* self;
TypedData_Get_Struct(val, RepeatedField, &RepeatedField_type, self);
self->array = array;
self->arena = arena;
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(array, val);
}
PBRUBY_ASSERT(ruby_to_RepeatedField(val)->type_info.type == type_info.type);
PBRUBY_ASSERT(ruby_to_RepeatedField(val)->type_info.def.msgdef ==
type_info.def.msgdef);
PBRUBY_ASSERT(ruby_to_RepeatedField(val)->array == array);
return val;
}
static VALUE RepeatedField_new_this_type(RepeatedField* from) {
VALUE arena_rb = Arena_new();
upb_Array* array = upb_Array_New(Arena_get(arena_rb), from->type_info.type);
VALUE ret = RepeatedField_GetRubyWrapper(array, from->type_info, arena_rb);
PBRUBY_ASSERT(ruby_to_RepeatedField(ret)->type_class == from->type_class);
return ret;
}
void RepeatedField_Inspect(StringBuilder* b, const upb_Array* array,
TypeInfo info) {
bool first = true;
StringBuilder_Printf(b, "[");
size_t n = array ? upb_Array_Size(array) : 0;
for (size_t i = 0; i < n; i++) {
if (first) {
first = false;
} else {
StringBuilder_Printf(b, ", ");
}
StringBuilder_PrintMsgval(b, upb_Array_Get(array, i), info);
}
StringBuilder_Printf(b, "]");
}
VALUE RepeatedField_deep_copy(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
VALUE new_rptfield = RepeatedField_new_this_type(self);
RepeatedField* new_self = ruby_to_RepeatedField(new_rptfield);
VALUE arena_rb = new_self->arena;
upb_Array* new_array = RepeatedField_GetMutable(new_rptfield);
upb_Arena* arena = Arena_get(arena_rb);
size_t elements = upb_Array_Size(self->array);
upb_Array_Resize(new_array, elements, arena);
size_t size = upb_Array_Size(self->array);
for (size_t i = 0; i < size; i++) {
upb_MessageValue msgval = upb_Array_Get(self->array, i);
upb_MessageValue copy = Msgval_DeepCopy(msgval, self->type_info, arena);
upb_Array_Set(new_array, i, copy);
}
return new_rptfield;
}
const upb_Array* RepeatedField_GetUpbArray(VALUE val, const upb_FieldDef* field,
upb_Arena* arena) {
RepeatedField* self;
TypeInfo type_info = TypeInfo_get(field);
if (!RB_TYPE_P(val, T_DATA) || !RTYPEDDATA_P(val) ||
RTYPEDDATA_TYPE(val) != &RepeatedField_type) {
rb_raise(cTypeError, "Expected repeated field array");
}
self = ruby_to_RepeatedField(val);
if (self->type_info.type != type_info.type) {
rb_raise(cTypeError, "Repeated field array has wrong element type");
}
if (self->type_info.def.msgdef != type_info.def.msgdef) {
rb_raise(cTypeError, "Repeated field array has wrong message/enum class");
}
Arena_fuse(self->arena, arena);
return self->array;
}
static int index_position(VALUE _index, RepeatedField* repeated_field) {
int index = NUM2INT(_index);
if (index < 0) index += upb_Array_Size(repeated_field->array);
return index;
}
static VALUE RepeatedField_subarray(RepeatedField* self, long beg, long len) {
long size = (long)upb_Array_Size(self->array);
// Match Ruby Array#[beg, len] semantics and avoid an out-of-bounds read
// through upb_Array_Get (which only has a debug-only UPB_ASSERT for
// bounds): clamp len to what is actually available, and reject negative
// len up front.
if (len < 0) return Qnil;
if (beg < 0 || beg > size) return Qnil;
if (len > size - beg) len = size - beg;
VALUE ary = rb_ary_new2(len);
for (long i = beg; i < beg + len; i++) {
upb_MessageValue msgval = upb_Array_Get(self->array, i);
VALUE elem = Convert_UpbToRuby(msgval, self->type_info, self->arena);
rb_ary_push(ary, elem);
}
return ary;
}
/**
* ruby-doc: RepeatedField
*
*/
/*
* ruby-doc: RepeatedField#each
*
* Invokes the block once for each element of the repeated field. RepeatedField
* also includes Enumerable; combined with this method, the repeated field thus
* acts like an ordinary Ruby sequence.
*
* @yield [Object]
* @return [self]
*/
static VALUE RepeatedField_each(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
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);
rb_yield(val);
}
return _self;
}
/*
* ruby-doc: RepeatedField#[]
*
* Accesses the element at the given index. Returns nil on out-of-bounds
*
* @param index [Integer]
* @return [Object,nil]
*/
static VALUE RepeatedField_index(int argc, VALUE* argv, VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
long size = upb_Array_Size(self->array);
VALUE arg = argv[0];
long beg, len;
if (argc == 1) {
if (FIXNUM_P(arg)) {
/* standard case */
upb_MessageValue msgval;
int index = index_position(argv[0], self);
if (index < 0 || (size_t)index >= upb_Array_Size(self->array)) {
return Qnil;
}
msgval = upb_Array_Get(self->array, index);
return Convert_UpbToRuby(msgval, self->type_info, self->arena);
} else {
/* check if idx is Range */
switch (rb_range_beg_len(arg, &beg, &len, size, 0)) {
case Qfalse:
break;
case Qnil:
return Qnil;
default:
return RepeatedField_subarray(self, beg, len);
}
}
}
/* assume 2 arguments */
beg = NUM2LONG(argv[0]);
len = NUM2LONG(argv[1]);
if (beg < 0) {
beg += size;
}
if (beg > size) {
return Qnil;
}
return RepeatedField_subarray(self, beg, len);
}
/*
* ruby-doc: RepeatedField#[]=
*
* Sets the element at the given index. On out-of-bounds assignments, extends
* the array and fills the hole (if any) with default values.
*
* @param index [Integer]
* @param value [Object
* @return [nil]
*/
static VALUE RepeatedField_index_set(VALUE _self, VALUE _index, VALUE val) {
RepeatedField* self = ruby_to_RepeatedField(_self);
int size = upb_Array_Size(self->array);
upb_Array* array = RepeatedField_GetMutable(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_MessageValue msgval = Convert_RubyToUpb(val, "", self->type_info, arena);
int index = index_position(_index, self);
if (index < 0 || index >= (INT_MAX - 1)) {
return Qnil;
}
if (index >= size) {
upb_Array_Resize(array, index + 1, arena);
upb_MessageValue fill;
memset(&fill, 0, sizeof(fill));
for (int i = size; i < index; i++) {
// Fill default values.
// TODO: should this happen at the upb level?
upb_Array_Set(array, i, fill);
}
}
upb_Array_Set(array, index, msgval);
return Qnil;
}
/*
* ruby-doc: RepeatedField#push
*
* Adds a new element to the repeated field.
*
* @param value [Object]
* @return [self]
*/
static VALUE RepeatedField_push_vararg(int argc, VALUE* argv, VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_Array* array = RepeatedField_GetMutable(_self);
int i;
for (i = 0; i < argc; i++) {
upb_MessageValue msgval =
Convert_RubyToUpb(argv[i], "", self->type_info, arena);
upb_Array_Append(array, msgval, arena);
}
return _self;
}
/*
* ruby-doc: RepeatedField#<<
*
* Adds a new element to the repeated field.
*
* @param value [Object]
* @return [self]
*/
static VALUE RepeatedField_push(VALUE _self, VALUE val) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_Array* array = RepeatedField_GetMutable(_self);
upb_MessageValue msgval = Convert_RubyToUpb(val, "", self->type_info, arena);
upb_Array_Append(array, msgval, arena);
return _self;
}
/*
* Private ruby method, used by RepeatedField.pop
*/
static VALUE RepeatedField_pop_one(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
size_t size = upb_Array_Size(self->array);
upb_Array* array = RepeatedField_GetMutable(_self);
upb_MessageValue last;
VALUE ret;
if (size == 0) {
return Qnil;
}
last = upb_Array_Get(self->array, size - 1);
ret = Convert_UpbToRuby(last, self->type_info, self->arena);
upb_Array_Resize(array, size - 1, Arena_get(self->arena));
return ret;
}
/*
* ruby-doc: RepeatedField#replace
*
* Replaces the contents of the repeated field with the given list of elements.
*
* @param list [Array]
* @return [Array]
*/
static VALUE RepeatedField_replace(VALUE _self, VALUE list) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Array* array = RepeatedField_GetMutable(_self);
int i;
Check_Type(list, T_ARRAY);
upb_Array_Resize(array, 0, Arena_get(self->arena));
for (i = 0; i < RARRAY_LEN(list); i++) {
RepeatedField_push(_self, rb_ary_entry(list, i));
}
return list;
}
/*
* ruby-doc: RepeatedField#clear
*
* Clears (removes all elements from) this repeated field.
*
* @return [self]
*/
static VALUE RepeatedField_clear(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Array* array = RepeatedField_GetMutable(_self);
upb_Array_Resize(array, 0, Arena_get(self->arena));
return _self;
}
/*
* ruby-doc: RepeatedField#length
*
* Returns the length of this repeated field.
*
* @return [Integer]
*/
static VALUE RepeatedField_length(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
return INT2NUM(upb_Array_Size(self->array));
}
/*
* ruby-doc: RepeatedField#dup
*
* Duplicates this repeated field with a shallow copy. References to all
* non-primitive element objects (e.g., submessages) are shared.
*
* @return [RepeatedField]
*/
static VALUE RepeatedField_dup(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
VALUE new_rptfield = RepeatedField_new_this_type(self);
RepeatedField* new_rptfield_self = ruby_to_RepeatedField(new_rptfield);
upb_Array* new_array = RepeatedField_GetMutable(new_rptfield);
upb_Arena* arena = Arena_get(new_rptfield_self->arena);
int size = upb_Array_Size(self->array);
int i;
Arena_fuse(self->arena, arena);
for (i = 0; i < size; i++) {
upb_MessageValue msgval = upb_Array_Get(self->array, i);
upb_Array_Append(new_array, msgval, arena);
}
return new_rptfield;
}
/*
* ruby-doc: RepeatedField#to_ary
*
* Used when converted implicitly into array, e.g. compared to an Array.
* Also called as a fallback of Object#to_a
*
* @return [Array]
*/
VALUE RepeatedField_to_ary(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
int size = upb_Array_Size(self->array);
VALUE ary = rb_ary_new2(size);
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);
rb_ary_push(ary, val);
}
return ary;
}
/*
* ruby-doc: RepeatedField#==
*
* Compares this repeated field to another. Repeated fields are equal if their
* element types are equal, their lengths are equal, and each element is equal.
* Elements are compared as per normal Ruby semantics, by calling their :==
* methods (or performing a more efficient comparison for primitive types).
*
* Repeated fields with dissimilar element types are never equal, even if value
* comparison (for example, between integers and floats) would have otherwise
* indicated that every element has equal value.
*
* @param other [RepeatedField]
* @return [Boolean]
*/
VALUE RepeatedField_eq(VALUE _self, VALUE _other) {
RepeatedField* self;
RepeatedField* other;
if (_self == _other) {
return Qtrue;
}
if (TYPE(_other) == T_ARRAY) {
VALUE self_ary = RepeatedField_to_ary(_self);
return rb_equal(self_ary, _other);
}
self = ruby_to_RepeatedField(_self);
other = ruby_to_RepeatedField(_other);
size_t n = upb_Array_Size(self->array);
if (self->type_info.type != other->type_info.type ||
self->type_class != other->type_class ||
upb_Array_Size(other->array) != n) {
return Qfalse;
}
for (size_t i = 0; i < n; i++) {
upb_MessageValue val1 = upb_Array_Get(self->array, i);
upb_MessageValue val2 = upb_Array_Get(other->array, i);
if (!Msgval_IsEqual(val1, val2, self->type_info)) {
return Qfalse;
}
}
return Qtrue;
}
/*
* ruby-doc: RepeatedField#frozen?
*
* 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.
*
* @return [Boolean]
*/
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;
}
/*
* ruby-doc: RepeatedField#freeze
*
* Freezes the repeated field object. We have to intercept this so we can freeze
* the underlying representation, not just the Ruby wrapper.
*
* @return [self]
*/
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 (!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;
}
/*
* ruby-doc: RepeatedField#hash
*
* Returns a hash value computed from this repeated field's elements.
*
* @return [Integer]
*/
VALUE RepeatedField_hash(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
uint64_t hash = 0;
size_t n = upb_Array_Size(self->array);
for (size_t i = 0; i < n; i++) {
upb_MessageValue val = upb_Array_Get(self->array, i);
hash = Msgval_GetHash(val, self->type_info, hash);
}
return LL2NUM(hash);
}
/*
* ruby-doc: RepeatedField#+
*
* Returns a new repeated field that contains the concatenated list of this
* repeated field's elements and other's elements. The other (second) list may
* be either another repeated field or a Ruby array.
*
* @param other [Array,RepeatedField]
* @return [RepeatedField]
*/
VALUE RepeatedField_plus(VALUE _self, VALUE list) {
VALUE dupped_ = RepeatedField_dup(_self);
if (TYPE(list) == T_ARRAY) {
int i;
for (i = 0; i < RARRAY_LEN(list); i++) {
VALUE elem = rb_ary_entry(list, i);
RepeatedField_push(dupped_, elem);
}
} else if (RB_TYPE_P(list, T_DATA) && RTYPEDDATA_P(list) &&
RTYPEDDATA_TYPE(list) == &RepeatedField_type) {
RepeatedField* self = ruby_to_RepeatedField(_self);
RepeatedField* list_rptfield = ruby_to_RepeatedField(list);
RepeatedField* dupped = ruby_to_RepeatedField(dupped_);
upb_Array* dupped_array = RepeatedField_GetMutable(dupped_);
upb_Arena* arena = Arena_get(dupped->arena);
Arena_fuse(list_rptfield->arena, arena);
int size = upb_Array_Size(list_rptfield->array);
int i;
if (self->type_info.type != list_rptfield->type_info.type ||
self->type_class != list_rptfield->type_class) {
rb_raise(rb_eArgError,
"Attempt to append RepeatedField with different element type.");
}
for (i = 0; i < size; i++) {
upb_MessageValue msgval = upb_Array_Get(list_rptfield->array, i);
upb_Array_Append(dupped_array, msgval, arena);
}
} else {
rb_raise(rb_eArgError, "Unknown type appending to RepeatedField");
}
return dupped_;
}
/*
* ruby-doc: RepeatedField#concat
*
* concats the passed in array to self. Returns a Ruby array.
*
* @param other [RepeatedField]
* @return [Array]
*/
VALUE RepeatedField_concat(VALUE _self, VALUE list) {
int i;
Check_Type(list, T_ARRAY);
for (i = 0; i < RARRAY_LEN(list); i++) {
RepeatedField_push(_self, rb_ary_entry(list, i));
}
return _self;
}
/*
* ruby-doc: RepeatedField#initialize
*
* Creates a new repeated field. The provided type must be a Ruby symbol, and
* can take on the same values as those accepted by {FieldDescriptor#type=}. If
* the type is :message or :enum, type_class must be non-nil, and must be the
* Ruby class or module returned by {Descriptor#msgclass} or
* {EnumDescriptor#enummodule}, respectively. An initial list of elements may
* also be provided.
*
* @param type [Symbol]
* @param type_class [Class<AbstractMessage>, Module]
* @paramdefault type_class nil
* @param initial_elems [Array]
* @paramdefault initial_elems []
* @return [RepeatedField]
*/
VALUE RepeatedField_init(int argc, VALUE* argv, VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Arena* arena;
VALUE ary = Qnil;
self->arena = Arena_new();
arena = Arena_get(self->arena);
if (argc < 1) {
rb_raise(rb_eArgError, "Expected at least 1 argument.");
}
self->type_info = TypeInfo_FromClass(argc, argv, 0, &self->type_class, &ary);
self->array = upb_Array_New(arena, self->type_info.type);
VALUE stored_val = ObjectCache_TryAdd(self->array, _self);
PBRUBY_ASSERT(stored_val == _self);
if (ary != Qnil) {
if (!RB_TYPE_P(ary, T_ARRAY)) {
rb_raise(rb_eArgError, "Expected array as initialize argument");
}
for (int i = 0; i < RARRAY_LEN(ary); i++) {
RepeatedField_push(_self, rb_ary_entry(ary, i));
}
}
return Qnil;
}
void RepeatedField_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "RepeatedField", rb_cObject);
rb_define_alloc_func(klass, RepeatedField_alloc);
rb_gc_register_address(&cRepeatedField);
cRepeatedField = klass;
rb_define_method(klass, "initialize", RepeatedField_init, -1);
rb_define_method(klass, "each", RepeatedField_each, 0);
rb_define_method(klass, "[]", RepeatedField_index, -1);
rb_define_method(klass, "at", RepeatedField_index, -1);
rb_define_method(klass, "[]=", RepeatedField_index_set, 2);
rb_define_method(klass, "push", RepeatedField_push_vararg, -1);
rb_define_method(klass, "<<", RepeatedField_push, 1);
rb_define_private_method(klass, "pop_one", RepeatedField_pop_one, 0);
rb_define_method(klass, "replace", RepeatedField_replace, 1);
rb_define_method(klass, "clear", RepeatedField_clear, 0);
rb_define_method(klass, "length", RepeatedField_length, 0);
rb_define_method(klass, "size", RepeatedField_length, 0);
rb_define_method(klass, "dup", RepeatedField_dup, 0);
// Also define #clone so that we don't inherit Object#clone.
rb_define_method(klass, "clone", RepeatedField_dup, 0);
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);
rb_include_module(klass, rb_mEnumerable);
}
@@ -0,0 +1,45 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef RUBY_PROTOBUF_REPEATED_FIELD_H_
#define RUBY_PROTOBUF_REPEATED_FIELD_H_
#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(const upb_Array* msg, TypeInfo type_info,
VALUE arena);
// Gets the underlying upb_Array for this Ruby RepeatedField object, which must
// have a type that matches |f|. If this is not a repeated field or the type
// doesn't match, raises an exception.
const upb_Array* RepeatedField_GetUpbArray(VALUE value, const upb_FieldDef* f,
upb_Arena* arena);
// Implements #inspect for this repeated field by appending its contents to |b|.
void RepeatedField_Inspect(StringBuilder* b, const upb_Array* array,
TypeInfo info);
// Returns a deep copy of this RepeatedField object.
VALUE RepeatedField_deep_copy(VALUE obj);
// Ruby class of Google::Protobuf::RepeatedField.
extern VALUE cRepeatedField;
// Call at startup to register all types in this module.
void RepeatedField_register(VALUE module);
// Recursively freeze RepeatedField.
VALUE RepeatedField_freeze(VALUE _self);
#endif // RUBY_PROTOBUF_REPEATED_FIELD_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// -----------------------------------------------------------------------------
// Ruby <-> upb data conversion functions. Strictly free of dependencies on
// Ruby interpreter internals.
#include "shared_convert.h"
bool shared_Msgval_IsEqual(upb_MessageValue val1, upb_MessageValue val2,
upb_CType type, const upb_MessageDef* msgdef,
upb_Status* status) {
switch (type) {
case kUpb_CType_Bool:
return memcmp(&val1, &val2, 1) == 0;
case kUpb_CType_Float:
case kUpb_CType_Int32:
case kUpb_CType_UInt32:
case kUpb_CType_Enum:
return memcmp(&val1, &val2, 4) == 0;
case kUpb_CType_Double:
case kUpb_CType_Int64:
case kUpb_CType_UInt64:
return memcmp(&val1, &val2, 8) == 0;
case kUpb_CType_String:
case kUpb_CType_Bytes:
return val1.str_val.size == val2.str_val.size &&
memcmp(val1.str_val.data, val2.str_val.data, val1.str_val.size) ==
0;
case kUpb_CType_Message: {
const upb_MiniTable* m = upb_MessageDef_MiniTable(msgdef);
const int options = 0;
return upb_Message_IsEqual(val1.msg_val, val2.msg_val, m, options);
}
default:
upb_Status_SetErrorMessage(status, "Internal error, unexpected type");
return false;
}
}
uint64_t shared_Msgval_GetHash(upb_MessageValue val, upb_CType type,
const upb_MessageDef* msgdef, uint64_t seed,
upb_Status* status) {
switch (type) {
case kUpb_CType_Bool:
return _upb_Hash(&val, 1, seed);
case kUpb_CType_Float:
case kUpb_CType_Int32:
case kUpb_CType_UInt32:
case kUpb_CType_Enum:
return _upb_Hash(&val, 4, seed);
case kUpb_CType_Double:
case kUpb_CType_Int64:
case kUpb_CType_UInt64:
return _upb_Hash(&val, 8, seed);
case kUpb_CType_String:
case kUpb_CType_Bytes:
return _upb_Hash(val.str_val.data, val.str_val.size, seed);
case kUpb_CType_Message:
return shared_Message_Hash(val.msg_val, msgdef, seed, status);
default:
upb_Status_SetErrorMessage(status, "Internal error, unexpected type");
return 0;
}
}
@@ -0,0 +1,26 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// -----------------------------------------------------------------------------
// Ruby <-> upb data conversion functions. Strictly free of dependencies on
// Ruby interpreter internals.
#ifndef RUBY_PROTOBUF_SHARED_CONVERT_H_
#define RUBY_PROTOBUF_SHARED_CONVERT_H_
#include "ruby-upb.h"
#include "shared_message.h"
bool shared_Msgval_IsEqual(upb_MessageValue val1, upb_MessageValue val2,
upb_CType type, const upb_MessageDef* msgdef,
upb_Status* status);
uint64_t shared_Msgval_GetHash(upb_MessageValue val, upb_CType type,
const upb_MessageDef* msgdef, uint64_t seed,
upb_Status* status);
#endif // RUBY_PROTOBUF_SHARED_CONVERT_H_
@@ -0,0 +1,37 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// -----------------------------------------------------------------------------
// Ruby Message functions. Strictly free of dependencies on
// Ruby interpreter internals.
#include "shared_message.h"
// Support function for Message_Hash. Returns a hash value for the given
// message.
uint64_t shared_Message_Hash(const upb_Message* msg, const upb_MessageDef* m,
uint64_t seed, upb_Status* status) {
upb_Arena* arena = upb_Arena_New();
char* data;
size_t size;
// Hash a deterministically serialized payloads with no unknown fields.
upb_EncodeStatus encode_status = upb_Encode(
msg, upb_MessageDef_MiniTable(m),
kUpb_EncodeOption_SkipUnknown | kUpb_EncodeOption_Deterministic, arena,
&data, &size);
if (encode_status == kUpb_EncodeStatus_Ok) {
uint64_t ret = _upb_Hash(data, size, seed);
upb_Arena_Free(arena);
return ret;
}
upb_Arena_Free(arena);
upb_Status_SetErrorMessage(status, "Error calculating hash");
return 0;
}
@@ -0,0 +1,21 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// -----------------------------------------------------------------------------
// Ruby Message functions. Strictly free of dependencies on
// Ruby interpreter internals.
#ifndef RUBY_PROTOBUF_SHARED_MESSAGE_H_
#define RUBY_PROTOBUF_SHARED_MESSAGE_H_
#include "ruby-upb.h"
// Returns a hash value for the given message.
uint64_t shared_Message_Hash(const upb_Message* msg, const upb_MessageDef* m,
uint64_t seed, upb_Status* status);
#endif // RUBY_PROTOBUF_SHARED_MESSAGE_H_
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2019 Yibo Cai
Copyright 2022 Google LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,207 @@
// Copyright 2023 Google LLC
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
/* This is a wrapper for the Google range-sse.cc algorithm which checks whether
* a sequence of bytes is a valid UTF-8 sequence and finds the longest valid
* prefix of the UTF-8 sequence.
*
* The key difference is that it checks for as much ASCII symbols as possible
* and then falls back to the range-sse.cc algorithm. The changes to the
* algorithm are cosmetic, mostly to trick the clang compiler to produce optimal
* code.
*
* For API see the utf8_validity.h header.
*/
#include "utf8_range.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#if defined(__GNUC__)
#define FORCE_INLINE_ATTR __attribute__((always_inline)) inline
#elif defined(_MSC_VER)
#define FORCE_INLINE_ATTR __forceinline
#else
#define FORCE_INLINE_ATTR inline
#endif
static FORCE_INLINE_ATTR uint64_t utf8_range_UnalignedLoad64(
const void* p) {
uint64_t t;
memcpy(&t, p, sizeof t);
return t;
}
static FORCE_INLINE_ATTR int utf8_range_AsciiIsAscii(unsigned char c) {
return c < 128;
}
static FORCE_INLINE_ATTR int utf8_range_IsTrailByteOk(const char c) {
return (int8_t)(c) <= (int8_t)(0xBF);
}
/* If return_position is false then it returns 1 if |data| is a valid utf8
* sequence, otherwise returns 0.
* If return_position is set to true, returns the length in bytes of the prefix
of |data| that is all structurally valid UTF-8.
*/
static size_t utf8_range_ValidateUTF8Naive(const char* data, const char* end,
int return_position) {
/* We return err_pos in the loop which is always 0 if !return_position */
size_t err_pos = 0;
size_t codepoint_bytes = 0;
/* The early check is done because of early continue's on codepoints of all
* sizes, i.e. we first check for ascii and if it is, we call continue, then
* for 2 byte codepoints, etc. This is done in order to reduce indentation and
* improve readability of the codepoint validity check.
*/
while (data + codepoint_bytes < end) {
if (return_position) {
err_pos += codepoint_bytes;
}
data += codepoint_bytes;
const size_t len = end - data;
const unsigned char byte1 = data[0];
/* We do not skip many ascii bytes at the same time as this function is
used for tail checking (< 16 bytes) and for non x86 platforms. We also
don't think that cases where non-ASCII codepoints are followed by ascii
happen often. For small strings it also introduces some penalty. For
purely ascii UTF8 strings (which is the overwhelming case) we call
SkipAscii function which is multiplatform and extremely fast.
*/
/* [00..7F] ASCII -> 1 byte */
if (utf8_range_AsciiIsAscii(byte1)) {
codepoint_bytes = 1;
continue;
}
/* [C2..DF], [80..BF] -> 2 bytes */
if (len >= 2 && byte1 >= 0xC2 && byte1 <= 0xDF &&
utf8_range_IsTrailByteOk(data[1])) {
codepoint_bytes = 2;
continue;
}
if (len >= 3) {
const unsigned char byte2 = data[1];
const unsigned char byte3 = data[2];
/* Is byte2, byte3 between [0x80, 0xBF]
* Check for 0x80 was done above.
*/
if (!utf8_range_IsTrailByteOk(byte2) ||
!utf8_range_IsTrailByteOk(byte3)) {
return err_pos;
}
if (/* E0, A0..BF, 80..BF */
((byte1 == 0xE0 && byte2 >= 0xA0) ||
/* E1..EC, 80..BF, 80..BF */
(byte1 >= 0xE1 && byte1 <= 0xEC) ||
/* ED, 80..9F, 80..BF */
(byte1 == 0xED && byte2 <= 0x9F) ||
/* EE..EF, 80..BF, 80..BF */
(byte1 >= 0xEE && byte1 <= 0xEF))) {
codepoint_bytes = 3;
continue;
}
if (len >= 4) {
const unsigned char byte4 = data[3];
/* Is byte4 between 0x80 ~ 0xBF */
if (!utf8_range_IsTrailByteOk(byte4)) {
return err_pos;
}
if (/* F0, 90..BF, 80..BF, 80..BF */
((byte1 == 0xF0 && byte2 >= 0x90) ||
/* F1..F3, 80..BF, 80..BF, 80..BF */
(byte1 >= 0xF1 && byte1 <= 0xF3) ||
/* F4, 80..8F, 80..BF, 80..BF */
(byte1 == 0xF4 && byte2 <= 0x8F))) {
codepoint_bytes = 4;
continue;
}
}
}
return err_pos;
}
if (return_position) {
err_pos += codepoint_bytes;
}
/* if return_position is false, this returns 1.
* if return_position is true, this returns err_pos.
*/
return err_pos + (1 - return_position);
}
#if defined(__SSE4_1__) || (defined(__ARM_NEON) && defined(__ARM_64BIT_STATE))
/* Returns the number of bytes needed to skip backwards to get to the first
byte of codepoint.
*/
static inline int utf8_range_CodepointSkipBackwards(int32_t codepoint_word) {
const int8_t* const codepoint = (const int8_t*)(&codepoint_word);
if (!utf8_range_IsTrailByteOk(codepoint[3])) {
return 1;
} else if (!utf8_range_IsTrailByteOk(codepoint[2])) {
return 2;
} else if (!utf8_range_IsTrailByteOk(codepoint[1])) {
return 3;
}
return 0;
}
#endif // __SSE4_1__
/* Skipping over ASCII as much as possible, per 8 bytes. It is intentional
as most strings to check for validity consist only of 1 byte codepoints.
*/
static inline const char* utf8_range_SkipAscii(const char* data,
const char* end) {
while (8 <= end - data &&
(utf8_range_UnalignedLoad64(data) & 0x8080808080808080) == 0) {
data += 8;
}
while (data < end && utf8_range_AsciiIsAscii(*data)) {
++data;
}
return data;
}
#if defined(__SSE4_1__)
#include "utf8_range_sse.inc"
#elif defined(__ARM_NEON) && defined(__ARM_64BIT_STATE)
#include "utf8_range_neon.inc"
#endif
static FORCE_INLINE_ATTR size_t utf8_range_Validate(
const char* data, size_t len, int return_position) {
if (len == 0) return 1 - return_position;
// Save buffer start address for later use
const char* const data_original = data;
const char* const end = data + len;
data = utf8_range_SkipAscii(data, end);
/* SIMD algorithm always outperforms the naive version for any data of
length >=16.
*/
if (end - data < 16) {
return (return_position ? (data - data_original) : 0) +
utf8_range_ValidateUTF8Naive(data, end, return_position);
}
#if defined(__SSE4_1__) || (defined(__ARM_NEON) && defined(__ARM_64BIT_STATE))
return utf8_range_ValidateUTF8Simd(
data_original, data, end, return_position);
#else
return (return_position ? (data - data_original) : 0) +
utf8_range_ValidateUTF8Naive(data, end, return_position);
#endif
}
bool utf8_range_IsValid(const char* data, size_t len) {
return utf8_range_Validate(data, len, /*return_position=*/0) != 0;
}
size_t utf8_range_ValidPrefix(const char* data, size_t len) {
return utf8_range_Validate(data, len, /*return_position=*/1);
}
@@ -0,0 +1,23 @@
#ifndef THIRD_PARTY_UTF8_RANGE_UTF8_RANGE_H_
#define THIRD_PARTY_UTF8_RANGE_UTF8_RANGE_H_
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// Returns 1 if the sequence of characters is a valid UTF-8 sequence, otherwise
// 0.
bool utf8_range_IsValid(const char* data, size_t len);
// Returns the length in bytes of the prefix of str that is all
// structurally valid UTF-8.
size_t utf8_range_ValidPrefix(const char* data, size_t len);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // THIRD_PARTY_UTF8_RANGE_UTF8_RANGE_H_
@@ -0,0 +1,117 @@
#include <arm_neon.h>
/* This code is almost the same as SSE implementation, please reference
* utf8-range-sse.inc for detailed explanation.
* The only difference is the range adjustment step. NEON code is more
* straightforward.
*/
static FORCE_INLINE_ATTR size_t utf8_range_ValidateUTF8Simd(
const char* data_original, const char* data, const char* end,
int return_position) {
const uint8x16_t first_len_tbl = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3,
};
const uint8x16_t first_range_tbl = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8,
};
const uint8x16_t range_min_tbl = {
0x00, 0x80, 0x80, 0x80, 0xA0, 0x80, 0x90, 0x80,
0xC2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};
const uint8x16_t range_max_tbl = {
0x7F, 0xBF, 0xBF, 0xBF, 0xBF, 0x9F, 0xBF, 0x8F,
0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
/* Range adjustment in NEON uint8x16x2 table. Note that lanes are interleaved
* in register. The table below is plotted vertically to ease understanding.
* The 1st column is for E0~EF, 2nd column for F0~FF.
*/
// clang-format off
const uint8_t range_adjust_tbl_data[] = {
/* index -> 0~15 16~31 <- index */
/* E0 -> */ 2, 3, /* <- F0 */
0, 0,
0, 0,
0, 0,
0, 4, /* <- F4 */
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
/* ED -> */ 3, 0,
0, 0,
0, 0,
};
// clang-format on
const uint8x16x2_t range_adjust_tbl = vld2q_u8(range_adjust_tbl_data);
const uint8x16_t const_1 = vdupq_n_u8(1);
const uint8x16_t const_2 = vdupq_n_u8(2);
const uint8x16_t const_e0 = vdupq_n_u8(0xE0);
uint8x16_t prev_input = vdupq_n_u8(0);
uint8x16_t prev_first_len = vdupq_n_u8(0);
uint8x16_t error = vdupq_n_u8(0);
while (end - data >= 16) {
const uint8x16_t input = vld1q_u8((const uint8_t*)data);
const uint8x16_t high_nibbles = vshrq_n_u8(input, 4);
const uint8x16_t first_len = vqtbl1q_u8(first_len_tbl, high_nibbles);
uint8x16_t range = vqtbl1q_u8(first_range_tbl, high_nibbles);
range = vorrq_u8(range, vextq_u8(prev_first_len, first_len, 15));
uint8x16_t shift2 = vextq_u8(prev_first_len, first_len, 14);
shift2 = vqsubq_u8(shift2, const_1);
range = vorrq_u8(range, shift2);
uint8x16_t shift3 = vextq_u8(prev_first_len, first_len, 13);
shift3 = vqsubq_u8(shift3, const_2);
range = vorrq_u8(range, shift3);
uint8x16_t shift1 = vextq_u8(prev_input, input, 15);
shift1 = vsubq_u8(shift1, const_e0);
range = vaddq_u8(range, vqtbl2q_u8(range_adjust_tbl, shift1));
const uint8x16_t min_range = vqtbl1q_u8(range_min_tbl, range);
const uint8x16_t max_range = vqtbl1q_u8(range_max_tbl, range);
if (return_position) {
error = vcltq_u8(input, min_range);
error = vorrq_u8(error, vcgtq_u8(input, max_range));
if (vmaxvq_u32(vreinterpretq_u32_u8(error))) {
break;
}
} else {
error = vorrq_u8(error, vcltq_u8(input, min_range));
error = vorrq_u8(error, vcgtq_u8(input, max_range));
}
prev_input = input;
prev_first_len = first_len;
data += 16;
}
if (return_position && data == data_original) {
return utf8_range_ValidateUTF8Naive(data, end, return_position);
}
const int32_t prev = vgetq_lane_s32(vreinterpretq_s32_u8(prev_input), 3);
data -= utf8_range_CodepointSkipBackwards(prev);
if (return_position) {
return (data - data_original) +
utf8_range_ValidateUTF8Naive(data, end, return_position);
}
if (vmaxvq_u32(vreinterpretq_u32_u8(error))) {
return 0;
}
return utf8_range_ValidateUTF8Naive(data, end, return_position);
}
@@ -0,0 +1,272 @@
#include <emmintrin.h>
#include <smmintrin.h>
#include <tmmintrin.h>
static FORCE_INLINE_ATTR size_t utf8_range_ValidateUTF8Simd(
const char* data_original, const char* data, const char* end,
int return_position) {
/* This code checks that utf-8 ranges are structurally valid 16 bytes at once
* using superscalar instructions.
* The mapping between ranges of codepoint and their corresponding utf-8
* sequences is below.
*/
/*
* U+0000...U+007F 00...7F
* U+0080...U+07FF C2...DF 80...BF
* U+0800...U+0FFF E0 A0...BF 80...BF
* U+1000...U+CFFF E1...EC 80...BF 80...BF
* U+D000...U+D7FF ED 80...9F 80...BF
* U+E000...U+FFFF EE...EF 80...BF 80...BF
* U+10000...U+3FFFF F0 90...BF 80...BF 80...BF
* U+40000...U+FFFFF F1...F3 80...BF 80...BF 80...BF
* U+100000...U+10FFFF F4 80...8F 80...BF 80...BF
*/
/* First we compute the type for each byte, as given by the table below.
* This type will be used as an index later on.
*/
/*
* Index Min Max Byte Type
* 0 00 7F Single byte sequence
* 1,2,3 80 BF Second, third and fourth byte for many of the sequences.
* 4 A0 BF Second byte after E0
* 5 80 9F Second byte after ED
* 6 90 BF Second byte after F0
* 7 80 8F Second byte after F4
* 8 C2 F4 First non ASCII byte
* 9..15 7F 80 Invalid byte
*/
/* After the first step we compute the index for all bytes, then we permute
the bytes according to their indices to check the ranges from the range
table.
* The range for a given type can be found in the range_min_table and
range_max_table, the range for type/index X is in range_min_table[X] ...
range_max_table[X].
*/
/* Algorithm:
* Put index zero to all bytes.
* Find all non ASCII characters, give them index 8.
* For each tail byte in a codepoint sequence, give it an index corresponding
to the 1 based index from the end.
* If the first byte of the codepoint is in the [C0...DF] range, we write
index 1 in the following byte.
* If the first byte of the codepoint is in the range [E0...EF], we write
indices 2 and 1 in the next two bytes.
* If the first byte of the codepoint is in the range [F0...FF] we write
indices 3,2,1 into the next three bytes.
* For finding the number of bytes we need to look at high nibbles (4 bits)
and do the lookup from the table, it can be done with shift by 4 + shuffle
instructions. We call it `first_len`.
* Then we shift first_len by 8 bits to get the indices of the 2nd bytes.
* Saturating sub 1 and shift by 8 bits to get the indices of the 3rd bytes.
* Again to get the indices of the 4th bytes.
* Take OR of all that 4 values and check within range.
*/
/* For example:
* input C3 80 68 E2 80 20 A6 F0 A0 80 AC 20 F0 93 80 80
* first_len 1 0 0 2 0 0 0 3 0 0 0 0 3 0 0 0
* 1st byte 8 0 0 8 0 0 0 8 0 0 0 0 8 0 0 0
* 2nd byte 0 1 0 0 2 0 0 0 3 0 0 0 0 3 0 0 // Shift + sub
* 3rd byte 0 0 0 0 0 1 0 0 0 2 0 0 0 0 2 0 // Shift + sub
* 4th byte 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 // Shift + sub
* Index 8 1 0 8 2 1 0 8 3 2 1 0 8 3 2 1 // OR of results
*/
/* Checking for errors:
* Error checking is done by looking up the high nibble (4 bits) of each byte
against an error checking table.
* Because the lookup value for the second byte depends of the value of the
first byte in codepoint, we use saturated operations to adjust the index.
* Specifically we need to add 2 for E0, 3 for ED, 3 for F0 and 4 for F4 to
match the correct index.
* If we subtract from all bytes EF then EO -> 241, ED -> 254, F0 -> 1,
F4 -> 5
* Do saturating sub 240, then E0 -> 1, ED -> 14 and we can do lookup to
match the adjustment
* Add saturating 112, then F0 -> 113, F4 -> 117, all that were > 16 will
be more 128 and lookup in ef_fe_table will return 0 but for F0
and F4 it will be 4 and 5 accordingly
*/
/*
* Then just check the appropriate ranges with greater/smaller equal
instructions. Check tail with a naive algorithm.
* To save from previous 16 byte checks we just align previous_first_len to
get correct continuations of the codepoints.
*/
/*
* Map high nibble of "First Byte" to legal character length minus 1
* 0x00 ~ 0xBF --> 0
* 0xC0 ~ 0xDF --> 1
* 0xE0 ~ 0xEF --> 2
* 0xF0 ~ 0xFF --> 3
*/
const __m128i first_len_table =
_mm_setr_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3);
/* Map "First Byte" to 8-th item of range table (0xC2 ~ 0xF4) */
const __m128i first_range_table =
_mm_setr_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8);
/*
* Range table, map range index to min and max values
*/
const __m128i range_min_table =
_mm_setr_epi8(0x00, 0x80, 0x80, 0x80, 0xA0, 0x80, 0x90, 0x80, 0xC2, 0x7F,
0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F);
const __m128i range_max_table =
_mm_setr_epi8(0x7F, 0xBF, 0xBF, 0xBF, 0xBF, 0x9F, 0xBF, 0x8F, 0xF4, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80);
/*
* Tables for fast handling of four special First Bytes(E0,ED,F0,F4), after
* which the Second Byte are not 80~BF. It contains "range index adjustment".
* +------------+---------------+------------------+----------------+
* | First Byte | original range| range adjustment | adjusted range |
* +------------+---------------+------------------+----------------+
* | E0 | 2 | 2 | 4 |
* +------------+---------------+------------------+----------------+
* | ED | 2 | 3 | 5 |
* +------------+---------------+------------------+----------------+
* | F0 | 3 | 3 | 6 |
* +------------+---------------+------------------+----------------+
* | F4 | 4 | 4 | 8 |
* +------------+---------------+------------------+----------------+
*/
/* df_ee_table[1] -> E0, df_ee_table[14] -> ED as ED - E0 = 13 */
// The values represent the adjustment in the Range Index table for a correct
// index.
const __m128i df_ee_table =
_mm_setr_epi8(0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0);
/* ef_fe_table[1] -> F0, ef_fe_table[5] -> F4, F4 - F0 = 4 */
// The values represent the adjustment in the Range Index table for a correct
// index.
const __m128i ef_fe_table =
_mm_setr_epi8(0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
__m128i prev_input = _mm_set1_epi8(0);
__m128i prev_first_len = _mm_set1_epi8(0);
__m128i error = _mm_set1_epi8(0);
while (end - data >= 16) {
const __m128i input = _mm_loadu_si128((const __m128i*)(data));
/* high_nibbles = input >> 4 */
const __m128i high_nibbles =
_mm_and_si128(_mm_srli_epi16(input, 4), _mm_set1_epi8(0x0F));
/* first_len = legal character length minus 1 */
/* 0 for 00~7F, 1 for C0~DF, 2 for E0~EF, 3 for F0~FF */
/* first_len = first_len_table[high_nibbles] */
__m128i first_len = _mm_shuffle_epi8(first_len_table, high_nibbles);
/* First Byte: set range index to 8 for bytes within 0xC0 ~ 0xFF */
/* range = first_range_table[high_nibbles] */
__m128i range = _mm_shuffle_epi8(first_range_table, high_nibbles);
/* Second Byte: set range index to first_len */
/* 0 for 00~7F, 1 for C0~DF, 2 for E0~EF, 3 for F0~FF */
/* range |= (first_len, prev_first_len) << 1 byte */
range = _mm_or_si128(range, _mm_alignr_epi8(first_len, prev_first_len, 15));
/* Third Byte: set range index to saturate_sub(first_len, 1) */
/* 0 for 00~7F, 0 for C0~DF, 1 for E0~EF, 2 for F0~FF */
__m128i tmp1;
__m128i tmp2;
/* tmp1 = saturate_sub(first_len, 1) */
tmp1 = _mm_subs_epu8(first_len, _mm_set1_epi8(1));
/* tmp2 = saturate_sub(prev_first_len, 1) */
tmp2 = _mm_subs_epu8(prev_first_len, _mm_set1_epi8(1));
/* range |= (tmp1, tmp2) << 2 bytes */
range = _mm_or_si128(range, _mm_alignr_epi8(tmp1, tmp2, 14));
/* Fourth Byte: set range index to saturate_sub(first_len, 2) */
/* 0 for 00~7F, 0 for C0~DF, 0 for E0~EF, 1 for F0~FF */
/* tmp1 = saturate_sub(first_len, 2) */
tmp1 = _mm_subs_epu8(first_len, _mm_set1_epi8(2));
/* tmp2 = saturate_sub(prev_first_len, 2) */
tmp2 = _mm_subs_epu8(prev_first_len, _mm_set1_epi8(2));
/* range |= (tmp1, tmp2) << 3 bytes */
range = _mm_or_si128(range, _mm_alignr_epi8(tmp1, tmp2, 13));
/*
* Now we have below range indices calculated
* Correct cases:
* - 8 for C0~FF
* - 3 for 1st byte after F0~FF
* - 2 for 1st byte after E0~EF or 2nd byte after F0~FF
* - 1 for 1st byte after C0~DF or 2nd byte after E0~EF or
* 3rd byte after F0~FF
* - 0 for others
* Error cases:
* >9 for non ascii First Byte overlapping
* E.g., F1 80 C2 90 --> 8 3 10 2, where 10 indicates error
*/
/* Adjust Second Byte range for special First Bytes(E0,ED,F0,F4) */
/* Overlaps lead to index 9~15, which are illegal in range table */
__m128i shift1;
__m128i pos;
__m128i range2;
/* shift1 = (input, prev_input) << 1 byte */
shift1 = _mm_alignr_epi8(input, prev_input, 15);
pos = _mm_sub_epi8(shift1, _mm_set1_epi8(0xEF));
/*
* shift1: | EF F0 ... FE | FF 00 ... ... DE | DF E0 ... EE |
* pos: | 0 1 15 | 16 17 239| 240 241 255|
* pos-240: | 0 0 0 | 0 0 0 | 0 1 15 |
* pos+112: | 112 113 127| >= 128 | >= 128 |
*/
tmp1 = _mm_subs_epu8(pos, _mm_set1_epi8(-16));
range2 = _mm_shuffle_epi8(df_ee_table, tmp1);
tmp2 = _mm_adds_epu8(pos, _mm_set1_epi8(112));
range2 = _mm_add_epi8(range2, _mm_shuffle_epi8(ef_fe_table, tmp2));
range = _mm_add_epi8(range, range2);
/* Load min and max values per calculated range index */
__m128i min_range = _mm_shuffle_epi8(range_min_table, range);
__m128i max_range = _mm_shuffle_epi8(range_max_table, range);
/* Check value range */
if (return_position) {
error = _mm_cmplt_epi8(input, min_range);
error = _mm_or_si128(error, _mm_cmpgt_epi8(input, max_range));
/* 5% performance drop from this conditional branch */
if (!_mm_testz_si128(error, error)) {
break;
}
} else {
error = _mm_or_si128(error, _mm_cmplt_epi8(input, min_range));
error = _mm_or_si128(error, _mm_cmpgt_epi8(input, max_range));
}
prev_input = input;
prev_first_len = first_len;
data += 16;
}
/* If we got to the end, we don't need to skip any bytes backwards */
if (return_position && data == data_original) {
return utf8_range_ValidateUTF8Naive(data, end, return_position);
}
/* Find previous codepoint (not 80~BF) */
data -= utf8_range_CodepointSkipBackwards(_mm_extract_epi32(prev_input, 3));
if (return_position) {
return (data - data_original) +
utf8_range_ValidateUTF8Naive(data, end, return_position);
}
/* Test if there was any error */
if (!_mm_testz_si128(error, error)) {
return 0;
}
/* Check the tail */
return utf8_range_ValidateUTF8Naive(data, end, return_position);
}
@@ -0,0 +1,61 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
# require mixins before we hook them into the java & c code
require 'google/protobuf/message_exts'
require 'google/protobuf/internal/object_cache'
# We define these before requiring the platform-specific modules.
# That way the module init can grab references to these.
module Google
module Protobuf
class Error < StandardError; end
class ParseError < Error; end
class TypeError < ::TypeError; end
PREFER_FFI = case ENV['PROTOCOL_BUFFERS_RUBY_IMPLEMENTATION']
when nil, "", /^native$/i
false
when /^ffi$/i
true
else
warn "Unexpected value `#{ENV['PROTOCOL_BUFFERS_RUBY_IMPLEMENTATION']}` for environment variable `PROTOCOL_BUFFERS_RUBY_IMPLEMENTATION`. Should be either \"FFI\", \"NATIVE\"."
false
end
def self.encode(msg, options = {})
msg.to_proto(options)
end
def self.encode_json(msg, options = {})
msg.to_json(options)
end
def self.decode(klass, proto, options = {})
klass.decode(proto, options)
end
def self.decode_json(klass, json, options = {})
klass.decode_json(json, options)
end
IMPLEMENTATION = if PREFER_FFI
begin
require 'google/protobuf_ffi'
:FFI
rescue LoadError
warn "Caught exception `#{$!.message}` while loading FFI implementation of google/protobuf."
warn "Falling back to native implementation."
require 'google/protobuf_native'
:NATIVE
end
else
require 'google/protobuf_native'
:NATIVE
end
end
end
@@ -0,0 +1,17 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/any.proto
require 'google/protobuf'
descriptor_data = "\n\x19google/protobuf/any.proto\x12\x0fgoogle.protobuf\"&\n\x03\x41ny\x12\x10\n\x08type_url\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x42v\n\x13\x63om.google.protobufB\x08\x41nyProtoP\x01Z,google.golang.org/protobuf/types/known/anypb\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
Any = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Any").msgclass
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/api.proto
require 'google/protobuf'
require 'google/protobuf/source_context_pb'
require 'google/protobuf/type_pb'
descriptor_data = "\n\x19google/protobuf/api.proto\x12\x0fgoogle.protobuf\x1a$google/protobuf/source_context.proto\x1a\x1agoogle/protobuf/type.proto\"\x92\x02\n\x03\x41pi\x12\x0c\n\x04name\x18\x01 \x01(\t\x12(\n\x07methods\x18\x02 \x03(\x0b\x32\x17.google.protobuf.Method\x12(\n\x07options\x18\x03 \x03(\x0b\x32\x17.google.protobuf.Option\x12\x0f\n\x07version\x18\x04 \x01(\t\x12\x36\n\x0esource_context\x18\x05 \x01(\x0b\x32\x1e.google.protobuf.SourceContext\x12&\n\x06mixins\x18\x06 \x03(\x0b\x32\x16.google.protobuf.Mixin\x12\'\n\x06syntax\x18\x07 \x01(\x0e\x32\x17.google.protobuf.Syntax\x12\x0f\n\x07\x65\x64ition\x18\x08 \x01(\t\"\xee\x01\n\x06Method\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x18\n\x10request_type_url\x18\x02 \x01(\t\x12\x19\n\x11request_streaming\x18\x03 \x01(\x08\x12\x19\n\x11response_type_url\x18\x04 \x01(\t\x12\x1a\n\x12response_streaming\x18\x05 \x01(\x08\x12(\n\x07options\x18\x06 \x03(\x0b\x32\x17.google.protobuf.Option\x12+\n\x06syntax\x18\x07 \x01(\x0e\x32\x17.google.protobuf.SyntaxB\x02\x18\x01\x12\x13\n\x07\x65\x64ition\x18\x08 \x01(\tB\x02\x18\x01\"#\n\x05Mixin\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04root\x18\x02 \x01(\tBv\n\x13\x63om.google.protobufB\x08\x41piProtoP\x01Z,google.golang.org/protobuf/types/known/apipb\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
Api = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Api").msgclass
Method = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Method").msgclass
Mixin = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Mixin").msgclass
end
end
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/duration.proto
require 'google/protobuf'
descriptor_data = "\n\x1egoogle/protobuf/duration.proto\x12\x0fgoogle.protobuf\"*\n\x08\x44uration\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\x42\x83\x01\n\x13\x63om.google.protobufB\rDurationProtoP\x01Z1google.golang.org/protobuf/types/known/durationpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
Duration = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Duration").msgclass
end
end
@@ -0,0 +1,17 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/empty.proto
require 'google/protobuf'
descriptor_data = "\n\x1bgoogle/protobuf/empty.proto\x12\x0fgoogle.protobuf\"\x07\n\x05\x45mptyB}\n\x13\x63om.google.protobufB\nEmptyProtoP\x01Z.google.golang.org/protobuf/types/known/emptypb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
Empty = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Empty").msgclass
end
end
@@ -0,0 +1,175 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
##
# Message Descriptor - Descriptor for short.
class Descriptor
attr :descriptor_pool, :msg_class
include Enumerable
# FFI Interface methods and setup
extend ::FFI::DataConverter
native_type ::FFI::Type::POINTER
class << self
prepend Google::Protobuf::Internal::TypeSafety
include Google::Protobuf::Internal::PointerHelper
# @param value [Descriptor] Descriptor to convert to an FFI native type
# @param _ [Object] Unused
def to_native(value, _ = nil)
msg_def_ptr = value.nil? ? nil : value.instance_variable_get(:@msg_def)
return ::FFI::Pointer::NULL if msg_def_ptr.nil?
raise "Underlying msg_def was null!" if msg_def_ptr.null?
msg_def_ptr
end
##
# @param msg_def [::FFI::Pointer] MsgDef pointer to be wrapped
# @param _ [Object] Unused
def from_native(msg_def, _ = nil)
return nil if msg_def.nil? or msg_def.null?
file_def = Google::Protobuf::FFI.get_message_file_def msg_def
descriptor_from_file_def(file_def, msg_def)
end
end
def to_native
self.class.to_native(self)
end
##
# Great write up of this strategy:
# See https://blog.appsignal.com/2018/08/07/ruby-magic-changing-the-way-ruby-creates-objects.html
def self.new(*arguments, &block)
raise "Descriptor objects may not be created from Ruby."
end
def to_s
inspect
end
def inspect
"Descriptor - (not the message class) #{name}"
end
def file_descriptor
@descriptor_pool.send(:get_file_descriptor, Google::Protobuf::FFI.get_message_file_def(@msg_def))
end
def name
@name ||= Google::Protobuf::FFI.get_message_fullname(self)
end
def each_oneof &block
n = Google::Protobuf::FFI.oneof_count(self)
0.upto(n-1) do |i|
yield(Google::Protobuf::FFI.get_oneof_by_index(self, i))
end
nil
end
def each &block
n = Google::Protobuf::FFI.field_count(self)
0.upto(n-1) do |i|
yield(Google::Protobuf::FFI.get_field_by_index(self, i))
end
nil
end
def lookup(name)
Google::Protobuf::FFI.get_field_by_name(self, name, name.size)
end
def lookup_oneof(name)
Google::Protobuf::FFI.get_oneof_by_name(self, name, name.size)
end
def msgclass
@msg_class ||= build_message_class
end
def options
@options ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.message_options(self, size_ptr, temporary_arena)
opts = Google::Protobuf::MessageOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
opts.clear_features()
opts.freeze
end
end
def to_proto
@to_proto ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.message_to_proto(self, size_ptr, temporary_arena)
Google::Protobuf::DescriptorProto.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
end
end
private
extend Google::Protobuf::Internal::Convert
def initialize(msg_def, descriptor_pool)
@msg_def = msg_def
@msg_class = nil
@descriptor_pool = descriptor_pool
end
def self.private_constructor(msg_def, descriptor_pool)
instance = allocate
instance.send(:initialize, msg_def, descriptor_pool)
instance
end
def wrapper?
if defined? @wrapper
@wrapper
else
@wrapper = case Google::Protobuf::FFI.get_well_known_type self
when :DoubleValue, :FloatValue, :Int64Value, :UInt64Value, :Int32Value, :UInt32Value, :StringValue, :BytesValue, :BoolValue
true
else
false
end
end
end
def self.get_message(msg, descriptor, arena)
return nil if msg.nil? or msg.null?
message = OBJECT_CACHE.get(msg.address)
if message.nil?
message = descriptor.msgclass.send(:private_constructor, arena, msg: msg)
end
message
end
def pool
@descriptor_pool
end
end
class FFI
# MessageDef
attach_function :new_message_from_def, :upb_Message_New, [MiniTable.by_ref, Internal::Arena], :Message
attach_function :field_count, :upb_MessageDef_FieldCount, [Descriptor], :int
attach_function :get_message_file_def, :upb_MessageDef_File, [:pointer], :FileDef
attach_function :get_message_fullname, :upb_MessageDef_FullName, [Descriptor], :string
attach_function :get_mini_table, :upb_MessageDef_MiniTable, [Descriptor], MiniTable.ptr
attach_function :oneof_count, :upb_MessageDef_OneofCount, [Descriptor], :int
attach_function :message_options, :Descriptor_serialized_options, [Descriptor, :pointer, Internal::Arena], :pointer
attach_function :get_well_known_type, :upb_MessageDef_WellKnownType, [Descriptor], WellKnown
attach_function :find_msg_def_by_name, :upb_MessageDef_FindByNameWithSize, [Descriptor, :string, :size_t, :FieldDefPointer, :OneofDefPointer], :bool
attach_function :message_to_proto, :Descriptor_serialized_to_proto, [Descriptor, :pointer, Internal::Arena], :pointer
end
end
end
@@ -0,0 +1,83 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
class FFI
# DefPool
attach_function :add_serialized_file, :upb_DefPool_AddFile, [:DefPool, :FileDescriptorProto, Status.by_ref], :FileDef
attach_function :free_descriptor_pool, :upb_DefPool_Free, [:DefPool], :void
attach_function :create_descriptor_pool,:upb_DefPool_New, [], :DefPool
attach_function :disable_closed_enum_checking, :upb_DefPool_DisableClosedEnumChecking, [:DefPool], :void
attach_function :get_extension_registry,:upb_DefPool_ExtensionRegistry, [:DefPool], :ExtensionRegistry
attach_function :lookup_enum, :upb_DefPool_FindEnumByName, [:DefPool, :string], EnumDescriptor
attach_function :lookup_extension, :upb_DefPool_FindExtensionByName,[:DefPool, :string], FieldDescriptor
attach_function :lookup_msg, :upb_DefPool_FindMessageByName, [:DefPool, :string], Descriptor
attach_function :lookup_service, :upb_DefPool_FindServiceByName, [:DefPool, :string], ServiceDescriptor
attach_function :lookup_file, :upb_DefPool_FindFileByName, [:DefPool, :string], FileDescriptor
# FileDescriptorProto
attach_function :parse, :FileDescriptorProto_parse, [:binary_string, :size_t, Internal::Arena], :FileDescriptorProto
end
class DescriptorPool
attr :descriptor_pool
attr_accessor :descriptor_class_by_def
def initialize
@descriptor_pool = ::FFI::AutoPointer.new(Google::Protobuf::FFI.create_descriptor_pool, Google::Protobuf::FFI.method(:free_descriptor_pool))
@descriptor_class_by_def = {}
# Ruby treats all enums as open.
Google::Protobuf::FFI.disable_closed_enum_checking(@descriptor_pool)
# Should always be the last expression of the initializer to avoid
# leaking references to this object before construction is complete.
Google::Protobuf::OBJECT_CACHE.try_add @descriptor_pool.address, self
end
def add_serialized_file(file_contents)
# Allocate memory sized to file_contents
memBuf = ::FFI::MemoryPointer.new(:char, file_contents.bytesize)
# Insert the data
memBuf.put_bytes(0, file_contents)
temporary_arena = Google::Protobuf::FFI.create_arena
file_descriptor_proto = Google::Protobuf::FFI.parse memBuf, file_contents.bytesize, temporary_arena
raise ArgumentError.new("Unable to parse FileDescriptorProto") if file_descriptor_proto.null?
status = Google::Protobuf::FFI::Status.new
file_descriptor = Google::Protobuf::FFI.add_serialized_file @descriptor_pool, file_descriptor_proto, status
if file_descriptor.null?
raise TypeError.new("Unable to build file to DescriptorPool: #{Google::Protobuf::FFI.error_message(status)}")
else
@descriptor_class_by_def[file_descriptor.address] = FileDescriptor.new file_descriptor, self
end
end
def lookup name
Google::Protobuf::FFI.lookup_msg(@descriptor_pool, name) ||
Google::Protobuf::FFI.lookup_enum(@descriptor_pool, name) ||
Google::Protobuf::FFI.lookup_extension(@descriptor_pool, name) ||
Google::Protobuf::FFI.lookup_service(@descriptor_pool, name) ||
Google::Protobuf::FFI.lookup_file(@descriptor_pool, name)
end
def self.generated_pool
@@generated_pool ||= DescriptorPool.new
end
private
# Implementation details below are subject to breaking changes without
# warning and are intended for use only within the gem.
def get_file_descriptor file_def
return nil if file_def.null?
@descriptor_class_by_def[file_def.address] ||= FileDescriptor.new(file_def, self)
end
end
end
end
@@ -0,0 +1,183 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
class EnumDescriptor
attr :descriptor_pool, :enum_def
include Enumerable
# FFI Interface methods and setup
extend ::FFI::DataConverter
native_type ::FFI::Type::POINTER
class << self
prepend Google::Protobuf::Internal::TypeSafety
include Google::Protobuf::Internal::PointerHelper
# @param value [EnumDescriptor] EnumDescriptor to convert to an FFI native type
# @param _ [Object] Unused
def to_native(value, _)
value.instance_variable_get(:@enum_def) || ::FFI::Pointer::NULL
end
##
# @param enum_def [::FFI::Pointer] EnumDef pointer to be wrapped
# @param _ [Object] Unused
def from_native(enum_def, _)
return nil if enum_def.nil? or enum_def.null?
file_def = Google::Protobuf::FFI.get_message_file_def enum_def
descriptor_from_file_def(file_def, enum_def)
end
end
def self.new(*arguments, &block)
raise "Descriptor objects may not be created from Ruby."
end
def file_descriptor
@descriptor_pool.send(:get_file_descriptor, Google::Protobuf::FFI.get_enum_file_descriptor(self))
end
def name
Google::Protobuf::FFI.get_enum_fullname(self)
end
def to_s
inspect
end
def inspect
"#{self.class.name}: #{name}"
end
def lookup_name(name)
self.class.send(:lookup_name, self, name)
end
def lookup_value(number)
self.class.send(:lookup_value, self, number)
end
def each &block
n = Google::Protobuf::FFI.enum_value_count(self)
0.upto(n - 1) do |i|
enum_value = Google::Protobuf::FFI.enum_value_by_index(self, i)
yield(Google::Protobuf::FFI.enum_name(enum_value).to_sym, Google::Protobuf::FFI.enum_number(enum_value))
end
nil
end
def enummodule
if @module.nil?
@module = build_enum_module
end
@module
end
def options
@options ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.enum_options(self, size_ptr, temporary_arena)
opts = Google::Protobuf::EnumOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
opts.clear_features()
opts.freeze
end
end
def to_proto
@to_proto ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.enum_to_proto(self, size_ptr, temporary_arena)
Google::Protobuf::EnumDescriptorProto.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
end
end
private
def initialize(enum_def, descriptor_pool)
@descriptor_pool = descriptor_pool
@enum_def = enum_def
@module = nil
end
def self.private_constructor(enum_def, descriptor_pool)
instance = allocate
instance.send(:initialize, enum_def, descriptor_pool)
instance
end
def self.lookup_value(enum_def, number)
enum_value = Google::Protobuf::FFI.enum_value_by_number(enum_def, number)
if enum_value.null?
nil
else
Google::Protobuf::FFI.enum_name(enum_value).to_sym
end
end
def self.lookup_name(enum_def, name)
enum_value = Google::Protobuf::FFI.enum_value_by_name(enum_def, name.to_s, name.size)
if enum_value.null?
nil
else
Google::Protobuf::FFI.enum_number(enum_value)
end
end
def build_enum_module
descriptor = self
dynamic_module = Module.new do
@descriptor = descriptor
class << self
attr_accessor :descriptor
end
def self.lookup(number)
descriptor.lookup_value number
end
def self.resolve(name)
descriptor.lookup_name name
end
end
self.each do |name, value|
if name[0] < 'A' || name[0] > 'Z'
if name[0] >= 'a' and name[0] <= 'z'
name = name[0].upcase + name[1..-1] # auto capitalize
else
warn(
"Enum value '#{name}' does not start with an uppercase letter " +
"as is required for Ruby constants.")
next
end
end
dynamic_module.const_set(name.to_sym, value)
end
dynamic_module
end
end
class FFI
# EnumDescriptor
attach_function :get_enum_file_descriptor, :upb_EnumDef_File, [EnumDescriptor], :FileDef
attach_function :enum_value_by_name, :upb_EnumDef_FindValueByNameWithSize,[EnumDescriptor, :string, :size_t], :EnumValueDef
attach_function :enum_value_by_number, :upb_EnumDef_FindValueByNumber, [EnumDescriptor, :int], :EnumValueDef
attach_function :get_enum_fullname, :upb_EnumDef_FullName, [EnumDescriptor], :string
attach_function :enum_options, :EnumDescriptor_serialized_options, [EnumDescriptor, :pointer, Internal::Arena], :pointer
attach_function :enum_to_proto, :EnumDescriptor_serialized_to_proto, [EnumDescriptor, :pointer, Internal::Arena], :pointer
attach_function :enum_value_by_index, :upb_EnumDef_Value, [EnumDescriptor, :int], :EnumValueDef
attach_function :enum_value_count, :upb_EnumDef_ValueCount, [EnumDescriptor], :int
attach_function :enum_name, :upb_EnumValueDef_Name, [:EnumValueDef], :string
attach_function :enum_number, :upb_EnumValueDef_Number, [:EnumValueDef], :int
end
end
end
@@ -0,0 +1,213 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
class FFI
extend ::FFI::Library
# Workaround for Bazel's use of symlinks + JRuby's __FILE__ and `caller`
# that resolves them.
if ENV['BAZEL'] == 'true'
ffi_lib ::FFI::Compiler::Loader.find 'protobuf_c_ffi', ENV['PWD']
else
ffi_lib ::FFI::Compiler::Loader.find 'protobuf_c_ffi'
end
## Map
Upb_Map_Begin = -1
## Encoding Status
Upb_Status_MaxMessage = 511
Upb_Encode_Deterministic = 1
Upb_Encode_SkipUnknown = 2
## JSON Encoding options
# When set, emits 0/default values. TODO: proto3 only?
Upb_JsonEncode_EmitDefaults = 1
# When set, use normal (snake_case) field names instead of JSON (camelCase) names.
Upb_JsonEncode_UseProtoNames = 2
# When set, emits enums as their integer values instead of as their names.
Upb_JsonEncode_FormatEnumsAsIntegers = 4
## JSON Decoding options
Upb_JsonDecode_IgnoreUnknown = 1
## JSON Decoding results
Upb_JsonDecodeResult_Ok = 0
Upb_JsonDecodeResult_Error = 2
typedef :pointer, :Array
typedef :pointer, :DefPool
typedef :pointer, :EnumValueDef
typedef :pointer, :ExtensionRegistry
typedef :pointer, :FieldDefPointer
typedef :pointer, :FileDef
typedef :pointer, :FileDescriptorProto
typedef :pointer, :Map
typedef :pointer, :Message # Instances of a message
typedef :pointer, :OneofDefPointer
typedef :pointer, :binary_string
if ::FFI::Platform::ARCH == "aarch64"
typedef :u_int8_t, :uint8_t
typedef :u_int16_t, :uint16_t
typedef :u_int32_t, :uint32_t
typedef :u_int64_t, :uint64_t
end
FieldType = enum(
:double, 1,
:float,
:int64,
:uint64,
:int32,
:fixed64,
:fixed32,
:bool,
:string,
:group,
:message,
:bytes,
:uint32,
:enum,
:sfixed32,
:sfixed64,
:sint32,
:sint64
)
CType = enum(
:bool, 1,
:float,
:int32,
:uint32,
:enum,
:message,
:double,
:int64,
:uint64,
:string,
:bytes
)
Label = enum(
:optional, 1,
:required,
:repeated
)
# All the different kind of well known type messages. For simplicity of check,
# number wrappers and string wrappers are grouped together. Make sure the
# order and merber of these groups are not changed.
WellKnown = enum(
:Unspecified,
:Any,
:FieldMask,
:Duration,
:Timestamp,
# number wrappers
:DoubleValue,
:FloatValue,
:Int64Value,
:UInt64Value,
:Int32Value,
:UInt32Value,
# string wrappers
:StringValue,
:BytesValue,
:BoolValue,
:Value,
:ListValue,
:Struct
)
DecodeStatus = enum(
:Ok,
:Malformed, # Wire format was corrupt
:OutOfMemory, # Arena alloc failed
:BadUtf8, # String field had bad UTF-8
:MaxDepthExceeded, # Exceeded UPB_DECODE_MAXDEPTH
# CheckRequired failed, but the parse otherwise succeeded.
:MissingRequired,
)
EncodeStatus = enum(
:Ok,
:OutOfMemory, # Arena alloc failed
:MaxDepthExceeded, # Exceeded UPB_DECODE_MAXDEPTH
# CheckRequired failed, but the parse otherwise succeeded.
:MissingRequired,
)
class StringView < ::FFI::Struct
layout :data, :pointer,
:size, :size_t
end
class MiniTable < ::FFI::Struct
layout :fields, :pointer,
:size, :uint16_t,
:field_count, :uint16_t,
:ext, :uint8_t, # upb_ExtMode, declared as uint8_t so sizeof(ext) == 1
:dense_below, :uint8_t,
:table_mask, :uint8_t,
:required_count, :uint8_t # Required fields have the lowest hasbits.
# To statically initialize the tables of variable length, we need a flexible
# array member, and we need to compile in gnu99 mode (constant initialization
# of flexible array members is a GNU extension, not in C99 unfortunately. */
# _upb_FastTable_Entry fasttable[];
end
class Status < ::FFI::Struct
layout :ok, :bool,
:msg, [:char, Upb_Status_MaxMessage]
def initialize
super
FFI.clear self
end
end
class MessageValue < ::FFI::Union
layout :bool_val, :bool,
:float_val, :float,
:double_val, :double,
:int32_val, :int32_t,
:int64_val, :int64_t,
:uint32_val, :uint32_t,
:uint64_val,:uint64_t,
:map_val, :pointer,
:msg_val, :pointer,
:array_val,:pointer,
:str_val, StringView
end
Upb_Message_Begin = -1
class MutableMessageValue < ::FFI::Union
layout :map, :Map,
:msg, :Message,
:array, :Array
end
# Status
attach_function :clear, :upb_Status_Clear, [Status.by_ref], :void
attach_function :error_message, :upb_Status_ErrorMessage, [Status.by_ref], :string
# Generic
attach_function :memcmp, [:pointer, :pointer, :size_t], :int
attach_function :memcpy, [:pointer, :pointer, :size_t], :int
# Alternatives to pre-processor macros
def self.decode_max_depth(i)
i << 16
end
end
end
end
@@ -0,0 +1,346 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
class FieldDescriptor
attr :field_def, :descriptor_pool
include Google::Protobuf::Internal::Convert
# FFI Interface methods and setup
extend ::FFI::DataConverter
native_type ::FFI::Type::POINTER
class << self
prepend Google::Protobuf::Internal::TypeSafety
include Google::Protobuf::Internal::PointerHelper
# @param value [FieldDescriptor] FieldDescriptor to convert to an FFI native type
# @param _ [Object] Unused
def to_native(value, _)
field_def_ptr = value.instance_variable_get(:@field_def)
warn "Underlying field_def was nil!" if field_def_ptr.nil?
raise "Underlying field_def was null!" if !field_def_ptr.nil? and field_def_ptr.null?
field_def_ptr
end
##
# @param field_def [::FFI::Pointer] FieldDef pointer to be wrapped
# @param _ [Object] Unused
def from_native(field_def, _ = nil)
return nil if field_def.nil? or field_def.null?
file_def = Google::Protobuf::FFI.file_def_by_raw_field_def(field_def)
descriptor_from_file_def(file_def, field_def)
end
end
def self.new(*arguments, &block)
raise "Descriptor objects may not be created from Ruby."
end
def to_s
inspect
end
def inspect
"#{self.class.name}: #{name}"
end
def name
@name ||= Google::Protobuf::FFI.get_full_name(self)
end
def json_name
@json_name ||= Google::Protobuf::FFI.get_json_name(self)
end
def number
@number ||= Google::Protobuf::FFI.get_number(self)
end
def type
@type ||= Google::Protobuf::FFI.get_type(self)
end
# DEPRECATED: Use required? or repeated? instead.
def label
@label ||= Google::Protobuf::FFI.get_label(self)
end
def default
return nil if Google::Protobuf::FFI.is_sub_message(self)
if Google::Protobuf::FFI.is_repeated(self)
message_value = Google::Protobuf::FFI::MessageValue.new
else
message_value = Google::Protobuf::FFI.get_default(self)
end
enum_def = Google::Protobuf::FFI.get_subtype_as_enum(self)
if enum_def.null?
convert_upb_to_ruby message_value, c_type
else
convert_upb_to_ruby message_value, c_type, enum_def
end
end
def submsg_name
if defined? @submsg_name
@submsg_name
else
@submsg_name = case c_type
when :enum
Google::Protobuf::FFI.get_enum_fullname Google::Protobuf::FFI.get_subtype_as_enum self
when :message
Google::Protobuf::FFI.get_message_fullname Google::Protobuf::FFI.get_subtype_as_message self
else
nil
end
end
end
##
# Tests if this field has been set on the argument message.
#
# @param msg [Google::Protobuf::Message]
# @return [Object] Value of the field on this message.
# @raise [TypeError] If the field is not defined on this message.
def get(msg)
if msg.class.descriptor == Google::Protobuf::FFI.get_containing_message_def(self)
msg.send :get_field, self
else
raise TypeError.new "get method called on wrong message type"
end
end
def subtype
if defined? @subtype
@subtype
else
@subtype = case c_type
when :enum
Google::Protobuf::FFI.get_subtype_as_enum(self)
when :message
Google::Protobuf::FFI.get_subtype_as_message(self)
else
nil
end
end
end
##
# Tests if this field has been set on the argument message.
#
# @param msg [Google::Protobuf::Message]
# @return [Boolean] True iff message has this field set
# @raise [TypeError] If this field does not exist on the message
# @raise [ArgumentError] If this field does not track presence
def has?(msg)
if msg.class.descriptor != Google::Protobuf::FFI.get_containing_message_def(self)
raise TypeError.new "has method called on wrong message type"
end
unless has_presence?
raise ArgumentError.new "does not track presence"
end
Google::Protobuf::FFI.get_message_has msg.instance_variable_get(:@msg), self
end
##
# Tests if this field tracks presence.
#
# @return [Boolean] True iff this field tracks presence
def has_presence?
@has_presence ||= Google::Protobuf::FFI.get_has_presence(self)
end
##
# Tests if this is a repeated field that uses packed encoding.
#
# @return [Boolean] True iff this field is packed
def is_packed?
@is_packed ||= Google::Protobuf::FFI.get_is_packed(self)
end
# @param msg [Google::Protobuf::Message]
def clear(msg)
if msg.class.descriptor != Google::Protobuf::FFI.get_containing_message_def(self)
raise TypeError.new "clear method called on wrong message type"
end
Google::Protobuf::FFI.clear_message_field msg.instance_variable_get(:@msg), self
nil
end
##
# call-seq:
# FieldDescriptor.set(message, value)
#
# Sets the value corresponding to this field to the given value on the given
# message. Raises an exception if message is of the wrong type. Performs the
# ordinary type-checks for field setting.
#
# @param msg [Google::Protobuf::Message]
# @param value [Object]
def set(msg, value)
if msg.class.descriptor != Google::Protobuf::FFI.get_containing_message_def(self)
raise TypeError.new "set method called on wrong message type"
end
unless set_value_on_message value, msg.instance_variable_get(:@msg), msg.instance_variable_get(:@arena)
raise RuntimeError.new "allocation failed"
end
nil
end
def map?
@map ||= Google::Protobuf::FFI.is_map self
end
def required?
@required ||= Google::Protobuf::FFI.is_required self
end
def repeated?
@repeated ||= Google::Protobuf::FFI.is_repeated self
end
def sub_message?
@sub_message ||= Google::Protobuf::FFI.is_sub_message self
end
def wrapper?
if defined? @wrapper
@wrapper
else
message_descriptor = Google::Protobuf::FFI.get_subtype_as_message(self)
@wrapper = message_descriptor.nil? ? false : message_descriptor.send(:wrapper?)
end
end
def options
@options ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.field_options(self, size_ptr, temporary_arena)
opts = Google::Protobuf::FieldOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
opts.clear_features()
opts.freeze
end
end
def to_proto
@to_proto ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.field_to_proto(self, size_ptr, temporary_arena)
Google::Protobuf::FieldDescriptorProto.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
end
end
private
def initialize(field_def, descriptor_pool)
@field_def = field_def
@descriptor_pool = descriptor_pool
end
def self.private_constructor(field_def, descriptor_pool)
instance = allocate
instance.send(:initialize, field_def, descriptor_pool)
instance
end
# TODO Can this be added to the public API?
def real_containing_oneof
@real_containing_oneof ||= Google::Protobuf::FFI.real_containing_oneof self
end
# Implementation details below are subject to breaking changes without
# warning and are intended for use only within the gem.
##
# Sets the field this FieldDescriptor represents to the given value on the given message.
# @param value [Object] Value to be set
# @param msg [::FFI::Pointer] Pointer the the upb_Message
# @param arena [Arena] Arena of the message that owns msg
def set_value_on_message(value, msg, arena, wrap: false)
message_to_alter = msg
field_def_to_set = self
if map?
raise TypeError.new "Expected map" unless value.is_a? Google::Protobuf::Map
message_descriptor = subtype
key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1)
key_field_type = Google::Protobuf::FFI.get_type(key_field_def)
raise TypeError.new "Map key type does not match field's key type" unless key_field_type == value.send(:key_type)
value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2)
value_field_type = Google::Protobuf::FFI.get_type(value_field_def)
raise TypeError.new "Map value type does not match field's value type" unless value_field_type == value.send(:value_type)
raise TypeError.new "Map value type has wrong message/enum class" unless value_field_def.subtype == value.send(:descriptor)
arena.fuse(value.send(:arena))
message_value = Google::Protobuf::FFI::MessageValue.new
message_value[:map_val] = value.send(:map_ptr)
elsif repeated?
raise TypeError.new "Expected repeated field array" unless value.is_a? RepeatedField
raise TypeError.new "Repeated field array has wrong message/enum class" unless value.send(:type) == type
arena.fuse(value.send(:arena))
message_value = Google::Protobuf::FFI::MessageValue.new
message_value[:array_val] = value.send(:array)
else
if value.nil? and (sub_message? or !real_containing_oneof.nil?)
Google::Protobuf::FFI.clear_message_field message_to_alter, field_def_to_set
return true
end
if wrap
value_field_def = Google::Protobuf::FFI.get_field_by_number subtype, 1
type_for_conversion = Google::Protobuf::FFI.get_c_type(value_field_def)
raise RuntimeError.new "Not expecting to get a msg or enum when unwrapping" if [:enum, :message].include? type_for_conversion
message_value = convert_ruby_to_upb(value, arena, type_for_conversion, nil)
message_to_alter = Google::Protobuf::FFI.get_mutable_message(msg, self, arena)[:msg]
field_def_to_set = value_field_def
else
message_value = convert_ruby_to_upb(value, arena, c_type, subtype)
end
end
Google::Protobuf::FFI.set_message_field message_to_alter, field_def_to_set, message_value, arena
end
def c_type
@c_type ||= Google::Protobuf::FFI.get_c_type(self)
end
end
class FFI
# MessageDef
attach_function :get_field_by_index, :upb_MessageDef_Field, [Descriptor, :int], FieldDescriptor
attach_function :get_field_by_name, :upb_MessageDef_FindFieldByNameWithSize,[Descriptor, :string, :size_t], FieldDescriptor
attach_function :get_field_by_number, :upb_MessageDef_FindFieldByNumber, [Descriptor, :uint32_t], FieldDescriptor
# FieldDescriptor
attach_function :field_options, :FieldDescriptor_serialized_options, [FieldDescriptor, :pointer, Internal::Arena], :pointer
attach_function :get_containing_message_def, :upb_FieldDef_ContainingType, [FieldDescriptor], Descriptor
attach_function :get_c_type, :upb_FieldDef_CType, [FieldDescriptor], CType
attach_function :get_default, :upb_FieldDef_Default, [FieldDescriptor], MessageValue.by_value
attach_function :get_subtype_as_enum, :upb_FieldDef_EnumSubDef, [FieldDescriptor], EnumDescriptor
attach_function :get_has_presence, :upb_FieldDef_HasPresence, [FieldDescriptor], :bool
attach_function :get_is_packed, :upb_FieldDef_IsPacked, [FieldDescriptor], :bool
attach_function :is_map, :upb_FieldDef_IsMap, [FieldDescriptor], :bool
attach_function :is_required, :upb_FieldDef_IsRequired, [FieldDescriptor], :bool
attach_function :is_repeated, :upb_FieldDef_IsRepeated, [FieldDescriptor], :bool
attach_function :is_sub_message, :upb_FieldDef_IsSubMessage, [FieldDescriptor], :bool
attach_function :get_json_name, :upb_FieldDef_JsonName, [FieldDescriptor], :string
attach_function :get_label, :upb_FieldDef_Label, [FieldDescriptor], Label
attach_function :get_subtype_as_message, :upb_FieldDef_MessageSubDef, [FieldDescriptor], Descriptor
attach_function :get_full_name, :upb_FieldDef_Name, [FieldDescriptor], :string
attach_function :get_number, :upb_FieldDef_Number, [FieldDescriptor], :uint32_t
attach_function :get_type, :upb_FieldDef_Type, [FieldDescriptor], FieldType
attach_function :file_def_by_raw_field_def, :upb_FieldDef_File, [:pointer], :FileDef
attach_function :field_to_proto, :FieldDescriptor_serialized_to_proto,[FieldDescriptor, :pointer, Internal::Arena], :pointer
end
end
end
@@ -0,0 +1,85 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
class FFI
# FileDescriptor
attach_function :file_def_name, :upb_FileDef_Name, [:FileDef], :string
attach_function :file_def_pool, :upb_FileDef_Pool, [:FileDef], :DefPool
attach_function :file_options, :FileDescriptor_serialized_options, [:FileDef, :pointer, Internal::Arena], :pointer
attach_function :file_to_proto, :FileDescriptor_serialized_to_proto, [:FileDef, :pointer, Internal::Arena], :pointer
end
class FileDescriptor
attr :descriptor_pool, :file_def
# FFI Interface methods and setup
extend ::FFI::DataConverter
native_type ::FFI::Type::POINTER
class << self
prepend Google::Protobuf::Internal::TypeSafety
include Google::Protobuf::Internal::PointerHelper
# @param value [FileDescriptor] FileDescriptor to convert to an FFI native type
# @param _ [Object] Unused
def to_native(value, _)
file_def_ptr = value.nil? ? nil : value.instance_variable_get(:@file_def)
return ::FFI::Pointer::NULL if file_def_ptr.nil?
raise "Underlying file_def was null!" if file_def_ptr.null?
file_def_ptr
end
##
# @param file_def [::FFI::Pointer] FileDef pointer to be wrapped
# @param _ [Object] Unused
def from_native(file_def, _ = nil)
return nil if file_def.nil? or file_def.null?
descriptor_from_file_def(file_def)
end
end
def initialize(file_def, descriptor_pool)
@descriptor_pool = descriptor_pool
@file_def = file_def
end
def to_s
inspect
end
def inspect
"#{self.class.name}: #{name}"
end
def name
Google::Protobuf::FFI.file_def_name(@file_def)
end
def options
@options ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.file_options(@file_def, size_ptr, temporary_arena)
opts = Google::Protobuf::FileOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
opts.clear_features()
opts.freeze
end
end
def to_proto
@to_proto ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.file_to_proto(@file_def, size_ptr, temporary_arena)
Google::Protobuf::FileDescriptorProto.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
end
end
end
end
end
@@ -0,0 +1,60 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
##
# Implementation details below are subject to breaking changes without
# warning and are intended for use only within the gem.
module Google
module Protobuf
module Internal
class Arena
# FFI Interface methods and setup
extend ::FFI::DataConverter
native_type ::FFI::Type::POINTER
class << self
prepend Google::Protobuf::Internal::TypeSafety
# @param value [Arena] Arena to convert to an FFI native type
# @param _ [Object] Unused
def to_native(value, _)
value.instance_variable_get(:@arena) || ::FFI::Pointer::NULL
end
##
# @param value [::FFI::Pointer] Arena pointer to be wrapped
# @param _ [Object] Unused
def from_native(value, _)
new(value)
end
end
def initialize(pointer)
@arena = ::FFI::AutoPointer.new(pointer, Google::Protobuf::FFI.method(:free_arena))
@pinned_messages = []
end
def fuse(other_arena)
return if other_arena == self
unless Google::Protobuf::FFI.fuse_arena(self, other_arena)
raise RuntimeError.new "Unable to fuse arenas. This should never happen since Ruby does not use initial blocks"
end
end
end
end
class FFI
# Arena
attach_function :create_arena, :Arena_create, [], Internal::Arena
attach_function :fuse_arena, :upb_Arena_Fuse, [Internal::Arena, Internal::Arena], :bool
# Argument takes a :pointer rather than a typed Arena here due to
# implementation details of FFI::AutoPointer.
attach_function :free_arena, :upb_Arena_Free, [:pointer], :void
attach_function :arena_malloc, :upb_Arena_Malloc, [Internal::Arena, :size_t], :pointer
end
end
end
@@ -0,0 +1,292 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
##
# Implementation details below are subject to breaking changes without
# warning and are intended for use only within the gem.
module Google
module Protobuf
module Internal
module Convert
# Arena should be the
# @param value [Object] Value to convert
# @param arena [Arena] Arena that owns the Message where the MessageValue
# will be set
# @return [Google::Protobuf::FFI::MessageValue]
def convert_ruby_to_upb(value, arena, c_type, msg_or_enum_def)
raise ArgumentError.new "Expected Descriptor or EnumDescriptor, instead got #{msg_or_enum_def.class}" unless [NilClass, Descriptor, EnumDescriptor].include? msg_or_enum_def.class
return_value = Google::Protobuf::FFI::MessageValue.new
case c_type
when :float
raise TypeError.new "Expected number type for float field '#{name}' (given #{value.class})." unless value.respond_to? :to_f
return_value[:float_val] = value.to_f
when :double
raise TypeError.new "Expected number type for double field '#{name}' (given #{value.class})." unless value.respond_to? :to_f
return_value[:double_val] = value.to_f
when :bool
raise TypeError.new "Invalid argument for boolean field '#{name}' (given #{value.class})." unless [TrueClass, FalseClass].include? value.class
return_value[:bool_val] = value
when :string
raise TypeError.new "Invalid argument for string field '#{name}' (given #{value.class})." unless value.is_a?(String) or value.is_a?(Symbol)
value = value.to_s if value.is_a?(Symbol)
if value.encoding == Encoding::UTF_8
unless value.valid_encoding?
raise Encoding::InvalidByteSequenceError.new "String is invalid UTF-8"
end
string_value = value
else
string_value = value.to_s.encode("UTF-8")
end
return_value[:str_val][:size] = string_value.bytesize
return_value[:str_val][:data] = Google::Protobuf::FFI.arena_malloc(arena, string_value.bytesize)
# TODO - how important is it to still use arena malloc, versus the following?
# buffer = ::FFI::MemoryPointer.new(:char, string_value.bytesize)
# buffer.put_bytes(0, string_value)
# return_value[:str_val][:data] = buffer
raise NoMemoryError.new "Cannot allocate #{string_value.bytesize} bytes for string on Arena" if return_value[:str_val][:data].nil? || return_value[:str_val][:data].null?
return_value[:str_val][:data].write_string(string_value)
when :bytes
raise TypeError.new "Invalid argument for bytes field '#{name}' (given #{value.class})." unless value.is_a? String
string_value = value.encode("ASCII-8BIT")
return_value[:str_val][:size] = string_value.bytesize
return_value[:str_val][:data] = Google::Protobuf::FFI.arena_malloc(arena, string_value.bytesize)
raise NoMemoryError.new "Cannot allocate #{string_value.bytesize} bytes for bytes on Arena" if return_value[:str_val][:data].nil? || return_value[:str_val][:data].null?
return_value[:str_val][:data].write_string_length(string_value, string_value.bytesize)
when :message
raise TypeError.new "nil message not allowed here." if value.nil?
if value.is_a? Hash
raise RuntimeError.new "Attempted to initialize message from Hash for field #{name} but have no definition" if msg_or_enum_def.nil?
new_message = msg_or_enum_def.msgclass.
send(:private_constructor, arena, initial_value: value)
return_value[:msg_val] = new_message.instance_variable_get(:@msg)
return return_value
end
descriptor = value.class.respond_to?(:descriptor) ? value.class.descriptor : nil
if descriptor != msg_or_enum_def
wkt = Google::Protobuf::FFI.get_well_known_type(msg_or_enum_def)
case wkt
when :Timestamp
raise TypeError.new "Invalid type #{value.class} to assign to submessage field '#{name}'." unless value.kind_of? Time
new_message = Google::Protobuf::FFI.new_message_from_def Google::Protobuf::FFI.get_mini_table(msg_or_enum_def), arena
sec = Google::Protobuf::FFI::MessageValue.new
sec[:int64_val] = value.tv_sec
sec_field_def = Google::Protobuf::FFI.get_field_by_number msg_or_enum_def, 1
raise "Should be impossible" unless Google::Protobuf::FFI.set_message_field new_message, sec_field_def, sec, arena
nsec_field_def = Google::Protobuf::FFI.get_field_by_number msg_or_enum_def, 2
nsec = Google::Protobuf::FFI::MessageValue.new
nsec[:int32_val] = value.tv_nsec
raise "Should be impossible" unless Google::Protobuf::FFI.set_message_field new_message, nsec_field_def, nsec, arena
return_value[:msg_val] = new_message
when :Duration
raise TypeError.new "Invalid type #{value.class} to assign to submessage field '#{name}'." unless value.kind_of? Numeric
new_message = Google::Protobuf::FFI.new_message_from_def Google::Protobuf::FFI.get_mini_table(msg_or_enum_def), arena
sec = Google::Protobuf::FFI::MessageValue.new
sec[:int64_val] = value
sec_field_def = Google::Protobuf::FFI.get_field_by_number msg_or_enum_def, 1
raise "Should be impossible" unless Google::Protobuf::FFI.set_message_field new_message, sec_field_def, sec, arena
nsec_field_def = Google::Protobuf::FFI.get_field_by_number msg_or_enum_def, 2
nsec = Google::Protobuf::FFI::MessageValue.new
nsec[:int32_val] = ((value.to_f - value.to_i) * 1000000000).round
raise "Should be impossible" unless Google::Protobuf::FFI.set_message_field new_message, nsec_field_def, nsec, arena
return_value[:msg_val] = new_message
else
raise TypeError.new "Invalid type #{value.class} to assign to submessage field '#{name}'."
end
else
arena.fuse(value.instance_variable_get(:@arena))
return_value[:msg_val] = value.instance_variable_get :@msg
end
when :enum
return_value[:int32_val] = case value
when Numeric
value.to_i
when String, Symbol
enum_number = EnumDescriptor.send(:lookup_name, msg_or_enum_def, value.to_s)
#TODO add the bad value to the error message after tests pass
raise RangeError.new "Unknown symbol value for enum field '#{name}'." if enum_number.nil?
enum_number
else
raise TypeError.new "Expected number or symbol type for enum field '#{name}'."
end
#TODO After all tests pass, improve error message across integer type by including actual offending value
when :int32
raise TypeError.new "Expected number type for integral field '#{name}' (given #{value.class})." unless value.is_a? Numeric
raise RangeError.new "Non-integral floating point value assigned to integer field '#{name}' (given #{value.class})." if value.floor != value
raise RangeError.new "Value assigned to int32 field '#{name}' (given #{value.class}) with more than 32-bits." unless value.to_i.bit_length < 32
return_value[:int32_val] = value.to_i
when :uint32
raise TypeError.new "Expected number type for integral field '#{name}' (given #{value.class})." unless value.is_a? Numeric
raise RangeError.new "Non-integral floating point value assigned to integer field '#{name}' (given #{value.class})." if value.floor != value
raise RangeError.new "Assigning negative value to unsigned integer field '#{name}' (given #{value.class})." if value < 0
raise RangeError.new "Value assigned to uint32 field '#{name}' (given #{value.class}) with more than 32-bits." unless value.to_i.bit_length < 33
return_value[:uint32_val] = value.to_i
when :int64
raise TypeError.new "Expected number type for integral field '#{name}' (given #{value.class})." unless value.is_a? Numeric
raise RangeError.new "Non-integral floating point value assigned to integer field '#{name}' (given #{value.class})." if value.floor != value
raise RangeError.new "Value assigned to int64 field '#{name}' (given #{value.class}) with more than 64-bits." unless value.to_i.bit_length < 64
return_value[:int64_val] = value.to_i
when :uint64
raise TypeError.new "Expected number type for integral field '#{name}' (given #{value.class})." unless value.is_a? Numeric
raise RangeError.new "Non-integral floating point value assigned to integer field '#{name}' (given #{value.class})." if value.floor != value
raise RangeError.new "Assigning negative value to unsigned integer field '#{name}' (given #{value.class})." if value < 0
raise RangeError.new "Value assigned to uint64 field '#{name}' (given #{value.class}) with more than 64-bits." unless value.to_i.bit_length < 65
return_value[:uint64_val] = value.to_i
else
raise RuntimeError.new "Unsupported type #{c_type}"
end
return_value
end
##
# Safe to call without an arena if the caller has checked that c_type
# is not :message.
# @param message_value [Google::Protobuf::FFI::MessageValue] Value to be converted.
# @param c_type [Google::Protobuf::FFI::CType] Enum representing the type of message_value
# @param msg_or_enum_def [::FFI::Pointer] Pointer to the MsgDef or EnumDef definition
# @param arena [Google::Protobuf::Internal::Arena] Arena to create Message instances, if needed
def convert_upb_to_ruby(message_value, c_type, msg_or_enum_def = nil, arena = nil)
throw TypeError.new "Expected MessageValue but got #{message_value.class}" unless message_value.is_a? Google::Protobuf::FFI::MessageValue
case c_type
when :bool
message_value[:bool_val]
when :int32
message_value[:int32_val]
when :uint32
message_value[:uint32_val]
when :double
message_value[:double_val]
when :int64
message_value[:int64_val]
when :uint64
message_value[:uint64_val]
when :string
if message_value[:str_val][:size].zero?
""
else
message_value[:str_val][:data].read_string_length(message_value[:str_val][:size]).force_encoding("UTF-8").freeze
end
when :bytes
if message_value[:str_val][:size].zero?
""
else
message_value[:str_val][:data].read_string_length(message_value[:str_val][:size]).force_encoding("ASCII-8BIT").freeze
end
when :float
message_value[:float_val]
when :enum
EnumDescriptor.send(:lookup_value, msg_or_enum_def, message_value[:int32_val]) || message_value[:int32_val]
when :message
raise "Null Arena for message" if arena.nil?
Descriptor.send(:get_message, message_value[:msg_val], msg_or_enum_def, arena)
else
raise RuntimeError.new "Unexpected type #{c_type}"
end
end
def to_h_internal(msg, message_descriptor)
return nil if msg.nil? or msg.null?
hash = {}
iter = ::FFI::MemoryPointer.new(:size_t, 1)
iter.write(:size_t, Google::Protobuf::FFI::Upb_Message_Begin)
message_value = Google::Protobuf::FFI::MessageValue.new
field_def_ptr = ::FFI::MemoryPointer.new :pointer
while Google::Protobuf::FFI::message_next(msg, message_descriptor, nil, field_def_ptr, message_value, iter) do
field_descriptor = FieldDescriptor.from_native field_def_ptr.get_pointer(0)
if field_descriptor.map?
hash_entry = map_create_hash(message_value[:map_val], field_descriptor)
elsif field_descriptor.repeated?
hash_entry = repeated_field_create_array(message_value[:array_val], field_descriptor, field_descriptor.type)
else
hash_entry = scalar_create_hash(message_value, field_descriptor.type, field_descriptor: field_descriptor)
end
hash[field_descriptor.name.to_sym] = hash_entry
end
hash
end
def map_create_hash(map_ptr, field_descriptor)
return {} if map_ptr.nil? or map_ptr.null?
return_value = {}
message_descriptor = field_descriptor.send(:subtype)
key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1)
key_field_type = Google::Protobuf::FFI.get_type(key_field_def)
value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2)
value_field_type = Google::Protobuf::FFI.get_type(value_field_def)
iter = ::FFI::MemoryPointer.new(:size_t, 1)
iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin)
while Google::Protobuf::FFI.map_next(map_ptr, iter) do
iter_size_t = iter.read(:size_t)
key_message_value = Google::Protobuf::FFI.map_key(map_ptr, iter_size_t)
value_message_value = Google::Protobuf::FFI.map_value(map_ptr, iter_size_t)
hash_key = convert_upb_to_ruby(key_message_value, key_field_type)
hash_value = scalar_create_hash(value_message_value, value_field_type, msg_or_enum_descriptor: value_field_def.subtype)
return_value[hash_key] = hash_value
end
return_value
end
def repeated_field_create_array(array, field_descriptor, type)
return_value = []
n = (array.nil? || array.null?) ? 0 : Google::Protobuf::FFI.array_size(array)
0.upto(n - 1) do |i|
message_value = Google::Protobuf::FFI.get_msgval_at(array, i)
return_value << scalar_create_hash(message_value, type, field_descriptor: field_descriptor)
end
return_value
end
# @param field_descriptor [FieldDescriptor] Descriptor of the field to convert to a hash.
def scalar_create_hash(message_value, type, field_descriptor: nil, msg_or_enum_descriptor: nil)
if [:message, :enum].include? type
if field_descriptor.nil?
if msg_or_enum_descriptor.nil?
raise "scalar_create_hash requires either a FieldDescriptor, MessageDescriptor, or EnumDescriptor as an argument, but received only nil"
end
else
msg_or_enum_descriptor = field_descriptor.subtype
end
if type == :message
to_h_internal(message_value[:msg_val], msg_or_enum_descriptor)
elsif type == :enum
convert_upb_to_ruby message_value, type, msg_or_enum_descriptor
end
else
convert_upb_to_ruby message_value, type
end
end
def message_value_deep_copy(message_value, type, descriptor, arena)
raise unless message_value.is_a? Google::Protobuf::FFI::MessageValue
new_message_value = Google::Protobuf::FFI::MessageValue.new
case type
when :string, :bytes
# TODO - how important is it to still use arena malloc, versus using FFI MemoryPointers?
new_message_value[:str_val][:size] = message_value[:str_val][:size]
new_message_value[:str_val][:data] = Google::Protobuf::FFI.arena_malloc(arena, message_value[:str_val][:size])
raise NoMemoryError.new "Allocation failed" if new_message_value[:str_val][:data].nil? or new_message_value[:str_val][:data].null?
Google::Protobuf::FFI.memcpy(new_message_value[:str_val][:data], message_value[:str_val][:data], message_value[:str_val][:size])
when :message
new_message_value[:msg_val] = descriptor.msgclass.send(:deep_copy, message_value[:msg_val], arena).instance_variable_get(:@msg)
else
Google::Protobuf::FFI.memcpy(new_message_value.to_ptr, message_value.to_ptr, Google::Protobuf::FFI::MessageValue.size)
end
new_message_value
end
end
end
end
end
@@ -0,0 +1,36 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2023 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
module Internal
module PointerHelper
# Utility code to defensively walk the object graph from a file_def to
# the pool, and either retrieve the wrapper object for the given pointer
# or create one. Assumes that the caller is the wrapper class for the
# given pointer and that it implements `private_constructor`.
def descriptor_from_file_def(file_def, pointer = nil)
pointer = file_def if pointer.nil?
raise RuntimeError.new "FileDef is nil" if file_def.nil?
raise RuntimeError.new "FileDef is null" if file_def.null?
pool_def = Google::Protobuf::FFI.file_def_pool file_def
raise RuntimeError.new "PoolDef is nil" if pool_def.nil?
raise RuntimeError.new "PoolDef is null" if pool_def.null?
pool = Google::Protobuf::OBJECT_CACHE.get(pool_def.address)
raise "Cannot find pool in ObjectCache!" if pool.nil?
descriptor = pool.descriptor_class_by_def[pointer.address]
if descriptor.nil?
pool.descriptor_class_by_def[pointer.address] = private_constructor(pointer, pool)
else
descriptor
end
end
end
end
end
end
@@ -0,0 +1,25 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
# A to_native DataConverter method that raises an error if the value is not of the same type.
# Adapted from to https://www.varvet.com/blog/advanced-topics-in-ruby-ffi/
module Google
module Protobuf
module Internal
module TypeSafety
def to_native(value, ctx = nil)
if value.kind_of?(self) or value.nil?
super
else
raise TypeError.new "Expected a kind of #{name}, was #{value.class}"
end
end
end
end
end
end
@@ -0,0 +1,433 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
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_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
end
class Map
include Enumerable
##
# call-seq:
# Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {})
# => new map
#
# Allocates a new Map container. This constructor may be called with 2, 3, or 4
# arguments. The first two arguments are always present and are symbols (taking
# on the same values as field-type symbols in message descriptors) that
# indicate the type of the map key and value fields.
#
# The supported key types are: :int32, :int64, :uint32, :uint64, :bool,
# :string, :bytes.
#
# The supported value types are: :int32, :int64, :uint32, :uint64, :bool,
# :string, :bytes, :enum, :message.
#
# The third argument, value_typeclass, must be present if value_type is :enum
# or :message. As in RepeatedField#new, this argument must be a message class
# (for :message) or enum module (for :enum).
#
# The last argument, if present, provides initial content for map. Note that
# this may be an ordinary Ruby hashmap or another Map instance with identical
# key and value types. Also note that this argument may be present whether or
# not value_typeclass is present (and it is unambiguously separate from
# value_typeclass because value_typeclass's presence is strictly determined by
# value_type). The contents of this initial hashmap or Map instance are
# shallow-copied into the new Map: the original map is unmodified, but
# references to underlying objects will be shared if the value type is a
# message type.
def self.new(key_type, value_type, value_typeclass = nil, init_hashmap = {})
instance = allocate
# TODO This argument mangling doesn't agree with the type signature,
# but does align with the text of the comments and is required to make unit tests pass.
if init_hashmap.empty? and ![:enum, :message].include?(value_type)
init_hashmap = value_typeclass
value_typeclass = nil
end
instance.send(:initialize, key_type, value_type, value_type_class: value_typeclass, initial_values: init_hashmap)
instance
end
##
# call-seq:
# Map.keys => [list_of_keys]
#
# Returns the list of keys contained in the map, in unspecified order.
def keys
return_value = []
internal_iterator do |iterator|
key_message_value = Google::Protobuf::FFI.map_key(@map_ptr, iterator)
return_value << convert_upb_to_ruby(key_message_value, key_type)
end
return_value
end
##
# call-seq:
# Map.values => [list_of_values]
#
# Returns the list of values contained in the map, in unspecified order.
def values
return_value = []
internal_iterator do |iterator|
value_message_value = Google::Protobuf::FFI.map_value(@map_ptr, iterator)
return_value << convert_upb_to_ruby(value_message_value, value_type, descriptor, arena)
end
return_value
end
##
# call-seq:
# Map.[](key) => value
#
# Accesses the element at the given key. Throws an exception if the key type is
# incorrect. Returns nil when the key is not present in the map.
def [](key)
value = Google::Protobuf::FFI::MessageValue.new
key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
if Google::Protobuf::FFI.map_get(@map_ptr, key_message_value, value)
convert_upb_to_ruby(value, value_type, descriptor, arena)
end
end
##
# call-seq:
# Map.[]=(key, value) => value
#
# Inserts or overwrites the value at the given key with the given new value.
# Throws an exception if the key type is incorrect. Returns the new value that
# was just inserted.
def []=(key, value)
raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
value_message_value = convert_ruby_to_upb(value, arena, value_type, descriptor)
Google::Protobuf::FFI.map_set(@map_ptr, key_message_value, value_message_value, arena)
value
end
def has_key?(key)
key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
Google::Protobuf::FFI.map_get(@map_ptr, key_message_value, nil)
end
##
# call-seq:
# Map.delete(key) => old_value
#
# Deletes the value at the given key, if any, returning either the old value or
# nil if none was present. Throws an exception if the key is of the wrong type.
def delete(key)
raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
value = Google::Protobuf::FFI::MessageValue.new
key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
if Google::Protobuf::FFI.map_delete(@map_ptr, key_message_value, value)
convert_upb_to_ruby(value, value_type, descriptor, arena)
else
nil
end
end
def clear
raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
Google::Protobuf::FFI.map_clear(@map_ptr)
nil
end
def length
Google::Protobuf::FFI.map_size(@map_ptr)
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
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
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
##
# call-seq:
# Map.dup => new_map
#
# Duplicates this map with a shallow copy. References to all non-primitive
# element objects (e.g., submessages) are shared.
def dup
internal_dup
end
alias clone dup
##
# call-seq:
# Map.==(other) => boolean
#
# Compares this map to another. Maps are equal if they have identical key sets,
# and for each key, the values in both maps compare equal. Elements are
# compared as per normal Ruby semantics, by calling their :== methods (or
# performing a more efficient comparison for primitive types).
#
# Maps with dissimilar key types or value types/typeclasses are never equal,
# even if value comparison (for example, between integers and floats) would
# have otherwise indicated that every element has equal value.
def ==(other)
if other.is_a? Hash
other = self.class.send(:private_constructor, key_type, value_type, descriptor, initial_values: other)
elsif !other.is_a? Google::Protobuf::Map
return false
end
return true if object_id == other.object_id
return false if key_type != other.send(:key_type) or value_type != other.send(:value_type) or descriptor != other.send(:descriptor) or length != other.length
other_map_ptr = other.send(:map_ptr)
each_msg_val do |key_message_value, value_message_value|
other_value = Google::Protobuf::FFI::MessageValue.new
return false unless Google::Protobuf::FFI.map_get(other_map_ptr, key_message_value, other_value)
return false unless Google::Protobuf::FFI.message_value_equal(value_message_value, other_value, value_type, descriptor)
end
true
end
def hash
return_value = 0
each_msg_val do |key_message_value, value_message_value|
return_value += Google::Protobuf::FFI.message_value_hash(key_message_value, key_type, nil, 0)
return_value += Google::Protobuf::FFI.message_value_hash(value_message_value, value_type, descriptor, 0)
end
return_value
end
##
# call-seq:
# Map.to_h => {}
#
# Returns a Ruby Hash object containing all the values within the map
def to_h
return {} if map_ptr.nil? or map_ptr.null?
return_value = {}
each_msg_val do |key_message_value, value_message_value|
hash_key = convert_upb_to_ruby(key_message_value, key_type)
hash_value = scalar_create_hash(value_message_value, value_type, msg_or_enum_descriptor: descriptor)
return_value[hash_key] = hash_value
end
return_value
end
def inspect
key_value_pairs = []
each_msg_val do |key_message_value, value_message_value|
key_string = convert_upb_to_ruby(key_message_value, key_type).inspect
if value_type == :message
sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(descriptor)
value_string = sub_msg_descriptor.msgclass.send(:inspect_internal, value_message_value[:msg_val])
else
value_string = convert_upb_to_ruby(value_message_value, value_type, descriptor).inspect
end
key_value_pairs << "#{key_string}=>#{value_string}"
end
"{#{key_value_pairs.join(", ")}}"
end
##
# call-seq:
# Map.merge(other_map) => map
#
# Copies key/value pairs from other_map into a copy of this map. If a key is
# set in other_map and this map, the value from other_map overwrites the value
# in the new copy of this map. Returns the new copy of this map with merged
# contents.
def merge(other)
internal_merge(other)
end
##
# call-seq:
# Map.each(&block)
#
# Invokes &block on each |key, value| pair in the map, in unspecified order.
# Note that Map also includes Enumerable; map thus acts like a normal Ruby
# sequence.
def each &block
each_msg_val do |key_message_value, value_message_value|
key_value = convert_upb_to_ruby(key_message_value, key_type)
value_value = convert_upb_to_ruby(value_message_value, value_type, descriptor, arena)
yield key_value, value_value
end
nil
end
private
attr :arena, :map_ptr, :key_type, :value_type, :descriptor, :name
include Google::Protobuf::Internal::Convert
def internal_iterator
iter = ::FFI::MemoryPointer.new(:size_t, 1)
iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin)
while Google::Protobuf::FFI.map_next(@map_ptr, iter) do
iter_size_t = iter.read(:size_t)
yield iter_size_t
end
end
def each_msg_val &block
internal_iterator do |iterator|
key_message_value = Google::Protobuf::FFI.map_key(@map_ptr, iterator)
value_message_value = Google::Protobuf::FFI.map_value(@map_ptr, iterator)
yield key_message_value, value_message_value
end
end
def internal_dup
instance = self.class.send(:private_constructor, key_type, value_type, descriptor, arena: arena)
new_map_ptr = instance.send(:map_ptr)
each_msg_val do |key_message_value, value_message_value|
Google::Protobuf::FFI.map_set(new_map_ptr, key_message_value, value_message_value, arena)
end
instance
end
def internal_merge_into_self(other)
case other
when Hash
other.each do |key, value|
key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
value_message_value = convert_ruby_to_upb(value, arena, value_type, descriptor)
Google::Protobuf::FFI.map_set(@map_ptr, key_message_value, value_message_value, arena)
end
when Google::Protobuf::Map
unless key_type == other.send(:key_type) and value_type == other.send(:value_type) and descriptor == other.descriptor
raise ArgumentError.new "Attempt to merge Map with mismatching types" #TODO Improve error message by adding type information
end
arena.fuse(other.send(:arena))
iter = ::FFI::MemoryPointer.new(:size_t, 1)
iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin)
other.send(:each_msg_val) do |key_message_value, value_message_value|
Google::Protobuf::FFI.map_set(@map_ptr, key_message_value, value_message_value, arena)
end
else
raise ArgumentError.new "Unknown type merging into Map" #TODO improve this error message by including type information
end
self
end
def internal_merge(other)
internal_dup.internal_merge_into_self(other)
end
def initialize(key_type, value_type, value_type_class: nil, initial_values: nil, arena: nil, map: nil, descriptor: nil, name: nil)
@name = name || 'Map'
unless [:int32, :int64, :uint32, :uint64, :bool, :string, :bytes].include? key_type
raise ArgumentError.new "Invalid key type for map." #TODO improve error message to include what type was passed
end
@key_type = key_type
unless [:int32, :int64, :uint32, :uint64, :bool, :string, :bytes, :enum, :message].include? value_type
raise ArgumentError.new "Invalid value type for map." #TODO improve error message to include what type was passed
end
@value_type = value_type
if !descriptor.nil?
raise ArgumentError "Expected descriptor to be a Descriptor or EnumDescriptor" unless [EnumDescriptor, Descriptor].include? descriptor.class
@descriptor = descriptor
elsif [:message, :enum].include? value_type
raise ArgumentError.new "Expected at least 3 arguments for message/enum." if value_type_class.nil?
descriptor = value_type_class.respond_to?(:descriptor) ? value_type_class.descriptor : nil
raise ArgumentError.new "Type class #{value_type_class} has no descriptor. Please pass a class or enum as returned by the DescriptorPool." if descriptor.nil?
@descriptor = descriptor
else
@descriptor = nil
end
@arena = arena || Google::Protobuf::FFI.create_arena
@map_ptr = map || Google::Protobuf::FFI.create_map(@arena, @key_type, @value_type)
internal_merge_into_self(initial_values) unless initial_values.nil?
# Should always be the last expression of the initializer to avoid
# leaking references to this object before construction is complete.
OBJECT_CACHE.try_add(@map_ptr.address, self)
end
##
# 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 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
message_descriptor = field.send(:subtype)
key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1)
key_field_type = Google::Protobuf::FFI.get_type(key_field_def)
value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2)
value_field_type = Google::Protobuf::FFI.get_type(value_field_def)
instance.send(:initialize, key_field_type, value_field_type, initial_values: value, name: field.name, arena: arena, map: map, descriptor: value_field_def.subtype)
instance
end
def self.private_constructor(key_type, value_type, descriptor, initial_values: nil, arena: nil)
instance = allocate
instance.send(:initialize, key_type, value_type, descriptor: descriptor, initial_values: initial_values, arena: arena)
instance
end
extend Google::Protobuf::Internal::Convert
def self.deep_copy(map)
instance = allocate
instance.send(:initialize, map.send(:key_type), map.send(:value_type), descriptor: map.send(:descriptor))
map.send(:each_msg_val) do |key_message_value, value_message_value|
Google::Protobuf::FFI.map_set(instance.send(:map_ptr), key_message_value, message_value_deep_copy(value_message_value, map.send(:value_type), map.send(:descriptor), instance.send(:arena)), instance.send(:arena))
end
instance
end
end
end
end
@@ -0,0 +1,785 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2023 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
# Decorates Descriptor with the `build_message_class` method that defines
# Message classes.
module Google
module Protobuf
class FFI
# Message
attach_function :clear_message_field, :upb_Message_ClearFieldByDef, [:Message, FieldDescriptor], :void
attach_function :get_message_value, :upb_Message_GetFieldByDef, [:Message, FieldDescriptor], MessageValue.by_value
attach_function :get_message_has, :upb_Message_HasFieldByDef, [:Message, FieldDescriptor], :bool
attach_function :set_message_field, :upb_Message_SetFieldByDef, [:Message, FieldDescriptor, MessageValue.by_value, Internal::Arena], :bool
attach_function :encode_message, :upb_Encode, [:Message, MiniTable.by_ref, :size_t, Internal::Arena, :pointer, :pointer], EncodeStatus
attach_function :json_decode_message_detecting_nonconformance, :upb_JsonDecodeDetectingNonconformance, [:binary_string, :size_t, :Message, Descriptor, :DefPool, :int, Internal::Arena, Status.by_ref], :int
attach_function :json_encode_message, :upb_JsonEncode, [:Message, Descriptor, :DefPool, :int, :binary_string, :size_t, Status.by_ref], :size_t
attach_function :decode_message, :upb_Decode, [:binary_string, :size_t, :Message, MiniTable.by_ref, :ExtensionRegistry, :int, Internal::Arena], DecodeStatus
attach_function :get_mutable_message, :upb_Message_Mutable, [:Message, FieldDescriptor, Internal::Arena], MutableMessageValue.by_value
attach_function :get_message_which_oneof, :upb_Message_WhichOneofByDef, [:Message, OneofDescriptor], FieldDescriptor
attach_function :message_discard_unknown, :upb_Message_DiscardUnknown, [:Message, Descriptor, :DefPool, :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
attach_function :message_shallow_copy, :upb_Message_ShallowCopy, [:Message, :Message, MiniTable.by_ref, Internal::Arena], :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
end
class Descriptor
def build_message_class
descriptor = self
Class.new(Google::Protobuf::const_get(:AbstractMessage)) do
@descriptor = descriptor
class << self
attr_accessor :descriptor
private
attr_accessor :oneof_field_names
include ::Google::Protobuf::Internal::Convert
end
alias original_method_missing method_missing
def method_missing(method_name, *args)
method_missing_internal method_name, *args, mode: :method_missing
end
def respond_to_missing?(method_name, include_private = false)
method_missing_internal(method_name, mode: :respond_to_missing?) || super
end
##
# Public constructor. Automatically allocates from a new Arena.
def self.new(initial_value = nil)
instance = allocate
instance.send(:initialize, initial_value)
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
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
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
mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor)
new_msg = Google::Protobuf::FFI.new_message_from_def(mini_table, @arena)
unless Google::Protobuf::FFI.message_shallow_copy(new_msg, @msg, mini_table, @arena)
raise RuntimeError.new "Failed to shallow copy message."
end
self.class.private_constructor(@arena, msg: new_msg)
end
alias clone dup
def eql?(other)
return false unless self.class === other
encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown
temporary_arena = Google::Protobuf::FFI.create_arena
mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor)
size_one = ::FFI::MemoryPointer.new(:size_t, 1)
encoding_one = ::FFI::MemoryPointer.new(:pointer, 1)
encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table, encoding_options, temporary_arena, encoding_one.to_ptr, size_one)
raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding LHS of `eql?()`" unless encoding_status == :Ok
size_two = ::FFI::MemoryPointer.new(:size_t, 1)
encoding_two = ::FFI::MemoryPointer.new(:pointer, 1)
encoding_status = Google::Protobuf::FFI.encode_message(other.instance_variable_get(:@msg), mini_table, encoding_options, temporary_arena, encoding_two.to_ptr, size_two)
raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding RHS of `eql?()`" unless encoding_status == :Ok
if encoding_one.null? or encoding_two.null?
raise ParseError.new "Error comparing messages"
end
size_one.read(:size_t) == size_two.read(:size_t) and Google::Protobuf::FFI.memcmp(encoding_one.read(:pointer), encoding_two.read(:pointer), size_one.read(:size_t)).zero?
end
alias == eql?
def hash
encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown
temporary_arena = Google::Protobuf::FFI.create_arena
mini_table_ptr = Google::Protobuf::FFI.get_mini_table(self.class.descriptor)
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
encoding = ::FFI::MemoryPointer.new(:pointer, 1)
encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table_ptr, encoding_options, temporary_arena, encoding.to_ptr, size_ptr)
if encoding_status != :Ok or encoding.null?
raise ParseError.new "Error calculating hash"
end
encoding.read(:pointer).read_string(size_ptr.read(:size_t)).hash
end
def to_h
to_h_internal @msg, self.class.descriptor
end
##
# call-seq:
# Message.inspect => string
#
# Returns a human-readable string representing this message. It will be
# formatted as "<MessageType: field1: value1, field2: value2, ...>". Each
# field's value is represented according to its own #inspect method.
def inspect
self.class.inspect_internal @msg
end
def to_s
self.inspect
end
##
# call-seq:
# Message.[](index) => value
# Accesses a field's value by field name. The provided field name
# should be a string.
def [](name)
raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String
index_internal name
end
##
# call-seq:
# Message.[]=(index, value)
# Sets a field's value by field name. The provided field name should
# be a string.
# @param name [String] Name of the field to be set
# @param value [Object] Value to set the field to
def []=(name, value)
raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String
index_assign_internal(value, name: name)
end
##
# call-seq:
# MessageClass.decode(data, options) => message
#
# Decodes the given data (as a string containing bytes in protocol buffers wire
# format) under the interpretation given by this message class's definition
# and returns a message object with the corresponding field values.
# @param data [String] Binary string in Protobuf wire format to decode
# @param options [Hash] options for the decoder
# @option options [Integer] :recursion_limit Set to maximum decoding depth for message (default is 64)
def self.decode(data, options = {})
raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash
raise ArgumentError.new "Expected string for binary protobuf data." unless data.is_a? String
decoding_options = 0
depth = options[:recursion_limit]
if depth.is_a? Numeric
decoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i)
end
message = new
mini_table_ptr = Google::Protobuf::FFI.get_mini_table(message.class.descriptor)
status = Google::Protobuf::FFI.decode_message(
data,
data.bytesize,
message.instance_variable_get(:@msg),
mini_table_ptr,
Google::Protobuf::FFI.get_extension_registry(message.class.descriptor.send(:pool).descriptor_pool),
decoding_options,
message.instance_variable_get(:@arena)
)
raise ParseError.new "Error occurred during parsing" unless status == :Ok
message
end
##
# call-seq:
# MessageClass.encode(msg, options) => bytes
#
# Encodes the given message object to its serialized form in protocol buffers
# wire format.
# @param options [Hash] options for the encoder
# @option options [Integer] :recursion_limit Set to maximum encoding depth for message (default is 64)
def self.encode(message, options = {})
raise ArgumentError.new "Message of wrong type." unless message.is_a? self
raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash
encoding_options = 0
depth = options[:recursion_limit]
if depth.is_a? Numeric
encoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i)
end
encode_internal(message.instance_variable_get(:@msg), encoding_options) do |encoding, size, _|
if encoding.nil? or encoding.null?
raise RuntimeError.new "Exceeded maximum depth (possibly cycle)"
else
encoding.read_string_length(size).force_encoding("ASCII-8BIT").freeze
end
end
end
##
# all-seq:
# MessageClass.decode_json(data, options = {}) => message
#
# Decodes the given data (as a string containing bytes in protocol buffers wire
# format) under the interpretation given by this message class's definition
# and returns a message object with the corresponding field values.
#
# @param options [Hash] options for the decoder
# @option options [Boolean] :ignore_unknown_fields Set true to ignore unknown fields (default is to raise an error)
# @return [Message]
def self.decode_json(data, options = {})
decoding_options = 0
unless options.is_a? Hash
if options.respond_to? :to_h
options = options.to_h
else
#TODO can this error message be improve to include what was received?
raise ArgumentError.new "Expected hash arguments"
end
end
raise ArgumentError.new "Expected string for JSON data." unless data.is_a? String
raise RuntimeError.new "Cannot parse a wrapper directly" if descriptor.send(:wrapper?)
if options[:ignore_unknown_fields]
decoding_options |= Google::Protobuf::FFI::Upb_JsonDecode_IgnoreUnknown
end
message = new
pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool
status = Google::Protobuf::FFI::Status.new
result = Google::Protobuf::FFI.json_decode_message_detecting_nonconformance(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status)
case result
when Google::Protobuf::FFI::Upb_JsonDecodeResult_Error
raise ParseError.new "Error occurred during parsing: #{Google::Protobuf::FFI.error_message(status)}"
end
message
end
def self.encode_json(message, options = {})
encoding_options = 0
unless options.is_a? Hash
if options.respond_to? :to_h
options = options.to_h
else
#TODO can this error message be improve to include what was received?
raise ArgumentError.new "Expected hash arguments"
end
end
if options[:preserve_proto_fieldnames]
encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_UseProtoNames
end
if options[:emit_defaults]
encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_EmitDefaults
end
if options[:format_enums_as_integers]
encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_FormatEnumsAsIntegers
end
buffer_size = 1024
buffer = ::FFI::MemoryPointer.new(:char, buffer_size)
status = Google::Protobuf::FFI::Status.new
msg = message.instance_variable_get(:@msg)
pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool
size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status)
unless status[:ok]
raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}"
end
if size >= buffer_size
buffer_size = size + 1
buffer = ::FFI::MemoryPointer.new(:char, buffer_size)
status.clear
size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status)
unless status[:ok]
raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}"
end
if size >= buffer_size
raise ParseError.new "Inconsistent JSON encoding sizes - was #{buffer_size - 1}, now #{size}"
end
end
buffer.read_string_length(size).force_encoding("UTF-8").freeze
end
private
# Implementation details below are subject to breaking changes without
# warning and are intended for use only within the gem.
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)
# 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
map_from_field_descriptor field_descriptor
end
elsif field_descriptor.repeated?
define_method(field_name) do
repeated_field_from_field_descriptor field_descriptor
end
elsif field_descriptor.sub_message?
define_method(field_name) do
message_from_field_descriptor field_descriptor
end
else
define_method(field_name) do
scalar_from_field_descriptor field_descriptor
end
end
define_method("#{field_name}=") do |value|
index_assign_internal(value, field_descriptor: field_descriptor)
end
define_method("clear_#{field_name}") do
clear_internal(field_descriptor)
end
if field_descriptor.type == :enum
if field_descriptor.repeated?
define_method("#{field_name}_const") do
return_value = []
repeated_field_from_field_descriptor(field_descriptor).send(:each_msg_val) do |msg_val|
return_value << msg_val[:int32_val]
end
return_value
end
else
define_method("#{field_name}_const") do
message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
message_value[:int32_val]
end
end
end
if !field_descriptor.repeated? and field_descriptor.wrapper?
define_method("#{field_name}_as_value") do
get_field(field_descriptor, unwrap: true)
end
define_method("#{field_name}_as_value=") do |value|
if value.nil?
clear_internal(field_descriptor)
else
index_assign_internal(value, field_descriptor: field_descriptor, wrap: true)
end
end
end
if field_descriptor.has_presence?
define_method("has_#{field_name}?") do
Google::Protobuf::FFI.get_message_has(@msg, field_descriptor)
end
end
end
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
unless instance_methods(true).include?(field_name)
define_method(field_name) do
field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor)
if field_descriptor.nil?
return
else
return field_descriptor.name.to_sym
end
end
define_method("clear_#{field_name}") do
field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor)
unless field_descriptor.nil?
clear_internal(field_descriptor)
end
end
define_method("has_#{field_name}?") do
!Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor).nil?
end
end
end
setup_accessors!
setup_oneof_accessors!
def self.private_constructor(arena, msg: nil, initial_value: nil)
instance = allocate
instance.send(:initialize, initial_value, arena, msg)
instance
end
def self.inspect_field(field_descriptor, c_type, message_value)
if field_descriptor.sub_message?
sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor)
sub_msg_descriptor.msgclass.send(:inspect_internal, message_value[:msg_val])
else
convert_upb_to_ruby(message_value, c_type, field_descriptor.subtype).inspect
end
end
# @param msg [::FFI::Pointer] Pointer to the Message
def self.inspect_internal(msg)
field_output = []
descriptor.each do |field_descriptor|
next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(msg, field_descriptor)
if field_descriptor.map?
# TODO Adapted - from map#each_msg_val and map#inspect- can this be refactored to reduce echo without introducing a arena allocation?
message_descriptor = field_descriptor.subtype
key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1)
key_field_type = Google::Protobuf::FFI.get_type(key_field_def)
value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2)
value_field_type = Google::Protobuf::FFI.get_type(value_field_def)
message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor)
iter = ::FFI::MemoryPointer.new(:size_t, 1)
iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin)
key_value_pairs = []
while Google::Protobuf::FFI.map_next(message_value[:map_val], iter) do
iter_size_t = iter.read(:size_t)
key_message_value = Google::Protobuf::FFI.map_key(message_value[:map_val], iter_size_t)
value_message_value = Google::Protobuf::FFI.map_value(message_value[:map_val], iter_size_t)
key_string = convert_upb_to_ruby(key_message_value, key_field_type).inspect
value_string = inspect_field(value_field_def, value_field_type, value_message_value)
key_value_pairs << "#{key_string}=>#{value_string}"
end
field_output << "#{field_descriptor.name}: {#{key_value_pairs.join(", ")}}"
elsif field_descriptor.repeated?
# TODO Adapted - from repeated_field#each - can this be refactored to reduce echo?
repeated_field_output = []
message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor)
array = message_value[:array_val]
n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array)
0.upto(n - 1) do |i|
element = Google::Protobuf::FFI.get_msgval_at(array, i)
repeated_field_output << inspect_field(field_descriptor, field_descriptor.send(:c_type), element)
end
field_output << "#{field_descriptor.name}: [#{repeated_field_output.join(", ")}]"
else
message_value = Google::Protobuf::FFI.get_message_value msg, field_descriptor
rendered_value = inspect_field(field_descriptor, field_descriptor.send(:c_type), message_value)
field_output << "#{field_descriptor.name}: #{rendered_value}"
end
end
"<#{name}: #{field_output.join(', ')}>"
end
def self.deep_copy(msg, arena = nil)
arena ||= Google::Protobuf::FFI.create_arena
encode_internal(msg) do |encoding, size, mini_table_ptr|
message = private_constructor(arena)
if encoding.nil? or encoding.null? or Google::Protobuf::FFI.decode_message(encoding, size, message.instance_variable_get(:@msg), mini_table_ptr, nil, 0, arena) != :Ok
raise ParseError.new "Error occurred copying proto"
end
message
end
end
def self.encode_internal(msg, encoding_options = 0)
temporary_arena = Google::Protobuf::FFI.create_arena
mini_table_ptr = Google::Protobuf::FFI.get_mini_table(descriptor)
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
pointer_ptr = ::FFI::MemoryPointer.new(:pointer, 1)
encoding_status = Google::Protobuf::FFI.encode_message(msg, mini_table_ptr, encoding_options, temporary_arena, pointer_ptr.to_ptr, size_ptr)
raise "Encoding failed due to #{encoding_status}" unless encoding_status == :Ok
yield pointer_ptr.read(:pointer), size_ptr.read(:size_t), mini_table_ptr
end
def method_missing_internal(method_name, *args, mode: nil)
raise ArgumentError.new "method_missing_internal called with invalid mode #{mode.inspect}" unless [:respond_to_missing?, :method_missing].include? mode
#TODO not being allowed is not the same thing as not responding, but this is needed to pass tests
if method_name.to_s.end_with? '='
if self.class.send(:oneof_field_names).include? method_name.to_s[0..-2].to_sym
return false if mode == :respond_to_missing?
raise RuntimeError.new "Oneof accessors are read-only."
end
end
original_method_missing(method_name, *args) if mode == :method_missing
end
def clear_internal(field_def)
raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
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?
end
#TODO - well known types keeps us on our toes by overloading methods.
# How much of the public API needs to be defended?
def index_assign_internal(value, name: nil, field_descriptor: nil, wrap: false)
raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
if field_descriptor.nil?
field_descriptor = self.class.descriptor.lookup(name)
if field_descriptor.nil?
raise ArgumentError.new "Unknown field: #{name}"
end
end
unless field_descriptor.send :set_value_on_message, value, @msg, @arena, wrap: wrap
raise RuntimeError.new "allocation failed"
end
end
##
# @param initial_value [Object] initial value of this Message
# @param arena [Arena] Optional; Arena where this message will be allocated
# @param msg [::FFI::Pointer] Optional; Message to initialize; creates
# one if omitted or nil.
def initialize(initial_value = nil, arena = nil, msg = nil)
@arena = arena || Google::Protobuf::FFI.create_arena
@msg = msg || Google::Protobuf::FFI.new_message_from_def(Google::Protobuf::FFI.get_mini_table(self.class.descriptor), @arena)
unless initial_value.nil?
raise ArgumentError.new "Expected hash arguments or message, not #{initial_value.class}" unless initial_value.respond_to? :each
field_def_ptr = ::FFI::MemoryPointer.new :pointer
oneof_def_ptr = ::FFI::MemoryPointer.new :pointer
initial_value.each do |key, value|
raise ArgumentError.new "Expected string or symbols as hash keys when initializing proto from hash." unless [String, Symbol].include? key.class
unless Google::Protobuf::FFI.find_msg_def_by_name self.class.descriptor, key.to_s, key.to_s.bytesize, field_def_ptr, oneof_def_ptr
raise ArgumentError.new "Unknown field name '#{key}' in initialization map entry."
end
raise NotImplementedError.new "Haven't added oneofsupport yet" unless oneof_def_ptr.get_pointer(0).null?
raise NotImplementedError.new "Expected a field def" if field_def_ptr.get_pointer(0).null?
field_descriptor = FieldDescriptor.from_native field_def_ptr.get_pointer(0)
next if value.nil?
if field_descriptor.map?
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: @arena, values: value), name: key.to_s)
else
index_assign_internal(value, name: key.to_s)
end
end
end
# Should always be the last expression of the initializer to avoid
# leaking references to this object before construction is complete.
Google::Protobuf::OBJECT_CACHE.try_add @msg.address, self
end
##
# Gets a field of this message identified by FieldDescriptor.
#
# @param field [FieldDescriptor] Field to retrieve.
# @param unwrap [Boolean](false) If true, unwraps wrappers.
def get_field(field, unwrap: false)
if field.map?
map_from_field_descriptor field
elsif field.repeated?
repeated_field_from_field_descriptor field
elsif field.sub_message?
return nil unless Google::Protobuf::FFI.get_message_has @msg, 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
value_field_def = Google::Protobuf::FFI.get_field_by_number sub_message_def, 1
message_value = Google::Protobuf::FFI.get_message_value wrapper_message_value[:msg_val], value_field_def
convert_upb_to_ruby message_value, Google::Protobuf::FFI.get_c_type(value_field_def)
else
nil
end
else
message_from_field_descriptor field
end
else
scalar_from_field_descriptor field
end
end
##
# 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: @arena, array: array)
repeated_field.freeze if frozen?
end
repeated_field
end
##
# 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: @arena, map: map)
map_field.freeze if frozen?
end
map_field
end
end
end
end
end
end
@@ -0,0 +1,124 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2024 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
class MethodDescriptor
attr :method_def, :descriptor_pool
include Google::Protobuf::Internal::Convert
# FFI Interface methods and setup
extend ::FFI::DataConverter
native_type ::FFI::Type::POINTER
class << self
prepend Google::Protobuf::Internal::TypeSafety
include Google::Protobuf::Internal::PointerHelper
# @param value [MethodDescriptor] MethodDescriptor to convert to an FFI native type
# @param _ [Object] Unused
def to_native(value, _)
method_def_ptr = value.nil? ? nil : value.instance_variable_get(:@method_def)
return ::FFI::Pointer::NULL if method_def_ptr.nil?
raise "Underlying method_def was null!" if method_def_ptr.null?
method_def_ptr
end
##
# @param service_def [::FFI::Pointer] MethodDef pointer to be wrapped
# @param _ [Object] Unused
def from_native(method_def, _ = nil)
return nil if method_def.nil? or method_def.null?
service_def = Google::Protobuf::FFI.raw_service_def_by_raw_method_def(method_def)
file_def = Google::Protobuf::FFI.file_def_by_raw_service_def(service_def)
descriptor_from_file_def(file_def, method_def)
end
end
def self.new(*arguments, &block)
raise "Descriptor objects may not be created from Ruby."
end
def to_s
inspect
end
def inspect
"#{self.class.name}: #{name}"
end
def name
@name ||= Google::Protobuf::FFI.get_method_name(self)
end
def options
@options ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.method_options(self, size_ptr, temporary_arena)
Google::Protobuf::MethodOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze).freeze
end
end
def input_type
@input_type ||= Google::Protobuf::FFI.method_input_type(self)
end
def output_type
@output_type ||= Google::Protobuf::FFI.method_output_type(self)
end
def client_streaming
@client_streaming ||= Google::Protobuf::FFI.method_client_streaming(self)
end
def server_streaming
@server_streaming ||= Google::Protobuf::FFI.method_server_streaming(self)
end
def to_proto
@to_proto ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.method_to_proto(self, size_ptr, temporary_arena)
Google::Protobuf::MethodDescriptorProto.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
end
end
private
def initialize(method_def, descriptor_pool)
@method_def = method_def
@descriptor_pool = descriptor_pool
end
def self.private_constructor(method_def, descriptor_pool)
instance = allocate
instance.send(:initialize, method_def, descriptor_pool)
instance
end
def c_type
@c_type ||= Google::Protobuf::FFI.get_c_type(self)
end
end
class FFI
# MethodDef
attach_function :raw_service_def_by_raw_method_def, :upb_MethodDef_Service, [:pointer], :pointer
attach_function :get_method_name, :upb_MethodDef_Name, [MethodDescriptor], :string
attach_function :method_options, :MethodDescriptor_serialized_options, [MethodDescriptor, :pointer, Internal::Arena], :pointer
attach_function :method_input_type, :upb_MethodDef_InputType, [MethodDescriptor], Descriptor
attach_function :method_output_type, :upb_MethodDef_OutputType, [MethodDescriptor], Descriptor
attach_function :method_client_streaming, :upb_MethodDef_ClientStreaming, [MethodDescriptor], :bool
attach_function :method_server_streaming, :upb_MethodDef_ServerStreaming, [MethodDescriptor], :bool
attach_function :method_to_proto, :MethodDescriptor_serialized_to_proto, [MethodDescriptor, :pointer, Internal::Arena], :pointer
end
end
end
@@ -0,0 +1,30 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
private
SIZEOF_LONG = ::FFI::MemoryPointer.new(:long).size
SIZEOF_VALUE = ::FFI::Pointer::SIZE
def self.interpreter_supports_non_finalized_keys_in_weak_map?
! defined? JRUBY_VERSION
end
def self.cache_implementation
if interpreter_supports_non_finalized_keys_in_weak_map? and SIZEOF_LONG >= SIZEOF_VALUE
Google::Protobuf::Internal::ObjectCache
else
Google::Protobuf::Internal::LegacyObjectCache
end
end
public
OBJECT_CACHE = cache_implementation.new
end
end
@@ -0,0 +1,107 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2022 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
class OneofDescriptor
attr :descriptor_pool, :oneof_def
include Enumerable
# FFI Interface methods and setup
extend ::FFI::DataConverter
native_type ::FFI::Type::POINTER
class << self
prepend Google::Protobuf::Internal::TypeSafety
include Google::Protobuf::Internal::PointerHelper
# @param value [OneofDescriptor] FieldDescriptor to convert to an FFI native type
# @param _ [Object] Unused
def to_native(value, _ = nil)
value.instance_variable_get(:@oneof_def) || ::FFI::Pointer::NULL
end
##
# @param oneof_def [::FFI::Pointer] OneofDef pointer to be wrapped
# @param _ [Object] Unused
def from_native(oneof_def, _ = nil)
return nil if oneof_def.nil? or oneof_def.null?
message_descriptor = Google::Protobuf::FFI.get_oneof_containing_type oneof_def
raise RuntimeError.new "Message Descriptor is nil" if message_descriptor.nil?
file_def = Google::Protobuf::FFI.get_message_file_def message_descriptor.to_native
descriptor_from_file_def(file_def, oneof_def)
end
end
def self.new(*arguments, &block)
raise "OneofDescriptor objects may not be created from Ruby."
end
def name
Google::Protobuf::FFI.get_oneof_name(self)
end
def each &block
n = Google::Protobuf::FFI.get_oneof_field_count(self)
0.upto(n-1) do |i|
yield(Google::Protobuf::FFI.get_oneof_field_by_index(self, i))
end
nil
end
def options
@options ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.oneof_options(self, size_ptr, temporary_arena)
opts = Google::Protobuf::OneofOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
opts.clear_features()
opts.freeze
end
end
def to_proto
@to_proto ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.oneof_to_proto(self, size_ptr, temporary_arena)
Google::Protobuf::OneofDescriptorProto.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
end
end
private
def initialize(oneof_def, descriptor_pool)
@descriptor_pool = descriptor_pool
@oneof_def = oneof_def
end
def self.private_constructor(oneof_def, descriptor_pool)
instance = allocate
instance.send(:initialize, oneof_def, descriptor_pool)
instance
end
end
class FFI
# MessageDef
attach_function :get_oneof_by_name, :upb_MessageDef_FindOneofByNameWithSize, [Descriptor, :string, :size_t], OneofDescriptor
attach_function :get_oneof_by_index, :upb_MessageDef_Oneof, [Descriptor, :int], OneofDescriptor
# OneofDescriptor
attach_function :get_oneof_name, :upb_OneofDef_Name, [OneofDescriptor], :string
attach_function :get_oneof_field_count, :upb_OneofDef_FieldCount, [OneofDescriptor], :int
attach_function :get_oneof_field_by_index, :upb_OneofDef_Field, [OneofDescriptor, :int], FieldDescriptor
attach_function :get_oneof_containing_type,:upb_OneofDef_ContainingType, [:pointer], Descriptor
attach_function :oneof_options, :OneOfDescriptor_serialized_options, [OneofDescriptor, :pointer, Internal::Arena], :pointer
attach_function :oneof_to_proto, :OneOfDescriptor_serialized_to_proto, [OneofDescriptor, :pointer, Internal::Arena], :pointer
# FieldDescriptor
attach_function :real_containing_oneof, :upb_FieldDef_RealContainingOneof, [FieldDescriptor], OneofDescriptor
end
end
end
@@ -0,0 +1,411 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
#
# This class makes RepeatedField act (almost-) like a Ruby Array.
# It has convenience methods that extend the core C or Java based
# methods.
#
# This is a best-effort to mirror Array behavior. Two comments:
# 1) patches always welcome :)
# 2) if performance is an issue, feel free to rewrite the method
# in C. The source code has plenty of examples
#
# KNOWN ISSUES
# - #[]= doesn't allow less used approaches such as `arr[1, 2] = 'fizz'`
# - #concat should return the orig array
# - #push should accept multiple arguments and push them all at the same time
#
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 :array_freeze, :upb_Array_Freeze, [:Array, MiniTable.by_ref], :void
attach_function :array_frozen?, :upb_Array_IsFrozen, [:Array], :bool
end
class RepeatedField
include Enumerable
##
# call-seq:
# RepeatedField.new(type, type_class = nil, initial_values = [])
#
# Creates a new repeated field. The provided type must be a Ruby symbol, and
# an take on the same values as those accepted by FieldDescriptor#type=. If
# the type is :message or :enum, type_class must be non-nil, and must be the
# Ruby class or module returned by Descriptor#msgclass or
# EnumDescriptor#enummodule, respectively. An initial list of elements may also
# be provided.
def self.new(type, type_class = nil, initial_values = [])
instance = allocate
# TODO This argument mangling doesn't agree with the type signature in the comments
# but is required to make unit tests pass;
if type_class.is_a?(Enumerable) and initial_values.empty? and ![:enum, :message].include?(type)
initial_values = type_class
type_class = nil
end
instance.send(:initialize, type, type_class: type_class, initial_values: initial_values)
instance
end
##
# call-seq:
# RepeatedField.each(&block)
#
# Invokes the block once for each element of the repeated field. RepeatedField
# also includes Enumerable; combined with this method, the repeated field thus
# acts like an ordinary Ruby sequence.
def each &block
each_msg_val do |element|
yield(convert_upb_to_ruby(element, type, descriptor, arena))
end
self
end
def [](*args)
count = length
if args.size < 1
raise ArgumentError.new "Index or range is a required argument."
end
if args[0].is_a? Range
if args.size > 1
raise ArgumentError.new "Expected 1 when passing Range argument, but got #{args.size}"
end
range = args[0]
# Handle begin-less and/or endless ranges, when supported.
index_of_first = range.respond_to?(:begin) ? range.begin : range.last
index_of_first = 0 if index_of_first.nil?
end_of_range = range.respond_to?(:end) ? range.end : range.last
index_of_last = end_of_range.nil? ? -1 : end_of_range
if index_of_last < 0
index_of_last += count
end
unless range.exclude_end? and !end_of_range.nil?
index_of_last += 1
end
index_of_first += count if index_of_first < 0
length = index_of_last - index_of_first
return [] if length.zero?
elsif args[0].is_a? Integer
index_of_first = args[0]
index_of_first += count if index_of_first < 0
if args.size > 2
raise ArgumentError.new "Expected 1 or 2 arguments, but got #{args.size}"
end
if args.size == 1 # No length specified, return one element
if array.null? or index_of_first < 0 or index_of_first >= count
return nil
else
return convert_upb_to_ruby(Google::Protobuf::FFI.get_msgval_at(array, index_of_first), type, descriptor, arena)
end
else
length = [args[1],count].min
end
else
raise NotImplementedError
end
if array.null? or index_of_first < 0 or index_of_first > count
nil
else
if index_of_first + length > count
length = count - index_of_first
end
if length < 0
nil
else
subarray(index_of_first, length)
end
end
end
alias at []
def []=(index, value)
raise FrozenError if frozen?
count = length
index += count if index < 0
return nil if index < 0
if index >= count
resize(index+1)
empty_message_value = Google::Protobuf::FFI::MessageValue.new # Implicitly clear
count.upto(index-1) do |i|
Google::Protobuf::FFI.array_set(array, i, empty_message_value)
end
end
Google::Protobuf::FFI.array_set(array, index, convert_ruby_to_upb(value, arena, type, descriptor))
nil
end
def push(*elements)
raise FrozenError if frozen?
internal_push(*elements)
end
def <<(element)
raise FrozenError if frozen?
push element
end
def replace(replacements)
raise FrozenError if frozen?
clear
push(*replacements)
end
def clear
raise FrozenError if frozen?
resize 0
self
end
def length
array.null? ? 0 : Google::Protobuf::FFI.array_size(array)
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
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
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
instance = self.class.allocate
instance.send(:initialize, type, descriptor: descriptor, arena: arena)
each_msg_val do |element|
instance.send(:append_msg_val, element)
end
instance
end
alias clone dup
def ==(other)
return true if other.object_id == object_id
if other.is_a? RepeatedField
return false unless other.length == length
each_msg_val_with_index do |msg_val, i|
other_msg_val = Google::Protobuf::FFI.get_msgval_at(other.send(:array), i)
unless Google::Protobuf::FFI.message_value_equal(msg_val, other_msg_val, type, descriptor)
return false
end
end
return true
elsif other.is_a? Enumerable
return to_ary == other.to_a
end
false
end
##
# call-seq:
# RepeatedField.to_ary => array
#
# Used when converted implicitly into array, e.g. compared to an Array.
# Also called as a fallback of Object#to_a
def to_ary
return_value = []
each do |element|
return_value << element
end
return_value
end
def hash
return_value = 0
each_msg_val do |msg_val|
return_value = Google::Protobuf::FFI.message_value_hash(msg_val, type, descriptor, return_value)
end
return_value
end
def +(other)
if other.is_a? RepeatedField
if type != other.instance_variable_get(:@type) or descriptor != other.instance_variable_get(:@descriptor)
raise ArgumentError.new "Attempt to append RepeatedField with different element type."
end
fuse_arena(other.send(:arena))
super_set = dup
other.send(:each_msg_val) do |msg_val|
super_set.send(:append_msg_val, msg_val)
end
super_set
elsif other.is_a? Enumerable
super_set = dup
super_set.push(*other.to_a)
else
raise ArgumentError.new "Unknown type appending to RepeatedField"
end
end
def concat(other)
raise ArgumentError.new "Expected Enumerable, but got #{other.class}" unless other.is_a? Enumerable
push(*other.to_a)
end
private
include Google::Protobuf::Internal::Convert
attr :name, :arena, :array, :type, :descriptor
def internal_push(*elements)
elements.each do |element|
append_msg_val convert_ruby_to_upb(element, arena, type, descriptor)
end
self
end
def pop_one
raise FrozenError if frozen?
count = length
return nil if length.zero?
last_element = Google::Protobuf::FFI.get_msgval_at(array, count-1)
return_value = convert_upb_to_ruby(last_element, type, descriptor, arena)
resize(count-1)
return_value
end
def subarray(start, length)
return_result = []
(start..(start + length - 1)).each do |i|
element = Google::Protobuf::FFI.get_msgval_at(array, i)
return_result << convert_upb_to_ruby(element, type, descriptor, arena)
end
return_result
end
def each_msg_val_with_index &block
n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array)
0.upto(n-1) do |i|
yield Google::Protobuf::FFI.get_msgval_at(array, i), i
end
end
def each_msg_val &block
each_msg_val_with_index do |msg_val, _|
yield msg_val
end
end
# @param msg_val [Google::Protobuf::FFI::MessageValue] Value to append
def append_msg_val(msg_val)
unless Google::Protobuf::FFI.append_array(array, msg_val, arena)
raise NoMemoryError.new "Could not allocate room for #{msg_val} in Arena"
end
end
# @param new_size [Integer] New size of the array
def resize(new_size)
unless Google::Protobuf::FFI.array_resize(array, new_size, arena)
raise NoMemoryError.new "Array resize to #{new_size} failed!"
end
end
def initialize(type, type_class: nil, initial_values: nil, name: nil, arena: nil, array: nil, descriptor: nil)
@name = name || 'RepeatedField'
raise ArgumentError.new "Expected argument type to be a Symbol" unless type.is_a? Symbol
field_number = Google::Protobuf::FFI::FieldType[type]
raise ArgumentError.new "Unsupported type '#{type}'" if field_number.nil?
if !descriptor.nil?
@descriptor = descriptor
elsif [:message, :enum].include? type
raise ArgumentError.new "Expected at least 2 arguments for message/enum." if type_class.nil?
descriptor = type_class.respond_to?(:descriptor) ? type_class.descriptor : nil
raise ArgumentError.new "Type class #{type_class} has no descriptor. Please pass a class or enum as returned by the DescriptorPool." if descriptor.nil?
@descriptor = descriptor
else
@descriptor = nil
end
@type = type
@arena = arena || Google::Protobuf::FFI.create_arena
@array = array || Google::Protobuf::FFI.create_array(@arena, @type)
unless initial_values.nil?
unless initial_values.is_a? Enumerable
raise ArgumentError.new "Expected array as initializer value for repeated field '#{name}' (given #{initial_values.class})."
end
internal_push(*initial_values)
end
# Should always be the last expression of the initializer to avoid
# leaking references to this object before construction is complete.
OBJECT_CACHE.try_add(@array.address, self)
end
##
# 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 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
options[:descriptor] = field.subtype
end
instance.send(:initialize, field.type, **options)
instance
end
def fuse_arena(other_arena)
@arena.fuse(other_arena)
end
extend Google::Protobuf::Internal::Convert
def self.deep_copy(repeated_field)
instance = allocate
instance.send(:initialize, repeated_field.send(:type), descriptor: repeated_field.send(:descriptor))
instance.send(:resize, repeated_field.length)
new_array = instance.send(:array)
repeated_field.send(:each_msg_val_with_index) do |element, i|
Google::Protobuf::FFI.array_set(new_array, i, message_value_deep_copy(element, repeated_field.send(:type), repeated_field.send(:descriptor), instance.send(:arena)))
end
instance
end
end
end
end
require 'google/protobuf/repeated_field'
@@ -0,0 +1,117 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2024 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
class ServiceDescriptor
attr :service_def, :descriptor_pool
include Enumerable
include Google::Protobuf::Internal::Convert
# FFI Interface methods and setup
extend ::FFI::DataConverter
native_type ::FFI::Type::POINTER
class << self
prepend Google::Protobuf::Internal::TypeSafety
include Google::Protobuf::Internal::PointerHelper
# @param value [ServiceDescriptor] ServiceDescriptor to convert to an FFI native type
# @param _ [Object] Unused
def to_native(value, _)
service_def_ptr = value.nil? ? nil : value.instance_variable_get(:@service_def)
return ::FFI::Pointer::NULL if service_def_ptr.nil?
raise "Underlying service_def was null!" if service_def_ptr.null?
service_def_ptr
end
##
# @param service_def [::FFI::Pointer] ServiceDef pointer to be wrapped
# @param _ [Object] Unused
def from_native(service_def, _ = nil)
return nil if service_def.nil? or service_def.null?
file_def = Google::Protobuf::FFI.file_def_by_raw_service_def(service_def)
descriptor_from_file_def(file_def, service_def)
end
end
def self.new(*arguments, &block)
raise "Descriptor objects may not be created from Ruby."
end
def to_s
inspect
end
def inspect
"#{self.class.name}: #{name}"
end
def name
@name ||= Google::Protobuf::FFI.get_service_full_name(self)
end
def file_descriptor
@descriptor_pool.send(:get_file_descriptor, Google::Protobuf::FFI.file_def_by_raw_service_def(@service_def))
end
def each &block
n = Google::Protobuf::FFI.method_count(self)
0.upto(n-1) do |i|
yield(Google::Protobuf::FFI.get_method_by_index(self, i))
end
nil
end
def options
@options ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.service_options(self, size_ptr, temporary_arena)
Google::Protobuf::ServiceOptions.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze).freeze
end
end
def to_proto
@to_proto ||= begin
size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
temporary_arena = Google::Protobuf::FFI.create_arena
buffer = Google::Protobuf::FFI.service_to_proto(self, size_ptr, temporary_arena)
Google::Protobuf::ServiceDescriptorProto.decode(buffer.read_string_length(size_ptr.read(:size_t)).force_encoding("ASCII-8BIT").freeze)
end
end
private
def initialize(service_def, descriptor_pool)
@service_def = service_def
@descriptor_pool = descriptor_pool
end
def self.private_constructor(service_def, descriptor_pool)
instance = allocate
instance.send(:initialize, service_def, descriptor_pool)
instance
end
def c_type
@c_type ||= Google::Protobuf::FFI.get_c_type(self)
end
end
class FFI
# ServiceDef
attach_function :file_def_by_raw_service_def, :upb_ServiceDef_File, [:pointer], :FileDef
attach_function :get_service_full_name, :upb_ServiceDef_FullName, [ServiceDescriptor], :string
attach_function :method_count, :upb_ServiceDef_MethodCount, [ServiceDescriptor], :int
attach_function :get_method_by_index, :upb_ServiceDef_Method, [ServiceDescriptor, :int], MethodDescriptor
attach_function :service_options, :ServiceDescriptor_serialized_options, [ServiceDescriptor, :pointer, Internal::Arena], :pointer
attach_function :service_to_proto, :ServiceDescriptor_serialized_to_proto, [ServiceDescriptor, :pointer, Internal::Arena], :pointer
end
end
end
@@ -0,0 +1,17 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/field_mask.proto
require 'google/protobuf'
descriptor_data = "\n google/protobuf/field_mask.proto\x12\x0fgoogle.protobuf\"\x1a\n\tFieldMask\x12\r\n\x05paths\x18\x01 \x03(\tB\x85\x01\n\x13\x63om.google.protobufB\x0e\x46ieldMaskProtoP\x01Z2google.golang.org/protobuf/types/known/fieldmaskpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
FieldMask = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.FieldMask").msgclass
end
end
@@ -0,0 +1,99 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2023 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
module Internal
# A pointer -> Ruby Object cache that keeps references to Ruby wrapper
# objects. This allows us to look up any Ruby wrapper object by the address
# of the object it is wrapping. That way we can avoid ever creating two
# different wrapper objects for the same C object, which saves memory and
# preserves object identity.
#
# We use WeakMap for the cache. If sizeof(long) > sizeof(VALUE), we also
# need a secondary Hash to store WeakMap keys, because our pointer keys may
# need to be stored as Bignum instead of Fixnum. Since WeakMap is weak for
# both keys and values, a Bignum key will cause the WeakMap entry to be
# collected immediately unless there is another reference to the Bignum.
# This happens on 64-bit Windows, on which pointers are 64 bits but longs
# are 32 bits. In this case, we enable the secondary Hash to hold the keys
# and prevent them from being collected.
class ObjectCache
def initialize
@map = ObjectSpace::WeakMap.new
@mutex = Mutex.new
end
def get(key)
@map[key]
end
def try_add(key, value)
@map[key] || @mutex.synchronize do
@map[key] ||= value
end
end
end
class LegacyObjectCache
def initialize
@secondary_map = {}
@map = ObjectSpace::WeakMap.new
@mutex = Mutex.new
end
def get(key)
value = if secondary_key = @secondary_map[key]
@map[secondary_key]
else
@mutex.synchronize do
@map[(@secondary_map[key] ||= Object.new)]
end
end
# GC if we could remove at least 2000 entries or 20% of the table size
# (whichever is greater). Since the cost of the GC pass is O(N), we
# want to make sure that we condition this on overall table size, to
# avoid O(N^2) CPU costs.
cutoff = (@secondary_map.size * 0.2).ceil
cutoff = 2_000 if cutoff < 2_000
if (@secondary_map.size - @map.size) > cutoff
purge
end
value
end
def try_add(key, value)
if secondary_key = @secondary_map[key]
if old_value = @map[secondary_key]
return old_value
end
end
@mutex.synchronize do
secondary_key ||= (@secondary_map[key] ||= Object.new)
@map[secondary_key] ||= value
end
end
private
def purge
@mutex.synchronize do
@secondary_map.each do |key, secondary_key|
unless @map.key?(secondary_key)
@secondary_map.delete(key)
end
end
end
nil
end
end
end
end
end
@@ -0,0 +1,19 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/json_enumvalue_options.proto
require 'google/protobuf'
require 'google/protobuf/descriptor_pb'
descriptor_data = "\n,google/protobuf/json_enumvalue_options.proto\x12\x0cpb.enumvalue\x1a google/protobuf/descriptor.proto\"(\n\x14JsonEnumValueOptions\x12\x0e\n\x06string\x18\x01 \x01(\tX\x01:\\\n\x04json\x12!.google.protobuf.EnumValueOptions\x18\xe6\x07 \x01(\x0b\x32\".pb.enumvalue.JsonEnumValueOptionsB\x06\xb2\x01\x03\x08\xea\x07\x42\x1a\n\x18\x63om.google.protobuf.utilb\x08\x65\x64itionsp\xe9\x07"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Pb
module Enumvalue
JsonEnumValueOptions = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pb.enumvalue.JsonEnumValueOptions").msgclass
end
end
@@ -0,0 +1,16 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/json_options.proto
require 'google/protobuf'
require 'google/protobuf/json_enumvalue_options_pb'
descriptor_data = "\n\"google/protobuf/json_options.proto\x12\x02pb\x1a,google/protobuf/json_enumvalue_options.protoB\x1a\n\x18\x63om.google.protobuf.utilP\x00\x62\x08\x65\x64itionsp\xe9\x07"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Pb
end
@@ -0,0 +1,39 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
module Google
module Protobuf
module MessageExts
#this is only called in jruby; mri loades the ClassMethods differently
def self.included(klass)
klass.extend(ClassMethods)
end
module ClassMethods
end
def to_json(options = {})
self.class.encode_json(self, options)
end
def to_proto(options = {})
self.class.encode(self, options)
end
def to_hash
self.to_h
end
end
class AbstractMessage
include MessageExts
extend MessageExts::ClassMethods
end
private_constant :AbstractMessage
end
end
@@ -0,0 +1,25 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/compiler/plugin.proto
require 'google/protobuf'
require 'google/protobuf/descriptor_pb'
descriptor_data = "\n%google/protobuf/compiler/plugin.proto\x12\x18google.protobuf.compiler\x1a google/protobuf/descriptor.proto\"F\n\x07Version\x12\r\n\x05major\x18\x01 \x01(\x05\x12\r\n\x05minor\x18\x02 \x01(\x05\x12\r\n\x05patch\x18\x03 \x01(\x05\x12\x0e\n\x06suffix\x18\x04 \x01(\t\"\x81\x02\n\x14\x43odeGeneratorRequest\x12\x18\n\x10\x66ile_to_generate\x18\x01 \x03(\t\x12\x11\n\tparameter\x18\x02 \x01(\t\x12\x38\n\nproto_file\x18\x0f \x03(\x0b\x32$.google.protobuf.FileDescriptorProto\x12\x45\n\x17source_file_descriptors\x18\x11 \x03(\x0b\x32$.google.protobuf.FileDescriptorProto\x12;\n\x10\x63ompiler_version\x18\x03 \x01(\x0b\x32!.google.protobuf.compiler.Version\"\x92\x03\n\x15\x43odeGeneratorResponse\x12\r\n\x05\x65rror\x18\x01 \x01(\t\x12\x1a\n\x12supported_features\x18\x02 \x01(\x04\x12\x17\n\x0fminimum_edition\x18\x03 \x01(\x05\x12\x17\n\x0fmaximum_edition\x18\x04 \x01(\x05\x12\x42\n\x04\x66ile\x18\x0f \x03(\x0b\x32\x34.google.protobuf.compiler.CodeGeneratorResponse.File\x1a\x7f\n\x04\x46ile\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x17\n\x0finsertion_point\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x0f \x01(\t\x12?\n\x13generated_code_info\x18\x10 \x01(\x0b\x32\".google.protobuf.GeneratedCodeInfo\"W\n\x07\x46\x65\x61ture\x12\x10\n\x0c\x46\x45\x41TURE_NONE\x10\x00\x12\x1b\n\x17\x46\x45\x41TURE_PROTO3_OPTIONAL\x10\x01\x12\x1d\n\x19\x46\x45\x41TURE_SUPPORTS_EDITIONS\x10\x02\x42r\n\x1c\x63om.google.protobuf.compilerB\x0cPluginProtosZ)google.golang.org/protobuf/types/pluginpb\xaa\x02\x18Google.Protobuf.Compiler"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
module Compiler
Version = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.compiler.Version").msgclass
CodeGeneratorRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.compiler.CodeGeneratorRequest").msgclass
CodeGeneratorResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.compiler.CodeGeneratorResponse").msgclass
CodeGeneratorResponse::File = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.compiler.CodeGeneratorResponse.File").msgclass
CodeGeneratorResponse::Feature = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.compiler.CodeGeneratorResponse.Feature").enummodule
end
end
end
@@ -0,0 +1,177 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
require 'forwardable'
#
# This class makes RepeatedField act (almost-) like a Ruby Array.
# It has convenience methods that extend the core C or Java based
# methods.
#
# This is a best-effort to mirror Array behavior. Two comments:
# 1) patches always welcome :)
# 2) if performance is an issue, feel free to rewrite the method
# in jruby and C. The source code has plenty of examples
#
# KNOWN ISSUES
# - #[]= doesn't allow less used approaches such as `arr[1, 2] = 'fizz'`
# - #concat should return the orig array
# - #push should accept multiple arguments and push them all at the same time
#
module Google
module Protobuf
class RepeatedField
extend Forwardable
# methods defined in C or Java:
# +
# [], at
# []=
# concat
# clear
# dup, clone
# each
# push, <<
# replace
# length, size
# ==
# to_ary, to_a
# also all enumerable
#
# NOTE: using delegators rather than method_missing to make the
# relationship explicit instead of implicit
def_delegators :to_ary,
:&, :*, :-, :'<=>',
:assoc, :bsearch, :bsearch_index, :combination, :compact, :count,
:cycle, :difference, :dig, :drop, :drop_while, :eql?, :fetch, :find_index, :flatten,
:include?, :index, :inspect, :intersection, :join,
:pack, :permutation, :product, :pretty_print, :pretty_print_cycle,
:rassoc, :repeated_combination, :repeated_permutation, :reverse,
:rindex, :rotate, :sample, :shuffle, :shelljoin,
:to_s, :transpose, :union, :uniq, :|
def first(n=nil)
if n.nil?
return self[0]
elsif n < 0
raise ArgumentError, "negative array size"
else
return self[0...n]
end
end
def last(n=nil)
if n.nil?
return self[-1]
elsif n < 0
raise ArgumentError, "negative array size"
else
start = [self.size-n, 0].max
return self[start...self.size]
end
end
def pop(n=nil)
if n
results = []
n.times{ results << pop_one }
return results
else
return pop_one
end
end
def empty?
self.size == 0
end
# array aliases into enumerable
alias_method :slice, :[]
alias_method :values_at, :select
alias_method :map, :collect
class << self
def define_array_wrapper_method(method_name)
define_method(method_name) do |*args, &block|
arr = self.to_a
result = arr.send(method_name, *args)
self.replace(arr)
return result if result
return block ? block.call : result
end
end
private :define_array_wrapper_method
def define_array_wrapper_with_result_method(method_name)
define_method(method_name) do |*args, &block|
# result can be an Enumerator, Array, or nil
# Enumerator can sometimes be returned if a block is an optional argument and it is not passed in
# nil usually specifies that no change was made
result = self.to_a.send(method_name, *args, &block)
if result
new_arr = result.to_a
self.replace(new_arr)
if result.is_a?(Enumerator)
# generate a fresh enum; rewinding the exiting one, in Ruby 2.2, will
# reset the enum with the same length, but all the #next calls will
# return nil
result = new_arr.to_enum
# generate a wrapper enum so any changes which occur by a chained
# enum can be captured
ie = ProxyingEnumerator.new(self, result)
result = ie.to_enum
end
end
result
end
end
private :define_array_wrapper_with_result_method
end
%w(delete delete_at shift slice! unshift).each do |method_name|
define_array_wrapper_method(method_name)
end
%w(collect! compact! delete_if each_index fill flatten! insert reverse!
rotate! select! shuffle! sort! sort_by! uniq!).each do |method_name|
define_array_wrapper_with_result_method(method_name)
end
alias_method :keep_if, :select!
alias_method :map!, :collect!
alias_method :reject!, :delete_if
# propagates changes made by user of enumerator back to the original repeated field.
# This only applies in cases where the calling function which created the enumerator,
# such as #sort!, modifies itself rather than a new array, such as #sort
class ProxyingEnumerator < Struct.new(:repeated_field, :external_enumerator)
def each(*args, &block)
results = []
external_enumerator.each_with_index do |val, i|
result = yield(val)
results << result
#nil means no change occurred from yield; usually occurs when #to_a is called
if result
repeated_field[i] = result if result != val
end
end
results
end
end
end
end
end
@@ -0,0 +1,17 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/source_context.proto
require 'google/protobuf'
descriptor_data = "\n$google/protobuf/source_context.proto\x12\x0fgoogle.protobuf\"\"\n\rSourceContext\x12\x11\n\tfile_name\x18\x01 \x01(\tB\x8a\x01\n\x13\x63om.google.protobufB\x12SourceContextProtoP\x01Z6google.golang.org/protobuf/types/known/sourcecontextpb\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
SourceContext = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.SourceContext").msgclass
end
end
@@ -0,0 +1,20 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/struct.proto
require 'google/protobuf'
descriptor_data = "\n\x1cgoogle/protobuf/struct.proto\x12\x0fgoogle.protobuf\"\x84\x01\n\x06Struct\x12\x33\n\x06\x66ields\x18\x01 \x03(\x0b\x32#.google.protobuf.Struct.FieldsEntry\x1a\x45\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value:\x02\x38\x01\"\xea\x01\n\x05Value\x12\x30\n\nnull_value\x18\x01 \x01(\x0e\x32\x1a.google.protobuf.NullValueH\x00\x12\x16\n\x0cnumber_value\x18\x02 \x01(\x01H\x00\x12\x16\n\x0cstring_value\x18\x03 \x01(\tH\x00\x12\x14\n\nbool_value\x18\x04 \x01(\x08H\x00\x12/\n\x0cstruct_value\x18\x05 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x12\x30\n\nlist_value\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.ListValueH\x00\x42\x06\n\x04kind\"3\n\tListValue\x12&\n\x06values\x18\x01 \x03(\x0b\x32\x16.google.protobuf.Value*\x1b\n\tNullValue\x12\x0e\n\nNULL_VALUE\x10\x00\x42\x7f\n\x13\x63om.google.protobufB\x0bStructProtoP\x01Z/google.golang.org/protobuf/types/known/structpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
Struct = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Struct").msgclass
Value = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Value").msgclass
ListValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.ListValue").msgclass
NullValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.NullValue").enummodule
end
end
@@ -0,0 +1,17 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/timestamp.proto
require 'google/protobuf'
descriptor_data = "\n\x1fgoogle/protobuf/timestamp.proto\x12\x0fgoogle.protobuf\"+\n\tTimestamp\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\x42\x85\x01\n\x13\x63om.google.protobufB\x0eTimestampProtoP\x01Z2google.golang.org/protobuf/types/known/timestamppb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
Timestamp = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Timestamp").msgclass
end
end
@@ -0,0 +1,27 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/type.proto
require 'google/protobuf'
require 'google/protobuf/any_pb'
require 'google/protobuf/source_context_pb'
descriptor_data = "\n\x1agoogle/protobuf/type.proto\x12\x0fgoogle.protobuf\x1a\x19google/protobuf/any.proto\x1a$google/protobuf/source_context.proto\"\xe8\x01\n\x04Type\x12\x0c\n\x04name\x18\x01 \x01(\t\x12&\n\x06\x66ields\x18\x02 \x03(\x0b\x32\x16.google.protobuf.Field\x12\x0e\n\x06oneofs\x18\x03 \x03(\t\x12(\n\x07options\x18\x04 \x03(\x0b\x32\x17.google.protobuf.Option\x12\x36\n\x0esource_context\x18\x05 \x01(\x0b\x32\x1e.google.protobuf.SourceContext\x12\'\n\x06syntax\x18\x06 \x01(\x0e\x32\x17.google.protobuf.Syntax\x12\x0f\n\x07\x65\x64ition\x18\x07 \x01(\t\"\xd5\x05\n\x05\x46ield\x12)\n\x04kind\x18\x01 \x01(\x0e\x32\x1b.google.protobuf.Field.Kind\x12\x37\n\x0b\x63\x61rdinality\x18\x02 \x01(\x0e\x32\".google.protobuf.Field.Cardinality\x12\x0e\n\x06number\x18\x03 \x01(\x05\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x10\n\x08type_url\x18\x06 \x01(\t\x12\x13\n\x0boneof_index\x18\x07 \x01(\x05\x12\x0e\n\x06packed\x18\x08 \x01(\x08\x12(\n\x07options\x18\t \x03(\x0b\x32\x17.google.protobuf.Option\x12\x11\n\tjson_name\x18\n \x01(\t\x12\x15\n\rdefault_value\x18\x0b \x01(\t\"\xc8\x02\n\x04Kind\x12\x10\n\x0cTYPE_UNKNOWN\x10\x00\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"t\n\x0b\x43\x61rdinality\x12\x17\n\x13\x43\x41RDINALITY_UNKNOWN\x10\x00\x12\x18\n\x14\x43\x41RDINALITY_OPTIONAL\x10\x01\x12\x18\n\x14\x43\x41RDINALITY_REQUIRED\x10\x02\x12\x18\n\x14\x43\x41RDINALITY_REPEATED\x10\x03\"\xdf\x01\n\x04\x45num\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\tenumvalue\x18\x02 \x03(\x0b\x32\x1a.google.protobuf.EnumValue\x12(\n\x07options\x18\x03 \x03(\x0b\x32\x17.google.protobuf.Option\x12\x36\n\x0esource_context\x18\x04 \x01(\x0b\x32\x1e.google.protobuf.SourceContext\x12\'\n\x06syntax\x18\x05 \x01(\x0e\x32\x17.google.protobuf.Syntax\x12\x0f\n\x07\x65\x64ition\x18\x06 \x01(\t\"S\n\tEnumValue\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06number\x18\x02 \x01(\x05\x12(\n\x07options\x18\x03 \x03(\x0b\x32\x17.google.protobuf.Option\";\n\x06Option\x12\x0c\n\x04name\x18\x01 \x01(\t\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any*C\n\x06Syntax\x12\x11\n\rSYNTAX_PROTO2\x10\x00\x12\x11\n\rSYNTAX_PROTO3\x10\x01\x12\x13\n\x0fSYNTAX_EDITIONS\x10\x02\x42{\n\x13\x63om.google.protobufB\tTypeProtoP\x01Z-google.golang.org/protobuf/types/known/typepb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
Type = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Type").msgclass
Field = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Field").msgclass
Field::Kind = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Field.Kind").enummodule
Field::Cardinality = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Field.Cardinality").enummodule
Enum = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Enum").msgclass
EnumValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.EnumValue").msgclass
Option = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Option").msgclass
Syntax = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Syntax").enummodule
end
end
@@ -0,0 +1,211 @@
#!/usr/bin/ruby
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
require 'google/protobuf/any_pb'
require 'google/protobuf/duration_pb'
require 'google/protobuf/field_mask_pb'
require 'google/protobuf/struct_pb'
require 'google/protobuf/timestamp_pb'
module Google
module Protobuf
Any.class_eval do
def self.pack(msg, type_url_prefix='type.googleapis.com/')
any = self.new
any.pack(msg, type_url_prefix)
any
end
def pack(msg, type_url_prefix='type.googleapis.com/')
if type_url_prefix.empty? or type_url_prefix[-1] != '/' then
self.type_url = "#{type_url_prefix}/#{msg.class.descriptor.name}"
else
self.type_url = "#{type_url_prefix}#{msg.class.descriptor.name}"
end
self.value = msg.to_proto
end
def unpack(klass)
if self.is(klass) then
klass.decode(self.value)
else
nil
end
end
def type_name
return self.type_url.split("/")[-1]
end
def is(klass)
return self.type_name == klass.descriptor.name
end
end
Timestamp.class_eval do
def to_time
Time.at(seconds, nanos, :nanosecond)
end
def self.from_time(time)
new.from_time(time)
end
def from_time(time)
self.seconds = time.to_i
self.nanos = time.nsec
self
end
def to_i
self.seconds
end
def to_f
self.seconds + (self.nanos.quo(1_000_000_000))
end
end
Duration.class_eval do
def to_f
self.seconds + (self.nanos.to_f / 1_000_000_000)
end
end
class UnexpectedStructType < Google::Protobuf::Error; end
Value.class_eval do
def to_ruby(recursive = false)
case self.kind
when :struct_value
if recursive
self.struct_value.to_h
else
self.struct_value
end
when :list_value
if recursive
self.list_value.to_a
else
self.list_value
end
when :null_value
nil
when :number_value
self.number_value
when :string_value
self.string_value
when :bool_value
self.bool_value
else
raise UnexpectedStructType
end
end
def self.from_ruby(value)
self.new.from_ruby(value)
end
def from_ruby(value)
case value
when NilClass
self.null_value = :NULL_VALUE
when Numeric
self.number_value = value
when String
self.string_value = value
when TrueClass
self.bool_value = true
when FalseClass
self.bool_value = false
when Struct
self.struct_value = value
when Hash
self.struct_value = Struct.from_hash(value)
when ListValue
self.list_value = value
when Array
self.list_value = ListValue.from_a(value)
else
raise UnexpectedStructType
end
self
end
end
Struct.class_eval do
def [](key)
self.fields[key].to_ruby
rescue NoMethodError
nil
end
def []=(key, value)
unless key.is_a?(String)
raise UnexpectedStructType, "Struct keys must be strings."
end
self.fields[key] ||= Google::Protobuf::Value.new
self.fields[key].from_ruby(value)
end
def to_h
ret = {}
self.fields.each { |key, val| ret[key] = val.to_ruby(true) }
ret
end
def self.from_hash(hash)
ret = Struct.new
hash.each { |key, val| ret[key] = val }
ret
end
def has_key?(key)
self.fields.has_key?(key)
end
end
ListValue.class_eval do
include Enumerable
def length
self.values.length
end
def [](index)
self.values[index].to_ruby
end
def []=(index, value)
self.values[index].from_ruby(value)
end
def <<(value)
wrapper = Google::Protobuf::Value.new
wrapper.from_ruby(value)
self.values << wrapper
end
def each
self.values.each { |x| yield(x.to_ruby) }
end
def to_a
self.values.map { |x| x.to_ruby(true) }
end
def self.from_a(arr)
ret = ListValue.new
arr.each { |val| ret << val }
ret
end
end
end
end
@@ -0,0 +1,25 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google/protobuf/wrappers.proto
require 'google/protobuf'
descriptor_data = "\n\x1egoogle/protobuf/wrappers.proto\x12\x0fgoogle.protobuf\"\x1c\n\x0b\x44oubleValue\x12\r\n\x05value\x18\x01 \x01(\x01\"\x1b\n\nFloatValue\x12\r\n\x05value\x18\x01 \x01(\x02\"\x1b\n\nInt64Value\x12\r\n\x05value\x18\x01 \x01(\x03\"\x1c\n\x0bUInt64Value\x12\r\n\x05value\x18\x01 \x01(\x04\"\x1b\n\nInt32Value\x12\r\n\x05value\x18\x01 \x01(\x05\"\x1c\n\x0bUInt32Value\x12\r\n\x05value\x18\x01 \x01(\r\"\x1a\n\tBoolValue\x12\r\n\x05value\x18\x01 \x01(\x08\"\x1c\n\x0bStringValue\x12\r\n\x05value\x18\x01 \x01(\t\"\x1b\n\nBytesValue\x12\r\n\x05value\x18\x01 \x01(\x0c\x42\x83\x01\n\x13\x63om.google.protobufB\rWrappersProtoP\x01Z1google.golang.org/protobuf/types/known/wrapperspb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module Google
module Protobuf
DoubleValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.DoubleValue").msgclass
FloatValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.FloatValue").msgclass
Int64Value = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Int64Value").msgclass
UInt64Value = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.UInt64Value").msgclass
Int32Value = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Int32Value").msgclass
UInt32Value = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.UInt32Value").msgclass
BoolValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.BoolValue").msgclass
StringValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.StringValue").msgclass
BytesValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.BytesValue").msgclass
end
end
@@ -0,0 +1,52 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2023 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
require 'ffi-compiler/loader'
require 'google/protobuf/ffi/ffi'
require 'google/protobuf/ffi/internal/type_safety'
require 'google/protobuf/ffi/internal/pointer_helper'
require 'google/protobuf/ffi/internal/arena'
require 'google/protobuf/ffi/internal/convert'
require 'google/protobuf/ffi/descriptor'
require 'google/protobuf/ffi/enum_descriptor'
require 'google/protobuf/ffi/field_descriptor'
require 'google/protobuf/ffi/oneof_descriptor'
require 'google/protobuf/ffi/method_descriptor'
require 'google/protobuf/ffi/service_descriptor'
require 'google/protobuf/ffi/file_descriptor'
require 'google/protobuf/ffi/descriptor_pool'
require 'google/protobuf/ffi/map'
require 'google/protobuf/ffi/object_cache'
require 'google/protobuf/ffi/repeated_field'
require 'google/protobuf/ffi/message'
module Google
module Protobuf
def self.deep_copy(object)
case object
when RepeatedField
RepeatedField.send(:deep_copy, object)
when Google::Protobuf::Map
Google::Protobuf::Map.deep_copy(object)
when Google::Protobuf::MessageExts
object.class.send(:deep_copy, object.instance_variable_get(:@msg))
else
raise NotImplementedError
end
end
def self.discard_unknown(message)
raise FrozenError if message.frozen?
raise ArgumentError.new "Expected message, got #{message.class} instead." if message.instance_variable_get(:@msg).nil?
pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool
unless Google::Protobuf::FFI.message_discard_unknown(message.instance_variable_get(:@msg), message.class.descriptor, pool_def, 128)
raise RuntimeError.new "Messages nested too deeply."
end
nil
end
end
end
@@ -0,0 +1,19 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2023 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
if RUBY_PLATFORM == "java"
require 'json'
require 'google/protobuf_java'
else
begin
require "google/#{RUBY_VERSION.sub(/\.\d+$/, '')}/protobuf_c"
rescue LoadError
require 'google/protobuf_c'
end
end
require 'google/protobuf/repeated_field'
@@ -0,0 +1,100 @@
# # @param task [FFI::Compiler::CompileTask] task to configure
def configure_common_compile_task(task)
if FileUtils.pwd.include? 'ext'
src_dir = '.'
third_party_path = 'third_party/utf8_range'
else
src_dir = 'ext/google/protobuf_c'
third_party_path = 'ext/google/protobuf_c/third_party/utf8_range'
end
task.add_include_path third_party_path
task.add_define 'NDEBUG'
task.cflags << "-std=gnu99 -O3"
[
:convert, :defs, :map, :message, :protobuf, :repeated_field
].each { |file| task.exclude << "/#{file}.c" }
task.ext_dir = src_dir
task.source_dirs = [src_dir]
if RbConfig::CONFIG['target_os'] =~ /darwin|linux/
task.cflags << "-Wall -Wsign-compare -Wno-declaration-after-statement"
end
end
# FFI::CompilerTask's constructor walks the filesystem at task definition time
# to create subtasks for each source file, so files from third_party must be
# copied into place before the task is defined for it to work correctly.
# TODO Is there a sane way to check for generated protos under lib too?
def with_generated_files
expected_path = FileUtils.pwd.include?('ext') ? 'third_party/utf8_range' : 'ext/google/protobuf_c/third_party/utf8_range'
if File.directory?(expected_path)
yield
else
task :default do
# It is possible, especially in cases like the first invocation of
# `rake test` following `rake clean` or a fresh checkout that the
# `copy_third_party` task has been executed since initial task definition.
# If so, run the task definition block now and invoke it explicitly.
if File.directory?(expected_path)
yield
Rake::Task[:default].invoke
else
raise "Missing directory #{File.absolute_path(expected_path)}." +
" Did you forget to run `rake copy_third_party` before building" +
" native extensions?"
end
end
end
end
begin
require "ffi-compiler/compile_task"
desc "Compile Protobuf library for FFI"
namespace "ffi-protobuf" do
with_generated_files do
# Compile Ruby UPB separately in order to limit use of -DUPB_BUILD_API to one
# compilation unit.
desc "Compile UPB library for FFI"
namespace "ffi-upb" do
with_generated_files do
FFI::Compiler::CompileTask.new('ruby-upb') do |c|
configure_common_compile_task c
c.add_define "UPB_BUILD_API"
c.exclude << "/glue.c"
c.exclude << "/shared_message.c"
c.exclude << "/shared_convert.c"
if RbConfig::CONFIG['target_os'] =~ /darwin|linux/
c.cflags << "-fvisibility=hidden"
end
end
end
end
FFI::Compiler::CompileTask.new 'protobuf_c_ffi' do |c|
configure_common_compile_task c
# Ruby UPB was already compiled with different flags.
c.exclude << "/utf8_range.c"
c.exclude << "/ruby-upb.c"
end
# Setup dependencies so that the .o files generated by building ffi-upb are
# available to link here.
# TODO Can this be simplified? Can the single shared library be used
# instead of the object files?
protobuf_c_task = Rake::Task[:default]
protobuf_c_shared_lib_task = Rake::Task[protobuf_c_task.prereqs.last]
ruby_upb_shared_lib_task = Rake::Task[:"ffi-upb:default"].prereqs.first
Rake::Task[ruby_upb_shared_lib_task].prereqs.each do |dependency|
protobuf_c_shared_lib_task.prereqs.prepend dependency
end
end
end
rescue LoadError
desc "Compile Protobuf library for FFI"
namespace "ffi-protobuf" do
task :default do
warn "Skipping build of FFI; `gem install ffi-compiler` to enable."
end
end
end