/* * Copyright (c) 2009-2021, Google LLC * All rights reserved. * * 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 LLC 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 Google LLC 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. */ /* * lupb_Message -- Message/Array/Map objects in Lua/C that wrap upb/msg.h */ #include "upb/msg.h" #include #include #include #include #include #include "lauxlib.h" #include "lua/upb.h" #include "upb/collections/map.h" #include "upb/json/decode.h" #include "upb/json/encode.h" #include "upb/port/def.inc" #include "upb/reflection/message.h" #include "upb/text/encode.h" /* * Message/Map/Array objects. These objects form a directed graph: a message * can contain submessages, arrays, and maps, which can then point to other * messages. This graph can technically be cyclic, though this is an error and * a cyclic graph cannot be serialized. So it's better to think of this as a * tree of objects. * * The actual data exists at the upb level (upb_Message, upb_Map, upb_Array), * independently of Lua. The upb objects contain all the canonical data and * edges between objects. Lua wrapper objects expose the upb objects to Lua, * but ultimately they are just wrappers. They pass through all reads and * writes to the underlying upb objects. * * Each upb object lives in a upb arena. We have a Lua object to wrap the upb * arena, but arenas are never exposed to the user. The Lua arena object just * serves to own the upb arena and free it at the proper time, once the Lua GC * has determined that there are no more references to anything that lives in * that arena. All wrapper objects strongly reference the arena to which they * belong. * * A global object cache stores a mapping of C pointer (upb_Message*, * upb_Array*, upb_Map*) to a corresponding Lua wrapper. These references are * weak so that the wrappers can be collected if they are no longer needed. A * new wrapper object can always be recreated later. * * +-----+ * lupb_Arena |cache|-weak-+ * | ^ +-----+ | * | | V * Lua level | +------------lupb_Message * ----------------|-----------------|------------------------------------------ * upb level | | * | +----V----------------------------------+ * +->upb_Arena | upb_Message ...(empty arena storage) | * +---------------------------------------+ * * If the user creates a reference between two objects that have different * arenas, we need to fuse the two arenas together, so that the blocks will * outlive both arenas. * * +-------------------------->(fused)<----------------+ * | | * V +-----+ V * lupb_Arena +-weak-|cache|-weak-+ lupb_Arena * | ^ | +-----+ | ^ | * | | V V | | * Lua level | +------------lupb_Message lupb_Message--+ | * ----------------|-----------------|----------------------|-----------|------ * upb level | | | | * | +----V--------+ +----V--------+ V * +->upb_Arena | upb_Message | | upb_Message | upb_Arena * +------|------+ +--^----------+ * +------------------+ * Key invariants: * 1. every wrapper references the arena that contains it. * 2. every fused arena includes all arenas that own upb objects reachable * from that arena. In other words, when a wrapper references an arena, * this is sufficient to ensure that any upb object reachable from that * wrapper will stay alive. * * Additionally, every message object contains a strong reference to the * corresponding Descriptor object. Likewise, array/map objects reference a * Descriptor object if they are typed to store message values. */ #define LUPB_ARENA "lupb.arena" #define LUPB_ARRAY "lupb.array" #define LUPB_MAP "lupb.map" #define LUPB_MSG "lupb.msg" #define LUPB_ARENA_INDEX 1 #define LUPB_MSGDEF_INDEX 2 /* For msg, and map/array that store msg */ static void lupb_Message_Newmsgwrapper(lua_State* L, int narg, upb_MessageValue val); static upb_Message* lupb_msg_check(lua_State* L, int narg); static upb_CType lupb_checkfieldtype(lua_State* L, int narg) { uint32_t n = lupb_checkuint32(L, narg); bool ok = n >= kUpb_CType_Bool && n <= kUpb_CType_Bytes; luaL_argcheck(L, ok, narg, "invalid field type"); return n; } char cache_key; /* lupb_cacheinit() * * Creates the global cache used by lupb_cacheget() and lupb_cacheset(). */ static void lupb_cacheinit(lua_State* L) { /* Create our object cache. */ lua_newtable(L); /* Cache metatable gives the cache weak values */ lua_createtable(L, 0, 1); lua_pushstring(L, "v"); lua_setfield(L, -2, "__mode"); lua_setmetatable(L, -2); /* Set cache in the registry. */ lua_rawsetp(L, LUA_REGISTRYINDEX, &cache_key); } /* lupb_cacheget() * * Pushes cache[key] and returns true if this key is present in the cache. * Otherwise returns false and leaves nothing on the stack. */ static bool lupb_cacheget(lua_State* L, const void* key) { if (key == NULL) { lua_pushnil(L); return true; } lua_rawgetp(L, LUA_REGISTRYINDEX, &cache_key); lua_rawgetp(L, -1, key); if (lua_isnil(L, -1)) { lua_pop(L, 2); /* Pop table, nil. */ return false; } else { lua_replace(L, -2); /* Replace cache table. */ return true; } } /* lupb_cacheset() * * Sets cache[key] = val, where "val" is the value at the top of the stack. * Does not pop the value. */ static void lupb_cacheset(lua_State* L, const void* key) { lua_rawgetp(L, LUA_REGISTRYINDEX, &cache_key); lua_pushvalue(L, -2); lua_rawsetp(L, -2, key); lua_pop(L, 1); /* Pop table. */ } /* lupb_Arena *****************************************************************/ /* lupb_Arena only exists to wrap a upb_Arena. It is never exposed to users; it * is an internal memory management detail. Other wrapper objects refer to this * object from their userdata to keep the arena-owned data alive. */ typedef struct { upb_Arena* arena; } lupb_Arena; static upb_Arena* lupb_Arena_check(lua_State* L, int narg) { lupb_Arena* a = luaL_checkudata(L, narg, LUPB_ARENA); return a->arena; } upb_Arena* lupb_Arena_pushnew(lua_State* L) { lupb_Arena* a = lupb_newuserdata(L, sizeof(lupb_Arena), 1, LUPB_ARENA); a->arena = upb_Arena_New(); return a->arena; } /** * lupb_Arena_Fuse() * * Merges |from| into |to| so that there is a single arena group that contains * both, and both arenas will point at this new table. */ static void lupb_Arena_Fuse(lua_State* L, int to, int from) { upb_Arena* to_arena = lupb_Arena_check(L, to); upb_Arena* from_arena = lupb_Arena_check(L, from); upb_Arena_Fuse(to_arena, from_arena); } static void lupb_Arena_Fuseobjs(lua_State* L, int to, int from) { lua_getiuservalue(L, to, LUPB_ARENA_INDEX); lua_getiuservalue(L, from, LUPB_ARENA_INDEX); lupb_Arena_Fuse(L, lua_absindex(L, -2), lua_absindex(L, -1)); lua_pop(L, 2); } static int lupb_Arena_gc(lua_State* L) { upb_Arena* a = lupb_Arena_check(L, 1); upb_Arena_Free(a); return 0; } static const struct luaL_Reg lupb_Arena_mm[] = {{"__gc", lupb_Arena_gc}, {NULL, NULL}}; /* lupb_Arenaget() * * Returns the arena from the given message, array, or map object. */ static upb_Arena* lupb_Arenaget(lua_State* L, int narg) { upb_Arena* arena; lua_getiuservalue(L, narg, LUPB_ARENA_INDEX); arena = lupb_Arena_check(L, -1); lua_pop(L, 1); return arena; } /* upb <-> Lua type conversion ************************************************/ /* Whether string data should be copied into the containing arena. We can * avoid a copy if the string data is only needed temporarily (like for a map * lookup). */ typedef enum { LUPB_COPY, /* Copy string data into the arena. */ LUPB_REF /* Reference the Lua copy of the string data. */ } lupb_copy_t; /** * lupb_tomsgval() * * Converts the given Lua value |narg| to a upb_MessageValue. */ static upb_MessageValue lupb_tomsgval(lua_State* L, upb_CType type, int narg, int container, lupb_copy_t copy) { upb_MessageValue ret; switch (type) { case kUpb_CType_Int32: case kUpb_CType_Enum: ret.int32_val = lupb_checkint32(L, narg); break; case kUpb_CType_Int64: ret.int64_val = lupb_checkint64(L, narg); break; case kUpb_CType_UInt32: ret.uint32_val = lupb_checkuint32(L, narg); break; case kUpb_CType_UInt64: ret.uint64_val = lupb_checkuint64(L, narg); break; case kUpb_CType_Double: ret.double_val = lupb_checkdouble(L, narg); break; case kUpb_CType_Float: ret.float_val = lupb_checkfloat(L, narg); break; case kUpb_CType_Bool: ret.bool_val = lupb_checkbool(L, narg); break; case kUpb_CType_String: case kUpb_CType_Bytes: { size_t len; const char* ptr = lupb_checkstring(L, narg, &len); switch (copy) { case LUPB_COPY: { upb_Arena* arena = lupb_Arenaget(L, container); char* data = upb_Arena_Malloc(arena, len); memcpy(data, ptr, len); ret.str_val = upb_StringView_FromDataAndSize(data, len); break; } case LUPB_REF: ret.str_val = upb_StringView_FromDataAndSize(ptr, len); break; } break; } case kUpb_CType_Message: ret.msg_val = lupb_msg_check(L, narg); /* Typecheck message. */ lua_getiuservalue(L, container, LUPB_MSGDEF_INDEX); lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX); luaL_argcheck(L, lua_rawequal(L, -1, -2), narg, "message type mismatch"); lua_pop(L, 2); break; } return ret; } void lupb_pushmsgval(lua_State* L, int container, upb_CType type, upb_MessageValue val) { switch (type) { case kUpb_CType_Int32: case kUpb_CType_Enum: lupb_pushint32(L, val.int32_val); return; case kUpb_CType_Int64: lupb_pushint64(L, val.int64_val); return; case kUpb_CType_UInt32: lupb_pushuint32(L, val.uint32_val); return; case kUpb_CType_UInt64: lupb_pushuint64(L, val.uint64_val); return; case kUpb_CType_Double: lua_pushnumber(L, val.double_val); return; case kUpb_CType_Float: lua_pushnumber(L, val.float_val); return; case kUpb_CType_Bool: lua_pushboolean(L, val.bool_val); return; case kUpb_CType_String: case kUpb_CType_Bytes: lua_pushlstring(L, val.str_val.data, val.str_val.size); return; case kUpb_CType_Message: assert(container); if (!lupb_cacheget(L, val.msg_val)) { lupb_Message_Newmsgwrapper(L, container, val); } return; } LUPB_UNREACHABLE(); } /* lupb_array *****************************************************************/ typedef struct { upb_Array* arr; upb_CType type; } lupb_array; static lupb_array* lupb_array_check(lua_State* L, int narg) { return luaL_checkudata(L, narg, LUPB_ARRAY); } /** * lupb_array_checkindex() * * Checks the array index at Lua stack index |narg| to verify that it is an * integer between 1 and |max|, inclusively. Also corrects it to be zero-based * for C. */ static int lupb_array_checkindex(lua_State* L, int narg, uint32_t max) { uint32_t n = lupb_checkuint32(L, narg); luaL_argcheck(L, n != 0 && n <= max, narg, "invalid array index"); return n - 1; /* Lua uses 1-based indexing. */ } /* lupb_array Public API */ /* lupb_Array_New(): * * Handles: * Array(upb.TYPE_INT32) * Array(message_type) */ static int lupb_Array_New(lua_State* L) { int arg_count = lua_gettop(L); lupb_array* larray; upb_Arena* arena; if (lua_type(L, 1) == LUA_TNUMBER) { upb_CType type = lupb_checkfieldtype(L, 1); larray = lupb_newuserdata(L, sizeof(*larray), 1, LUPB_ARRAY); larray->type = type; } else { lupb_MessageDef_check(L, 1); larray = lupb_newuserdata(L, sizeof(*larray), 2, LUPB_ARRAY); larray->type = kUpb_CType_Message; lua_pushvalue(L, 1); lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX); } arena = lupb_Arena_pushnew(L); lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); larray->arr = upb_Array_New(arena, larray->type); lupb_cacheset(L, larray->arr); if (arg_count > 1) { /* Set initial fields from table. */ int msg = arg_count + 1; lua_pushnil(L); while (lua_next(L, 2) != 0) { lua_pushvalue(L, -2); /* now stack is key, val, key */ lua_insert(L, -3); /* now stack is key, key, val */ lua_settable(L, msg); } } return 1; } /* lupb_Array_Newindex(): * * Handles: * array[idx] = val * * idx can be within the array or one past the end to extend. */ static int lupb_Array_Newindex(lua_State* L) { lupb_array* larray = lupb_array_check(L, 1); size_t size = upb_Array_Size(larray->arr); uint32_t n = lupb_array_checkindex(L, 2, size + 1); upb_MessageValue msgval = lupb_tomsgval(L, larray->type, 3, 1, LUPB_COPY); if (n == size) { upb_Array_Append(larray->arr, msgval, lupb_Arenaget(L, 1)); } else { upb_Array_Set(larray->arr, n, msgval); } if (larray->type == kUpb_CType_Message) { lupb_Arena_Fuseobjs(L, 1, 3); } return 0; /* 1 for chained assignments? */ } /* lupb_array_index(): * * Handles: * array[idx] -> val * * idx must be within the array. */ static int lupb_array_index(lua_State* L) { lupb_array* larray = lupb_array_check(L, 1); size_t size = upb_Array_Size(larray->arr); uint32_t n = lupb_array_checkindex(L, 2, size); upb_MessageValue val = upb_Array_Get(larray->arr, n); lupb_pushmsgval(L, 1, larray->type, val); return 1; } /* lupb_array_len(): * * Handles: * #array -> len */ static int lupb_array_len(lua_State* L) { lupb_array* larray = lupb_array_check(L, 1); lua_pushnumber(L, upb_Array_Size(larray->arr)); return 1; } static const struct luaL_Reg lupb_array_mm[] = { {"__index", lupb_array_index}, {"__len", lupb_array_len}, {"__newindex", lupb_Array_Newindex}, {NULL, NULL}}; /* lupb_map *******************************************************************/ typedef struct { upb_Map* map; upb_CType key_type; upb_CType value_type; } lupb_map; #define MAP_MSGDEF_INDEX 1 static lupb_map* lupb_map_check(lua_State* L, int narg) { return luaL_checkudata(L, narg, LUPB_MAP); } /* lupb_map Public API */ /** * lupb_Map_New * * Handles: * new_map = upb.Map(key_type, value_type) * new_map = upb.Map(key_type, value_msgdef) */ static int lupb_Map_New(lua_State* L) { upb_Arena* arena; lupb_map* lmap; if (lua_type(L, 2) == LUA_TNUMBER) { lmap = lupb_newuserdata(L, sizeof(*lmap), 1, LUPB_MAP); lmap->value_type = lupb_checkfieldtype(L, 2); } else { lupb_MessageDef_check(L, 2); lmap = lupb_newuserdata(L, sizeof(*lmap), 2, LUPB_MAP); lmap->value_type = kUpb_CType_Message; lua_pushvalue(L, 2); lua_setiuservalue(L, -2, MAP_MSGDEF_INDEX); } arena = lupb_Arena_pushnew(L); lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); lmap->key_type = lupb_checkfieldtype(L, 1); lmap->map = upb_Map_New(arena, lmap->key_type, lmap->value_type); lupb_cacheset(L, lmap->map); return 1; } /** * lupb_map_index * * Handles: * map[key] */ static int lupb_map_index(lua_State* L) { lupb_map* lmap = lupb_map_check(L, 1); upb_MessageValue key = lupb_tomsgval(L, lmap->key_type, 2, 1, LUPB_REF); upb_MessageValue val; if (upb_Map_Get(lmap->map, key, &val)) { lupb_pushmsgval(L, 1, lmap->value_type, val); } else { lua_pushnil(L); } return 1; } /** * lupb_map_len * * Handles: * map_len = #map */ static int lupb_map_len(lua_State* L) { lupb_map* lmap = lupb_map_check(L, 1); lua_pushnumber(L, upb_Map_Size(lmap->map)); return 1; } /** * lupb_Map_Newindex * * Handles: * map[key] = val * map[key] = nil # to remove from map */ static int lupb_Map_Newindex(lua_State* L) { lupb_map* lmap = lupb_map_check(L, 1); upb_Map* map = lmap->map; upb_MessageValue key = lupb_tomsgval(L, lmap->key_type, 2, 1, LUPB_REF); if (lua_isnil(L, 3)) { upb_Map_Delete(map, key, NULL); } else { upb_MessageValue val = lupb_tomsgval(L, lmap->value_type, 3, 1, LUPB_COPY); upb_Map_Set(map, key, val, lupb_Arenaget(L, 1)); if (lmap->value_type == kUpb_CType_Message) { lupb_Arena_Fuseobjs(L, 1, 3); } } return 0; } static int lupb_MapIterator_Next(lua_State* L) { int map = lua_upvalueindex(2); size_t* iter = lua_touserdata(L, lua_upvalueindex(1)); lupb_map* lmap = lupb_map_check(L, map); upb_MessageValue key, val; if (upb_Map_Next(lmap->map, &key, &val, iter)) { lupb_pushmsgval(L, map, lmap->key_type, key); lupb_pushmsgval(L, map, lmap->value_type, val); return 2; } else { return 0; } } /** * lupb_map_pairs() * * Handles: * pairs(map) */ static int lupb_map_pairs(lua_State* L) { size_t* iter = lua_newuserdata(L, sizeof(*iter)); lupb_map_check(L, 1); *iter = kUpb_Map_Begin; lua_pushvalue(L, 1); /* Upvalues are [iter, lupb_map]. */ lua_pushcclosure(L, &lupb_MapIterator_Next, 2); return 1; } /* upb_mapiter ]]] */ static const struct luaL_Reg lupb_map_mm[] = {{"__index", lupb_map_index}, {"__len", lupb_map_len}, {"__newindex", lupb_Map_Newindex}, {"__pairs", lupb_map_pairs}, {NULL, NULL}}; /* lupb_Message * *******************************************************************/ typedef struct { upb_Message* msg; } lupb_Message; /* lupb_Message helpers */ static upb_Message* lupb_msg_check(lua_State* L, int narg) { lupb_Message* msg = luaL_checkudata(L, narg, LUPB_MSG); return msg->msg; } static const upb_MessageDef* lupb_Message_Getmsgdef(lua_State* L, int msg) { lua_getiuservalue(L, msg, LUPB_MSGDEF_INDEX); const upb_MessageDef* m = lupb_MessageDef_check(L, -1); lua_pop(L, 1); return m; } static const upb_FieldDef* lupb_msg_tofield(lua_State* L, int msg, int field) { size_t len; const char* fieldname = luaL_checklstring(L, field, &len); const upb_MessageDef* m = lupb_Message_Getmsgdef(L, msg); return upb_MessageDef_FindFieldByNameWithSize(m, fieldname, len); } static const upb_FieldDef* lupb_msg_checkfield(lua_State* L, int msg, int field) { const upb_FieldDef* f = lupb_msg_tofield(L, msg, field); if (f == NULL) { luaL_error(L, "no such field '%s'", lua_tostring(L, field)); } return f; } upb_Message* lupb_msg_pushnew(lua_State* L, int narg) { const upb_MessageDef* m = lupb_MessageDef_check(L, narg); lupb_Message* lmsg = lupb_newuserdata(L, sizeof(lupb_Message), 2, LUPB_MSG); upb_Arena* arena = lupb_Arena_pushnew(L); lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); lua_pushvalue(L, 1); lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX); lmsg->msg = upb_Message_New(upb_MessageDef_MiniTable(m), arena); lupb_cacheset(L, lmsg->msg); return lmsg->msg; } /** * lupb_Message_Newmsgwrapper() * * Creates a new wrapper for a message, copying the arena and msgdef references * from |narg| (which should be an array or map). */ static void lupb_Message_Newmsgwrapper(lua_State* L, int narg, upb_MessageValue val) { lupb_Message* lmsg = lupb_newuserdata(L, sizeof(*lmsg), 2, LUPB_MSG); lmsg->msg = (upb_Message*)val.msg_val; /* XXX: cast isn't great. */ lupb_cacheset(L, lmsg->msg); /* Copy both arena and msgdef into the wrapper. */ lua_getiuservalue(L, narg, LUPB_ARENA_INDEX); lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX); lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX); } /** * lupb_Message_Newud() * * Creates the Lua userdata for a new wrapper object, adding a reference to * the msgdef if necessary. */ static void* lupb_Message_Newud(lua_State* L, int narg, size_t size, const char* type, const upb_FieldDef* f) { if (upb_FieldDef_CType(f) == kUpb_CType_Message) { /* Wrapper needs a reference to the msgdef. */ void* ud = lupb_newuserdata(L, size, 2, type); lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX); lupb_MessageDef_pushsubmsgdef(L, f); lua_setiuservalue(L, -2, LUPB_MSGDEF_INDEX); return ud; } else { return lupb_newuserdata(L, size, 1, type); } } /** * lupb_Message_Newwrapper() * * Creates a new Lua wrapper object to wrap the given array, map, or message. */ static void lupb_Message_Newwrapper(lua_State* L, int narg, const upb_FieldDef* f, upb_MutableMessageValue val) { if (upb_FieldDef_IsMap(f)) { const upb_MessageDef* entry = upb_FieldDef_MessageSubDef(f); const upb_FieldDef* key_f = upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_KeyFieldNumber); const upb_FieldDef* val_f = upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_ValueFieldNumber); lupb_map* lmap = lupb_Message_Newud(L, narg, sizeof(*lmap), LUPB_MAP, val_f); lmap->key_type = upb_FieldDef_CType(key_f); lmap->value_type = upb_FieldDef_CType(val_f); lmap->map = val.map; } else if (upb_FieldDef_IsRepeated(f)) { lupb_array* larr = lupb_Message_Newud(L, narg, sizeof(*larr), LUPB_ARRAY, f); larr->type = upb_FieldDef_CType(f); larr->arr = val.array; } else { lupb_Message* lmsg = lupb_Message_Newud(L, narg, sizeof(*lmsg), LUPB_MSG, f); lmsg->msg = val.msg; } /* Copy arena ref to new wrapper. This may be a different arena than the * underlying data was originally constructed from, but if so both arenas * must be in the same group. */ lua_getiuservalue(L, narg, LUPB_ARENA_INDEX); lua_setiuservalue(L, -2, LUPB_ARENA_INDEX); lupb_cacheset(L, val.msg); } /** * lupb_msg_typechecksubmsg() * * Typechecks the given array, map, or msg against this upb_FieldDef. */ static void lupb_msg_typechecksubmsg(lua_State* L, int narg, int msgarg, const upb_FieldDef* f) { /* Typecheck this map's msgdef against this message field. */ lua_getiuservalue(L, narg, LUPB_MSGDEF_INDEX); lua_getiuservalue(L, msgarg, LUPB_MSGDEF_INDEX); lupb_MessageDef_pushsubmsgdef(L, f); luaL_argcheck(L, lua_rawequal(L, -1, -2), narg, "message type mismatch"); lua_pop(L, 2); } /* lupb_Message Public API */ /** * lupb_MessageDef_call * * Handles: * new_msg = MessageClass() * new_msg = MessageClass{foo = "bar", baz = 3, quux = {foo = 3}} */ int lupb_MessageDef_call(lua_State* L) { int arg_count = lua_gettop(L); lupb_msg_pushnew(L, 1); if (arg_count > 1) { /* Set initial fields from table. */ int msg = arg_count + 1; lua_pushnil(L); while (lua_next(L, 2) != 0) { lua_pushvalue(L, -2); /* now stack is key, val, key */ lua_insert(L, -3); /* now stack is key, key, val */ lua_settable(L, msg); } } return 1; } /** * lupb_msg_index * * Handles: * msg.foo * msg["foo"] * msg[field_descriptor] # (for extensions) (TODO) */ static int lupb_msg_index(lua_State* L) { upb_Message* msg = lupb_msg_check(L, 1); const upb_FieldDef* f = lupb_msg_checkfield(L, 1, 2); if (upb_FieldDef_IsRepeated(f) || upb_FieldDef_IsSubMessage(f)) { /* Wrapped type; get or create wrapper. */ upb_Arena* arena = upb_FieldDef_IsRepeated(f) ? lupb_Arenaget(L, 1) : NULL; upb_MutableMessageValue val = upb_Message_Mutable(msg, f, arena); if (!lupb_cacheget(L, val.msg)) { lupb_Message_Newwrapper(L, 1, f, val); } } else { /* Value type, just push value and return .*/ upb_MessageValue val = upb_Message_GetFieldByDef(msg, f); lupb_pushmsgval(L, 0, upb_FieldDef_CType(f), val); } return 1; } /** * lupb_Message_Newindex() * * Handles: * msg.foo = bar * msg["foo"] = bar * msg[field_descriptor] = bar # (for extensions) (TODO) */ static int lupb_Message_Newindex(lua_State* L) { upb_Message* msg = lupb_msg_check(L, 1); const upb_FieldDef* f = lupb_msg_checkfield(L, 1, 2); upb_MessageValue msgval; bool merge_arenas = true; if (upb_FieldDef_IsMap(f)) { lupb_map* lmap = lupb_map_check(L, 3); const upb_MessageDef* entry = upb_FieldDef_MessageSubDef(f); const upb_FieldDef* key_f = upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_KeyFieldNumber); const upb_FieldDef* val_f = upb_MessageDef_FindFieldByNumber(entry, kUpb_MapEntry_ValueFieldNumber); upb_CType key_type = upb_FieldDef_CType(key_f); upb_CType value_type = upb_FieldDef_CType(val_f); luaL_argcheck(L, lmap->key_type == key_type, 3, "key type mismatch"); luaL_argcheck(L, lmap->value_type == value_type, 3, "value type mismatch"); if (value_type == kUpb_CType_Message) { lupb_msg_typechecksubmsg(L, 3, 1, val_f); } msgval.map_val = lmap->map; } else if (upb_FieldDef_IsRepeated(f)) { lupb_array* larr = lupb_array_check(L, 3); upb_CType type = upb_FieldDef_CType(f); luaL_argcheck(L, larr->type == type, 3, "array type mismatch"); if (type == kUpb_CType_Message) { lupb_msg_typechecksubmsg(L, 3, 1, f); } msgval.array_val = larr->arr; } else if (upb_FieldDef_IsSubMessage(f)) { upb_Message* msg = lupb_msg_check(L, 3); lupb_msg_typechecksubmsg(L, 3, 1, f); msgval.msg_val = msg; } else { msgval = lupb_tomsgval(L, upb_FieldDef_CType(f), 3, 1, LUPB_COPY); merge_arenas = false; } if (merge_arenas) { lupb_Arena_Fuseobjs(L, 1, 3); } upb_Message_SetFieldByDef(msg, f, msgval, lupb_Arenaget(L, 1)); /* Return the new value for chained assignments. */ lua_pushvalue(L, 3); return 1; } /** * lupb_msg_tostring() * * Handles: * tostring(msg) * print(msg) * etc. */ static int lupb_msg_tostring(lua_State* L) { upb_Message* msg = lupb_msg_check(L, 1); const upb_MessageDef* m; char buf[1024]; size_t size; lua_getiuservalue(L, 1, LUPB_MSGDEF_INDEX); m = lupb_MessageDef_check(L, -1); size = upb_TextEncode(msg, m, NULL, 0, buf, sizeof(buf)); if (size < sizeof(buf)) { lua_pushlstring(L, buf, size); } else { char* ptr = malloc(size + 1); upb_TextEncode(msg, m, NULL, 0, ptr, size + 1); lua_pushlstring(L, ptr, size); free(ptr); } return 1; } static const struct luaL_Reg lupb_msg_mm[] = { {"__index", lupb_msg_index}, {"__newindex", lupb_Message_Newindex}, {"__tostring", lupb_msg_tostring}, {NULL, NULL}}; /* lupb_Message toplevel * **********************************************************/ static int lupb_getoptions(lua_State* L, int narg) { int options = 0; if (lua_gettop(L) >= narg) { size_t len = lua_rawlen(L, narg); for (size_t i = 1; i <= len; i++) { lua_rawgeti(L, narg, i); options |= lupb_checkuint32(L, -1); lua_pop(L, 1); } } return options; } /** * lupb_decode() * * Handles: * msg = upb.decode(MessageClass, bin_string) */ static int lupb_decode(lua_State* L) { size_t len; const upb_MessageDef* m = lupb_MessageDef_check(L, 1); const char* pb = lua_tolstring(L, 2, &len); const upb_MiniTable* layout = upb_MessageDef_MiniTable(m); upb_Message* msg = lupb_msg_pushnew(L, 1); upb_Arena* arena = lupb_Arenaget(L, -1); char* buf; /* Copy input data to arena, message will reference it. */ buf = upb_Arena_Malloc(arena, len); memcpy(buf, pb, len); upb_DecodeStatus status = upb_Decode(buf, len, msg, layout, NULL, kUpb_DecodeOption_AliasString, arena); if (status != kUpb_DecodeStatus_Ok) { lua_pushstring(L, "Error decoding protobuf."); return lua_error(L); } return 1; } /** * lupb_Encode() * * Handles: * bin_string = upb.encode(msg) */ static int lupb_Encode(lua_State* L) { const upb_Message* msg = lupb_msg_check(L, 1); const upb_MessageDef* m = lupb_Message_Getmsgdef(L, 1); const upb_MiniTable* layout = upb_MessageDef_MiniTable(m); int options = lupb_getoptions(L, 2); upb_Arena* arena = lupb_Arena_pushnew(L); char* buf; size_t size; upb_EncodeStatus status = upb_Encode(msg, (const void*)layout, options, arena, &buf, &size); if (status != kUpb_EncodeStatus_Ok) { lua_pushstring(L, "Error encoding protobuf."); return lua_error(L); } lua_pushlstring(L, buf, size); return 1; } /** * lupb_jsondecode() * * Handles: * text_string = upb.json_decode(MessageClass, json_str, * {upb.JSONDEC_IGNOREUNKNOWN}) */ static int lupb_jsondecode(lua_State* L) { size_t len; const upb_MessageDef* m = lupb_MessageDef_check(L, 1); const char* json = lua_tolstring(L, 2, &len); int options = lupb_getoptions(L, 3); upb_Message* msg; upb_Arena* arena; upb_Status status; msg = lupb_msg_pushnew(L, 1); arena = lupb_Arenaget(L, -1); upb_Status_Clear(&status); upb_JsonDecode(json, len, msg, m, NULL, options, arena, &status); lupb_checkstatus(L, &status); return 1; } /** * lupb_jsonencode() * * Handles: * text_string = upb.json_encode(msg, {upb.JSONENC_EMITDEFAULTS}) */ static int lupb_jsonencode(lua_State* L) { upb_Message* msg = lupb_msg_check(L, 1); const upb_MessageDef* m = lupb_Message_Getmsgdef(L, 1); int options = lupb_getoptions(L, 2); char buf[1024]; size_t size; upb_Status status; upb_Status_Clear(&status); size = upb_JsonEncode(msg, m, NULL, options, buf, sizeof(buf), &status); lupb_checkstatus(L, &status); if (size < sizeof(buf)) { lua_pushlstring(L, buf, size); } else { char* ptr = malloc(size + 1); upb_JsonEncode(msg, m, NULL, options, ptr, size + 1, &status); lupb_checkstatus(L, &status); lua_pushlstring(L, ptr, size); free(ptr); } return 1; } /** * lupb_textencode() * * Handles: * text_string = upb.text_encode(msg, {upb.TXTENC_SINGLELINE}) */ static int lupb_textencode(lua_State* L) { upb_Message* msg = lupb_msg_check(L, 1); const upb_MessageDef* m = lupb_Message_Getmsgdef(L, 1); int options = lupb_getoptions(L, 2); char buf[1024]; size_t size; size = upb_TextEncode(msg, m, NULL, options, buf, sizeof(buf)); if (size < sizeof(buf)) { lua_pushlstring(L, buf, size); } else { char* ptr = malloc(size + 1); upb_TextEncode(msg, m, NULL, options, ptr, size + 1); lua_pushlstring(L, ptr, size); free(ptr); } return 1; } static void lupb_setfieldi(lua_State* L, const char* field, int i) { lua_pushinteger(L, i); lua_setfield(L, -2, field); } static const struct luaL_Reg lupb_msg_toplevel_m[] = { {"Array", lupb_Array_New}, {"Map", lupb_Map_New}, {"decode", lupb_decode}, {"encode", lupb_Encode}, {"json_decode", lupb_jsondecode}, {"json_encode", lupb_jsonencode}, {"text_encode", lupb_textencode}, {NULL, NULL}}; void lupb_msg_registertypes(lua_State* L) { lupb_setfuncs(L, lupb_msg_toplevel_m); lupb_register_type(L, LUPB_ARENA, NULL, lupb_Arena_mm); lupb_register_type(L, LUPB_ARRAY, NULL, lupb_array_mm); lupb_register_type(L, LUPB_MAP, NULL, lupb_map_mm); lupb_register_type(L, LUPB_MSG, NULL, lupb_msg_mm); lupb_setfieldi(L, "TXTENC_SINGLELINE", UPB_TXTENC_SINGLELINE); lupb_setfieldi(L, "TXTENC_SKIPUNKNOWN", UPB_TXTENC_SKIPUNKNOWN); lupb_setfieldi(L, "TXTENC_NOSORT", UPB_TXTENC_NOSORT); lupb_setfieldi(L, "ENCODE_DETERMINISTIC", kUpb_EncodeOption_Deterministic); lupb_setfieldi(L, "ENCODE_SKIPUNKNOWN", kUpb_EncodeOption_SkipUnknown); lupb_setfieldi(L, "JSONENC_EMITDEFAULTS", upb_JsonEncode_EmitDefaults); lupb_setfieldi(L, "JSONENC_PROTONAMES", upb_JsonEncode_UseProtoNames); lupb_setfieldi(L, "JSONDEC_IGNOREUNKNOWN", upb_JsonDecode_IgnoreUnknown); lupb_cacheinit(L); }