]> git.lizzy.rs Git - minetest.git/blob - src/script/common/c_internal.cpp
Use a Lua error handler that calls tostring (#11913)
[minetest.git] / src / script / common / c_internal.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "common/c_internal.h"
21 #include "util/numeric.h"
22 #include "debug.h"
23 #include "log.h"
24 #include "porting.h"
25 #include "settings.h"
26 #include <algorithm> // std::find
27
28 std::string script_get_backtrace(lua_State *L)
29 {
30         lua_getglobal(L, "debug");
31         lua_getfield(L, -1, "traceback");
32         lua_call(L, 0, 1);
33         std::string result = luaL_checkstring(L, -1);
34         lua_pop(L, 2);
35         return result;
36 }
37
38 int script_exception_wrapper(lua_State *L, lua_CFunction f)
39 {
40         try {
41                 return f(L);  // Call wrapped function and return result.
42         } catch (const char *s) {  // Catch and convert exceptions.
43                 lua_pushstring(L, s);
44         } catch (std::exception &e) {
45                 lua_pushstring(L, e.what());
46         }
47         return lua_error(L);  // Rethrow as a Lua error.
48 }
49
50 int script_error_handler(lua_State *L)
51 {
52         lua_getglobal(L, "core");
53         lua_getfield(L, -1, "error_handler");
54         if (!lua_isnil(L, -1)) {
55                 lua_pushvalue(L, 1);
56         } else {
57                 // No Lua error handler available. Call debug.traceback(tostring(#1), level).
58                 lua_getglobal(L, "debug");
59                 lua_getfield(L, -1, "traceback");
60                 lua_getglobal(L, "tostring");
61                 lua_pushvalue(L, 1);
62                 lua_call(L, 1, 1);
63         }
64         lua_pushinteger(L, 2); // Stack level
65         lua_call(L, 2, 1);
66         return 1;
67 }
68
69 /*
70  * Note that we can't get tracebacks for LUA_ERRMEM or LUA_ERRERR (without
71  * hacking Lua internals).  For LUA_ERRMEM, this is because memory errors will
72  * not execute the error handler, and by the time lua_pcall returns the
73  * execution stack will have already been unwound.  For LUA_ERRERR, there was
74  * another error while trying to generate a backtrace from a LUA_ERRRUN.  It is
75  * presumed there is an error with the internal Lua state and thus not possible
76  * to gather a coherent backtrace.  Realistically, the best we can do here is
77  * print which C function performed the failing pcall.
78  */
79 void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn)
80 {
81         if (pcall_result == 0)
82                 return;
83
84         const char *err_type;
85         switch (pcall_result) {
86         case LUA_ERRRUN:
87                 err_type = "Runtime";
88                 break;
89         case LUA_ERRMEM:
90                 err_type = "OOM";
91                 break;
92         case LUA_ERRERR:
93                 err_type = "Double fault";
94                 break;
95         default:
96                 err_type = "Unknown";
97         }
98
99         if (!mod)
100                 mod = "??";
101
102         if (!fxn)
103                 fxn = "??";
104
105         const char *err_descr = lua_tostring(L, -1);
106         if (!err_descr)
107                 err_descr = "<no description>";
108
109         char buf[256];
110         porting::mt_snprintf(buf, sizeof(buf), "%s error from mod '%s' in callback %s(): ",
111                 err_type, mod, fxn);
112
113         std::string err_msg(buf);
114         err_msg += err_descr;
115
116         if (pcall_result == LUA_ERRMEM) {
117                 err_msg += "\nCurrent Lua memory usage: "
118                         + itos(lua_gc(L, LUA_GCCOUNT, 0) >> 10) + " MB";
119         }
120
121         throw LuaError(err_msg);
122 }
123
124 static void script_log_add_source(lua_State *L, std::string &message, int stack_depth)
125 {
126         lua_Debug ar;
127
128         if (lua_getstack(L, stack_depth, &ar)) {
129                 FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed");
130                 message.append(" (at " + std::string(ar.short_src) + ":"
131                         + std::to_string(ar.currentline) + ")");
132         } else {
133                 message.append(" (at ?:?)");
134         }
135 }
136
137 bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to,
138         int stack_depth)
139 {
140         thread_local std::vector<u64> logged_messages;
141
142         script_log_add_source(L, message, stack_depth);
143         u64 hash = murmur_hash_64_ua(message.data(), message.length(), 0xBADBABE);
144
145         if (std::find(logged_messages.begin(), logged_messages.end(), hash)
146                         == logged_messages.end()) {
147
148                 logged_messages.emplace_back(hash);
149                 log_to << message << std::endl;
150                 return true;
151         }
152         return false;
153 }
154
155 DeprecatedHandlingMode get_deprecated_handling_mode()
156 {
157         static thread_local bool configured = false;
158         static thread_local DeprecatedHandlingMode ret = DeprecatedHandlingMode::Ignore;
159
160         // Only read settings on first call
161         if (!configured) {
162                 std::string value = g_settings->get("deprecated_lua_api_handling");
163                 if (value == "log") {
164                         ret = DeprecatedHandlingMode::Log;
165                 } else if (value == "error") {
166                         ret = DeprecatedHandlingMode::Error;
167                 }
168                 configured = true;
169         }
170
171         return ret;
172 }
173
174 void log_deprecated(lua_State *L, std::string message, int stack_depth)
175 {
176         DeprecatedHandlingMode mode = get_deprecated_handling_mode();
177         if (mode == DeprecatedHandlingMode::Ignore)
178                 return;
179
180         script_log_add_source(L, message, stack_depth);
181         warningstream << message << std::endl;
182
183         if (mode == DeprecatedHandlingMode::Error)
184                 script_error(L, LUA_ERRRUN, NULL, NULL);
185         else
186                 infostream << script_get_backtrace(L) << std::endl;
187 }
188
189 void call_string_dump(lua_State *L, int idx)
190 {
191         // Retrieve string.dump from insecure env to avoid it being tampered with
192         lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
193         if (!lua_isnil(L, -1))
194                 lua_getfield(L, -1, "string");
195         else
196                 lua_getglobal(L, "string");
197         lua_getfield(L, -1, "dump");
198         lua_remove(L, -2); // remove _G
199         lua_remove(L, -2); // remove 'string' table
200         lua_pushvalue(L, idx);
201         lua_call(L, 1, 1);
202 }