#3860·mgba

mGBA crashes when executing `error({})` in lua

Author: holly-hackerCreated Sep 13, 2026Updated Sep 13, 2026

I was testing out compiling Haxe scripts to Lua to run in mGBA, and it generates the following code:

lua
-- ...

function _hx_handle_error(obj)
  local message = tostring(obj)
  if _G.debug and _G.debug.traceback then
    -- level 2 to skip _hx_handle_error
    message = _G.debug.traceback(message, 2)
  end
  return setmetatable({}, { __tostring = function() return message end })
end

_hx_static_init();
local success, err = _G.xpcall(function() 
  Example.main();
  _hx_luv.run();
end, _hx_handle_error)
if not success then _G.error(err) end

I'm not super familiar with either Lua or Haxe, but it seems Haxe uses xpcall to register an error handler which will change the error value into an object with a __tostring implementation and then let that go to the error call. It is valid in Lua to pass non-string values to error.

I've minimized this to the following repro:

lua
error({})

This crashes both mGBA 10.5 and the head of the main branch (both ran via a Nix flake).

KDE's crash manager reports the following stacktrace:

Stack trace of thread 271131:
#0  0x00007fbfdb581e5d __strlen_avx2 (libc.so.6 + 0x181e5d)
#1  0x00007fbfdb4b56b3 __strdup (libc.so.6 + 0xb56b3)
#2  0x00007fbfe01b7f4b _luaRun (libmgba.so.0.11 + 0x1b7f4b)
#3  0x0000561bc2dc6b12 _ZN4QGBA19ScriptingController4loadERNS_11VFileDeviceERK7QString (.mgba-qt-wrapped + 0x1fbb12)
#4  0x0000561bc2dc8577 _ZN4QGBA19ScriptingController8loadFileERK7QString (.mgba-qt-wrapped + 0x1fd577)
#5  0x0000561bc2dc86a7 _ZN4QGBA13ScriptingView4loadEv (.mgba-qt-wrapped + 0x1fd6a7)
#6  0x00007fbfdbf48ae1 _Z10doActivateILb0EEvP7QObjectiPPv (libQt5Core.so.5 + 0x348ae1)
#7  0x00007fbfdf969dd0 _ZN7QAction9triggeredEb (libQt5Widgets.so.5 + 0x169dd0)
#8  0x00007fbfdf96cc94 _ZN7QAction8activateENS_11ActionEventE (libQt5Widgets.so.5 + 0x16cc94)
#9  0x00007fbfdfb0d0a2 _ZN12QMenuPrivate19activateCausedStackERK7QVectorI8QPointerI7QWidgetEEP7QActionNS7_11ActionEventEb (libQt5Widgets.so.5 + 0x30d0a2)
#10 0x00007fbfdfb1547a _ZN12QMenuPrivate14activateActionEP7QActionNS0_11ActionEventEb (libQt5Widgets.so.5 + 0x31547a)
#11 0x00007fbfdf9b7508 _ZN7QWidget5eventEP6QEvent (libQt5Widgets.so.5 + 0x1b7508)
#12 0x00007fbfdf972277 _ZN19QApplicationPrivate13notify_helperEP7QObjectP6QEvent (libQt5Widgets.so.5 + 0x172277)
#13 0x00007fbfdf979f0f _ZN12QApplication6notifyEP7QObjectP6QEvent (libQt5Widgets.so.5 + 0x179f0f)
#14 0x00007fbfdbf0b0c8 _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent (libQt5Core.so.5 + 0x30b0c8)
#15 0x00007fbfdf977dba _ZN19QApplicationPrivate14sendMouseEventEP7QWidgetP11QMouseEventS1_S1_PS1_R8QPointerIS0_Ebb (libQt5Widgets.so.5 + 0x177dba)
#16 0x00007fbfdf9d3310 _ZN13QWidgetWindow16handleMouseEventEP11QMouseEvent (libQt5Widgets.so.5 + 0x1d3310)
#17 0x00007fbfdf9d624f _ZN13QWidgetWindow5eventEP6QEvent (libQt5Widgets.so.5 + 0x1d624f)
#18 0x00007fbfdf972277 _ZN19QApplicationPrivate13notify_helperEP7QObjectP6QEvent (libQt5Widgets.so.5 + 0x172277)
#19 0x00007fbfdbf0b0c8 _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent (libQt5Core.so.5 + 0x30b0c8)
#20 0x00007fbfdc336b77 _ZN22QGuiApplicationPrivate17processMouseEventEPN29QWindowSystemInterfacePrivate10MouseEventE (libQt5Gui.so.5 + 0x136b77)
#21 0x00007fbfdc304da4 _ZN22QWindowSystemInterface22sendWindowSystemEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE (libQt5Gui.so.5 + 0x104da4)
#22 0x00007fbfd29603d0 _ZL23userEventSourceDispatchP8_GSourcePFiPvES1_ (libQt5WaylandClient.so.5 + 0xb93d0)
#23 0x00007fbfdbb00c0b g_main_context_dispatch_unlocked (libglib-2.0.so.0 + 0x62c0b)
#24 0x00007fbfdbb041d8 g_main_context_iterate_unlocked.isra.0 (libglib-2.0.so.0 + 0x661d8)
#25 0x00007fbfdbb04a9f g_main_context_iteration (libglib-2.0.so.0 + 0x66a9f)
#26 0x00007fbfdbf6a463 _ZN20QEventDispatcherGlib13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE (libQt5Core.so.5 + 0x36a463)
#27 0x00007fbfdbf098ab _ZN10QEventLoop4execE6QFlagsINS_17ProcessEventsFlagEE (libQt5Core.so.5 + 0x3098ab)
#28 0x00007fbfdbf12b34 _ZN16QCoreApplication4execEv (libQt5Core.so.5 + 0x312b34)
#29 0x0000561bc2c5fd77 main (.mgba-qt-wrapped + 0x94d77)
#30 0x00007fbfdb42b285 __libc_start_call_main (libc.so.6 + 0x2b285)
#31 0x00007fbfdb42b338 __libc_start_main@@GLIBC_2.34 (libc.so.6 + 0x2b338)
#32 0x0000561bc2c614c5 _start (.mgba-qt-wrapped + 0x964c5)             

As a local workaround, I let an LLM generate the following patch which seems to fix it on my end. I don't know if it covers all cases where mGBA interprets a Lua object as a string value, though.

diff
diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c
index 27b52ae3..e2b9b2c6 100644
--- a/src/script/engines/lua.c
+++ b/src/script/engines/lua.c
@@ -1041,8 +1041,11 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi
 		luaContext->func = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX);
 		return true;
 	case LUA_ERRSYNTAX:
-		luaContext->lastError = strdup(lua_tostring(luaContext->lua, -1));
-		lua_pop(luaContext->lua, 1);
+		int errorIndex = lua_gettop(luaContext->lua);
+		const char* error = _luaTolstring(luaContext->lua, errorIndex, NULL);
+		luaContext->lastError = strdup(error ? error : "Unknown Lua error");
+		/* _luaTolstring pushes a string, in addition to the original error. */
+		lua_pop(luaContext->lua, 2);
 		_luaError(luaContext);
 		break;
 	default:
@@ -1208,8 +1211,11 @@ bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame*
 	lua_rawset(luaContext->lua, LUA_REGISTRYINDEX);
 
 	if (ret == LUA_ERRRUN) {
-		luaContext->lastError = strdup(lua_tostring(luaContext->lua, -1));
-		lua_pop(luaContext->lua, 1);
+		int errorIndex = lua_gettop(luaContext->lua);
+		const char* error = _luaTolstring(luaContext->lua, errorIndex, NULL);
+		luaContext->lastError = strdup(error ? error : "Unknown Lua error");
+		/* _luaTolstring pushes a string, in addition to the original error. */
+		lua_pop(luaContext->lua, 2);
 
 		_luaError(luaContext);
 	}