#include "common/c_internal.h"
#include "debug.h"
+#include "log.h"
+#include "settings.h"
std::string script_get_backtrace(lua_State *L)
{
return 1;
}
-void script_error(lua_State *L)
+int script_exception_wrapper(lua_State *L, lua_CFunction f)
{
- throw LuaError(NULL, lua_tostring(L, -1));
+ 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 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";
+ }
+
+ if (!mod)
+ mod = "??";
+
+ if (!fxn)
+ fxn = "??";
+
+ const char *err_descr = lua_tostring(L, -1);
+ if (!err_descr)
+ err_descr = "<no description>";
+
+ char buf[256];
+ 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";
+ }
+
+ throw LuaError(err_msg);
}
// 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)
+// - replaces the table and arguments with the return value,
+// computed depending on mode
+void script_run_callbacks_f(lua_State *L, int nargs,
+ RunCallbacksMode mode, const char *fxn)
{
- // Insert the return value into the lua stack, below the table
- assert(lua_gettop(L) >= nargs + 1);
+ FATAL_ERROR_IF(lua_gettop(L) < nargs + 1, "Not enough arguments");
- lua_pushnil(L);
- int rv = lua_gettop(L) - nargs - 1;
- lua_insert(L, rv);
+ // Insert error handler
+ PUSH_ERROR_HANDLER(L);
+ int error_handler = lua_gettop(L) - nargs - 1;
+ lua_insert(L, error_handler);
- // Insert error handler after return value
- lua_pushcfunction(L, script_error_handler);
- int errorhandler = rv + 1;
- lua_insert(L, errorhandler);
+ // Insert run_callbacks between error handler and table
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "run_callbacks");
+ lua_remove(L, -2);
+ lua_insert(L, error_handler + 1);
- // 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);
- }
+ // Insert mode after table
+ lua_pushnumber(L, (int) mode);
+ lua_insert(L, error_handler + 3);
- // 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;
+ // Stack now looks like this:
+ // ... <error handler> <run_callbacks> <table> <mode> <arg#1> <arg#2> ... <arg#n>
- // value removed, keep key for next iteration
- }
+ int result = lua_pcall(L, nargs + 2, 1, error_handler);
+ if (result != 0)
+ script_error(L, result, NULL, fxn);
- // Remove stuff from stack, leaving only the return value
- lua_settop(L, rv);
+ lua_remove(L, error_handler);
+}
- // 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);
+void log_deprecated(lua_State *L, const std::string &message)
+{
+ static bool configured = false;
+ static bool do_log = false;
+ static bool do_error = false;
+
+ // Only read settings on first call
+ if (!configured) {
+ std::string value = g_settings->get("deprecated_lua_api_handling");
+ if (value == "log") {
+ do_log = true;
+ } else if (value == "error") {
+ do_log = true;
+ do_error = true;
}
- else if(mode == RUN_CALLBACKS_MODE_OR ||
- mode == RUN_CALLBACKS_MODE_OR_SC){
- lua_pop(L, 1);
- lua_pushboolean(L, false);
+ }
+
+ if (do_log) {
+ warningstream << message << std::endl;
+ // L can be NULL if we get called by log_deprecated(const std::string &msg)
+ // from scripting_game.cpp.
+ if (L) {
+ if (do_error)
+ script_error(L, LUA_ERRRUN, NULL, NULL);
+ else
+ infostream << script_get_backtrace(L) << std::endl;
}
}
}
-