From 0fc97a1483961e0bc617ca67a348dcb2cbeb6bc0 Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Thu, 15 Dec 2022 07:37:49 -0500 Subject: [PATCH] Use a Lua error handler that calls tostring (#11913) --- builtin/init.lua | 3 +++ doc/lua_api.txt | 14 ++++++++++++++ src/script/common/c_internal.cpp | 24 ++++++++++++++++++++++-- src/script/common/c_internal.h | 6 ++++-- src/script/cpp_api/s_base.cpp | 7 ++----- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/builtin/init.lua b/builtin/init.lua index 96e7a937c..e03c2c6de 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -6,6 +6,9 @@ -- -- Initialize some very basic things +function core.error_handler(err, level) + return debug.traceback(tostring(err), level) +end do local function concat_args(...) local n, t = select("#", ...), {...} diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e017df880..ba6ff75f3 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -9956,3 +9956,17 @@ Bit Library Functions: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift, bit.arshift, bit.rol, bit.ror, bit.bswap See http://bitop.luajit.org/ for advanced information. + +Error Handling +-------------- + +When an error occurs that is not caught, Minetest calls the function +`minetest.error_handler` with the error object as its first argument. The second +argument is the stack level where the error occurred. The return value is the +error string that should be shown. By default this is a backtrace from +`debug.traceback`. If the error object is not a string, it is first converted +with `tostring` before being displayed. This means that you can use tables as +error objects so long as you give them `__tostring` metamethods. + +You can override `minetest.error_handler`. You should call the previous handler +with the correct stack level in your implementation. diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index ddd2d184c..79063141d 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -27,10 +27,11 @@ with this program; if not, write to the Free Software Foundation, Inc., std::string script_get_backtrace(lua_State *L) { - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_BACKTRACE); + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); lua_call(L, 0, 1); std::string result = luaL_checkstring(L, -1); - lua_pop(L, 1); + lua_pop(L, 2); return result; } @@ -46,6 +47,25 @@ int script_exception_wrapper(lua_State *L, lua_CFunction f) return lua_error(L); // Rethrow as a Lua error. } +int script_error_handler(lua_State *L) +{ + lua_getglobal(L, "core"); + lua_getfield(L, -1, "error_handler"); + if (!lua_isnil(L, -1)) { + lua_pushvalue(L, 1); + } else { + // No Lua error handler available. Call debug.traceback(tostring(#1), level). + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_getglobal(L, "tostring"); + lua_pushvalue(L, 1); + lua_call(L, 1, 1); + } + lua_pushinteger(L, 2); // Stack level + lua_call(L, 2, 1); + return 1; +} + /* * Note that we can't get tracebacks for LUA_ERRMEM or LUA_ERRERR (without * hacking Lua internals). For LUA_ERRMEM, this is because memory errors will diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index 5cbcbb7db..c8bce099c 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -54,7 +54,7 @@ enum { CUSTOM_RIDX_SCRIPTAPI, CUSTOM_RIDX_GLOBALS_BACKUP, CUSTOM_RIDX_CURRENT_MOD_NAME, - CUSTOM_RIDX_BACKTRACE, + CUSTOM_RIDX_ERROR_HANDLER, CUSTOM_RIDX_HTTP_API_LUA, CUSTOM_RIDX_METATABLE_MAP, @@ -78,7 +78,7 @@ enum { // Pushes the error handler onto the stack and returns its index #define PUSH_ERROR_HANDLER(L) \ - (lua_rawgeti((L), LUA_REGISTRYINDEX, CUSTOM_RIDX_BACKTRACE), lua_gettop((L))) + (lua_rawgeti((L), LUA_REGISTRYINDEX, CUSTOM_RIDX_ERROR_HANDLER), lua_gettop((L))) #define PCALL_RESL(L, RES) { \ int result_ = (RES); \ @@ -121,6 +121,8 @@ enum RunCallbacksMode std::string script_get_backtrace(lua_State *L); // Wrapper for CFunction calls that converts C++ exceptions to Lua errors int script_exception_wrapper(lua_State *L, lua_CFunction f); +// Acts as the error handler for lua_pcall +int script_error_handler(lua_State *L); // Takes an error from lua_pcall and throws it as a LuaError void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn); diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index e8d973de1..b91f59613 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -103,11 +103,8 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): #endif lua_rawseti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); - // Add and save an error handler - lua_getglobal(m_luastack, "debug"); - lua_getfield(m_luastack, -1, "traceback"); - lua_rawseti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_BACKTRACE); - lua_pop(m_luastack, 1); // pop debug + lua_pushcfunction(m_luastack, script_error_handler); + lua_rawseti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_ERROR_HANDLER); // Add a C++ wrapper function to catch exceptions thrown in Lua -> C++ calls #if USE_LUAJIT