From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id EA4896EC58; Wed, 23 Jun 2021 22:13:45 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org EA4896EC58 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1624475626; bh=z/Y5/Ntqh2SnEu3JIp4gBBfQ/ObxP3StmUSWAmvLtNo=; h=To:Cc:Date:In-Reply-To:References:Subject:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=QI4CHKRtEN1mvsRpHmqSeMFBaNhhTHmjhMLkVi1dwMVRF1oayCiXJJ/R7ehurZb8K nWC7iDNQxKBReDEI+2FA/yKrnujzA38ltgBxs9uwngau2EVmpM3g9M0CraB1dRjZTo wLDYo/ecXrxPyFxuhV+vlBcuv9h/VqooOdBm5iqA= Received: from smtp35.i.mail.ru (smtp35.i.mail.ru [94.100.177.95]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 9357A6EC58 for ; Wed, 23 Jun 2021 22:13:13 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 9357A6EC58 Received: by smtp35.i.mail.ru with esmtpa (envelope-from ) id 1lw8JE-00086m-0d; Wed, 23 Jun 2021 22:13:12 +0300 To: Vladislav Shpilevoy , Cyrill Gorcunov , Roman Khabibov Cc: Alexander Turenko , tarantool-patches@dev.tarantool.org Date: Wed, 23 Jun 2021 22:12:39 +0300 Message-Id: X-Mailer: git-send-email 2.31.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD954DFF1DC42D673FB2F1AA0EB8A504C8721532AB396CDCF09182A05F538085040FC948831C502E38B51C5FAFB56C1AFF321ECA56472C00F9D3CF3DC9BFCEBDFAA X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE70386A6136E33FD82EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637217E865CDB7D48CC8638F802B75D45FF36EB9D2243A4F8B5A6FCA7DBDB1FC311F39EFFDF887939037866D6147AF826D868263D9F4E56677A3CF570FCC107DC72117882F4460429724CE54428C33FAD305F5C1EE8F4F765FCAA867293B0326636D2E47CDBA5A96583BD4B6F7A4D31EC0BC014FD901B82EE079FA2833FD35BB23D27C277FBC8AE2E8BAA867293B0326636D2E47CDBA5A96583BA9C0B312567BB2376E601842F6C81A19E625A9149C048EE902A1BE408319B299F804269016115C9D8FC6C240DEA7642DBF02ECDB25306B2B78CF848AE20165D0A6AB1C7CE11FEE31F9513A7CA91E5559735652A29929C6CC4224003CC836476EA7A3FFF5B025636E2021AF6380DFAD1A18204E546F3947CB11811A4A51E3B096D1867E19FE1407959CC434672EE6371089D37D7C0E48F6C8AA50765F7900637BC468E7E89D8C5D6EFF80C71ABB335746BA297DBC24807EABDAD6C7F3747799A X-B7AD71C0: AC4F5C86D027EB782CDD5689AFBDA7A24209795067102C07E8F7B195E1C978319E86B45BBB3EF42AB1D76F471CF06CBF X-C1DE0DAB: C20DE7B7AB408E4181F030C43753B8183A4AFAF3EA6BDC44C234C8B12C006B7AE3700922B18356DDF0ECDB17F1A5907F87128921FA4D8B7EB1881A6453793CE9C32612AADDFBE061C61BE10805914D3804EBA3D8E7E5B87ABF8C51168CD8EBDB5F0C88D684269EDEDC48ACC2A39D04F89CDFB48F4795C241BDAD6C7F3747799A X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D34AAC3D1FDB34D048845AC0583F6DD2BC2872F181AF7B9F2563D9BA4D5692C10DD138389A44D2B76EA1D7E09C32AA3244C84F00F19D0D071761C692B7DAC917F3DD9ADFF0C0BDB8D1F83B48618A63566E0 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojbL9S8ysBdXgCFZEybMaxpKWe5p5PKQwS X-Mailru-Sender: FFAA8E4AEE17E37C3731A083A1A85ADEA1C0F6ECADBAAE5E8A820477FA3EE0CDB7EA9FE7735C3DBFC664A44C781FCEA7C77752E0C033A69EDF9F2CE1E9CF805D8CD356D4F938FF726C18EFA0BB12DBB0 X-Mras: Ok Subject: [Tarantool-patches] [PATCH 1/4] lua: move serializer helpers into its own file X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Alexander Turenko via Tarantool-patches Reply-To: Alexander Turenko Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" It is easier to glance on tightly coupled structures and functions, when they're not mixed with others. Just move without actual changes. Part of #3228 --- src/CMakeLists.txt | 1 + src/box/lua/call.c | 1 + src/box/lua/console.c | 1 + src/box/lua/execute.c | 1 + src/box/lua/info.c | 1 + src/box/lua/init.c | 2 + src/box/lua/serialize_lua.c | 1 + src/box/lua/slab.c | 1 + src/box/lua/tuple.c | 1 + src/box/sql/mem.c | 1 + src/lua/decimal.c | 2 + src/lua/fiber.c | 1 + src/lua/init.c | 5 + src/lua/msgpack.c | 1 + src/lua/msgpack.h | 1 + src/lua/pickle.c | 1 + src/lua/serializer.c | 651 ++++++++++++++++++++++++++++++ src/lua/serializer.h | 344 ++++++++++++++++ src/lua/utils.c | 601 +-------------------------- src/lua/utils.h | 292 +------------- third_party/lua-cjson/lua_cjson.c | 1 + third_party/lua-yaml/lyaml.cc | 1 + 22 files changed, 1023 insertions(+), 889 deletions(-) create mode 100644 src/lua/serializer.c create mode 100644 src/lua/serializer.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 324574fec..c0e272bd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -122,6 +122,7 @@ set (server_sources lua/trigger.c lua/msgpack.c lua/utils.c + lua/serializer.c lua/errno.c lua/tnt_iconv.c lua/error.c diff --git a/src/box/lua/call.c b/src/box/lua/call.c index 0315e720c..d42b54d42 100644 --- a/src/box/lua/call.c +++ b/src/box/lua/call.c @@ -38,6 +38,7 @@ #include "tt_static.h" #include "lua/utils.h" +#include "lua/serializer.h" #include "lua/msgpack.h" #include "lua/trigger.h" diff --git a/src/box/lua/console.c b/src/box/lua/console.c index 2e8204db6..d97c3ff30 100644 --- a/src/box/lua/console.c +++ b/src/box/lua/console.c @@ -34,6 +34,7 @@ #include "box/port.h" #include "box/error.h" #include "lua/utils.h" +#include "lua/serializer.h" #include "lua/fiber.h" #include "fiber.h" #include "coio.h" diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c index 926a0a61c..1b59b2e4a 100644 --- a/src/box/lua/execute.c +++ b/src/box/lua/execute.c @@ -1,5 +1,6 @@ #include "execute.h" #include "lua/utils.h" +#include "lua/serializer.h" #include "lua/msgpack.h" #include "box/sql/sqlInt.h" #include "box/port.h" diff --git a/src/box/lua/info.c b/src/box/lua/info.c index 0eb48b823..f2bc42eee 100644 --- a/src/box/lua/info.c +++ b/src/box/lua/info.c @@ -52,6 +52,7 @@ #include "box/raft.h" #include "box/txn_limbo.h" #include "lua/utils.h" +#include "lua/serializer.h" /* luaL_setmaphint */ #include "fiber.h" #include "sio.h" diff --git a/src/box/lua/init.c b/src/box/lua/init.c index 3a6d60864..6eb2ab651 100644 --- a/src/box/lua/init.c +++ b/src/box/lua/init.c @@ -34,6 +34,8 @@ #include #include +#include "lib/core/mp_extension_types.h" + #include "lua/utils.h" /* luaT_error() */ #include "lua/trigger.h" #include "lua/msgpack.h" diff --git a/src/box/lua/serialize_lua.c b/src/box/lua/serialize_lua.c index caa08a60f..7144305cf 100644 --- a/src/box/lua/serialize_lua.c +++ b/src/box/lua/serialize_lua.c @@ -34,6 +34,7 @@ #include "trivia/util.h" #include "lua/utils.h" +#include "lua/serializer.h" #include "say.h" #include "lib/core/decimal.h" diff --git a/src/box/lua/slab.c b/src/box/lua/slab.c index 9f5e7e95c..dd89a980f 100644 --- a/src/box/lua/slab.c +++ b/src/box/lua/slab.c @@ -32,6 +32,7 @@ #include "box/lua/slab.h" #include "lua/utils.h" +#include "lua/serializer.h" /* luaL_setmaphint */ #include #include diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c index f7198a025..609f2eda0 100644 --- a/src/box/lua/tuple.c +++ b/src/box/lua/tuple.c @@ -32,6 +32,7 @@ #include "box/xrow_update.h" #include "lua/utils.h" /* luaT_error() */ +#include "lua/serializer.h" #include "lua/msgpack.h" /* luamp_encode_XXX() */ #include "diag.h" /* diag_set() */ #include diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c index 6f3bf52e5..2595e2fd4 100644 --- a/src/box/sql/mem.c +++ b/src/box/sql/mem.c @@ -38,6 +38,7 @@ #include "mpstream/mpstream.h" #include "box/port.h" #include "lua/utils.h" +#include "lua/serializer.h" #include "lua/msgpack.h" #include "uuid/mp_uuid.h" #include "mp_decimal.h" diff --git a/src/lua/decimal.c b/src/lua/decimal.c index d3400521d..003680a48 100644 --- a/src/lua/decimal.c +++ b/src/lua/decimal.c @@ -79,6 +79,8 @@ ldecimal_##name(struct lua_State *L) { \ return 1; \ } +uint32_t CTID_DECIMAL; + /** Push a new decimal on the stack and return a pointer to it. */ decimal_t * lua_pushdecimal(struct lua_State *L) diff --git a/src/lua/fiber.c b/src/lua/fiber.c index 91898c283..d236e50c4 100644 --- a/src/lua/fiber.c +++ b/src/lua/fiber.c @@ -32,6 +32,7 @@ #include #include "lua/utils.h" +#include "lua/serializer.h" #include "backtrace.h" #include "tt_static.h" diff --git a/src/lua/init.c b/src/lua/init.c index 93e93a103..ff11d202b 100644 --- a/src/lua/init.c +++ b/src/lua/init.c @@ -69,6 +69,10 @@ #include #include +/* Don't include the entire header only for *_init(). */ +int +tarantool_lua_serializer_init(struct lua_State *L); + /** * The single Lua state of the transaction processor (tx) thread. */ @@ -475,6 +479,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv) tarantool_lua_socket_init(L); tarantool_lua_pickle_init(L); tarantool_lua_digest_init(L); + tarantool_lua_serializer_init(L); tarantool_lua_swim_init(L); tarantool_lua_decimal_init(L); luaopen_http_client_driver(L); diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c index 1e74a6a3c..b6ecf2b1e 100644 --- a/src/lua/msgpack.c +++ b/src/lua/msgpack.c @@ -31,6 +31,7 @@ #include "lua/msgpack.h" #include "mpstream/mpstream.h" #include "lua/utils.h" +#include "lua/serializer.h" #if defined(LUAJIT) #include diff --git a/src/lua/msgpack.h b/src/lua/msgpack.h index 5a91e2812..cf85654a5 100644 --- a/src/lua/msgpack.h +++ b/src/lua/msgpack.h @@ -41,6 +41,7 @@ extern "C" { #include +struct luaL_field; struct luaL_serializer; struct mpstream; struct serializer_opts; diff --git a/src/lua/pickle.c b/src/lua/pickle.c index 65208b5b3..b88f5ac75 100644 --- a/src/lua/pickle.c +++ b/src/lua/pickle.c @@ -38,6 +38,7 @@ #include #include "lua/utils.h" +#include "lua/serializer.h" #include "lua/msgpack.h" /* luaL_msgpack_default */ #include #include "bit/bit.h" diff --git a/src/lua/serializer.c b/src/lua/serializer.c new file mode 100644 index 000000000..6c3dd73af --- /dev/null +++ b/src/lua/serializer.c @@ -0,0 +1,651 @@ +/* + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY ``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 + * 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 +#include +#include /* modf, isfinite */ +#include +#include + +#include "lua/serializer.h" + +#include "trigger.h" +#include "lib/core/decimal.h" /* decimal_t */ +#include "lib/core/mp_extension_types.h" +#include "lua/error.h" + +#include "trivia/util.h" +#include "diag.h" +#include "serializer_opts.h" +#include "lua/utils.h" + +int luaL_map_metatable_ref = LUA_REFNIL; +int luaL_array_metatable_ref = LUA_REFNIL; +extern uint32_t CTID_UUID; +extern uint32_t CTID_DECIMAL; + +#define OPTION(type, name, defvalue) { #name, \ + offsetof(struct luaL_serializer, name), type, defvalue} +/** + * Configuration options for serializers + * @sa struct luaL_serializer + */ +static struct { + const char *name; + size_t offset; /* offset in structure */ + int type; + int defvalue; +} OPTIONS[] = { + OPTION(LUA_TBOOLEAN, encode_sparse_convert, 1), + OPTION(LUA_TNUMBER, encode_sparse_ratio, 2), + OPTION(LUA_TNUMBER, encode_sparse_safe, 10), + OPTION(LUA_TNUMBER, encode_max_depth, 128), + OPTION(LUA_TBOOLEAN, encode_deep_as_nil, 0), + OPTION(LUA_TBOOLEAN, encode_invalid_numbers, 1), + OPTION(LUA_TNUMBER, encode_number_precision, 14), + OPTION(LUA_TBOOLEAN, encode_load_metatables, 1), + OPTION(LUA_TBOOLEAN, encode_use_tostring, 0), + OPTION(LUA_TBOOLEAN, encode_invalid_as_nil, 0), + OPTION(LUA_TBOOLEAN, decode_invalid_numbers, 1), + OPTION(LUA_TBOOLEAN, decode_save_metatables, 1), + OPTION(LUA_TNUMBER, decode_max_depth, 128), + { NULL, 0, 0, 0}, +}; + +void +luaL_serializer_create(struct luaL_serializer *cfg) +{ + rlist_create(&cfg->on_update); + for (int i = 0; OPTIONS[i].name != NULL; i++) { + int *pval = (int *) ((char *) cfg + OPTIONS[i].offset); + *pval = OPTIONS[i].defvalue; + } +} + +void +luaL_serializer_copy_options(struct luaL_serializer *dst, + const struct luaL_serializer *src) +{ + memcpy(dst, src, offsetof(struct luaL_serializer, end_of_options)); +} + +/** + * Configure one field in @a cfg. Value of the field is kept on + * Lua stack after this function, and should be popped manually. + * @param L Lua stack. + * @param i Index of option in OPTIONS[]. + * @param cfg Serializer to inherit configuration. + * @retval Pointer to the value of option. + * @retval NULL if option is not in the table. + */ +static int * +luaL_serializer_parse_option(struct lua_State *L, int i, + struct luaL_serializer *cfg) +{ + lua_getfield(L, 2, OPTIONS[i].name); + if (lua_isnil(L, -1)) + return NULL; + /* + * Update struct luaL_serializer using pointer to a + * configuration value (all values must be `int` for that). + */ + int *pval = (int *) ((char *) cfg + OPTIONS[i].offset); + switch (OPTIONS[i].type) { + case LUA_TBOOLEAN: + *pval = lua_toboolean(L, -1); + break; + case LUA_TNUMBER: + *pval = lua_tointeger(L, -1); + break; + default: + unreachable(); + } + return pval; +} + +void +luaL_serializer_parse_options(struct lua_State *L, + struct luaL_serializer *cfg) +{ + for (int i = 0; OPTIONS[i].name != NULL; ++i) { + luaL_serializer_parse_option(L, i, cfg); + lua_pop(L, 1); + } +} + +/** + * @brief serializer.cfg{} Lua binding for serializers. + * serializer.cfg is a table that contains current configuration values from + * luaL_serializer structure. serializer.cfg has overriden __call() method + * to change configuration keys in internal userdata (like box.cfg{}). + * Please note that direct change in serializer.cfg.key will not affect + * internal state of userdata. Changes via cfg() are reflected in + * both Lua cfg table, and C serializer structure. + * @param L lua stack + * @return 0 + */ +static int +luaL_serializer_cfg(struct lua_State *L) +{ + /* Serializer.cfg */ + luaL_checktype(L, 1, LUA_TTABLE); + /* Updated parameters. */ + luaL_checktype(L, 2, LUA_TTABLE); + struct luaL_serializer *cfg = luaL_checkserializer(L); + for (int i = 0; OPTIONS[i].name != NULL; ++i) { + if (luaL_serializer_parse_option(L, i, cfg) == NULL) + lua_pop(L, 1); + else + lua_setfield(L, 1, OPTIONS[i].name); + } + trigger_run(&cfg->on_update, cfg); + return 0; +} + +/** + * @brief serializer.new() Lua binding. + * @param L stack + * @param reg methods to register + * @param parent parent serializer to inherit configuration + * @return new serializer + */ +struct luaL_serializer * +luaL_newserializer(struct lua_State *L, const char *modname, const luaL_Reg *reg) +{ + luaL_checkstack(L, 1, "too many upvalues"); + + /* Create new module */ + lua_newtable(L); + + /* Create new configuration */ + struct luaL_serializer *serializer = (struct luaL_serializer *) + lua_newuserdata(L, sizeof(*serializer)); + luaL_getmetatable(L, LUAL_SERIALIZER); + lua_setmetatable(L, -2); + luaL_serializer_create(serializer); + + for (; reg->name != NULL; reg++) { + /* push luaL_serializer as upvalue */ + lua_pushvalue(L, -1); + /* register method */ + lua_pushcclosure(L, reg->func, 1); + lua_setfield(L, -3, reg->name); + } + + /* Add cfg{} */ + lua_newtable(L); /* cfg */ + lua_newtable(L); /* metatable */ + lua_pushvalue(L, -3); /* luaL_serializer */ + lua_pushcclosure(L, luaL_serializer_cfg, 1); + lua_setfield(L, -2, "__call"); + lua_setmetatable(L, -2); + /* Save configuration values to serializer.cfg */ + for (int i = 0; OPTIONS[i].name != NULL; i++) { + int *pval = (int *) ((char *) serializer + OPTIONS[i].offset); + switch (OPTIONS[i].type) { + case LUA_TBOOLEAN: + lua_pushboolean(L, *pval); + break; + case LUA_TNUMBER: + lua_pushinteger(L, *pval); + break; + default: + unreachable(); + } + lua_setfield(L, -2, OPTIONS[i].name); + } + lua_setfield(L, -3, "cfg"); + + lua_pop(L, 1); /* remove upvalues */ + + luaL_pushnull(L); + lua_setfield(L, -2, "NULL"); + lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_array_metatable_ref); + lua_setfield(L, -2, "array_mt"); + lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_map_metatable_ref); + lua_setfield(L, -2, "map_mt"); + + if (modname != NULL) { + /* Register module */ + lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); + lua_pushstring(L, modname); /* add alias */ + lua_pushvalue(L, -3); + lua_settable(L, -3); + lua_pop(L, 1); /* _LOADED */ + } + + return serializer; +} + +static int +lua_gettable_wrapper(lua_State *L) +{ + lua_gettable(L, -2); + return 1; +} + +static void +lua_field_inspect_ucdata(struct lua_State *L, struct luaL_serializer *cfg, + int idx, struct luaL_field *field) +{ + if (!cfg->encode_load_metatables) + return; + + /* + * Try to call LUAL_SERIALIZE method on udata/cdata + * LuaJIT specific: lua_getfield/lua_gettable raises exception on + * cdata if field doesn't exist. + */ + int top = lua_gettop(L); + lua_pushcfunction(L, lua_gettable_wrapper); + lua_pushvalue(L, idx); + lua_pushliteral(L, LUAL_SERIALIZE); + if (lua_pcall(L, 2, 1, 0) == 0 && !lua_isnil(L, -1)) { + if (!lua_isfunction(L, -1)) + luaL_error(L, "invalid " LUAL_SERIALIZE " value"); + /* copy object itself */ + lua_pushvalue(L, idx); + lua_pcall(L, 1, 1, 0); + /* replace obj with the unpacked value */ + lua_replace(L, idx); + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) + luaT_error(L); + } /* else ignore lua_gettable exceptions */ + lua_settop(L, top); /* remove temporary objects */ +} + +/** + * Call __serialize method of a table object by index + * if the former exists. + * + * If __serialize does not exist then function does nothing + * and the function returns 1; + * + * If __serialize exists, is a function (which doesn't + * raise any error) then a result of serialization + * replaces old value by the index and the function returns 0; + * + * If the serialization is a hint string (like 'array' or 'map'), + * then field->type, field->size and field->compact + * are set if necessary and the function returns 0; + * + * Otherwise it is an error, set diag and the funciton returns -1; + * + * Return values: + * -1 - error occurs, diag is set, the top of guest stack + * is undefined. + * 0 - __serialize field is available in the metatable, + * the result value is put in the origin slot, + * encoding is finished. + * 1 - __serialize field is not available in the metatable, + * proceed with default table encoding. + */ +static int +lua_field_try_serialize(struct lua_State *L, struct luaL_serializer *cfg, + int idx, struct luaL_field *field) +{ + if (luaL_getmetafield(L, idx, LUAL_SERIALIZE) == 0) + return 1; + if (lua_isfunction(L, -1)) { + /* copy object itself */ + lua_pushvalue(L, idx); + if (lua_pcall(L, 1, 1, 0) != 0) { + diag_set(LuajitError, lua_tostring(L, -1)); + return -1; + } + if (luaL_tofield(L, cfg, NULL, -1, field) != 0) + return -1; + lua_replace(L, idx); + return 0; + } + if (!lua_isstring(L, -1)) { + diag_set(LuajitError, "invalid " LUAL_SERIALIZE " value"); + return -1; + } + const char *type = lua_tostring(L, -1); + if (strcmp(type, "array") == 0 || strcmp(type, "seq") == 0 || + strcmp(type, "sequence") == 0) { + field->type = MP_ARRAY; /* Override type */ + field->size = luaL_arrlen(L, idx); + /* YAML: use flow mode if __serialize == 'seq' */ + if (cfg->has_compact && type[3] == '\0') + field->compact = true; + } else if (strcmp(type, "map") == 0 || strcmp(type, "mapping") == 0) { + field->type = MP_MAP; /* Override type */ + field->size = luaL_maplen(L, idx); + /* YAML: use flow mode if __serialize == 'map' */ + if (cfg->has_compact && type[3] == '\0') + field->compact = true; + } else { + diag_set(LuajitError, "invalid " LUAL_SERIALIZE " value"); + return -1; + } + /* Remove value set by luaL_getmetafield. */ + lua_pop(L, 1); + return 0; +} + +static int +lua_field_inspect_table(struct lua_State *L, struct luaL_serializer *cfg, + int idx, struct luaL_field *field) +{ + assert(lua_type(L, idx) == LUA_TTABLE); + uint32_t size = 0; + uint32_t max = 0; + + if (cfg->encode_load_metatables) { + int top = lua_gettop(L); + int res = lua_field_try_serialize(L, cfg, idx, field); + if (res == -1) + return -1; + assert(lua_gettop(L) == top); + (void)top; + if (res == 0) + return 0; + /* Fallthrough with res == 1 */ + } + + field->type = MP_ARRAY; + + /* Calculate size and check that table can represent an array */ + lua_pushnil(L); + while (lua_next(L, idx)) { + size++; + lua_pop(L, 1); /* pop the value */ + lua_Number k; + if (lua_type(L, -1) != LUA_TNUMBER || + ((k = lua_tonumber(L, -1)) != size && + (k < 1 || floor(k) != k))) { + /* Finish size calculation */ + while (lua_next(L, idx)) { + size++; + lua_pop(L, 1); /* pop the value */ + } + field->type = MP_MAP; + field->size = size; + return 0; + } + if (k > max) + max = k; + } + + /* Encode excessively sparse arrays as objects (if enabled) */ + if (cfg->encode_sparse_ratio > 0 && + max > size * (uint32_t)cfg->encode_sparse_ratio && + max > (uint32_t)cfg->encode_sparse_safe) { + if (!cfg->encode_sparse_convert) { + diag_set(LuajitError, "excessively sparse array"); + return -1; + } + field->type = MP_MAP; + field->size = size; + return 0; + } + + assert(field->type == MP_ARRAY); + field->size = max; + return 0; +} + +static void +lua_field_tostring(struct lua_State *L, struct luaL_serializer *cfg, int idx, + struct luaL_field *field) +{ + int top = lua_gettop(L); + lua_getglobal(L, "tostring"); + lua_pushvalue(L, idx); + lua_call(L, 1, 1); + lua_replace(L, idx); + lua_settop(L, top); + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) + luaT_error(L); +} + +int +luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, + const struct serializer_opts *opts, int index, + struct luaL_field *field) +{ + if (index < 0) + index = lua_gettop(L) + index + 1; + + double num; + double intpart; + size_t size; + +#define CHECK_NUMBER(x) ({ \ + if (!isfinite(x) && !cfg->encode_invalid_numbers) { \ + if (!cfg->encode_invalid_as_nil) { \ + diag_set(LuajitError, "number must not be NaN or Inf"); \ + return -1; \ + } \ + field->type = MP_NIL; \ + }}) + + switch (lua_type(L, index)) { + case LUA_TNUMBER: + num = lua_tonumber(L, index); + if (isfinite(num) && modf(num, &intpart) != 0.0) { + field->type = MP_DOUBLE; + field->dval = num; + } else if (num >= 0 && num < exp2(64)) { + field->type = MP_UINT; + field->ival = (uint64_t) num; + } else if (num >= -exp2(63) && num < exp2(63)) { + field->type = MP_INT; + field->ival = (int64_t) num; + } else { + field->type = MP_DOUBLE; + field->dval = num; + CHECK_NUMBER(num); + } + return 0; + case LUA_TCDATA: + { + GCcdata *cd = cdataV(L->base + index - 1); + void *cdata = (void *)cdataptr(cd); + + int64_t ival; + switch (cd->ctypeid) { + case CTID_BOOL: + field->type = MP_BOOL; + field->bval = *(bool*) cdata; + return 0; + case CTID_CCHAR: + case CTID_INT8: + ival = *(int8_t *) cdata; + field->type = (ival >= 0) ? MP_UINT : MP_INT; + field->ival = ival; + return 0; + case CTID_INT16: + ival = *(int16_t *) cdata; + field->type = (ival >= 0) ? MP_UINT : MP_INT; + field->ival = ival; + return 0; + case CTID_INT32: + ival = *(int32_t *) cdata; + field->type = (ival >= 0) ? MP_UINT : MP_INT; + field->ival = ival; + return 0; + case CTID_INT64: + ival = *(int64_t *) cdata; + field->type = (ival >= 0) ? MP_UINT : MP_INT; + field->ival = ival; + return 0; + case CTID_UINT8: + field->type = MP_UINT; + field->ival = *(uint8_t *) cdata; + return 0; + case CTID_UINT16: + field->type = MP_UINT; + field->ival = *(uint16_t *) cdata; + return 0; + case CTID_UINT32: + field->type = MP_UINT; + field->ival = *(uint32_t *) cdata; + return 0; + case CTID_UINT64: + field->type = MP_UINT; + field->ival = *(uint64_t *) cdata; + return 0; + case CTID_FLOAT: + field->type = MP_FLOAT; + field->fval = *(float *) cdata; + CHECK_NUMBER(field->fval); + return 0; + case CTID_DOUBLE: + field->type = MP_DOUBLE; + field->dval = *(double *) cdata; + CHECK_NUMBER(field->dval); + return 0; + case CTID_P_CVOID: + case CTID_P_VOID: + if (*(void **) cdata == NULL) { + field->type = MP_NIL; + return 0; + } + /* Fall through */ + default: + field->type = MP_EXT; + if (cd->ctypeid == CTID_DECIMAL) { + field->ext_type = MP_DECIMAL; + field->decval = (decimal_t *) cdata; + } else if (cd->ctypeid == CTID_UUID) { + field->ext_type = MP_UUID; + field->uuidval = (struct tt_uuid *) cdata; + } else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF && + opts != NULL && + opts->error_marshaling_enabled) { + field->ext_type = MP_ERROR; + } else { + field->ext_type = MP_UNKNOWN_EXTENSION; + } + } + return 0; + } + case LUA_TBOOLEAN: + field->type = MP_BOOL; + field->bval = lua_toboolean(L, index); + return 0; + case LUA_TNIL: + field->type = MP_NIL; + return 0; + case LUA_TSTRING: + field->sval.data = lua_tolstring(L, index, &size); + field->sval.len = (uint32_t) size; + field->type = MP_STR; + return 0; + case LUA_TTABLE: + { + field->compact = false; + return lua_field_inspect_table(L, cfg, index, field); + } + case LUA_TLIGHTUSERDATA: + case LUA_TUSERDATA: + field->sval.data = NULL; + field->sval.len = 0; + if (lua_touserdata(L, index) == NULL) { + field->type = MP_NIL; + return 0; + } + /* Fall through */ + default: + field->type = MP_EXT; + field->ext_type = MP_UNKNOWN_EXTENSION; + } +#undef CHECK_NUMBER + return 0; +} + +void +luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, + struct luaL_field *field) +{ + if (idx < 0) + idx = lua_gettop(L) + idx + 1; + assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION); /* must be called after tofield() */ + + if (cfg->encode_load_metatables) { + int type = lua_type(L, idx); + if (type == LUA_TCDATA) { + /* + * Don't call __serialize on primitive types + * https://github.com/tarantool/tarantool/issues/1226 + */ + GCcdata *cd = cdataV(L->base + idx - 1); + if (cd->ctypeid > CTID_CTYPEID) + lua_field_inspect_ucdata(L, cfg, idx, field); + } else if (type == LUA_TUSERDATA) { + lua_field_inspect_ucdata(L, cfg, idx, field); + } + } + + if (field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION && + cfg->encode_use_tostring) + lua_field_tostring(L, cfg, idx, field); + + if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION) + return; + + if (cfg->encode_invalid_as_nil) { + field->type = MP_NIL; + return; + } + + luaL_error(L, "unsupported Lua type '%s'", + lua_typename(L, lua_type(L, idx))); +} + +int +tarantool_lua_serializer_init(struct lua_State *L) +{ + static const struct luaL_Reg serializermeta[] = { + {NULL, NULL}, + }; + luaL_register_type(L, LUAL_SERIALIZER, serializermeta); + + lua_createtable(L, 0, 1); + lua_pushliteral(L, "map"); /* YAML will use flow mode */ + lua_setfield(L, -2, LUAL_SERIALIZE); + /* automatically reset hints on table change */ + luaL_loadstring(L, "setmetatable((...), nil); return rawset(...)"); + lua_setfield(L, -2, "__newindex"); + luaL_map_metatable_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + lua_createtable(L, 0, 1); + lua_pushliteral(L, "seq"); /* YAML will use flow mode */ + lua_setfield(L, -2, LUAL_SERIALIZE); + /* automatically reset hints on table change */ + luaL_loadstring(L, "setmetatable((...), nil); return rawset(...)"); + lua_setfield(L, -2, "__newindex"); + luaL_array_metatable_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + return 0; +} diff --git a/src/lua/serializer.h b/src/lua/serializer.h new file mode 100644 index 000000000..54b0bc11a --- /dev/null +++ b/src/lua/serializer.h @@ -0,0 +1,344 @@ +#ifndef TARANTOOL_LUA_SERIALIZER_H_INCLUDED +#define TARANTOOL_LUA_SERIALIZER_H_INCLUDED +/* + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY ``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 + * 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. + */ + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +#include +#include +#include /* isfinite */ +#include +#include + +#include "trigger.h" +#include "lib/core/decimal.h" /* decimal_t */ +#include "lib/core/mp_extension_types.h" +#include "lua/error.h" + +struct serializer_opts; +struct lua_State; +struct tt_uuid; + +#define LUAL_SERIALIZER "serializer" +#define LUAL_SERIALIZE "__serialize" + +extern int luaL_map_metatable_ref; +extern int luaL_array_metatable_ref; + +/** + * Common configuration options for Lua serializers (MsgPack, YAML, JSON) + */ +struct luaL_serializer { + /** + * luaL_tofield tries to classify table into one of four kinds + * during encoding: + * + * + map - at least one table index is not unsigned integer. + * + regular array - all array indexes are available. + * + sparse array - at least one array index is missing. + * + excessively sparse arrat - the number of values missing + * exceeds the configured ratio. + * + * An array is excessively sparse when **all** the following + * conditions are met: + * + * + encode_sparse_ratio > 0. + * + max(table) > encode_sparse_safe. + * + max(table) > count(table) * encode_sparse_ratio. + * + * luaL_tofield will never consider an array to be excessively sparse + * when encode_sparse_ratio = 0. The encode_sparse_safe limit ensures + * that small Lua arrays are always encoded as sparse arrays. + * By default, attempting to encode an excessively sparse array will + * generate an error. If encode_sparse_convert is set to true, + * excessively sparse arrays will be handled as maps. + * + * This conversion logic is modeled after Mark Pulford's CJSON module. + * @sa http://www.kyne.com.au/~mark/software/lua-cjson-manual.html + */ + int encode_sparse_convert; + /** @see encode_sparse_convert */ + int encode_sparse_ratio; + /** @see encode_sparse_convert */ + int encode_sparse_safe; + /** Max recursion depth for encoding (MsgPack, CJSON only) */ + int encode_max_depth; + /** + * A flag whether a table with too high nest level should + * be cropped. The not-encoded fields are replaced with + * one null. If not set, too high nesting is considered an + * error. + */ + int encode_deep_as_nil; + /** Enables encoding of NaN and Inf numbers */ + int encode_invalid_numbers; + /** Floating point numbers precision (YAML, CJSON only) */ + int encode_number_precision; + + /** + * Enables __serialize meta-value checking: + * + * + 'seq', 'sequence', 'array' - table encoded as an array + * + 'map', 'mappping' - table encoded as a map. + * 'seq' or 'map' also enable flow (compact) mode for YAML serializer + * (flow="[1,2,3]" vs block=" - 1\n - 2\n - 3\n"). + * + function - the meta-method is called to unpack serializable + * representation of table, cdata or userdata objects. + */ + int encode_load_metatables; + /** Enables tostring() usage for unknown types */ + int encode_use_tostring; + /** Use NULL for all unrecognizable types */ + int encode_invalid_as_nil; + + /** Enables decoding NaN and Inf numbers */ + int decode_invalid_numbers; + /** Save __serialize meta-value for decoded arrays and maps */ + int decode_save_metatables; + /** Max recursion depts for decoding (CJSON only) */ + int decode_max_depth; + + /** Enable support for compact represenation (internal, YAML-only). */ + int has_compact; + /** + * Border where copyable fields end. Is used to copy + * serializer options into an existing serializer without + * erasure of its non-option fields like triggers. + */ + char end_of_options[0]; + /** + * Trigger object to subscribe on updates of a more + * general serializer. For example, tuple serializer + * subscribes on msgpack. + */ + struct trigger update_trigger; + /** + * List of triggers on update of this serializer. To push + * updates down to dependent serializers. + */ + struct rlist on_update; +}; + +struct luaL_serializer * +luaL_newserializer(struct lua_State *L, const char *modname, const luaL_Reg *reg); + +/** + * Copy all option fields of @a src into @a dst. Other fields, + * such as triggers, are not touched. + */ +void +luaL_serializer_copy_options(struct luaL_serializer *dst, + const struct luaL_serializer *src); + +static inline struct luaL_serializer * +luaL_checkserializer(struct lua_State *L) { + return (struct luaL_serializer *) + luaL_checkudata(L, lua_upvalueindex(1), LUAL_SERIALIZER); +} + +/** + * Initialize serializer with default parameters. + * @param cfg Serializer to inherit configuration. + */ +void +luaL_serializer_create(struct luaL_serializer *cfg); + +/** + * Parse configuration table into @a cfg. + * @param L Lua stack. + * @param cfg Serializer to inherit configuration. + */ +void +luaL_serializer_parse_options(struct lua_State *l, + struct luaL_serializer *cfg); + +/** A single value on the Lua stack. */ +struct luaL_field { + union { + struct { + const char *data; + uint32_t len; + } sval; + int64_t ival; + double dval; + float fval; + bool bval; + /* Array or map. */ + uint32_t size; + decimal_t *decval; + struct tt_uuid *uuidval; + }; + enum mp_type type; + /* subtypes of MP_EXT */ + enum mp_extension_type ext_type; + bool compact; /* a flag used by YAML serializer */ +}; + +/** + * @brief Convert a value from the Lua stack to a lua_field structure. + * This function is designed for use with Lua bindings and data + * serialization functions (YAML, MsgPack, JSON, etc.). + * + * Conversion rules: + * - LUA_TNUMBER when is integer and >= 0 -> UINT + * - LUA_TNUMBER when is integer and < 0 -> INT + * - LUA_TNUMBER when is not integer -> DOUBLE + * - LUA_TBOOLEAN -> BOOL + * - LUA_TSTRING -> STRING + * - LUA_TNIL -> NIL + * - LUA_TTABLE when is array -> ARRAY + * - LUA_TTABLE when is not array -> MAP + * - LUA_TUSERDATA, LUA_TLIGHTUSERDATA, CTID_P_VOID when == NULL -> NIL + * - CTID_INT*, CTID_CCHAR when >= 0 -> UINT + * - CTID_INT*, CTID_CCHAR when < 0 -> INT + * - CTID_FLOAT -> FLOAT + * - CTID_DOUBLE -> DOUBLE + * - CTID_BOOL -> BOOL + * - otherwise -> EXT + * + * ARRAY vs MAP recognition works based on encode_sparse_convert, + * encode_sparse_ratio, encode_sparse_safe and encode_load_metatables config + * parameters (see above). Tables are not saved to lua_field structure and + * should be processed manually, according to returned type and size value. + * + * This function doesn't try to unpack unknown types and simple returns MP_EXT. + * The caller can use luaL_tofield() for basic conversion, then invoke internal + * hooks(if available) and then call luaL_checkfield(), which will try to + * unpack cdata/userdata objects or raise and error. + * + * @param L stack + * @param cfg configuration + * @param opts the Lua serializer additional options. + * @param index stack index + * @param field conversion result + * + * @retval 0 Success. + * @retval -1 Error. + */ +int +luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, + const struct serializer_opts *opts, int index, + struct luaL_field *field); + +/** + * @brief Try to convert userdata/cdata values using defined conversion logic. + * Must be used only after lua_tofield(). + * + * @param L stack + * @param cfg configuration + * @param idx stack index + * @param field conversion result + */ +void +luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, + struct luaL_field *field); + +/** + * @brief A wrapper for luaL_tofield() and luaL_convertfield() that + * tries to convert value or raise an error. + * @param L stack + * @param cfg configuration + * @param idx stack index + * @param field conversion result + * @sa lua_tofield() + * @sa luaL_convertfield() + * + * Common conversion order for tables: + * size/count detection -> (sparse array checking) -> (__serialize) + * + * Common conversion order for userdata/cdata objects: + * (internal trigger) -> (__serialize) -> (tostring) -> (nil) -> exception + * + * Common conversion order for other types: + * (tostring) -> (nil) -> exception + */ +static inline void +luaL_checkfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, + struct luaL_field *field) +{ + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) + luaT_error(L); + if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION) + return; + luaL_convertfield(L, cfg, idx, field); +} + +/** + * Push Lua Table with __serialize = 'map' hint onto the stack. + * Tables with __serialize hint are properly handled by all serializers. + * @param L stack + * @param idx index in the stack + */ +static inline void +luaL_setmaphint(struct lua_State *L, int idx) +{ + if (idx < 0) + idx = lua_gettop(L) + idx + 1; + assert(lua_type(L, idx) == LUA_TTABLE); + lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_map_metatable_ref); + lua_setmetatable(L, idx); +} + +/** + * Push Lua Table with __serialize = 'seq' hint onto the stack. + * Tables with __serialize hint are properly handled by all serializers. + * @param L stack + * @param idx index in the stack + */ +static inline void +luaL_setarrayhint(struct lua_State *L, int idx) +{ + if (idx < 0) + idx = lua_gettop(L) + idx + 1; + assert(lua_type(L, idx) == LUA_TTABLE); + lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_array_metatable_ref); + lua_setmetatable(L, idx); +} + +static inline void +luaL_checkfinite(struct lua_State *L, struct luaL_serializer *cfg, + lua_Number number) +{ + if (!cfg->decode_invalid_numbers && !isfinite(number)) + luaL_error(L, "number must not be NaN or Inf"); +} + +int +tarantool_lua_serializer_init(struct lua_State *L); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + +#endif /* TARANTOOL_LUA_SERIALIZER_H_INCLUDED */ diff --git a/src/lua/utils.c b/src/lua/utils.c index 3ce821374..34cec0eed 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -38,11 +38,7 @@ #include #include -#include "serializer_opts.h" - int luaL_nil_ref = LUA_REFNIL; -int luaL_map_metatable_ref = LUA_REFNIL; -int luaL_array_metatable_ref = LUA_REFNIL; static int luaT_newthread_ref = LUA_NOREF; @@ -50,9 +46,7 @@ static uint32_t CTID_STRUCT_IBUF; static uint32_t CTID_STRUCT_IBUF_PTR; static uint32_t CTID_CHAR_PTR; static uint32_t CTID_CONST_CHAR_PTR; -static uint32_t CTID_UUID; -uint32_t CTID_DECIMAL; - +uint32_t CTID_UUID; void * luaL_pushcdata(struct lua_State *L, uint32_t ctypeid) @@ -239,578 +233,6 @@ luaL_setcdatagc(struct lua_State *L, int idx) } -#define OPTION(type, name, defvalue) { #name, \ - offsetof(struct luaL_serializer, name), type, defvalue} -/** - * Configuration options for serializers - * @sa struct luaL_serializer - */ -static struct { - const char *name; - size_t offset; /* offset in structure */ - int type; - int defvalue; -} OPTIONS[] = { - OPTION(LUA_TBOOLEAN, encode_sparse_convert, 1), - OPTION(LUA_TNUMBER, encode_sparse_ratio, 2), - OPTION(LUA_TNUMBER, encode_sparse_safe, 10), - OPTION(LUA_TNUMBER, encode_max_depth, 128), - OPTION(LUA_TBOOLEAN, encode_deep_as_nil, 0), - OPTION(LUA_TBOOLEAN, encode_invalid_numbers, 1), - OPTION(LUA_TNUMBER, encode_number_precision, 14), - OPTION(LUA_TBOOLEAN, encode_load_metatables, 1), - OPTION(LUA_TBOOLEAN, encode_use_tostring, 0), - OPTION(LUA_TBOOLEAN, encode_invalid_as_nil, 0), - OPTION(LUA_TBOOLEAN, decode_invalid_numbers, 1), - OPTION(LUA_TBOOLEAN, decode_save_metatables, 1), - OPTION(LUA_TNUMBER, decode_max_depth, 128), - { NULL, 0, 0, 0}, -}; - -void -luaL_serializer_create(struct luaL_serializer *cfg) -{ - rlist_create(&cfg->on_update); - for (int i = 0; OPTIONS[i].name != NULL; i++) { - int *pval = (int *) ((char *) cfg + OPTIONS[i].offset); - *pval = OPTIONS[i].defvalue; - } -} - -void -luaL_serializer_copy_options(struct luaL_serializer *dst, - const struct luaL_serializer *src) -{ - memcpy(dst, src, offsetof(struct luaL_serializer, end_of_options)); -} - -/** - * Configure one field in @a cfg. Value of the field is kept on - * Lua stack after this function, and should be popped manually. - * @param L Lua stack. - * @param i Index of option in OPTIONS[]. - * @param cfg Serializer to inherit configuration. - * @retval Pointer to the value of option. - * @retval NULL if option is not in the table. - */ -static int * -luaL_serializer_parse_option(struct lua_State *L, int i, - struct luaL_serializer *cfg) -{ - lua_getfield(L, 2, OPTIONS[i].name); - if (lua_isnil(L, -1)) - return NULL; - /* - * Update struct luaL_serializer using pointer to a - * configuration value (all values must be `int` for that). - */ - int *pval = (int *) ((char *) cfg + OPTIONS[i].offset); - switch (OPTIONS[i].type) { - case LUA_TBOOLEAN: - *pval = lua_toboolean(L, -1); - break; - case LUA_TNUMBER: - *pval = lua_tointeger(L, -1); - break; - default: - unreachable(); - } - return pval; -} - -void -luaL_serializer_parse_options(struct lua_State *L, - struct luaL_serializer *cfg) -{ - for (int i = 0; OPTIONS[i].name != NULL; ++i) { - luaL_serializer_parse_option(L, i, cfg); - lua_pop(L, 1); - } -} - -/** - * @brief serializer.cfg{} Lua binding for serializers. - * serializer.cfg is a table that contains current configuration values from - * luaL_serializer structure. serializer.cfg has overriden __call() method - * to change configuration keys in internal userdata (like box.cfg{}). - * Please note that direct change in serializer.cfg.key will not affect - * internal state of userdata. Changes via cfg() are reflected in - * both Lua cfg table, and C serializer structure. - * @param L lua stack - * @return 0 - */ -static int -luaL_serializer_cfg(struct lua_State *L) -{ - /* Serializer.cfg */ - luaL_checktype(L, 1, LUA_TTABLE); - /* Updated parameters. */ - luaL_checktype(L, 2, LUA_TTABLE); - struct luaL_serializer *cfg = luaL_checkserializer(L); - for (int i = 0; OPTIONS[i].name != NULL; ++i) { - if (luaL_serializer_parse_option(L, i, cfg) == NULL) - lua_pop(L, 1); - else - lua_setfield(L, 1, OPTIONS[i].name); - } - trigger_run(&cfg->on_update, cfg); - return 0; -} - -/** - * @brief serializer.new() Lua binding. - * @param L stack - * @param reg methods to register - * @param parent parent serializer to inherit configuration - * @return new serializer - */ -struct luaL_serializer * -luaL_newserializer(struct lua_State *L, const char *modname, const luaL_Reg *reg) -{ - luaL_checkstack(L, 1, "too many upvalues"); - - /* Create new module */ - lua_newtable(L); - - /* Create new configuration */ - struct luaL_serializer *serializer = (struct luaL_serializer *) - lua_newuserdata(L, sizeof(*serializer)); - luaL_getmetatable(L, LUAL_SERIALIZER); - lua_setmetatable(L, -2); - luaL_serializer_create(serializer); - - for (; reg->name != NULL; reg++) { - /* push luaL_serializer as upvalue */ - lua_pushvalue(L, -1); - /* register method */ - lua_pushcclosure(L, reg->func, 1); - lua_setfield(L, -3, reg->name); - } - - /* Add cfg{} */ - lua_newtable(L); /* cfg */ - lua_newtable(L); /* metatable */ - lua_pushvalue(L, -3); /* luaL_serializer */ - lua_pushcclosure(L, luaL_serializer_cfg, 1); - lua_setfield(L, -2, "__call"); - lua_setmetatable(L, -2); - /* Save configuration values to serializer.cfg */ - for (int i = 0; OPTIONS[i].name != NULL; i++) { - int *pval = (int *) ((char *) serializer + OPTIONS[i].offset); - switch (OPTIONS[i].type) { - case LUA_TBOOLEAN: - lua_pushboolean(L, *pval); - break; - case LUA_TNUMBER: - lua_pushinteger(L, *pval); - break; - default: - unreachable(); - } - lua_setfield(L, -2, OPTIONS[i].name); - } - lua_setfield(L, -3, "cfg"); - - lua_pop(L, 1); /* remove upvalues */ - - luaL_pushnull(L); - lua_setfield(L, -2, "NULL"); - lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_array_metatable_ref); - lua_setfield(L, -2, "array_mt"); - lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_map_metatable_ref); - lua_setfield(L, -2, "map_mt"); - - if (modname != NULL) { - /* Register module */ - lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); - lua_pushstring(L, modname); /* add alias */ - lua_pushvalue(L, -3); - lua_settable(L, -3); - lua_pop(L, 1); /* _LOADED */ - } - - return serializer; -} - -static int -lua_gettable_wrapper(lua_State *L) -{ - lua_gettable(L, -2); - return 1; -} - -static void -lua_field_inspect_ucdata(struct lua_State *L, struct luaL_serializer *cfg, - int idx, struct luaL_field *field) -{ - if (!cfg->encode_load_metatables) - return; - - /* - * Try to call LUAL_SERIALIZE method on udata/cdata - * LuaJIT specific: lua_getfield/lua_gettable raises exception on - * cdata if field doesn't exist. - */ - int top = lua_gettop(L); - lua_pushcfunction(L, lua_gettable_wrapper); - lua_pushvalue(L, idx); - lua_pushliteral(L, LUAL_SERIALIZE); - if (lua_pcall(L, 2, 1, 0) == 0 && !lua_isnil(L, -1)) { - if (!lua_isfunction(L, -1)) - luaL_error(L, "invalid " LUAL_SERIALIZE " value"); - /* copy object itself */ - lua_pushvalue(L, idx); - lua_pcall(L, 1, 1, 0); - /* replace obj with the unpacked value */ - lua_replace(L, idx); - if (luaL_tofield(L, cfg, NULL, idx, field) < 0) - luaT_error(L); - } /* else ignore lua_gettable exceptions */ - lua_settop(L, top); /* remove temporary objects */ -} - -/** - * Call __serialize method of a table object by index - * if the former exists. - * - * If __serialize does not exist then function does nothing - * and the function returns 1; - * - * If __serialize exists, is a function (which doesn't - * raise any error) then a result of serialization - * replaces old value by the index and the function returns 0; - * - * If the serialization is a hint string (like 'array' or 'map'), - * then field->type, field->size and field->compact - * are set if necessary and the function returns 0; - * - * Otherwise it is an error, set diag and the funciton returns -1; - * - * Return values: - * -1 - error occurs, diag is set, the top of guest stack - * is undefined. - * 0 - __serialize field is available in the metatable, - * the result value is put in the origin slot, - * encoding is finished. - * 1 - __serialize field is not available in the metatable, - * proceed with default table encoding. - */ -static int -lua_field_try_serialize(struct lua_State *L, struct luaL_serializer *cfg, - int idx, struct luaL_field *field) -{ - if (luaL_getmetafield(L, idx, LUAL_SERIALIZE) == 0) - return 1; - if (lua_isfunction(L, -1)) { - /* copy object itself */ - lua_pushvalue(L, idx); - if (lua_pcall(L, 1, 1, 0) != 0) { - diag_set(LuajitError, lua_tostring(L, -1)); - return -1; - } - if (luaL_tofield(L, cfg, NULL, -1, field) != 0) - return -1; - lua_replace(L, idx); - return 0; - } - if (!lua_isstring(L, -1)) { - diag_set(LuajitError, "invalid " LUAL_SERIALIZE " value"); - return -1; - } - const char *type = lua_tostring(L, -1); - if (strcmp(type, "array") == 0 || strcmp(type, "seq") == 0 || - strcmp(type, "sequence") == 0) { - field->type = MP_ARRAY; /* Override type */ - field->size = luaL_arrlen(L, idx); - /* YAML: use flow mode if __serialize == 'seq' */ - if (cfg->has_compact && type[3] == '\0') - field->compact = true; - } else if (strcmp(type, "map") == 0 || strcmp(type, "mapping") == 0) { - field->type = MP_MAP; /* Override type */ - field->size = luaL_maplen(L, idx); - /* YAML: use flow mode if __serialize == 'map' */ - if (cfg->has_compact && type[3] == '\0') - field->compact = true; - } else { - diag_set(LuajitError, "invalid " LUAL_SERIALIZE " value"); - return -1; - } - /* Remove value set by luaL_getmetafield. */ - lua_pop(L, 1); - return 0; -} - -static int -lua_field_inspect_table(struct lua_State *L, struct luaL_serializer *cfg, - int idx, struct luaL_field *field) -{ - assert(lua_type(L, idx) == LUA_TTABLE); - uint32_t size = 0; - uint32_t max = 0; - - if (cfg->encode_load_metatables) { - int top = lua_gettop(L); - int res = lua_field_try_serialize(L, cfg, idx, field); - if (res == -1) - return -1; - assert(lua_gettop(L) == top); - (void)top; - if (res == 0) - return 0; - /* Fallthrough with res == 1 */ - } - - field->type = MP_ARRAY; - - /* Calculate size and check that table can represent an array */ - lua_pushnil(L); - while (lua_next(L, idx)) { - size++; - lua_pop(L, 1); /* pop the value */ - lua_Number k; - if (lua_type(L, -1) != LUA_TNUMBER || - ((k = lua_tonumber(L, -1)) != size && - (k < 1 || floor(k) != k))) { - /* Finish size calculation */ - while (lua_next(L, idx)) { - size++; - lua_pop(L, 1); /* pop the value */ - } - field->type = MP_MAP; - field->size = size; - return 0; - } - if (k > max) - max = k; - } - - /* Encode excessively sparse arrays as objects (if enabled) */ - if (cfg->encode_sparse_ratio > 0 && - max > size * (uint32_t)cfg->encode_sparse_ratio && - max > (uint32_t)cfg->encode_sparse_safe) { - if (!cfg->encode_sparse_convert) { - diag_set(LuajitError, "excessively sparse array"); - return -1; - } - field->type = MP_MAP; - field->size = size; - return 0; - } - - assert(field->type == MP_ARRAY); - field->size = max; - return 0; -} - -static void -lua_field_tostring(struct lua_State *L, struct luaL_serializer *cfg, int idx, - struct luaL_field *field) -{ - int top = lua_gettop(L); - lua_getglobal(L, "tostring"); - lua_pushvalue(L, idx); - lua_call(L, 1, 1); - lua_replace(L, idx); - lua_settop(L, top); - if (luaL_tofield(L, cfg, NULL, idx, field) < 0) - luaT_error(L); -} - -int -luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, - const struct serializer_opts *opts, int index, - struct luaL_field *field) -{ - if (index < 0) - index = lua_gettop(L) + index + 1; - - double num; - double intpart; - size_t size; - -#define CHECK_NUMBER(x) ({ \ - if (!isfinite(x) && !cfg->encode_invalid_numbers) { \ - if (!cfg->encode_invalid_as_nil) { \ - diag_set(LuajitError, "number must not be NaN or Inf"); \ - return -1; \ - } \ - field->type = MP_NIL; \ - }}) - - switch (lua_type(L, index)) { - case LUA_TNUMBER: - num = lua_tonumber(L, index); - if (isfinite(num) && modf(num, &intpart) != 0.0) { - field->type = MP_DOUBLE; - field->dval = num; - } else if (num >= 0 && num < exp2(64)) { - field->type = MP_UINT; - field->ival = (uint64_t) num; - } else if (num >= -exp2(63) && num < exp2(63)) { - field->type = MP_INT; - field->ival = (int64_t) num; - } else { - field->type = MP_DOUBLE; - field->dval = num; - CHECK_NUMBER(num); - } - return 0; - case LUA_TCDATA: - { - GCcdata *cd = cdataV(L->base + index - 1); - void *cdata = (void *)cdataptr(cd); - - int64_t ival; - switch (cd->ctypeid) { - case CTID_BOOL: - field->type = MP_BOOL; - field->bval = *(bool*) cdata; - return 0; - case CTID_CCHAR: - case CTID_INT8: - ival = *(int8_t *) cdata; - field->type = (ival >= 0) ? MP_UINT : MP_INT; - field->ival = ival; - return 0; - case CTID_INT16: - ival = *(int16_t *) cdata; - field->type = (ival >= 0) ? MP_UINT : MP_INT; - field->ival = ival; - return 0; - case CTID_INT32: - ival = *(int32_t *) cdata; - field->type = (ival >= 0) ? MP_UINT : MP_INT; - field->ival = ival; - return 0; - case CTID_INT64: - ival = *(int64_t *) cdata; - field->type = (ival >= 0) ? MP_UINT : MP_INT; - field->ival = ival; - return 0; - case CTID_UINT8: - field->type = MP_UINT; - field->ival = *(uint8_t *) cdata; - return 0; - case CTID_UINT16: - field->type = MP_UINT; - field->ival = *(uint16_t *) cdata; - return 0; - case CTID_UINT32: - field->type = MP_UINT; - field->ival = *(uint32_t *) cdata; - return 0; - case CTID_UINT64: - field->type = MP_UINT; - field->ival = *(uint64_t *) cdata; - return 0; - case CTID_FLOAT: - field->type = MP_FLOAT; - field->fval = *(float *) cdata; - CHECK_NUMBER(field->fval); - return 0; - case CTID_DOUBLE: - field->type = MP_DOUBLE; - field->dval = *(double *) cdata; - CHECK_NUMBER(field->dval); - return 0; - case CTID_P_CVOID: - case CTID_P_VOID: - if (*(void **) cdata == NULL) { - field->type = MP_NIL; - return 0; - } - /* Fall through */ - default: - field->type = MP_EXT; - if (cd->ctypeid == CTID_DECIMAL) { - field->ext_type = MP_DECIMAL; - field->decval = (decimal_t *) cdata; - } else if (cd->ctypeid == CTID_UUID) { - field->ext_type = MP_UUID; - field->uuidval = (struct tt_uuid *) cdata; - } else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF && - opts != NULL && - opts->error_marshaling_enabled) { - field->ext_type = MP_ERROR; - } else { - field->ext_type = MP_UNKNOWN_EXTENSION; - } - } - return 0; - } - case LUA_TBOOLEAN: - field->type = MP_BOOL; - field->bval = lua_toboolean(L, index); - return 0; - case LUA_TNIL: - field->type = MP_NIL; - return 0; - case LUA_TSTRING: - field->sval.data = lua_tolstring(L, index, &size); - field->sval.len = (uint32_t) size; - field->type = MP_STR; - return 0; - case LUA_TTABLE: - { - field->compact = false; - return lua_field_inspect_table(L, cfg, index, field); - } - case LUA_TLIGHTUSERDATA: - case LUA_TUSERDATA: - field->sval.data = NULL; - field->sval.len = 0; - if (lua_touserdata(L, index) == NULL) { - field->type = MP_NIL; - return 0; - } - /* Fall through */ - default: - field->type = MP_EXT; - field->ext_type = MP_UNKNOWN_EXTENSION; - } -#undef CHECK_NUMBER - return 0; -} - -void -luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, - struct luaL_field *field) -{ - if (idx < 0) - idx = lua_gettop(L) + idx + 1; - assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION); /* must be called after tofield() */ - - if (cfg->encode_load_metatables) { - int type = lua_type(L, idx); - if (type == LUA_TCDATA) { - /* - * Don't call __serialize on primitive types - * https://github.com/tarantool/tarantool/issues/1226 - */ - GCcdata *cd = cdataV(L->base + idx - 1); - if (cd->ctypeid > CTID_CTYPEID) - lua_field_inspect_ucdata(L, cfg, idx, field); - } else if (type == LUA_TUSERDATA) { - lua_field_inspect_ucdata(L, cfg, idx, field); - } - } - - if (field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION && - cfg->encode_use_tostring) - lua_field_tostring(L, cfg, idx, field); - - if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION) - return; - - if (cfg->encode_invalid_as_nil) { - field->type = MP_NIL; - return; - } - - luaL_error(L, "unsupported Lua type '%s'", - lua_typename(L, lua_type(L, idx))); -} - /** * A helper to register a single type metatable. */ @@ -1263,31 +685,10 @@ luaT_newthread(struct lua_State *L) int tarantool_lua_utils_init(struct lua_State *L) { - static const struct luaL_Reg serializermeta[] = { - {NULL, NULL}, - }; - - luaL_register_type(L, LUAL_SERIALIZER, serializermeta); /* Create NULL constant */ *(void **) luaL_pushcdata(L, CTID_P_VOID) = NULL; luaL_nil_ref = luaL_ref(L, LUA_REGISTRYINDEX); - lua_createtable(L, 0, 1); - lua_pushliteral(L, "map"); /* YAML will use flow mode */ - lua_setfield(L, -2, LUAL_SERIALIZE); - /* automatically reset hints on table change */ - luaL_loadstring(L, "setmetatable((...), nil); return rawset(...)"); - lua_setfield(L, -2, "__newindex"); - luaL_map_metatable_ref = luaL_ref(L, LUA_REGISTRYINDEX); - - lua_createtable(L, 0, 1); - lua_pushliteral(L, "seq"); /* YAML will use flow mode */ - lua_setfield(L, -2, LUAL_SERIALIZE); - /* automatically reset hints on table change */ - luaL_loadstring(L, "setmetatable((...), nil); return rawset(...)"); - lua_setfield(L, -2, "__newindex"); - luaL_array_metatable_ref = luaL_ref(L, LUA_REGISTRYINDEX); - int rc = luaL_cdef(L, "struct ibuf;"); assert(rc == 0); CTID_STRUCT_IBUF = luaL_ctypeid(L, "struct ibuf"); diff --git a/src/lua/utils.h b/src/lua/utils.h index 4a164868b..947d9240b 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -33,10 +33,9 @@ #include #include -#include /* modf, isfinite */ +#include /* floor */ #include /* enum mp_type */ -#include "trigger.h" #if defined(__cplusplus) extern "C" { @@ -56,10 +55,6 @@ extern "C" { #include "lua/error.h" -#include "lib/core/mp_extension_types.h" -#include "lib/core/decimal.h" /* decimal_t */ - -struct serializer_opts; struct lua_State; struct ibuf; typedef struct ibuf box_ibuf_t; @@ -73,6 +68,8 @@ struct tt_uuid; */ extern struct lua_State *tarantool_L; +extern uint32_t CTID_UUID; + struct tt_uuid * luaL_pushuuid(struct lua_State *L); @@ -197,250 +194,7 @@ luaL_maplen(struct lua_State *L, int idx) return size; } -/** - * Common configuration options for Lua serializers (MsgPack, YAML, JSON) - */ -struct luaL_serializer { - /** - * luaL_tofield tries to classify table into one of four kinds - * during encoding: - * - * + map - at least one table index is not unsigned integer. - * + regular array - all array indexes are available. - * + sparse array - at least one array index is missing. - * + excessively sparse arrat - the number of values missing - * exceeds the configured ratio. - * - * An array is excessively sparse when **all** the following - * conditions are met: - * - * + encode_sparse_ratio > 0. - * + max(table) > encode_sparse_safe. - * + max(table) > count(table) * encode_sparse_ratio. - * - * luaL_tofield will never consider an array to be excessively sparse - * when encode_sparse_ratio = 0. The encode_sparse_safe limit ensures - * that small Lua arrays are always encoded as sparse arrays. - * By default, attempting to encode an excessively sparse array will - * generate an error. If encode_sparse_convert is set to true, - * excessively sparse arrays will be handled as maps. - * - * This conversion logic is modeled after Mark Pulford's CJSON module. - * @sa http://www.kyne.com.au/~mark/software/lua-cjson-manual.html - */ - int encode_sparse_convert; - /** @see encode_sparse_convert */ - int encode_sparse_ratio; - /** @see encode_sparse_convert */ - int encode_sparse_safe; - /** Max recursion depth for encoding (MsgPack, CJSON only) */ - int encode_max_depth; - /** - * A flag whether a table with too high nest level should - * be cropped. The not-encoded fields are replaced with - * one null. If not set, too high nesting is considered an - * error. - */ - int encode_deep_as_nil; - /** Enables encoding of NaN and Inf numbers */ - int encode_invalid_numbers; - /** Floating point numbers precision (YAML, CJSON only) */ - int encode_number_precision; - - /** - * Enables __serialize meta-value checking: - * - * + 'seq', 'sequence', 'array' - table encoded as an array - * + 'map', 'mappping' - table encoded as a map. - * 'seq' or 'map' also enable flow (compact) mode for YAML serializer - * (flow="[1,2,3]" vs block=" - 1\n - 2\n - 3\n"). - * + function - the meta-method is called to unpack serializable - * representation of table, cdata or userdata objects. - */ - int encode_load_metatables; - /** Enables tostring() usage for unknown types */ - int encode_use_tostring; - /** Use NULL for all unrecognizable types */ - int encode_invalid_as_nil; - - /** Enables decoding NaN and Inf numbers */ - int decode_invalid_numbers; - /** Save __serialize meta-value for decoded arrays and maps */ - int decode_save_metatables; - /** Max recursion depts for decoding (CJSON only) */ - int decode_max_depth; - - /** Enable support for compact represenation (internal, YAML-only). */ - int has_compact; - /** - * Border where copyable fields end. Is used to copy - * serializer options into an existing serializer without - * erasure of its non-option fields like triggers. - */ - char end_of_options[0]; - /** - * Trigger object to subscribe on updates of a more - * general serializer. For example, tuple serializer - * subscribes on msgpack. - */ - struct trigger update_trigger; - /** - * List of triggers on update of this serializer. To push - * updates down to dependent serializers. - */ - struct rlist on_update; -}; - extern int luaL_nil_ref; -extern int luaL_map_metatable_ref; -extern int luaL_array_metatable_ref; - -#define LUAL_SERIALIZER "serializer" -#define LUAL_SERIALIZE "__serialize" - -struct luaL_serializer * -luaL_newserializer(struct lua_State *L, const char *modname, const luaL_Reg *reg); - -/** - * Copy all option fields of @a src into @a dst. Other fields, - * such as triggers, are not touched. - */ -void -luaL_serializer_copy_options(struct luaL_serializer *dst, - const struct luaL_serializer *src); - -static inline struct luaL_serializer * -luaL_checkserializer(struct lua_State *L) { - return (struct luaL_serializer *) - luaL_checkudata(L, lua_upvalueindex(1), LUAL_SERIALIZER); -} - -/** - * Initialize serializer with default parameters. - * @param cfg Serializer to inherit configuration. - */ -void -luaL_serializer_create(struct luaL_serializer *cfg); - -/** - * Parse configuration table into @a cfg. - * @param L Lua stack. - * @param cfg Serializer to inherit configuration. - */ -void -luaL_serializer_parse_options(struct lua_State *l, - struct luaL_serializer *cfg); - -/** A single value on the Lua stack. */ -struct luaL_field { - union { - struct { - const char *data; - uint32_t len; - } sval; - int64_t ival; - double dval; - float fval; - bool bval; - /* Array or map. */ - uint32_t size; - decimal_t *decval; - struct tt_uuid *uuidval; - }; - enum mp_type type; - /* subtypes of MP_EXT */ - enum mp_extension_type ext_type; - bool compact; /* a flag used by YAML serializer */ -}; - -/** - * @brief Convert a value from the Lua stack to a lua_field structure. - * This function is designed for use with Lua bindings and data - * serialization functions (YAML, MsgPack, JSON, etc.). - * - * Conversion rules: - * - LUA_TNUMBER when is integer and >= 0 -> UINT - * - LUA_TNUMBER when is integer and < 0 -> INT - * - LUA_TNUMBER when is not integer -> DOUBLE - * - LUA_TBOOLEAN -> BOOL - * - LUA_TSTRING -> STRING - * - LUA_TNIL -> NIL - * - LUA_TTABLE when is array -> ARRAY - * - LUA_TTABLE when is not array -> MAP - * - LUA_TUSERDATA, LUA_TLIGHTUSERDATA, CTID_P_VOID when == NULL -> NIL - * - CTID_INT*, CTID_CCHAR when >= 0 -> UINT - * - CTID_INT*, CTID_CCHAR when < 0 -> INT - * - CTID_FLOAT -> FLOAT - * - CTID_DOUBLE -> DOUBLE - * - CTID_BOOL -> BOOL - * - otherwise -> EXT - * - * ARRAY vs MAP recognition works based on encode_sparse_convert, - * encode_sparse_ratio, encode_sparse_safe and encode_load_metatables config - * parameters (see above). Tables are not saved to lua_field structure and - * should be processed manually, according to returned type and size value. - * - * This function doesn't try to unpack unknown types and simple returns MP_EXT. - * The caller can use luaL_tofield() for basic conversion, then invoke internal - * hooks(if available) and then call luaL_checkfield(), which will try to - * unpack cdata/userdata objects or raise and error. - * - * @param L stack - * @param cfg configuration - * @param opts the Lua serializer additional options. - * @param index stack index - * @param field conversion result - * - * @retval 0 Success. - * @retval -1 Error. - */ -int -luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, - const struct serializer_opts *opts, int index, - struct luaL_field *field); - -/** - * @brief Try to convert userdata/cdata values using defined conversion logic. - * Must be used only after lua_tofield(). - * - * @param L stack - * @param cfg configuration - * @param idx stack index - * @param field conversion result - */ -void -luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, - struct luaL_field *field); - -/** - * @brief A wrapper for luaL_tofield() and luaL_convertfield() that - * tries to convert value or raise an error. - * @param L stack - * @param cfg configuration - * @param idx stack index - * @param field conversion result - * @sa lua_tofield() - * @sa luaL_convertfield() - * - * Common conversion order for tables: - * size/count detection -> (sparse array checking) -> (__serialize) - * - * Common conversion order for userdata/cdata objects: - * (internal trigger) -> (__serialize) -> (tostring) -> (nil) -> exception - * - * Common conversion order for other types: - * (tostring) -> (nil) -> exception - */ -static inline void -luaL_checkfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, - struct luaL_field *field) -{ - if (luaL_tofield(L, cfg, NULL, idx, field) < 0) - luaT_error(L); - if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION) - return; - luaL_convertfield(L, cfg, idx, field); -} void luaL_register_type(struct lua_State *L, const char *type_name, @@ -556,38 +310,6 @@ luaT_toibuf(struct lua_State *L, int idx); int luaT_toerror(lua_State *L); -/** - * Push Lua Table with __serialize = 'map' hint onto the stack. - * Tables with __serialize hint are properly handled by all serializers. - * @param L stack - * @param idx index in the stack - */ -static inline void -luaL_setmaphint(struct lua_State *L, int idx) -{ - if (idx < 0) - idx = lua_gettop(L) + idx + 1; - assert(lua_type(L, idx) == LUA_TTABLE); - lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_map_metatable_ref); - lua_setmetatable(L, idx); -} - -/** - * Push Lua Table with __serialize = 'seq' hint onto the stack. - * Tables with __serialize hint are properly handled by all serializers. - * @param L stack - * @param idx index in the stack - */ -static inline void -luaL_setarrayhint(struct lua_State *L, int idx) -{ - if (idx < 0) - idx = lua_gettop(L) + idx + 1; - assert(lua_type(L, idx) == LUA_TTABLE); - lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_array_metatable_ref); - lua_setmetatable(L, idx); -} - /** * Push ffi's NULL (cdata: NULL) onto the stack. * Can be used as replacement of nil in Lua tables. @@ -616,14 +338,6 @@ luaL_isnull(struct lua_State *L, int idx) return false; } -static inline void -luaL_checkfinite(struct lua_State *L, struct luaL_serializer *cfg, - lua_Number number) -{ - if (!cfg->decode_invalid_numbers && !isfinite(number)) - luaL_error(L, "number must not be NaN or Inf"); -} - /** * @brief Creates a new Lua coroutine in a protected frame. If * call underneath succeeds, the created Lua state diff --git a/third_party/lua-cjson/lua_cjson.c b/third_party/lua-cjson/lua_cjson.c index dbd86cbab..5123b9a74 100644 --- a/third_party/lua-cjson/lua_cjson.c +++ b/third_party/lua-cjson/lua_cjson.c @@ -48,6 +48,7 @@ #include "strbuf.h" #include "lua/utils.h" +#include "lua/serializer.h" #include "mp_extension_types.h" /* MP_DECIMAL, MP_UUID */ #include "tt_static.h" #include "uuid/tt_uuid.h" /* tt_uuid_to_string(), UUID_STR_LEN */ diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc index 9c3a4a646..5469e9f4f 100644 --- a/third_party/lua-yaml/lyaml.cc +++ b/third_party/lua-yaml/lyaml.cc @@ -49,6 +49,7 @@ extern "C" { #include "b64.h" } /* extern "C" */ #include "lua/utils.h" +#include "lua/serializer.h" #include "lib/core/decimal.h" #include "tt_static.h" #include "mp_extension_types.h" /* MP_DECIMAL, MP_UUID */ -- 2.31.1