From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp55.i.mail.ru (smtp55.i.mail.ru [217.69.128.35]) (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 AF0744696C3 for ; Wed, 15 Apr 2020 12:32:04 +0300 (MSK) From: Leonid Vasiliev Date: Wed, 15 Apr 2020 12:31:56 +0300 Message-Id: In-Reply-To: References: In-Reply-To: References: Subject: [Tarantool-patches] [PATCH V3 1/7] error: add a Lua traceback to error List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: v.shpilevoy@tarantool.org Cc: tarantool-patches@dev.tarantool.org Lua backtrace can be enabled for all errors using a server-wide option, or on a per-error basis using box.error() and box.error.new() arguments. Co-authored-by: Vladislav Shpilevoy Needed for #4398 @TarantoolBot document Title: Lua error.traceback Lua error objects now feature 'traceback' optional attribute. It contains Lua traceback. C part is not present here. Traceback collection is a relatively expensive operation, so it is disabled by default. In case you need to enable it, there are 2 options: * Global option `traceback_enable` for `box.error.cfg` call: ``` box.error.cfg({traceback_enable = true}) ``` * Per object option, in case you want traceback only for certain cases: ``` box.error.new({ code = 1000, reason = 'Reason', traceback = true/false }) ``` --- src/box/lua/error.cc | 33 +++++++++++++++++++++++++- src/lib/core/diag.c | 16 +++++++++++++ src/lib/core/diag.h | 28 ++++++++++++++++++++++ src/lib/core/exception.cc | 1 + src/lua/error.c | 9 ++++++++ src/lua/error.lua | 7 ++++++ test/box/error.result | 59 +++++++++++++++++++++++++++++++++++++++++++++++ test/box/error.test.lua | 33 ++++++++++++++++++++++++++ 8 files changed, 185 insertions(+), 1 deletion(-) diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc index b2625bf..a2facf0 100644 --- a/src/box/lua/error.cc +++ b/src/box/lua/error.cc @@ -54,6 +54,8 @@ luaT_error_create(lua_State *L, int top_base) { uint32_t code = 0; const char *reason = NULL; + bool is_traceback_enabled = false; + bool is_traceback_specified = false; const char *file = ""; unsigned line = 0; lua_Debug info; @@ -87,6 +89,12 @@ luaT_error_create(lua_State *L, int top_base) if (reason == NULL) reason = ""; lua_pop(L, 1); + lua_getfield(L, top_base, "traceback"); + if (lua_isboolean(L, -1)) { + is_traceback_enabled = lua_toboolean(L, -1); + is_traceback_specified = true; + } + lua_pop(L, 1); } else { return NULL; } @@ -102,7 +110,14 @@ raise: } line = info.currentline; } - return box_error_new(file, line, code, "%s", reason); + + struct error *err = box_error_new(file, line, code, "%s", reason); + /* + * Explicit traceback option overrides the global setting. + */ + if (is_traceback_specified) + err->is_traceback_enabled = is_traceback_enabled; + return err; } static int @@ -180,6 +195,18 @@ luaT_error_set(struct lua_State *L) } static int +luaT_error_cfg(struct lua_State *L) +{ + if (lua_gettop(L) < 1 || !lua_istable(L, 1)) + return luaL_error(L, "Usage: box.error.cfg({}})"); + + lua_getfield(L, 1, "traceback_enable"); + if (lua_isboolean(L, -1)) + error_is_traceback_enabled = lua_toboolean(L, -1); + return 0; +} + +static int lbox_errinj_set(struct lua_State *L) { char *name = (char*)luaL_checkstring(L, 1); @@ -297,6 +324,10 @@ box_lua_error_init(struct lua_State *L) { lua_pushcfunction(L, luaT_error_set); lua_setfield(L, -2, "set"); } + { + lua_pushcfunction(L, luaT_error_cfg); + lua_setfield(L, -2, "cfg"); + } lua_setfield(L, -2, "__index"); } lua_setmetatable(L, -2); diff --git a/src/lib/core/diag.c b/src/lib/core/diag.c index e143db1..833454b 100644 --- a/src/lib/core/diag.c +++ b/src/lib/core/diag.c @@ -31,6 +31,8 @@ #include "diag.h" #include "fiber.h" +bool error_is_traceback_enabled = false; + int error_set_prev(struct error *e, struct error *prev) { @@ -97,6 +99,8 @@ error_create(struct error *e, e->errmsg[0] = '\0'; e->cause = NULL; e->effect = NULL; + e->traceback = NULL; + e->is_traceback_enabled = error_is_traceback_enabled; } struct diag * @@ -120,3 +124,15 @@ error_vformat_msg(struct error *e, const char *format, va_list ap) vsnprintf(e->errmsg, sizeof(e->errmsg), format, ap); } +void +error_set_traceback(struct error *e, const char *traceback) +{ + assert(e->traceback == NULL); + e->traceback = strdup(traceback); + /* + * Don't try to set it again. Traceback can be NULL in case of OOM, so + * it is not a reliable source of information whether need to collect a + * traceback. + */ + e->is_traceback_enabled = false; +} diff --git a/src/lib/core/diag.h b/src/lib/core/diag.h index 7a5454d..e918d30 100644 --- a/src/lib/core/diag.h +++ b/src/lib/core/diag.h @@ -42,6 +42,13 @@ extern "C" { #endif /* defined(__cplusplus) */ +/** + * Flag to turn on/off traceback automatic collection. Currently + * it works only for Lua. Stack is collected at the moment of + * error object push onto the stack. + */ +extern bool error_is_traceback_enabled; + enum { DIAG_ERRMSG_MAX = 512, DIAG_FILENAME_MAX = 256 @@ -110,6 +117,19 @@ struct error { */ struct error *cause; struct error *effect; + /** + * Optional traceback. At the moment it can contain only + * Lua traceback, and only when it is requested + * explicitly. + */ + char *traceback; + /** + * Flag whether a traceback should be collected when the + * error object is exposed to a user next time. When the + * tracing is disabled, or it is enabled but already + * collected for this error object, it becomes false. + */ + bool is_traceback_enabled; }; static inline void @@ -172,6 +192,14 @@ error_unlink_effect(struct error *e) int error_set_prev(struct error *e, struct error *prev); +/** + * Set traceback of @a e error object. It can be done only once. + * In case of OOM the traceback is kept NULL, and can't be + * collected again. + */ +void +error_set_traceback(struct error *e, const char *traceback); + NORETURN static inline void error_raise(struct error *e) { diff --git a/src/lib/core/exception.cc b/src/lib/core/exception.cc index 180cb0e..1717b76 100644 --- a/src/lib/core/exception.cc +++ b/src/lib/core/exception.cc @@ -42,6 +42,7 @@ extern "C" { static void exception_destroy(struct error *e) { + free(e->traceback); delete (Exception *) e; } diff --git a/src/lua/error.c b/src/lua/error.c index 18a990a..a21548c 100644 --- a/src/lua/error.c +++ b/src/lua/error.c @@ -85,6 +85,15 @@ luaT_pusherror(struct lua_State *L, struct error *e) * then set the finalizer. */ error_ref(e); + + if (e->traceback == NULL && e->is_traceback_enabled) { + int top = lua_gettop(L); + luaL_traceback(L, L, NULL, 0); + if (lua_isstring(L, -1)) + error_set_traceback(e, lua_tostring(L, -1)); + lua_settop(L, top); + } + assert(CTID_CONST_STRUCT_ERROR_REF != 0); struct error **ptr = (struct error **) luaL_pushcdata(L, CTID_CONST_STRUCT_ERROR_REF); diff --git a/src/lua/error.lua b/src/lua/error.lua index bdc9c71..93fd1b9 100644 --- a/src/lua/error.lua +++ b/src/lua/error.lua @@ -26,6 +26,8 @@ struct error { char _errmsg[DIAG_ERRMSG_MAX]; struct error *_cause; struct error *_effect; + char *err_traceback; + bool is_traceback_enabled; }; char * @@ -92,6 +94,10 @@ local function error_trace(err) } end +local function error_traceback(err) + return err.err_traceback ~= ffi.nullptr and ffi.string(err.err_traceback) or nil +end + local function error_errno(err) local e = err._saved_errno if e == 0 then @@ -131,6 +137,7 @@ local error_fields = { ["trace"] = error_trace; ["errno"] = error_errno; ["prev"] = error_prev; + ["traceback"] = error_traceback; } local function error_unpack(err) diff --git a/test/box/error.result b/test/box/error.result index df6d0eb..2502d88 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -831,3 +831,62 @@ assert(box.error.last() == e1) | --- | - true | ... + +-- +-- gh-4398: Lua traceback for errors. +-- +function t1(traceback, throw) \ + local opts = {code = 0, reason = 'Reason', traceback = traceback} \ + if throw then \ + box.error(opts) \ + else \ + return box.error.new(opts) \ + end \ +end + | --- + | ... +function t2(...) return t1(...), nil end + | --- + | ... +function t3(...) return t2(...), nil end + | --- + | ... + +function check_trace(trace) \ + local t1loc = trace:find('t1') \ + local t2loc = trace:find('t2') \ + local t3loc = trace:find('t3') \ + return t1loc < t2loc and t2loc < t3loc or {t1loc, t2loc, t3loc, trace} \ +end + | --- + | ... + +check_trace(t3(true, false):unpack().traceback) + | --- + | - true + | ... + +box.error.cfg{traceback_enable = true} + | --- + | ... +-- Explicit 'traceback = false' overrides the global setting. +t3(false, false):unpack().traceback + | --- + | - null + | ... +-- When explicit option is not specified, global setting works. +check_trace(t3(nil, false):unpack().traceback) + | --- + | - true + | ... + +box.error.cfg{traceback_enable = false} + | --- + | ... +_, e = pcall(t3, true, true) + | --- + | ... +check_trace(e:unpack().traceback) + | --- + | - true + | ... diff --git a/test/box/error.test.lua b/test/box/error.test.lua index 41baed5..6f12716 100644 --- a/test/box/error.test.lua +++ b/test/box/error.test.lua @@ -229,3 +229,36 @@ box.error({code = 111, reason = "err"}) box.error.last() box.error(e1) assert(box.error.last() == e1) + +-- +-- gh-4398: Lua traceback for errors. +-- +function t1(traceback, throw) \ + local opts = {code = 0, reason = 'Reason', traceback = traceback} \ + if throw then \ + box.error(opts) \ + else \ + return box.error.new(opts) \ + end \ +end +function t2(...) return t1(...), nil end +function t3(...) return t2(...), nil end + +function check_trace(trace) \ + local t1loc = trace:find('t1') \ + local t2loc = trace:find('t2') \ + local t3loc = trace:find('t3') \ + return t1loc < t2loc and t2loc < t3loc or {t1loc, t2loc, t3loc, trace} \ +end + +check_trace(t3(true, false):unpack().traceback) + +box.error.cfg{traceback_enable = true} +-- Explicit 'traceback = false' overrides the global setting. +t3(false, false):unpack().traceback +-- When explicit option is not specified, global setting works. +check_trace(t3(nil, false):unpack().traceback) + +box.error.cfg{traceback_enable = false} +_, e = pcall(t3, true, true) +check_trace(e:unpack().traceback) -- 2.7.4