diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e004f2154168252eeaf288b1ac0b19d97e06c21f..b8542656144dab77dcc1866f8861c5b7a6fec6da 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,7 @@ lua_source(lua_sources lua/fiber.lua) lua_source(lua_sources lua/buffer.lua) lua_source(lua_sources lua/uuid.lua) lua_source(lua_sources lua/crypto.lua) +lua_source(lua_sources lua/error.lua) lua_source(lua_sources lua/digest.lua) lua_source(lua_sources lua/msgpackffi.lua) lua_source(lua_sources lua/uri.lua) @@ -173,6 +174,7 @@ set (server_sources lua/utils.c lua/errno.c lua/tnt_iconv.c + lua/error.c lua/socket.c lua/pickle.c lua/fio.c @@ -195,6 +197,7 @@ set(api_headers ${CMAKE_SOURCE_DIR}/src/coio.h ${CMAKE_SOURCE_DIR}/src/coio_task.h ${CMAKE_SOURCE_DIR}/src/lua/utils.h + ${CMAKE_SOURCE_DIR}/src/lua/error.h ${CMAKE_SOURCE_DIR}/src/box/txn.h ${CMAKE_SOURCE_DIR}/src/box/key_def.h ${CMAKE_SOURCE_DIR}/src/box/field_def.h diff --git a/src/lua/error.c b/src/lua/error.c new file mode 100644 index 0000000000000000000000000000000000000000..71345927deb71a5499b256ac495a5c166d0c23e3 --- /dev/null +++ b/src/lua/error.c @@ -0,0 +1,116 @@ +/* + * Copyright 2010-2018, 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 <COPYRIGHT HOLDER> ``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 + * <COPYRIGHT HOLDER> 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 "error.h" + +#include <diag.h> +#include <fiber.h> +#include "utils.h" + +static int CTID_CONST_STRUCT_ERROR_REF = 0; + +struct error * +luaL_iserror(struct lua_State *L, int narg) +{ + assert(CTID_CONST_STRUCT_ERROR_REF != 0); + if (lua_type(L, narg) != LUA_TCDATA) + return NULL; + + uint32_t ctypeid; + void *data = luaL_checkcdata(L, narg, &ctypeid); + if (ctypeid != (uint32_t) CTID_CONST_STRUCT_ERROR_REF) + return NULL; + + struct error *e = *(struct error **) data; + assert(e->refs); + return e; +} + +static struct error * +luaL_checkerror(struct lua_State *L, int narg) +{ + struct error *error = luaL_iserror(L, narg); + if (error == NULL) { + luaL_error(L, "Invalid argument #%d (error expected, got %s)", + narg, lua_typename(L, lua_type(L, narg))); + } + return error; +} + +static int +luaL_error_gc(struct lua_State *L) +{ + struct error *error = luaL_checkerror(L, 1); + error_unref(error); + return 0; +} + +void +luaT_pusherror(struct lua_State *L, struct error *e) +{ + assert(CTID_CONST_STRUCT_ERROR_REF != 0); + struct error **ptr = (struct error **) + luaL_pushcdata(L, CTID_CONST_STRUCT_ERROR_REF); + *ptr = e; + /* The order is important - first reference the error, then set gc */ + error_ref(e); + lua_pushcfunction(L, luaL_error_gc); + luaL_setcdatagc(L, -2); +} + +int +luaT_error(lua_State *L) +{ + struct error *e = diag_last_error(&fiber()->diag); + assert(e != NULL); + /* + * gh-1955 luaT_pusherror allocates Lua objects, thus it may trigger + * GC. GC may invoke finalizers which are arbitrary Lua code, + * potentially invalidating last error object, hence error_ref + * below. + */ + error_ref(e); + luaT_pusherror(L, e); + error_unref(e); + lua_error(L); + unreachable(); + return 0; +} + +void +tarantool_lua_error_init(struct lua_State *L) +{ + /* Get CTypeID for `struct error *' */ + int rc = luaL_cdef(L, "struct error;"); + assert(rc == 0); + (void) rc; + CTID_CONST_STRUCT_ERROR_REF = luaL_ctypeid(L, "const struct error &"); + assert(CTID_CONST_STRUCT_ERROR_REF != 0); +} diff --git a/src/lua/error.h b/src/lua/error.h new file mode 100644 index 0000000000000000000000000000000000000000..67a43da863f7b89eee99fe39f1e6ccf3807faf77 --- /dev/null +++ b/src/lua/error.h @@ -0,0 +1,66 @@ +#ifndef TARANTOOL_LUA_ERROR_H +#define TARANTOOL_LUA_ERROR_H +/* + * Copyright 2010-2018, 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 <COPYRIGHT HOLDER> ``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 + * <COPYRIGHT HOLDER> 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 <lua.h> +#include <lauxlib.h> /* luaL_error */ + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + + +/** \cond public */ +struct error; + +/** + * Re-throws the last Tarantool error as a Lua object. + * \sa lua_error() + * \sa box_error_last() + */ +LUA_API int +luaT_error(lua_State *L); + +void +luaT_pusherror(struct lua_State *L, struct error *e); +/** \endcond public */ + +struct error * +luaL_iserror(struct lua_State *L, int narg); + +void +tarantool_lua_error_init(struct lua_State *L); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + +#endif /* TARANTOOL_LUA_ERROR_H */ diff --git a/src/lua/error.lua b/src/lua/error.lua new file mode 100644 index 0000000000000000000000000000000000000000..28fc0377d1887b416194967845469bc5a6bebd7b --- /dev/null +++ b/src/lua/error.lua @@ -0,0 +1,160 @@ +-- error.lua (internal file) + +local ffi = require('ffi') +ffi.cdef[[ +struct type_info; + +enum { + DIAG_ERRMSG_MAX = 512, + DIAG_FILENAME_MAX = 256 +}; + +typedef void (*error_f)(struct error *e); + +struct error { + error_f _destroy; + error_f _raise; + error_f _log; + const struct type_info *_type; + int _refs; + /** Line number. */ + unsigned _line; + /* Source file name. */ + char _file[DIAG_FILENAME_MAX]; + /* Error description. */ + char _errmsg[DIAG_ERRMSG_MAX]; +}; + +char * +exception_get_string(struct error *e, const struct method_info *method); +int +exception_get_int(struct error *e, const struct method_info *method); +]] + +local REFLECTION_CACHE = {} + +local function reflection_enumerate(err) + local key = tostring(err._type) + local result = REFLECTION_CACHE[key] + if result ~= nil then + return result + end + result = {} + -- See type_foreach_method() in reflection.h + local t = err._type + while t ~= nil do + local m = t.methods + while m.name ~= nil do + result[ffi.string(m.name)] = m + m = m + 1 + end + t = t.parent + end + REFLECTION_CACHE[key] = result + return result +end + +local function reflection_get(err, method) + if method.nargs ~= 0 then + return nil -- NYI + end + if method.rtype == ffi.C.CTYPE_INT then + return tonumber(ffi.C.exception_get_int(err, method)) + elseif method.rtype == ffi.C.CTYPE_CONST_CHAR_PTR then + local str = ffi.C.exception_get_string(err, method) + if str == nil then + return nil + end + return ffi.string(str) + end +end + +local function error_type(err) + return ffi.string(err._type.name) +end + +local function error_message(err) + return ffi.string(err._errmsg) +end + +local function error_trace(err) + if err._file[0] == 0 then + return {} + end + return { + { file = ffi.string(err._file), line = tonumber(err._line) }; + } +end + +local error_fields = { + ["type"] = error_type; + ["message"] = error_message; + ["trace"] = error_trace; +} + +local function error_unpack(err) + if not ffi.istype('struct error', err) then + error("Usage: error:unpack()") + end + local result = {} + for key, getter in pairs(error_fields) do + result[key] = getter(err) + end + for key, getter in pairs(reflection_enumerate(err)) do + local value = reflection_get(err, getter) + if value ~= nil then + result[key] = value + end + end + return result +end + +local function error_raise(err) + if not ffi.istype('struct error', err) then + error("Usage: error:raise()") + end + error(err) +end + +local function error_match(err, ...) + if not ffi.istype('struct error', err) then + error("Usage: error:match()") + end + return string.match(error_message(err), ...) +end + +local function error_serialize(err) + -- Return an error message only in admin console to keep compatibility + return error_message(err) +end + +local error_methods = { + ["unpack"] = error_unpack; + ["raise"] = error_raise; + ["match"] = error_match; -- Tarantool 1.6 backward compatibility + ["__serialize"] = error_serialize; +} + +local function error_index(err, key) + local getter = error_fields[key] + if getter ~= nil then + return getter(err) + end + getter = reflection_enumerate(err)[key] + if getter ~= nil and getter.nargs == 0 then + local val = reflection_get(err, getter) + if val ~= nil then + return val + end + end + return error_methods[key] +end + +local error_mt = { + __index = error_index; + __tostring = error_message; +}; + +ffi.metatype('struct error', error_mt); + +return error diff --git a/src/lua/init.c b/src/lua/init.c index 17f17c4dded5c499da1a56fefcbc34c43849ca3a..ca4b47f3a0139f21a065648284372b20a379515b 100644 --- a/src/lua/init.c +++ b/src/lua/init.c @@ -96,6 +96,7 @@ extern char strict_lua[], help_en_US_lua[], tap_lua[], fio_lua[], + error_lua[], argparse_lua[], iconv_lua[], /* jit.* library */ @@ -135,6 +136,7 @@ static const char *lua_modules[] = { "log", log_lua, "uri", uri_lua, "fio", fio_lua, + "error", error_lua, "csv", csv_lua, "clock", clock_lua, "socket", socket_lua, @@ -443,6 +445,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv) tarantool_lua_fiber_cond_init(L); tarantool_lua_fiber_channel_init(L); tarantool_lua_errno_init(L); + tarantool_lua_error_init(L); tarantool_lua_fio_init(L); tarantool_lua_socket_init(L); tarantool_lua_pickle_init(L); diff --git a/src/lua/init.lua b/src/lua/init.lua index 83f2c8d76c530c5ae7606af7dd0f0aff40db6e8a..fa324d34c81b1c26170235f8bd555a02a846328a 100644 --- a/src/lua/init.lua +++ b/src/lua/init.lua @@ -1,10 +1,10 @@ + -- init.lua -- internal file local ffi = require('ffi') ffi.cdef[[ struct type_info; struct method_info; -struct error; enum ctype { CTYPE_VOID = 0, @@ -18,27 +18,6 @@ struct type_info { const struct method_info *methods; }; -enum { - DIAG_ERRMSG_MAX = 512, - DIAG_FILENAME_MAX = 256 -}; - -typedef void (*error_f)(struct error *e); - -struct error { - error_f _destroy; - error_f _raise; - error_f _log; - const struct type_info *_type; - int _refs; - /** Line number. */ - unsigned _line; - /* Source file name. */ - char _file[DIAG_FILENAME_MAX]; - /* Error description. */ - char _errmsg[DIAG_ERRMSG_MAX]; -}; - enum { METHOD_ARG_MAX = 8 }; struct method_info { @@ -55,11 +34,6 @@ struct method_info { }; }; -char * -exception_get_string(struct error *e, const struct method_info *method); -int -exception_get_int(struct error *e, const struct method_info *method); - double tarantool_uptime(void); typedef int32_t pid_t; @@ -68,132 +42,6 @@ pid_t getpid(void); local fio = require("fio") -local REFLECTION_CACHE = {} - -local function reflection_enumerate(err) - local key = tostring(err._type) - local result = REFLECTION_CACHE[key] - if result ~= nil then - return result - end - result = {} - -- See type_foreach_method() in reflection.h - local t = err._type - while t ~= nil do - local m = t.methods - while m.name ~= nil do - result[ffi.string(m.name)] = m - m = m + 1 - end - t = t.parent - end - REFLECTION_CACHE[key] = result - return result -end - -local function reflection_get(err, method) - if method.nargs ~= 0 then - return nil -- NYI - end - if method.rtype == ffi.C.CTYPE_INT then - return tonumber(ffi.C.exception_get_int(err, method)) - elseif method.rtype == ffi.C.CTYPE_CONST_CHAR_PTR then - local str = ffi.C.exception_get_string(err, method) - if str == nil then - return nil - end - return ffi.string(str) - end -end - -local function error_type(err) - return ffi.string(err._type.name) -end - -local function error_message(err) - return ffi.string(err._errmsg) -end - -local function error_trace(err) - if err._file[0] == 0 then - return {} - end - return { - { file = ffi.string(err._file), line = tonumber(err._line) }; - } -end - -local error_fields = { - ["type"] = error_type; - ["message"] = error_message; - ["trace"] = error_trace; -} - -local function error_unpack(err) - if not ffi.istype('struct error', err) then - error("Usage: error:unpack()") - end - local result = {} - for key, getter in pairs(error_fields) do - result[key] = getter(err) - end - for key, getter in pairs(reflection_enumerate(err)) do - local value = reflection_get(err, getter) - if value ~= nil then - result[key] = value - end - end - return result -end - -local function error_raise(err) - if not ffi.istype('struct error', err) then - error("Usage: error:raise()") - end - error(err) -end - -local function error_match(err, ...) - if not ffi.istype('struct error', err) then - error("Usage: error:match()") - end - return string.match(error_message(err), ...) -end - -local function error_serialize(err) - -- Return an error message only in admin console to keep compatibility - return error_message(err) -end - -local error_methods = { - ["unpack"] = error_unpack; - ["raise"] = error_raise; - ["match"] = error_match; -- Tarantool 1.6 backward compatibility - ["__serialize"] = error_serialize; -} - -local function error_index(err, key) - local getter = error_fields[key] - if getter ~= nil then - return getter(err) - end - getter = reflection_enumerate(err)[key] - if getter ~= nil and getter.nargs == 0 then - local val = reflection_get(err, getter) - if val ~= nil then - return val - end - end - return error_methods[key] -end - -local error_mt = { - __index = error_index; - __tostring = error_message; -}; - -ffi.metatype('struct error', error_mt); - dostring = function(s, ...) local chunk, message = loadstring(s) if chunk == nil then @@ -343,6 +191,7 @@ table.insert(package.loaders, 5, rocks_loader_func(true)) -- croot 8 package.search = search + return { uptime = uptime; pid = pid; diff --git a/src/lua/utils.c b/src/lua/utils.c index 27772a9d3b611af2d90e204a0402d54fcdcbc7ee..978fe61f19ffe36549d2b087903b8cc22900b963 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -40,7 +40,6 @@ int luaL_nil_ref = LUA_REFNIL; int luaL_map_metatable_ref = LUA_REFNIL; int luaL_array_metatable_ref = LUA_REFNIL; -static int CTID_CONST_STRUCT_ERROR_REF = 0; void * luaL_pushcdata(struct lua_State *L, uint32_t ctypeid) @@ -859,73 +858,6 @@ luaL_toint64(struct lua_State *L, int idx) return 0; } -struct error * -luaL_iserror(struct lua_State *L, int narg) -{ - assert(CTID_CONST_STRUCT_ERROR_REF != 0); - if (lua_type(L, narg) != LUA_TCDATA) - return NULL; - - uint32_t ctypeid; - void *data = luaL_checkcdata(L, narg, &ctypeid); - if (ctypeid != (uint32_t) CTID_CONST_STRUCT_ERROR_REF) - return NULL; - - struct error *e = *(struct error **) data; - assert(e->refs); - return e; -} - -static struct error * -luaL_checkerror(struct lua_State *L, int narg) -{ - struct error *error = luaL_iserror(L, narg); - if (error == NULL) { - luaL_error(L, "Invalid argument #%d (error expected, got %s)", - narg, lua_typename(L, lua_type(L, narg))); - } - return error; -} - -static int -luaL_error_gc(struct lua_State *L) -{ - struct error *error = luaL_checkerror(L, 1); - error_unref(error); - return 0; -} - -void -luaT_pusherror(struct lua_State *L, struct error *e) -{ - assert(CTID_CONST_STRUCT_ERROR_REF != 0); - struct error **ptr = (struct error **) luaL_pushcdata(L, - CTID_CONST_STRUCT_ERROR_REF); - *ptr = e; - /* The order is important - first reference the error, then set gc */ - error_ref(e); - lua_pushcfunction(L, luaL_error_gc); - luaL_setcdatagc(L, -2); -} - -int -luaT_error(lua_State *L) -{ - struct error *e = diag_last_error(&fiber()->diag); - assert(e != NULL); - /* - * gh-1955 luaT_pusherror allocates Lua objects, thus it may trigger - * GC. GC may invoke finalizers which are arbitrary Lua code, - * potentially invalidating last error object, hence error_ref - * below. - */ - error_ref(e); - luaT_pusherror(L, e); - error_unref(e); - lua_error(L); - unreachable(); - return 0; -} int luaT_toerror(lua_State *L) @@ -1001,13 +933,6 @@ tarantool_lua_utils_init(struct lua_State *L) {NULL, NULL}, }; - /* Get CTypeID for `struct error *' */ - int rc = luaL_cdef(L, "struct error;"); - assert(rc == 0); - (void) rc; - CTID_CONST_STRUCT_ERROR_REF = luaL_ctypeid(L, "const struct error &"); - assert(CTID_CONST_STRUCT_ERROR_REF != 0); - luaL_register_type(L, LUAL_SERIALIZER, serializermeta); /* Create NULL constant */ *(void **) luaL_pushcdata(L, CTID_P_VOID) = NULL; @@ -1031,4 +956,3 @@ tarantool_lua_utils_init(struct lua_State *L) return 0; } - diff --git a/src/lua/utils.h b/src/lua/utils.h index ddf395523c91d5762455befc48a69ecef8accdc1..a47e3d2b45f2feede21a0e7ce1f64c7a9e8d0f3e 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -53,9 +53,10 @@ extern "C" { #include <lj_tab.h> #include <lj_meta.h> +#include "lua/error.h" + struct lua_State; struct ibuf; -struct error; /** * Single global lua_State shared by core and modules. @@ -411,14 +412,6 @@ luaL_touint64(struct lua_State *L, int idx); LUA_API int64_t luaL_toint64(struct lua_State *L, int idx); -/** - * Re-throws the last Tarantool error as a Lua object. - * \sa lua_error() - * \sa box_error_last() - */ -LUA_API int -luaT_error(lua_State *L); - /** * Like lua_call(), but with the proper support of Tarantool errors. * \sa lua_call() @@ -447,12 +440,6 @@ luaT_tolstring(lua_State *L, int idx, size_t *ssize); /** \endcond public */ -void -luaT_pusherror(struct lua_State *L, struct error *e); - -struct error * -luaL_iserror(struct lua_State *L, int narg); - /** * Convert the last value on the stack into Tarantool error and * set diagnostics.