*/
#include "common/c_internal.h"
+#include "util/numeric.h"
#include "debug.h"
+#include "log.h"
+#include "porting.h"
+#include "settings.h"
+#include <algorithm> // std::find
std::string script_get_backtrace(lua_State *L)
{
- std::string s;
- lua_getglobal(L, "debug");
- if(lua_istable(L, -1)){
- lua_getfield(L, -1, "traceback");
- if(lua_isfunction(L, -1)) {
- lua_call(L, 0, 1);
- if(lua_isstring(L, -1)){
- s = lua_tostring(L, -1);
- }
- }
- lua_pop(L, 1);
- }
+ lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_BACKTRACE);
+ lua_call(L, 0, 1);
+ std::string result = luaL_checkstring(L, -1);
lua_pop(L, 1);
- return s;
+ return result;
}
-int script_error_handler(lua_State *L) {
- lua_getglobal(L, "debug");
- if (!lua_istable(L, -1)) {
- lua_pop(L, 1);
- return 1;
+int script_exception_wrapper(lua_State *L, lua_CFunction f)
+{
+ try {
+ return f(L); // Call wrapped function and return result.
+ } catch (const char *s) { // Catch and convert exceptions.
+ lua_pushstring(L, s);
+ } catch (std::exception &e) {
+ lua_pushstring(L, e.what());
+ }
+ return lua_error(L); // Rethrow as a Lua error.
+}
+
+/*
+ * 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
+ * not execute the error handler, and by the time lua_pcall returns the
+ * execution stack will have already been unwound. For LUA_ERRERR, there was
+ * another error while trying to generate a backtrace from a LUA_ERRRUN. It is
+ * presumed there is an error with the internal Lua state and thus not possible
+ * to gather a coherent backtrace. Realistically, the best we can do here is
+ * print which C function performed the failing pcall.
+ */
+void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn)
+{
+ if (pcall_result == 0)
+ return;
+
+ const char *err_type;
+ switch (pcall_result) {
+ case LUA_ERRRUN:
+ err_type = "Runtime";
+ break;
+ case LUA_ERRMEM:
+ err_type = "OOM";
+ break;
+ case LUA_ERRERR:
+ err_type = "Double fault";
+ break;
+ default:
+ err_type = "Unknown";
}
- lua_getfield(L, -1, "traceback");
- if (!lua_isfunction(L, -1)) {
- lua_pop(L, 2);
- return 1;
+
+ if (!mod)
+ mod = "??";
+
+ if (!fxn)
+ fxn = "??";
+
+ const char *err_descr = lua_tostring(L, -1);
+ if (!err_descr)
+ err_descr = "<no description>";
+
+ char buf[256];
+ porting::mt_snprintf(buf, sizeof(buf), "%s error from mod '%s' in callback %s(): ",
+ err_type, mod, fxn);
+
+ std::string err_msg(buf);
+ err_msg += err_descr;
+
+ if (pcall_result == LUA_ERRMEM) {
+ err_msg += "\nCurrent Lua memory usage: "
+ + itos(lua_gc(L, LUA_GCCOUNT, 0) >> 10) + " MB";
}
- lua_pushvalue(L, 1);
- lua_pushinteger(L, 2);
- lua_call(L, 2, 1);
- return 1;
+
+ throw LuaError(err_msg);
}
-void script_error(lua_State *L)
+static void script_log_add_source(lua_State *L, std::string &message, int stack_depth)
{
- throw LuaError(NULL, lua_tostring(L, -1));
+ lua_Debug ar;
+
+ if (lua_getstack(L, stack_depth, &ar)) {
+ FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed");
+ message.append(" (at " + std::string(ar.short_src) + ":"
+ + std::to_string(ar.currentline) + ")");
+ } else {
+ message.append(" (at ?:?)");
+ }
}
-// Push the list of callbacks (a lua table).
-// Then push nargs arguments.
-// Then call this function, which
-// - runs the callbacks
-// - removes the table and arguments from the lua stack
-// - pushes the return value, computed depending on mode
-void script_run_callbacks(lua_State *L, int nargs, RunCallbacksMode mode)
+bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to,
+ int stack_depth)
{
- // Insert the return value into the lua stack, below the table
- assert(lua_gettop(L) >= nargs + 1);
-
- lua_pushnil(L);
- int rv = lua_gettop(L) - nargs - 1;
- lua_insert(L, rv);
-
- // Insert error handler after return value
- lua_pushcfunction(L, script_error_handler);
- int errorhandler = rv + 1;
- lua_insert(L, errorhandler);
-
- // Stack now looks like this:
- // ... <return value = nil> <error handler> <table> <arg#1> <arg#2> ... <arg#n>
-
- int table = errorhandler + 1;
- int arg = table + 1;
-
- luaL_checktype(L, table, LUA_TTABLE);
-
- // Foreach
- lua_pushnil(L);
- bool first_loop = true;
- while(lua_next(L, table) != 0){
- // key at index -2 and value at index -1
- luaL_checktype(L, -1, LUA_TFUNCTION);
- // Call function
- for(int i = 0; i < nargs; i++)
- lua_pushvalue(L, arg+i);
- if(lua_pcall(L, nargs, 1, errorhandler))
- script_error(L);
-
- // Move return value to designated space in stack
- // Or pop it
- if(first_loop){
- // Result of first callback is always moved
- lua_replace(L, rv);
- first_loop = false;
- } else {
- // Otherwise, what happens depends on the mode
- if(mode == RUN_CALLBACKS_MODE_FIRST)
- lua_pop(L, 1);
- else if(mode == RUN_CALLBACKS_MODE_LAST)
- lua_replace(L, rv);
- else if(mode == RUN_CALLBACKS_MODE_AND ||
- mode == RUN_CALLBACKS_MODE_AND_SC){
- if((bool)lua_toboolean(L, rv) == true &&
- (bool)lua_toboolean(L, -1) == false)
- lua_replace(L, rv);
- else
- lua_pop(L, 1);
- }
- else if(mode == RUN_CALLBACKS_MODE_OR ||
- mode == RUN_CALLBACKS_MODE_OR_SC){
- if((bool)lua_toboolean(L, rv) == false &&
- (bool)lua_toboolean(L, -1) == true)
- lua_replace(L, rv);
- else
- lua_pop(L, 1);
- }
- else
- assert(0);
- }
+ thread_local std::vector<u64> logged_messages;
- // Handle short circuit modes
- if(mode == RUN_CALLBACKS_MODE_AND_SC &&
- (bool)lua_toboolean(L, rv) == false)
- break;
- else if(mode == RUN_CALLBACKS_MODE_OR_SC &&
- (bool)lua_toboolean(L, rv) == true)
- break;
+ script_log_add_source(L, message, stack_depth);
+ u64 hash = murmur_hash_64_ua(message.data(), message.length(), 0xBADBABE);
- // value removed, keep key for next iteration
- }
+ if (std::find(logged_messages.begin(), logged_messages.end(), hash)
+ == logged_messages.end()) {
- // Remove stuff from stack, leaving only the return value
- lua_settop(L, rv);
+ logged_messages.emplace_back(hash);
+ log_to << message << std::endl;
+ return true;
+ }
+ return false;
+}
- // Fix return value in case no callbacks were called
- if(first_loop){
- if(mode == RUN_CALLBACKS_MODE_AND ||
- mode == RUN_CALLBACKS_MODE_AND_SC){
- lua_pop(L, 1);
- lua_pushboolean(L, true);
- }
- else if(mode == RUN_CALLBACKS_MODE_OR ||
- mode == RUN_CALLBACKS_MODE_OR_SC){
- lua_pop(L, 1);
- lua_pushboolean(L, false);
+DeprecatedHandlingMode get_deprecated_handling_mode()
+{
+ static thread_local bool configured = false;
+ static thread_local DeprecatedHandlingMode ret = DeprecatedHandlingMode::Ignore;
+
+ // Only read settings on first call
+ if (!configured) {
+ std::string value = g_settings->get("deprecated_lua_api_handling");
+ if (value == "log") {
+ ret = DeprecatedHandlingMode::Log;
+ } else if (value == "error") {
+ ret = DeprecatedHandlingMode::Error;
}
+ configured = true;
}
+
+ return ret;
}
+void log_deprecated(lua_State *L, std::string message, int stack_depth)
+{
+ DeprecatedHandlingMode mode = get_deprecated_handling_mode();
+ if (mode == DeprecatedHandlingMode::Ignore)
+ return;
+
+ script_log_add_source(L, message, stack_depth);
+ warningstream << message << std::endl;
+
+ if (mode == DeprecatedHandlingMode::Error)
+ script_error(L, LUA_ERRRUN, NULL, NULL);
+ else
+ infostream << script_get_backtrace(L) << std::endl;
+}