diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index 32abb8babe..b45c4e30c6 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -17,7 +17,7 @@ namespace MWLua MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager(); sol::table api(context.mLua->sol(), sol::create); - api["MODE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( + api["MODE"] = LuaUtil::makeStrictReadOnly(context.mLua->sol().create_table_with( "Static", CameraMode::Static, "FirstPerson", CameraMode::FirstPerson, "ThirdPerson", CameraMode::ThirdPerson, diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 1be6e086fa..8078af2e49 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -87,7 +87,7 @@ namespace MWLua return SDL_GetKeyName(SDL_GetKeyFromScancode(code)); }; - api["ACTION"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + api["ACTION"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"GameMenu", MWInput::A_GameMenu}, {"Screenshot", MWInput::A_Screenshot}, {"Inventory", MWInput::A_Inventory}, @@ -141,7 +141,7 @@ namespace MWLua {"ZoomOut", MWInput::A_ZoomOut} })); - api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + api["CONTROL_SWITCH"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"Controls", "playercontrols"}, {"Fighting", "playerfighting"}, {"Jumping", "playerjumping"}, @@ -151,7 +151,7 @@ namespace MWLua {"VanityMode", "vanitymode"} })); - api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + api["CONTROLLER_BUTTON"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"A", SDL_CONTROLLER_BUTTON_A}, {"B", SDL_CONTROLLER_BUTTON_B}, {"X", SDL_CONTROLLER_BUTTON_X}, @@ -169,7 +169,7 @@ namespace MWLua {"DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT} })); - api["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + api["CONTROLLER_AXIS"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"LeftX", SDL_CONTROLLER_AXIS_LEFTX}, {"LeftY", SDL_CONTROLLER_AXIS_LEFTY}, {"RightX", SDL_CONTROLLER_AXIS_RIGHTX}, @@ -183,7 +183,7 @@ namespace MWLua {"MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_MoveLeftRight)} })); - api["KEY"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + api["KEY"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"_0", SDL_SCANCODE_0}, {"_1", SDL_SCANCODE_1}, {"_2", SDL_SCANCODE_2}, diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index cf1458c883..3ad9baac61 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -47,7 +47,7 @@ namespace MWLua return LObject(getId(r.mHitObject), worldView->getObjectRegistry()); }); - api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + api["COLLISION_TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"World", MWPhysics::CollisionType_World}, {"Door", MWPhysics::CollisionType_Door}, {"Actor", MWPhysics::CollisionType_Actor}, diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 98ff5148d5..2a272ef862 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -113,12 +113,12 @@ namespace MWLua void addActorBindings(sol::table actor, const Context& context) { - actor["STANCE"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + actor["STANCE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"Nothing", MWMechanics::DrawState_Nothing}, {"Weapon", MWMechanics::DrawState_Weapon}, {"Spell", MWMechanics::DrawState_Spell}, })); - actor["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + actor["EQUIPMENT_SLOT"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"Helmet", MWWorld::InventoryStore::Slot_Helmet}, {"Cuirass", MWWorld::InventoryStore::Slot_Cuirass}, {"Greaves", MWWorld::InventoryStore::Slot_Greaves}, diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index 3b6265697e..87ae984cf9 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -16,7 +16,7 @@ namespace MWLua { void addWeaponBindings(sol::table weapon, const Context& context) { - weapon["TYPE"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + weapon["TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"ShortBladeOneHand", ESM::Weapon::ShortBladeOneHand}, {"LongBladeOneHand", ESM::Weapon::LongBladeOneHand}, {"LongBladeTwoHand", ESM::Weapon::LongBladeTwoHand}, diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index b66aa35368..eec6509034 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -183,7 +183,7 @@ namespace MWLua { luaManager->addUIMessage(message); }; - api["CONSOLE_COLOR"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + api["CONSOLE_COLOR"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"Default", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Default.substr(1))}, {"Error", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Error.substr(1))}, {"Success", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Success.substr(1))}, @@ -298,9 +298,9 @@ namespace MWLua sol::table typeTable = context.mLua->newTable(); for (const auto& it : LuaUi::widgetTypeToName()) typeTable.set(it.second, it.first); - api["TYPE"] = LuaUtil::makeReadOnly(typeTable); + api["TYPE"] = LuaUtil::makeStrictReadOnly(typeTable); - api["ALIGNMENT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + api["ALIGNMENT"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ { "Start", LuaUi::Alignment::Start }, { "Center", LuaUi::Alignment::Center }, { "End", LuaUi::Alignment::End } diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index ac32f12992..4056fb59f9 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -84,12 +84,22 @@ namespace LuaUtil end printGen = function(name) return function(...) return printToLog(name, ...) end end + function createStrictIndexFn(tbl) + return function(_, key) + local res = tbl[key] + if res ~= nil then + return res + else + error('Key not found: '..tostring(key), 2) + end + end + end function pairsForReadOnly(v) - local nextFn, t, firstKey = pairs(getmetatable(v).__index) + local nextFn, t, firstKey = pairs(getmetatable(v).t) return function(_, k) return nextFn(t, k) end, v, firstKey end function ipairsForReadOnly(v) - local nextFn, t, firstKey = ipairs(getmetatable(v).__index) + local nextFn, t, firstKey = ipairs(getmetatable(v).t) return function(_, k) return nextFn(t, k) end, v, firstKey end local function nextForArray(array, index) @@ -136,7 +146,7 @@ namespace LuaUtil mSandboxEnv = sol::nil; } - sol::table makeReadOnly(sol::table table) + sol::table makeReadOnly(const sol::table& table, bool strictIndex) { if (table == sol::nil) return table; @@ -146,7 +156,11 @@ namespace LuaUtil lua_State* luaState = table.lua_state(); sol::state_view lua(luaState); sol::table meta(lua, sol::create); - meta["__index"] = table; + meta["t"] = table; + if (strictIndex) + meta["__index"] = lua["createStrictIndexFn"](table); + else + meta["__index"] = table; meta["__pairs"] = lua["pairsForReadOnly"]; meta["__ipairs"] = lua["ipairsForReadOnly"]; @@ -158,7 +172,7 @@ namespace LuaUtil sol::table getMutableFromReadOnly(const sol::userdata& ro) { - return ro[sol::metatable_key].get()["__index"]; + return ro[sol::metatable_key].get()["t"]; } void LuaState::addCommonPackage(std::string packageName, sol::object package) diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index f9be5e9e99..bf947111dc 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -139,7 +139,9 @@ namespace LuaUtil // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. // Needed to forbid any changes in common resources that can be accessed from different sandboxes. - sol::table makeReadOnly(sol::table); + // `strictIndex = true` replaces default `__index` with a strict version that throws an error if key is not found. + sol::table makeReadOnly(const sol::table&, bool strictIndex = false); + inline sol::table makeStrictReadOnly(const sol::table& tbl) { return makeReadOnly(tbl, true); } sol::table getMutableFromReadOnly(const sol::userdata&); } diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index 7f52a770a1..a9c5c40a7c 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -214,7 +214,8 @@ namespace LuaUtil util["clamp"] = [](float value, float from, float to) { return std::clamp(value, from, to); }; // NOTE: `util["clamp"] = std::clamp` causes error 'AddressSanitizer: stack-use-after-scope' util["normalizeAngle"] = &Misc::normalizeAngle; - util["makeReadOnly"] = &makeReadOnly; + util["makeReadOnly"] = [](const sol::table& tbl) { return makeReadOnly(tbl, /*strictIndex=*/false); }; + util["makeStrictReadOnly"] = [](const sol::table& tbl) { return makeReadOnly(tbl, /*strictIndex=*/true); }; if (lua["bit32"] != sol::nil) { diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index 3dd959b4a5..19eb3a82cd 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -25,6 +25,12 @@ -- @param #table table Any table. -- @return #table The same table wrapped with read only userdata. +--- +-- Makes a table read only and overrides `__index` with the strict version that throws an error if the key is not found. +-- @function [parent=#util] makeStrictReadOnly +-- @param #table table Any table. +-- @return #table The same table wrapped with read only userdata. + --- -- Parses Lua code from string and returns as a function. -- @function [parent=#util] loadCode