|  | // Protocol Buffers - Google's data interchange format | 
|  | // Copyright 2014 Google Inc.  All rights reserved. | 
|  | // https://developers.google.com/protocol-buffers/ | 
|  | // | 
|  | // Redistribution and use in source and binary forms, with or without | 
|  | // modification, are permitted provided that the following conditions are | 
|  | // met: | 
|  | // | 
|  | //     * Redistributions of source code must retain the above copyright | 
|  | // notice, this list of conditions and the following disclaimer. | 
|  | //     * Redistributions in binary form must reproduce the above | 
|  | // copyright notice, this list of conditions and the following disclaimer | 
|  | // in the documentation and/or other materials provided with the | 
|  | // distribution. | 
|  | //     * Neither the name of Google Inc. nor the names of its | 
|  | // contributors may be used to endorse or promote products derived from | 
|  | // this software without specific prior written permission. | 
|  | // | 
|  | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
|  | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
|  | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
|  | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
|  | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
|  | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
|  | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
|  | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
|  | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | #include "protobuf.h" | 
|  |  | 
|  | #include <ruby/version.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_msgdef *entry = upb_fielddef_msgsubdef(field); | 
|  | return upb_msgdef_itof(entry, 1); | 
|  | } | 
|  |  | 
|  | const upb_fielddef* map_field_value(const upb_fielddef* field) { | 
|  | const upb_msgdef *entry = upb_fielddef_msgsubdef(field); | 
|  | return upb_msgdef_itof(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 char *name = upb_enumdef_iton(e, val); | 
|  | if (name) { | 
|  | StringBuilder_Printf(b, ":%s", name); | 
|  | } else { | 
|  | StringBuilder_Printf(b, "%" PRId32, val); | 
|  | } | 
|  | } | 
|  |  | 
|  | void StringBuilder_PrintMsgval(StringBuilder* b, upb_msgval val, | 
|  | TypeInfo info) { | 
|  | switch (info.type) { | 
|  | case UPB_TYPE_BOOL: | 
|  | StringBuilder_Printf(b, "%s", val.bool_val ? "true" : "false"); | 
|  | break; | 
|  | case UPB_TYPE_FLOAT: { | 
|  | VALUE str = rb_inspect(DBL2NUM(val.float_val)); | 
|  | StringBuilder_Printf(b, "%s", RSTRING_PTR(str)); | 
|  | break; | 
|  | } | 
|  | case UPB_TYPE_DOUBLE: { | 
|  | VALUE str = rb_inspect(DBL2NUM(val.double_val)); | 
|  | StringBuilder_Printf(b, "%s", RSTRING_PTR(str)); | 
|  | break; | 
|  | } | 
|  | case UPB_TYPE_INT32: | 
|  | StringBuilder_Printf(b, "%" PRId32, val.int32_val); | 
|  | break; | 
|  | case UPB_TYPE_UINT32: | 
|  | StringBuilder_Printf(b, "%" PRIu32, val.uint32_val); | 
|  | break; | 
|  | case UPB_TYPE_INT64: | 
|  | StringBuilder_Printf(b, "%" PRId64, val.int64_val); | 
|  | break; | 
|  | case UPB_TYPE_UINT64: | 
|  | StringBuilder_Printf(b, "%" PRIu64, val.uint64_val); | 
|  | break; | 
|  | case UPB_TYPE_STRING: | 
|  | StringBuilder_Printf(b, "\"%.*s\"", (int)val.str_val.size, val.str_val.data); | 
|  | break; | 
|  | case UPB_TYPE_BYTES: | 
|  | StringBuilder_Printf(b, "\"%.*s\"", (int)val.str_val.size, val.str_val.data); | 
|  | break; | 
|  | case UPB_TYPE_ENUM: | 
|  | StringBuilder_PrintEnum(b, val.int32_val, info.def.enumdef); | 
|  | break; | 
|  | case UPB_TYPE_MESSAGE: | 
|  | Message_PrintMessage(b, val.msg_val, info.def.msgdef); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------------- | 
|  | // Arena | 
|  | // ----------------------------------------------------------------------------- | 
|  |  | 
|  | typedef struct { | 
|  | upb_arena *arena; | 
|  | 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); | 
|  | } | 
|  |  | 
|  | static VALUE cArena; | 
|  |  | 
|  | const rb_data_type_t Arena_type = { | 
|  | "Google::Protobuf::Internal::Arena", | 
|  | { Arena_mark, Arena_free, NULL }, | 
|  | .flags = RUBY_TYPED_FREE_IMMEDIATELY, | 
|  | }; | 
|  |  | 
|  | static VALUE Arena_alloc(VALUE klass) { | 
|  | Arena *arena = ALLOC(Arena); | 
|  | arena->arena = upb_arena_new(); | 
|  | 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; | 
|  | } | 
|  |  | 
|  | VALUE Arena_new() { | 
|  | return Arena_alloc(cArena); | 
|  | } | 
|  |  | 
|  | void Arena_Pin(VALUE _arena, VALUE obj) { | 
|  | Arena *arena; | 
|  | TypedData_Get_Struct(_arena, Arena, &Arena_type, arena); | 
|  | if (arena->pinned_objs == Qnil) { | 
|  | arena->pinned_objs = rb_ary_new(); | 
|  | } | 
|  | rb_ary_push(arena->pinned_objs, obj); | 
|  | } | 
|  |  | 
|  | void Arena_register(VALUE module) { | 
|  | VALUE internal = rb_define_module_under(module, "Internal"); | 
|  | VALUE klass = rb_define_class_under(internal, "Arena", rb_cObject); | 
|  | rb_define_alloc_func(klass, Arena_alloc); | 
|  | rb_gc_register_address(&cArena); | 
|  | cArena = klass; | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------------- | 
|  | // Object Cache | 
|  | // ----------------------------------------------------------------------------- | 
|  |  | 
|  | // 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. For Ruby <2.7 we also need a secondary Hash | 
|  | // to store WeakMap keys because Ruby <2.7 WeakMap doesn't allow non-finalizable | 
|  | // keys. | 
|  |  | 
|  | #if RUBY_API_VERSION_CODE >= 20700 | 
|  | #define USE_SECONDARY_MAP 0 | 
|  | #else | 
|  | #define USE_SECONDARY_MAP 1 | 
|  | #endif | 
|  |  | 
|  | #if USE_SECONDARY_MAP | 
|  |  | 
|  | // Maps Numeric -> Object. The object is then used as a key into the WeakMap. | 
|  | // This is needed for Ruby <2.7 where a number cannot be a key to WeakMap. | 
|  | // The object is used only for its identity; it does not contain any data. | 
|  | VALUE secondary_map = Qnil; | 
|  |  | 
|  | static void SecondaryMap_Init() { | 
|  | rb_gc_register_address(&secondary_map); | 
|  | secondary_map = rb_hash_new(); | 
|  | } | 
|  |  | 
|  | static VALUE SecondaryMap_Get(VALUE key) { | 
|  | VALUE ret = rb_hash_lookup(secondary_map, key); | 
|  | if (ret == Qnil) { | 
|  | ret = rb_eval_string("Object.new"); | 
|  | rb_hash_aset(secondary_map, key, ret); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | static VALUE ObjectCache_GetKey(const void* key) { | 
|  | char buf[sizeof(key)]; | 
|  | memcpy(&buf, &key, sizeof(key)); | 
|  | intptr_t key_int = (intptr_t)key; | 
|  | PBRUBY_ASSERT((key_int & 3) == 0); | 
|  | VALUE ret = LL2NUM(key_int >> 2); | 
|  | #if USE_SECONDARY_MAP | 
|  | ret = SecondaryMap_Get(ret); | 
|  | #endif | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | // Public ObjectCache API. | 
|  |  | 
|  | VALUE weak_obj_cache = Qnil; | 
|  | ID item_get; | 
|  | ID item_set; | 
|  |  | 
|  | static void ObjectCache_Init() { | 
|  | rb_gc_register_address(&weak_obj_cache); | 
|  | VALUE klass = rb_eval_string("ObjectSpace::WeakMap"); | 
|  | weak_obj_cache = rb_class_new_instance(0, NULL, klass); | 
|  | item_get = rb_intern("[]"); | 
|  | item_set = rb_intern("[]="); | 
|  | #if USE_SECONDARY_MAP | 
|  | SecondaryMap_Init(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void ObjectCache_Add(const void* key, VALUE val) { | 
|  | PBRUBY_ASSERT(ObjectCache_Get(key) == Qnil); | 
|  | VALUE key_rb = ObjectCache_GetKey(key); | 
|  | rb_funcall(weak_obj_cache, item_set, 2, key_rb, val); | 
|  | PBRUBY_ASSERT(ObjectCache_Get(key) == val); | 
|  | } | 
|  |  | 
|  | // Returns the cached object for this key, if any. Otherwise returns Qnil. | 
|  | VALUE ObjectCache_Get(const void* key) { | 
|  | VALUE key_rb = ObjectCache_GetKey(key); | 
|  | return rb_funcall(weak_obj_cache, item_get, 1, key_rb); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * 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_msgdef *m; | 
|  | upb_msg *msg = Message_GetMutable(msg_rb, &m); | 
|  | if (!upb_msg_discardunknown(msg, m, 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_msgdef *m; | 
|  | const upb_msg *msg = Message_Get(obj, &m); | 
|  | upb_msg* 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() { | 
|  | ObjectCache_Init(); | 
|  |  | 
|  | VALUE google = rb_define_module("Google"); | 
|  | VALUE protobuf = rb_define_module_under(google, "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); | 
|  | } |