diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc index ab4d141298bd3d066a1d6bd6fc7199d784f4bbae..566919e69d705fc4078e1425eebbf4f90ba30205 100644 --- a/src/box/lua/error.cc +++ b/src/box/lua/error.cc @@ -112,37 +112,14 @@ lbox_error_last(lua_State *L) if (lua_gettop(L) >= 1) luaL_error(L, "box.error.last(): bad arguments"); - /* TODO: use struct error here */ - Exception *e = (Exception *) box_error_last(); - + struct error *e = box_error_last(); if (e == NULL) { lua_pushnil(L); - } else { - /* - * TODO: use luaL_pusherror here, move type_foreach_method - * to error_unpack() in Lua. - */ - lua_newtable(L); + return 1; + } - lua_pushstring(L, "type"); - lua_pushstring(L, e->type->name); - lua_settable(L, -3); - - type_foreach_method(e->type, method) { - if (method_invokable<const char *>(method, e)) { - const char *s = method_invoke<const char *>(method, e); - lua_pushstring(L, method->name); - lua_pushstring(L, s); - lua_settable(L, -3); - } else if (method_invokable<int>(method, e)) { - int code = method_invoke<int>(method, e); - lua_pushstring(L, method->name); - lua_pushinteger(L, code); - lua_settable(L, -3); - } - } - } - return 1; + luaL_pusherror(L, e); + return 1; } static int diff --git a/src/exception.cc b/src/exception.cc index db7eba8b1dc853ff13a586d7d90edeec44d3c79a..be94f575bc200451f8bee622ab2cc87e0455a650 100644 --- a/src/exception.cc +++ b/src/exception.cc @@ -35,6 +35,7 @@ #include <errno.h> #include "fiber.h" +#include "reflection.h" extern "C" { @@ -58,6 +59,26 @@ exception_log(struct error *error) e->log(); } +const char * +exception_get_string(struct error *e, const struct method *method) +{ + /* A workaround for for vtable */ + Exception *ex = (Exception *) e; + if (!method_invokable<const char *>(method, ex)) + return NULL; + return method_invoke<const char *>(method, ex); +} + +int +exception_get_int(struct error *e, const struct method *method) +{ + /* A workaround for vtable */ + Exception *ex = (Exception *) e; + if (!method_invokable<int>(method, ex)) + return 0; + return method_invoke<int>(method, ex); +} + } /* extern "C" */ /** out_of_memory::size is zero-initialized by the linker. */ @@ -66,8 +87,6 @@ static OutOfMemory out_of_memory(__FILE__, __LINE__, static const struct method exception_methods[] = { make_method(&type_Exception, "message", &Exception::get_errmsg), - make_method(&type_Exception, "file", &Exception::get_file), - make_method(&type_Exception, "line", &Exception::get_line), make_method(&type_Exception, "log", &Exception::log), METHODS_SENTINEL }; diff --git a/src/exception.h b/src/exception.h index 35c6722d0a48877fa4f3837aee8230a5d3c38b7b..2889bf7998ebd5a7194b9eaad5cbc7b7963aeb23 100644 --- a/src/exception.h +++ b/src/exception.h @@ -137,4 +137,9 @@ exception_init(); throw tnt_error(__VA_ARGS__); \ } while (0) +extern "C" const char * +exception_get_string(struct error *e, const struct method *method); +extern "C" int +exception_get_int(struct error *e, const struct method *method); + #endif /* TARANTOOL_EXCEPTION_H_INCLUDED */ diff --git a/src/ffisyms.cc b/src/ffisyms.cc index b727a648f5e8f4113f3abbadec5099219779f3ec..4b75e9de5c56071f258643165c30b86ae7d965ff 100644 --- a/src/ffisyms.cc +++ b/src/ffisyms.cc @@ -53,6 +53,7 @@ #include <lib/csv/csv.h> #include <lua/clock.h> #include "title.h" +#include "exception.h" #include <openssl/err.h> #include <openssl/evp.h> @@ -147,4 +148,6 @@ void *ffi_symbols[] = { (void *) OpenSSL_add_all_digests, (void *) OpenSSL_add_all_ciphers, (void *) ERR_load_crypto_strings, + (void *) exception_get_string, + (void *) exception_get_int, }; diff --git a/src/lua/init.lua b/src/lua/init.lua index 975d34a41af063f7b9cbe7dce5173204a514460a..9644bc3979968f25f6a5dcbf6a701689eed4e11e 100644 --- a/src/lua/init.lua +++ b/src/lua/init.lua @@ -6,6 +6,12 @@ struct type; struct method; struct error; +enum ctype { + CTYPE_VOID = 0, + CTYPE_INT, + CTYPE_CONST_CHAR_PTR +}; + struct type { const char *name; const struct type *parent; @@ -33,11 +39,26 @@ struct error { char _errmsg[DIAG_ERRMSG_MAX]; }; -/* TODO: remove these declarations */ -const struct error * -box_error_last(void); -const char * -box_error_message(const struct error *); +enum { METHOD_ARG_MAX = 8 }; + +struct method { + const struct type *owner; + const char *name; + enum ctype rtype; + enum ctype atype[METHOD_ARG_MAX]; + int nargs; + bool isconst; + + union { + /* Add extra space to get proper struct size in C */ + void *_spacer[2]; + }; +}; + +char * +exception_get_string(struct error *e, const struct method *method); +int +exception_get_int(struct error *e, const struct method *method); double tarantool_uptime(void); @@ -45,6 +66,44 @@ typedef int32_t pid_t; pid_t getpid(void); ]] +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 @@ -62,7 +121,6 @@ local function error_trace(err) } end --- TODO: Use reflection local error_fields = { ["type"] = error_type; ["message"] = error_message; @@ -77,6 +135,12 @@ local function error_unpack(err) 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 @@ -87,6 +151,13 @@ local function error_raise(err) 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) @@ -95,6 +166,7 @@ end local error_methods = { ["unpack"] = error_unpack; ["raise"] = error_raise; + ["match"] = error_match; -- Tarantool 1.6 backward compatibility ["__serialize"] = error_serialize; } @@ -103,6 +175,13 @@ local function error_index(err, 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 @@ -113,26 +192,6 @@ local error_mt = { ffi.metatype('struct error', error_mt); --- Override pcall to support Tarantool exceptions -local pcall_lua = pcall - -local function pcall_wrap(status, ...) - if status == true then - return true, ... - end - if ffi.istype('struct error', (...)) then - -- Return the Tarantool error as string to keep compatibility. - -- Caller should check box.error.last() to get additional information. - return false, tostring((...)) - elseif ... == 'C++ exception' then - return false, ffi.string(ffi.C.box_error_message(ffi.C.box_error_last())) - end - return status, ... -end -pcall = function(fun, ...) - return pcall_wrap(pcall_lua(fun, ...)) -end - dostring = function(s, ...) local chunk, message = loadstring(s) if chunk == nil then diff --git a/src/lua/utils.c b/src/lua/utils.c index 0c316f301033f30f06e00a420fa388675c8462fc..69ee5cd80a92e50808f25c51b7fc7c0be14cb805 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -900,7 +900,7 @@ luaL_error_gc(struct lua_State *L) return 0; } -static void +void luaL_pusherror(struct lua_State *L, struct error *e) { assert(CTID_CONST_STRUCT_ERROR_REF != 0); diff --git a/src/lua/utils.h b/src/lua/utils.h index b3967f9cfb8ef59268fc6055b020df889c2f2be8..fe78654471837f163b8d494a0adf3c47e065f785 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -497,6 +497,9 @@ lbox_call(lua_State *L, int nargs, int nreturns); int lbox_cpcall(lua_State *L, lua_CFunction func, void *ud); +void +luaL_pusherror(struct lua_State *L, struct error *e); + #if defined(__cplusplus) } /* extern "C" */ diff --git a/test/app-tap/pcall.result b/test/app-tap/pcall.result index 3d1c5592957d4b0bb541596026de2291a6c0bb4e..e38f08b3542f61086ea3f303cfcb3a6861bccd26 100644 --- a/test/app-tap/pcall.result +++ b/test/app-tap/pcall.result @@ -5,5 +5,10 @@ pcall inside xpcall: true pcall is ok pcall with Lua error(): false some message pcall with box.error(): false Illegal parameters, some message +pcall with box.error(): typeof ctype<const struct error &> +pcall with box.error(): .type ClientError +pcall with box.error(): .code 1 +pcall with box.error(): .message Illegal parameters, some message +pcall with box.error(): .match() some pcall with no return: 1 pcall with multireturn: true 1 2 3 diff --git a/test/app-tap/pcall.test.lua b/test/app-tap/pcall.test.lua index ef3b51f0203f470762a7f37d22e0e820538b7ca6..343fb366fdcea948116a715b58cd722e680a9525 100755 --- a/test/app-tap/pcall.test.lua +++ b/test/app-tap/pcall.test.lua @@ -1,5 +1,7 @@ #!/usr/bin/env tarantool +local ffi = require('ffi') + print[[ -------------------------------------------------------------------------------- -- #267: Bad exception catching @@ -32,6 +34,12 @@ local status, msg = pcall(function() box.error(box.error.ILLEGAL_PARAMS, 'some message') end) print('pcall with box.error():', status, msg) +print('pcall with box.error(): typeof', ffi.typeof(msg)) +print('pcall with box.error(): .type', msg.type) +print('pcall with box.error(): .code', msg.code) +print('pcall with box.error(): .message', msg.message) +-- Tarantool 1.6 backward compatibility +print('pcall with box.error(): .match()', msg:match('some')) print('pcall with no return:', select('#', pcall(function() end))) print('pcall with multireturn:', pcall(function() return 1, 2, 3 end)) diff --git a/test/box/misc.result b/test/box/misc.result index 50a2a2d147e60aed246543312b55ea5b5cdd25d9..4121b27f632b717a1000b2ad8d82857283056297 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -109,13 +109,40 @@ box.error.raise() --- - error: Illegal parameters, bla bla ... -box.error.last() +e +--- +- error: '[string "return e "]:1: variable ''e'' is not declared' +... +e = box.error.last() +--- +... +e:unpack() --- -- line: -1 +- type: ClientError code: 1 - type: ClientError message: Illegal parameters, bla bla - file: '[C]' + trace: + - file: '[C]' + line: 4294967295 +... +e.type +--- +- ClientError +... +e.code +--- +- 1 +... +e.message +--- +- Illegal parameters, bla bla +... +tostring(e) +--- +- Illegal parameters, bla bla +... +e = nil +--- ... box.error.clear() --- diff --git a/test/box/misc.test.lua b/test/box/misc.test.lua index 1c6c3aa983cdd2728bde1b1714282a522f6c740b..bd9bc98b7537d1f0a5c15d6a916cac99e1fad01b 100644 --- a/test/box/misc.test.lua +++ b/test/box/misc.test.lua @@ -34,11 +34,17 @@ box.error({code = 123, reason = 'test'}) box.error(box.error.ILLEGAL_PARAMS, "bla bla") box.error() box.error.raise() -box.error.last() +e +e = box.error.last() +e:unpack() +e.type +e.code +e.message +tostring(e) +e = nil box.error.clear() box.error.last() box.error.raise() - space = box.space.tweedledum ----------------