diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 90c987522d..a669a3c670 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -51,6 +51,9 @@ return { } )X"); + TestingOpenMW::VFSTestFile metaIndexErrorFile( + "return setmetatable({}, { __index = function(t, key) error('meta index error') end })"); + std::string genBigScript() { std::stringstream buf; @@ -70,7 +73,7 @@ return { { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "aaa/counter.lua", &counterFile }, { "bbb/tests.lua", &testsFile }, { "invalid.lua", &invalidScriptFile }, { "big.lua", &bigScriptFile }, - { "requireBig.lua", &requireBigScriptFile } }); + { "requireBig.lua", &requireBigScriptFile }, { "metaIndexError.lua", &metaIndexErrorFile } }); LuaUtil::ScriptsConfiguration mCfg; LuaUtil::LuaState mLua{ mVFS.get(), &mCfg }; @@ -223,4 +226,11 @@ return { // At this moment all instances of the script should be garbage-collected. EXPECT_LT(memWithoutScript, memWithScript); } + + TEST_F(LuaStateTest, SafeIndexMetamethod) + { + sol::table t = mLua.runInNewSandbox("metaIndexError.lua"); + // without safe get we crash here + EXPECT_ERROR(LuaUtil::safeGet(t, "any key"), "meta index error"); + } } diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 13e2208b68..3cf6378f2f 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -376,7 +376,7 @@ namespace LuaUtil sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res) { - if (!res.valid() && static_cast(res.get_type()) == LUA_TSTRING) + if (!res.valid()) throw std::runtime_error(std::string("Lua error: ") += res.get().what()); else return std::move(res); @@ -397,7 +397,7 @@ namespace LuaUtil std::string fileContent(std::istreambuf_iterator(*mVFS->get(path)), {}); sol::load_result res = mSol.load(fileContent, path, sol::load_mode::text); if (!res.valid()) - throw std::runtime_error("Lua error: " + res.get()); + throw std::runtime_error(std::string("Lua error: ") += res.get().what()); return res; } diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index aea1e32590..509b5e16e1 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -232,16 +232,36 @@ namespace LuaUtil } } + // work around for a (likely) sol3 bug + // when the index meta method throws, simply calling table.get crashes instead of re-throwing the error + template + sol::object safeGet(const sol::table& table, const Key& key) + { + auto index = table.traverse_raw_get>( + sol::metatable_key, sol::meta_function::index); + if (index) + { + sol::protected_function_result result = index.value()(table, key); + if (result.valid()) + return result.get(); + else + throw result.get(); + } + else + return table.raw_get(key); + } + // getFieldOrNil(table, "a", "b", "c") returns table["a"]["b"]["c"] or nil if some of the fields doesn't exist. template sol::object getFieldOrNil(const sol::object& table, std::string_view first, const Str&... str) { if (!table.is()) return sol::nil; + sol::object value = safeGet(table.as(), first); if constexpr (sizeof...(str) == 0) - return table.as()[first]; + return value; else - return getFieldOrNil(table.as()[first], str...); + return getFieldOrNil(value, str...); } // String representation of a Lua object. Should be used for debugging/logging purposes only.