From 3a67eb79825852f0f296177e837086abdbb76b6f Mon Sep 17 00:00:00 2001 From: Lucas Ritzdorf <42657792+LRitzdorf@users.noreply.github.com> Date: Fri, 5 Jun 2026 15:20:02 -0600 Subject: [PATCH] config/lua: improve error handle-ability with Lua `require` (#14937) * config/lua: make `require` throw an actual error for nonexistent modules `require` uses pcall to catch errors and display them to the user. This is usually okay, but it also hides errors if Lua tries to load a nonexistent module, which the Lua config might actually want to detect and handle on its own (e.g. by loading a different module, or disabling functionality). Ref #14534 * config/lua: make vanilla `require` available as `__require` If `safeLuaRequire()`'s error-catching behavior isn't wanted, this allows the user to call the original version directly, as `__require`. Or, they could bring it back as the default by doing e.g. `require = __require`, which might be desired to avoid breaking third-party modules that want to catch errors during module load. --- src/config/lua/ConfigManager.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/config/lua/ConfigManager.cpp b/src/config/lua/ConfigManager.cpp index 309604dfb..dfbb21a59 100644 --- a/src/config/lua/ConfigManager.cpp +++ b/src/config/lua/ConfigManager.cpp @@ -128,6 +128,17 @@ static int safeLuaRequire(lua_State* L) { lua_pop(L, 1); } + // if we failed to resolve the require'd module, return that as an actual + // error (so the user can catch it via pcall) + { + std::string moduleErrPrefix = std::format("module '{}' not found", moduleName); + if (err.starts_with(moduleErrPrefix)) + return luaL_error(L, err.c_str()); + } + + // otherwise, throw the error message into the config-errors list, and + // return an empty table to Lua (weird from a Lua perspective, but + // hopefully acceptable since the error is directly visible to the user) if (auto* mgr = CConfigManager::fromLuaState(L); mgr) { if (!moduleName.empty()) { trackRequiredLuaModulePath(L, mgr, moduleName); @@ -138,7 +149,7 @@ static int safeLuaRequire(lua_State* L) { lua_pop(L, 1); // error object - lua_newtable(L); // fallback module + lua_newtable(L); // empty table as fallback return const int fallbackIdx = lua_gettop(L); if (!moduleName.empty()) { @@ -322,8 +333,10 @@ void CConfigManager::reinitLuaState() { lua_getglobal(m_lua, "require"); if (lua_isfunction(m_lua, -1)) { + lua_pushvalue(m_lua, -1); + lua_setglobal(m_lua, "__require"); // original `require` as `__require` lua_pushcclosure(m_lua, safeLuaRequire, 1); - lua_setglobal(m_lua, "require"); + lua_setglobal(m_lua, "require"); // safe require as `require` } else lua_pop(m_lua, 1);