This commit is contained in:
+3
@@ -0,0 +1,3 @@
|
||||
import '../../../lib/google/tasks/ffi.rake'
|
||||
|
||||
task default: ['ffi-protobuf:default']
|
||||
+335
@@ -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));
|
||||
}
|
||||
}
|
||||
+50
@@ -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_
|
||||
+2084
File diff suppressed because it is too large
Load Diff
+82
@@ -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_
|
||||
+38
@@ -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)
|
||||
+135
@@ -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;
|
||||
}
|
||||
+768
@@ -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);
|
||||
}
|
||||
+48
@@ -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_
|
||||
+1468
File diff suppressed because it is too large
Load Diff
+82
@@ -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_
|
||||
+357
@@ -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);
|
||||
}
|
||||
}
|
||||
+100
@@ -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__
|
||||
+735
@@ -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);
|
||||
}
|
||||
+45
@@ -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_
|
||||
+18844
File diff suppressed because it is too large
Load Diff
+19260
File diff suppressed because it is too large
Load Diff
+69
@@ -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;
|
||||
}
|
||||
}
|
||||
+26
@@ -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_
|
||||
+37
@@ -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;
|
||||
}
|
||||
+21
@@ -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_
|
||||
Vendored
Executable
+22
@@ -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.
|
||||
Vendored
Executable
+207
@@ -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);
|
||||
}
|
||||
Vendored
Executable
+23
@@ -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_
|
||||
Vendored
Executable
+117
@@ -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);
|
||||
}
|
||||
Vendored
Executable
+272
@@ -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);
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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
|
||||
+17
@@ -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
|
||||
+22
@@ -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
|
||||
+72
File diff suppressed because one or more lines are too long
+17
@@ -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
|
||||
+17
@@ -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
|
||||
+175
@@ -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
|
||||
Executable
+83
@@ -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
|
||||
Executable
+183
@@ -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
|
||||
+213
@@ -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
|
||||
Executable
+346
@@ -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
|
||||
Executable
+85
@@ -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
|
||||
Executable
+60
@@ -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
|
||||
Executable
+292
@@ -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
|
||||
Executable
+36
@@ -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
|
||||
|
||||
Executable
+25
@@ -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
|
||||
|
||||
+433
@@ -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
|
||||
+785
@@ -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
|
||||
Executable
+124
@@ -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
|
||||
|
||||
+30
@@ -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
|
||||
Executable
+107
@@ -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
|
||||
Executable
+411
@@ -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'
|
||||
Executable
+117
@@ -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
|
||||
+17
@@ -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
|
||||
Executable
+99
@@ -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
|
||||
Executable
+19
@@ -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
|
||||
+16
@@ -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
|
||||
+39
@@ -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
|
||||
+25
@@ -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
|
||||
+177
@@ -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
|
||||
Executable
+17
@@ -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
|
||||
+20
@@ -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
|
||||
+17
@@ -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
|
||||
+27
@@ -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
|
||||
+211
@@ -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
|
||||
+25
@@ -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
|
||||
+52
@@ -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
|
||||
+19
@@ -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'
|
||||
+100
@@ -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
|
||||
Reference in New Issue
Block a user