/*! \file lua_mod.cpp * \brief Lua 5.4 scripting — embedded in engine.so. * * Embeds a sandboxed Lua 5.4 interpreter. Scripts live as LUA_* attributes * on objects. The lua() softcode function dispatches through this module. * * Bridge functions use engine-internal APIs (externs.h) for permission * checks and COM interfaces for cross-layer operations. */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" #include "engine_api.h" #include "libmux.h" #include "modules.h" #include "lua_mod.h" #include #include #include #include // Global pointer for bridge functions to reach the module instance. // Safe because there is exactly one CLuaMod instance per process. // static CLuaMod *g_pLuaMod = nullptr; // ========================================================================= // Per-execution context stored in Lua registry. // ========================================================================= struct lua_exec_ctx { dbref executor; dbref caller; dbref enactor; const UTF8 *pArgs[10]; int nArgs; // compiled_route distinguishes the JIT leg for diagnostics and any // future route-specific policy. It no longer forbids world effects: // #1751 Phase 4 deleted post-entry interpreter re-run, so the #1750 // "compiled path is effect-free" medicine is obsolete. Effects on the // compiled path are delivered exactly once under the same permission // checks as the interpreter (softcode contract). bool compiled_route; }; // effect_refused / lua_refuse_compiled_effect removed with the effect-free // corridor. Bridge functions below run on both routes. #define LUA_EXEC_CTX_KEY "mux_exec_ctx" #define LUA_MOD_KEY "mux_lua_mod" static lua_exec_ctx *get_exec_ctx(lua_State *L) { lua_getfield(L, LUA_REGISTRYINDEX, LUA_EXEC_CTX_KEY); lua_exec_ctx *ctx = static_cast(lua_touserdata(L, -1)); lua_pop(L, 1); return ctx; } static CLuaMod *get_lua_mod(lua_State *L) { (void)L; return g_pLuaMod; } // ========================================================================= // mux.* bridge functions (Lua C functions) // ========================================================================= // mux.notify(dbref, message) — send text to a player. // // Permission model matches @pemit: executor must be nearby, have // Long_Fingers, or control the target. If pemit_players is on and // the target is a connected player, page-lock is checked. If // pemit_any is on, any @pemit to a player is allowed. // static int bridge_notify(lua_State *L) { CLuaMod *mod = get_lua_mod(L); if (nullptr == mod) return 0; lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) return 0; dbref target = static_cast(luaL_checkinteger(L, 1)); const char *msg = luaL_checkstring(L, 2); dbref executor = ctx->executor; if (!Good_obj(target)) { lua_pushboolean(L, 0); return 1; } // Enforce locality constraints (matches do_pemit_single). // bool ok = nearby(executor, target) || Long_Fingers(executor) || Controls(executor, target); if ( !ok && isPlayer(target) && mudconf.pemit_players) { // Check page-lock without side effects. // ok = Connected(target) && could_doit(executor, target, A_LPAGE) && could_doit(target, executor, A_LPAGE); } if (!ok && mudconf.pemit_any) { ok = true; } if (!ok) { lua_pushboolean(L, 0); return 1; } mux_INotify *pNotify = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_Notify, nullptr, UseSameProcess, IID_INotify, reinterpret_cast(&pNotify)); if (MUX_SUCCEEDED(mr) && nullptr != pNotify) { pNotify->Notify(target, reinterpret_cast(msg)); pNotify->Release(); } lua_pushboolean(L, 1); return 1; } // mux.name(dbref) — return object name. // // Permission model matches name(): if read_rem_name is off, requires // nearby_or_control, isPlayer, or Long_Fingers. // static int bridge_name(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) { lua_pushnil(L); return 1; } dbref obj = static_cast(luaL_checkinteger(L, 1)); if (!Good_obj(obj)) { lua_pushnil(L); return 1; } if (!mudconf.read_rem_name) { if ( !Controls(ctx->executor, obj) && !nearby(ctx->executor, obj) && !isPlayer(obj) && !Long_Fingers(ctx->executor)) { lua_pushnil(L); return 1; } } const UTF8 *pName = Name(obj); if (nullptr != pName) { // For exits, return only the name before the first semicolon. // if (isExit(obj)) { const char *semi = strchr(reinterpret_cast(pName), ';'); if (semi) { lua_pushlstring(L, reinterpret_cast(pName), semi - reinterpret_cast(pName)); return 1; } } lua_pushstring(L, reinterpret_cast(pName)); return 1; } lua_pushnil(L); return 1; } // mux.owner(dbref) — return owner dbref. // Softcode owner() uses match_thing + Owner(); absolute #dbref is allowed // there too. Require Good_obj only (same as softcode after a successful match). // static int bridge_owner(lua_State *L) { dbref obj = static_cast(luaL_checkinteger(L, 1)); if (!Good_obj(obj)) { lua_pushnil(L); return 1; } mux_IObjectInfo *pOI = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_ObjectInfo, nullptr, UseSameProcess, IID_IObjectInfo, reinterpret_cast(&pOI)); if (MUX_SUCCEEDED(mr) && nullptr != pOI) { dbref owner; mr = pOI->GetOwner(obj, &owner); pOI->Release(); if (MUX_SUCCEEDED(mr)) { lua_pushinteger(L, owner); return 1; } } lua_pushnil(L); return 1; } // mux.location(dbref) — return location dbref. // // Permission model matches loc(): requires locatable(executor, obj, // enactor), which respects UNFINDABLE, nearby, see_all. // static int bridge_location(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) { lua_pushnil(L); return 1; } dbref obj = static_cast(luaL_checkinteger(L, 1)); if (!Good_obj(obj)) { lua_pushnil(L); return 1; } if (!locatable(ctx->executor, obj, ctx->enactor)) { lua_pushnil(L); return 1; } lua_pushinteger(L, Location(obj)); return 1; } // mux.get(dbref, attrname) — read an attribute value. // static int bridge_get(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) { lua_pushnil(L); return 1; } int obj = static_cast(luaL_checkinteger(L, 1)); const char *attrname = luaL_checkstring(L, 2); mux_IAttributeAccess *pAA = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_AttributeAccess, nullptr, UseSameProcess, IID_IAttributeAccess, reinterpret_cast(&pAA)); if (MUX_SUCCEEDED(mr) && nullptr != pAA) { UTF8 value[8000]; size_t nValue = 0; mr = pAA->GetAttribute(ctx->executor, static_cast(obj), reinterpret_cast(attrname), value, sizeof(value), &nValue); pAA->Release(); if (MUX_SUCCEEDED(mr)) { lua_pushlstring(L, reinterpret_cast(value), nValue); return 1; } } lua_pushnil(L); return 1; } // mux.set(dbref, attrname, value) — write an attribute value. // static int bridge_set(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) return luaL_error(L, "no execution context"); int obj = static_cast(luaL_checkinteger(L, 1)); const char *attrname = luaL_checkstring(L, 2); const char *value = luaL_checkstring(L, 3); mux_IAttributeAccess *pAA = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_AttributeAccess, nullptr, UseSameProcess, IID_IAttributeAccess, reinterpret_cast(&pAA)); if (MUX_SUCCEEDED(mr) && nullptr != pAA) { mr = pAA->SetAttribute(ctx->executor, static_cast(obj), reinterpret_cast(attrname), reinterpret_cast(value)); pAA->Release(); if (MUX_FAILED(mr)) { return luaL_error(L, "permission denied"); } } return 0; } // mux.eval(expression) — evaluate softcode expression. // static int bridge_eval(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) { lua_pushnil(L); return 1; } const char *expr = luaL_checkstring(L, 1); mux_IEvaluator *pEval = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_Evaluator, nullptr, UseSameProcess, IID_IEvaluator, reinterpret_cast(&pEval)); if (MUX_SUCCEEDED(mr) && nullptr != pEval) { UTF8 result[8000]; size_t nResult = 0; mr = pEval->Eval(ctx->executor, ctx->caller, ctx->enactor, reinterpret_cast(expr), result, sizeof(result), &nResult); pEval->Release(); if (MUX_SUCCEEDED(mr)) { lua_pushlstring(L, reinterpret_cast(result), nResult); return 1; } } lua_pushnil(L); return 1; } // mux.type(dbref) — return object type string. // static int bridge_type(lua_State *L) { int obj = static_cast(luaL_checkinteger(L, 1)); mux_IObjectInfo *pOI = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_ObjectInfo, nullptr, UseSameProcess, IID_IObjectInfo, reinterpret_cast(&pOI)); if (MUX_SUCCEEDED(mr) && nullptr != pOI) { int type = -1; mr = pOI->GetType(static_cast(obj), &type); pOI->Release(); if (MUX_SUCCEEDED(mr)) { // Type constants: 0=ROOM, 1=THING, 2=EXIT, 3=PLAYER static const char *types[] = {"ROOM", "THING", "EXIT", "PLAYER"}; if (type >= 0 && type <= 3) { lua_pushstring(L, types[type]); } else { lua_pushstring(L, "UNKNOWN"); } return 1; } } lua_pushnil(L); return 1; } // mux.flags(dbref) — return flag string. // static int bridge_flags(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); int obj = static_cast(luaL_checkinteger(L, 1)); mux_IObjectInfo *pOI = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_ObjectInfo, nullptr, UseSameProcess, IID_IObjectInfo, reinterpret_cast(&pOI)); if (MUX_SUCCEEDED(mr) && nullptr != pOI) { UTF8 *pFlags = nullptr; dbref looker = (ctx != nullptr) ? ctx->executor : static_cast(1); mr = pOI->DecodeFlags(looker, static_cast(obj), &pFlags); pOI->Release(); if (MUX_SUCCEEDED(mr) && nullptr != pFlags) { lua_pushstring(L, reinterpret_cast(pFlags)); return 1; } } lua_pushnil(L); return 1; } // mux.isplayer(dbref) — check if object is a player. // static int bridge_isplayer(lua_State *L) { int obj = static_cast(luaL_checkinteger(L, 1)); mux_IObjectInfo *pOI = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_ObjectInfo, nullptr, UseSameProcess, IID_IObjectInfo, reinterpret_cast(&pOI)); if (MUX_SUCCEEDED(mr) && nullptr != pOI) { bool bPlayer = false; mr = pOI->IsPlayer(static_cast(obj), &bPlayer); pOI->Release(); if (MUX_SUCCEEDED(mr)) { lua_pushboolean(L, bPlayer ? 1 : 0); return 1; } } lua_pushboolean(L, 0); return 1; } // mux.isconnected(dbref) — check if player is connected. // Match softcode hasflag(obj,CONNECTED): pub_flags || Examinable || self (#1287). // static int bridge_isconnected(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) { lua_pushboolean(L, 0); return 1; } dbref obj = static_cast(luaL_checkinteger(L, 1)); if ( !Good_obj(obj) || ( !mudconf.pub_flags && !Examinable(ctx->executor, obj) && obj != ctx->executor && obj != ctx->enactor)) { lua_pushboolean(L, 0); return 1; } mux_IObjectInfo *pOI = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_ObjectInfo, nullptr, UseSameProcess, IID_IObjectInfo, reinterpret_cast(&pOI)); if (MUX_SUCCEEDED(mr) && nullptr != pOI) { bool bConn = false; mr = pOI->IsConnected(obj, &bConn); pOI->Release(); if (MUX_SUCCEEDED(mr)) { lua_pushboolean(L, bConn ? 1 : 0); return 1; } } lua_pushboolean(L, 0); return 1; } // mux.pennies(dbref) — return object pennies. // Match softcode money(): Examinable required (#1287). Absolute dbref // without Examinable used to return balance for any object. // static int bridge_pennies(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) { lua_pushnil(L); return 1; } dbref obj = static_cast(luaL_checkinteger(L, 1)); if (!Good_obj(obj) || !Examinable(ctx->executor, obj)) { lua_pushnil(L); return 1; } mux_IObjectInfo *pOI = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_ObjectInfo, nullptr, UseSameProcess, IID_IObjectInfo, reinterpret_cast(&pOI)); if (MUX_SUCCEEDED(mr) && nullptr != pOI) { int pennies = 0; mr = pOI->GetPennies(obj, &pennies); pOI->Release(); if (MUX_SUCCEEDED(mr)) { lua_pushinteger(L, pennies); return 1; } } lua_pushnil(L); return 1; } // mux.iswizard(dbref) — check if object is a wizard. // Match softcode hasflag() object form: pub_flags || Examinable || self (#1287). // static int bridge_iswizard(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) { lua_pushboolean(L, 0); return 1; } dbref obj = static_cast(luaL_checkinteger(L, 1)); if ( !Good_obj(obj) || ( !mudconf.pub_flags && !Examinable(ctx->executor, obj) && obj != ctx->executor && obj != ctx->enactor)) { lua_pushboolean(L, 0); return 1; } mux_IPermissions *pPerms = nullptr; MUX_RESULT mr = mux_CreateInstance(CID_Permissions, nullptr, UseSameProcess, IID_IPermissions, reinterpret_cast(&pPerms)); if (MUX_SUCCEEDED(mr) && nullptr != pPerms) { bool bWizard = false; mr = pPerms->IsWizard(obj, &bWizard); pPerms->Release(); if (MUX_SUCCEEDED(mr)) { lua_pushboolean(L, bWizard ? 1 : 0); return 1; } } lua_pushboolean(L, 0); return 1; } // mux.controls(who, what) — check if who controls what. // // Permission model: executor can only query control relationships // where 'who' is themselves or an object they control. This // prevents scripts from probing wizard control relationships. // static int bridge_controls(lua_State *L) { lua_exec_ctx *ctx = get_exec_ctx(L); if (nullptr == ctx) { lua_pushboolean(L, 0); return 1; } dbref who = static_cast(luaL_checkinteger(L, 1)); dbref what = static_cast(luaL_checkinteger(L, 2)); if ( !Good_obj(who) || !Good_obj(what)) { lua_pushboolean(L, 0); return 1; } // Restrict: executor must be 'who' or must control 'who'. // if ( who != ctx->executor && !Controls(ctx->executor, who)) { lua_pushboolean(L, 0); return 1; } lua_pushboolean(L, Controls(who, what) ? 1 : 0); return 1; } // Bridge function table. // static const luaL_Reg bridge_funcs[] = { {"notify", bridge_notify}, {"pemit", bridge_notify}, {"name", bridge_name}, {"owner", bridge_owner}, {"location", bridge_location}, {"type", bridge_type}, {"flags", bridge_flags}, {"isplayer", bridge_isplayer}, {"isconnected", bridge_isconnected}, {"pennies", bridge_pennies}, {"get", bridge_get}, {"set", bridge_set}, {"eval", bridge_eval}, {"iswizard", bridge_iswizard}, {"controls", bridge_controls}, {nullptr, nullptr} }; // ========================================================================= // Custom memory allocator with limit enforcement. // ========================================================================= void *CLuaMod::LuaAlloc(void *ud, void *ptr, size_t osize, size_t nsize) { CLuaMod *self = static_cast(ud); if (nsize == 0) { // Free. if (ptr != nullptr) { self->m_nMemUsed -= osize; free(ptr); } return nullptr; } // Check memory limit. // size_t delta = nsize - (ptr ? osize : 0); if (self->m_nMemUsed + delta > static_cast(self->m_nMemLimit)) { self->m_bMemExceeded = true; return nullptr; // Allocation denied — triggers Lua OOM error. } void *newptr = realloc(ptr, nsize); if (newptr != nullptr) { self->m_nMemUsed += delta; if (self->m_nMemUsed > self->m_nMemPeak) { self->m_nMemPeak = self->m_nMemUsed; } } return newptr; } // ========================================================================= // Instruction count hook — enforces execution limits. // ========================================================================= // Fires every m_nInsnPoll VM instructions (#1591). // // The Lua module used to bound a chunk only by its own instruction and memory // limits, neither of which is what the rest of the server bounds on: softcode // bounds on wall time. alarm_clock is armed per command // (alarm_clock.set(mudconf.max_cmdsecs) in cque.cpp), the AST evaluator polls // it and answers "#-1 CPU LIMITED", and the JIT is handed the same flag as // dbt->alarm_flag. Lua referenced it nowhere, so lua() ignored max_cmdsecs // entirely. // // Shape borrowed from the JIT's guest-loop budget (#1571): count cheaply, do // the real check periodically. The hook fires on a poll interval rather than // once at the limit, checks the alarm, and only then accounts instructions -- // so the instruction limit keeps its previous meaning while wall time becomes // the bound that agrees with everything else. // // This does NOT bound time spent inside a C function; the hook cannot fire // there. The pattern-matcher half of #1591 is MatchInterrupt below, installed // into lstrlib.c for the same pcall window. // // Wall-clock escape for the Lua pattern matcher (#1591). // // InsnCountHook below cannot cover this: lua_sethook(LUA_MASKCOUNT) counts VM // instructions and does not fire inside a C function, and a pathological // pattern spends all its time inside one call to string.find. lstrlib.c's // own MAXCCALLS bounds recursion depth, which stops a stack overflow but not // exponential backtracking -- that grows in breadth, so depth stays under 200 // while the matcher runs unbounded. // // lstrlib.c calls this every MATCH_INTERRUPT_MASK+1 match() steps when the // pointer is installed. Returning non-zero raises "cpu limited" there, which // lands in the same m_bCpuLimited path as the instruction hook, so the caller // still sees "#-1 CPU LIMITED" -- one budget, one message, all four routes. // extern "C" int (*lua_match_interrupt)(lua_State *L); int CLuaMod::MatchInterrupt(lua_State *L) { if (!alarm_clock.alarmed) { return 0; } // Same instance recovery as InsnCountHook. Flagging the module is what // turns the Lua error into the softcode answer; without it the player // would get a raw "#-1 LUA ERROR: cpu limited" instead. void *ud = nullptr; lua_getallocf(L, &ud); CLuaMod *self = static_cast(ud); if (nullptr != self) { self->m_bCpuLimited = true; } return 1; } void CLuaMod::InsnCountHook(lua_State *L, lua_Debug *ar) { (void)ar; // The allocator ud is the module instance (lua_newstate(LuaAlloc, this)). void *ud = nullptr; lua_getallocf(L, &ud); CLuaMod *self = static_cast(ud); if (nullptr != self) { if (alarm_clock.alarmed) { self->m_bCpuLimited = true; luaL_error(L, "cpu limited"); } self->m_nInsnUsed += self->m_nInsnPoll; if (self->m_nInsnUsed < self->m_nInsnLimit) { return; } } luaL_error(L, "instruction limit exceeded"); } // ========================================================================= // Lua state creation and sandbox setup. // ========================================================================= bool CLuaMod::CreateLuaState(void) { m_nMemUsed = 0; m_nMemPeak = 0; m_bMemExceeded = false; m_L = lua_newstate(LuaAlloc, this); if (nullptr == m_L) { return false; } // Open whitelisted libraries. // luaL_requiref(m_L, "_G", luaopen_base, 1); lua_pop(m_L, 1); luaL_requiref(m_L, "string", luaopen_string, 1); // string.dump reifies bytecode and is an escape hatch for reconstructing // blocked loaders; softcode has no equivalent. (#1287) lua_getglobal(m_L, "string"); if (lua_istable(m_L, -1)) { lua_pushnil(m_L); lua_setfield(m_L, -2, "dump"); } lua_pop(m_L, 1); luaL_requiref(m_L, "table", luaopen_table, 1); lua_pop(m_L, 1); luaL_requiref(m_L, "math", luaopen_math, 1); lua_pop(m_L, 1); luaL_requiref(m_L, "utf8", luaopen_utf8, 1); lua_pop(m_L, 1); luaL_requiref(m_L, "coroutine", luaopen_coroutine, 1); lua_pop(m_L, 1); // Remove dangerous functions from the global table. // // string.dump is removed via the string library patch below if present; // base-library escapes that rebuild loaders are nilled here. static const char *blocked[] = { "load", "loadfile", "dofile", "require", "rawget", "rawset", "rawequal", "rawlen", "collectgarbage", nullptr }; for (int i = 0; blocked[i] != nullptr; i++) { lua_pushnil(m_L); lua_setglobal(m_L, blocked[i]); } // Remap print to do nothing (scripts should use mux.notify). // lua_pushcfunction(m_L, [](lua_State *) -> int { return 0; }); lua_setglobal(m_L, "print"); // Register mux.* bridge table. // luaL_newlib(m_L, bridge_funcs); lua_setglobal(m_L, "mux"); // Store module pointer in registry for bridge function access. // lua_pushlightuserdata(m_L, this); lua_setfield(m_L, LUA_REGISTRYINDEX, LUA_MOD_KEY); return true; } void CLuaMod::DestroyLuaState(void) { if (nullptr != m_L) { lua_close(m_L); m_L = nullptr; } } // ========================================================================= // Chunk execution with sandbox. // ========================================================================= // Execution-context setup shared by the interpreter and compiled routes. // // The bridge C functions (mux.eval, mux.name, ...) read the executor and // friends from the registry, and per-run values (mux.executor, mux.args) // are injected into the global mux table. This used to live inline in // ExecuteChunk only -- the interpreter leg -- so a bridge function invoked // from a COMPILED run found a cleared registry and stale mux fields. Now // that compiled chunks call the real bridge functions through the ordinary // Lua call path (#1745 follow-up), both routes must stage the same context. // // ctx is caller-owned: the registry holds a lightuserdata pointing at it, // so it must outlive the run. Returns the PREVIOUS registry value so the // teardown can restore rather than clear: nested runs (softcode -> lua // under brackets can re-enter) used to stomp the outer run's context to // nil, which #1750's adversarial review measured as a route-dependent // divergence. Restore with lua_restore_exec_context. // // Nesting also used to leave the OUTER mux.args / executor fields destroyed: // setup always overwrote the global mux table, and restore only put back the // registry pointer. An outer chunk that did mux.eval("lua(...)") then // re-read mux.args[k] saw the inner args (or nil) — plan residual "anytime // item 3", pinned by smoke TC071. When nesting, the previous mux table // fields are stacked in the registry and restored with the ctx pointer. // #define LUA_MUX_FIELDS_STACK "mux_fields_stack" // Save mux.executor/caller/enactor/args onto a registry stack (nesting). // static void lua_save_mux_fields(lua_State *L) { lua_getglobal(L, "mux"); if (!lua_istable(L, -1)) { lua_pop(L, 1); return; } lua_createtable(L, 4, 0); lua_getfield(L, -2, "executor"); lua_rawseti(L, -2, 1); lua_getfield(L, -2, "caller"); lua_rawseti(L, -2, 2); lua_getfield(L, -2, "enactor"); lua_rawseti(L, -2, 3); lua_getfield(L, -2, "args"); lua_rawseti(L, -2, 4); // stack: mux, saved lua_getfield(L, LUA_REGISTRYINDEX, LUA_MUX_FIELDS_STACK); if (!lua_istable(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, LUA_MUX_FIELDS_STACK); } // stack: mux, saved, stack const int n = static_cast(lua_rawlen(L, -1)); lua_pushvalue(L, -2); lua_rawseti(L, -2, n + 1); lua_pop(L, 3); // stack, saved, mux } // Pop the most recently saved mux fields back onto the global mux table. // static void lua_restore_mux_fields(lua_State *L) { lua_getfield(L, LUA_REGISTRYINDEX, LUA_MUX_FIELDS_STACK); if (!lua_istable(L, -1)) { lua_pop(L, 1); return; } const int n = static_cast(lua_rawlen(L, -1)); if (n < 1) { lua_pop(L, 1); return; } lua_rawgeti(L, -1, n); // stack: stack, saved lua_pushnil(L); lua_rawseti(L, -3, n); lua_getglobal(L, "mux"); if (!lua_istable(L, -1)) { lua_pop(L, 3); return; } // stack: stack, saved, mux lua_rawgeti(L, -2, 1); lua_setfield(L, -2, "executor"); lua_rawgeti(L, -2, 2); lua_setfield(L, -2, "caller"); lua_rawgeti(L, -2, 3); lua_setfield(L, -2, "enactor"); lua_rawgeti(L, -2, 4); lua_setfield(L, -2, "args"); lua_pop(L, 3); // mux, saved, stack } static lua_exec_ctx *lua_setup_exec_context(lua_State *L, lua_exec_ctx &ctx, dbref executor, dbref caller, dbref enactor, const UTF8 *pArgs[], int nArgs, bool compiled_route) { lua_getfield(L, LUA_REGISTRYINDEX, LUA_EXEC_CTX_KEY); lua_exec_ctx *prev = static_cast(lua_touserdata(L, -1)); lua_pop(L, 1); // Nesting: keep the outer mux.* per-run fields so the outer chunk still // sees its own args after the inner run returns (#1773 TC071). // if (nullptr != prev) { lua_save_mux_fields(L); } ctx.compiled_route = compiled_route; ctx.executor = executor; ctx.caller = caller; ctx.enactor = enactor; ctx.nArgs = (nArgs > 10) ? 10 : nArgs; for (int i = 0; i < ctx.nArgs; i++) { ctx.pArgs[i] = (pArgs != nullptr) ? pArgs[i] : nullptr; } lua_pushlightuserdata(L, &ctx); lua_setfield(L, LUA_REGISTRYINDEX, LUA_EXEC_CTX_KEY); // Inject mux.executor, mux.caller, mux.enactor, mux.args into // the global mux table. // lua_getglobal(L, "mux"); lua_pushinteger(L, executor); lua_setfield(L, -2, "executor"); lua_pushinteger(L, caller); lua_setfield(L, -2, "caller"); lua_pushinteger(L, enactor); lua_setfield(L, -2, "enactor"); // Build mux.args table. // int nSafe = (nArgs > 0 && pArgs != nullptr) ? nArgs : 0; lua_createtable(L, nSafe, 0); for (int i = 0; i < nSafe; i++) { if (pArgs[i] != nullptr) { lua_pushstring(L, reinterpret_cast(pArgs[i])); } else { lua_pushstring(L, ""); } lua_rawseti(L, -2, i + 1); } lua_setfield(L, -2, "args"); lua_pop(L, 1); // pop mux table return prev; } static void lua_restore_exec_context(lua_State *L, lua_exec_ctx *prev) { // Restore mux table fields before swapping the registry pointer back, // so any bridge that peeks at mux.args during teardown (none today) // still sees a coherent pair. // if (nullptr != prev) { lua_restore_mux_fields(L); } if (prev != nullptr) { lua_pushlightuserdata(L, prev); } else { lua_pushnil(L); } lua_setfield(L, LUA_REGISTRYINDEX, LUA_EXEC_CTX_KEY); } bool CLuaMod::ExecuteChunk(lua_State *L, dbref executor, dbref caller, dbref enactor, const UTF8 *pArgs[], int nArgs, UTF8 *pResult, size_t nResultMax, size_t *pnResultLen) { // The compiled chunk is on top of the Lua stack. Set up the execution // context and call it. lua_exec_ctx ctx; lua_exec_ctx *prev_ctx = lua_setup_exec_context(L, ctx, executor, caller, enactor, pArgs, nArgs, false); // Set instruction count hook. // m_bMemExceeded = false; m_bCpuLimited = false; // Poll often enough to notice the alarm, but never less often than the // instruction limit itself -- a small configured limit must still fire // where it always did. m_nInsnPoll = (m_nInsnLimit < LUA_ALARM_POLL_INSNS) ? m_nInsnLimit : LUA_ALARM_POLL_INSNS; if (m_nInsnPoll < 1) { m_nInsnPoll = 1; } m_nInsnUsed = 0; lua_sethook(L, InsnCountHook, LUA_MASKCOUNT, m_nInsnPoll); // Cover the C-function gap the count hook cannot reach (#1591). Set on // every call rather than once at startup: the pointer lives in lstrlib.c // and costs nothing to reassign, and this way it cannot be left dangling // by a module unload. lua_match_interrupt = CLuaMod::MatchInterrupt; // Call the chunk (it's below the mux table stuff we just popped). // int status = lua_pcall(L, 0, 1, 0); // Remove the hook. // lua_sethook(L, nullptr, 0, 0); lua_match_interrupt = nullptr; lua_restore_exec_context(L, prev_ctx); if (status != LUA_OK) { // Error. const char *errmsg = lua_tostring(L, -1); if (nullptr == errmsg) errmsg = "unknown error"; if (m_bCpuLimited) { // Answer exactly as the AST evaluator and the JIT do, rather than // wrapping it as a Lua error: one budget, one message (#1591). m_stats.cpu_limit_hits++; m_stats.errors++; const UTF8 *kMsg = S_("#-1 CPU LIMITED"); size_t n = strlen(reinterpret_cast(kMsg)); if (n >= nResultMax) { n = nResultMax - 1; } memcpy(pResult, kMsg, n); pResult[n] = '\0'; *pnResultLen = n; lua_pop(L, 1); return MUX_S_OK; } if (m_bMemExceeded) { m_stats.mem_limit_hits++; errmsg = "memory limit exceeded"; } else if (strstr(errmsg, "instruction limit") != nullptr) { m_stats.insn_limit_hits++; } m_stats.errors++; size_t n = mux_snprintf(pResult, nResultMax, T("#-1 LUA ERROR: %s"), errmsg); *pnResultLen = n; lua_pop(L, 1); return false; } // Success — convert return value to string. // size_t len = 0; const char *result = nullptr; if (lua_isnil(L, -1) || lua_isnone(L, -1)) { result = ""; len = 0; } else if (lua_isboolean(L, -1)) { result = lua_toboolean(L, -1) ? "1" : "0"; len = 1; } else { result = lua_tolstring(L, -1, &len); if (nullptr == result) { result = ""; len = 0; } } if (len >= nResultMax) { len = nResultMax - 1; } memcpy(pResult, result, len); pResult[len] = '\0'; *pnResultLen = len; lua_pop(L, 1); return true; } // ========================================================================= // CLuaMod — main module class. // ========================================================================= CLuaMod::CLuaMod(void) : m_cRef(1), m_pILog(nullptr), m_pIServerEventsControl(nullptr), m_pINotify(nullptr), m_pIObjectInfo(nullptr), m_pIAttributeAccess(nullptr), m_pIEvaluator(nullptr), m_pIPermissions(nullptr), m_pIJITCompile(nullptr), m_L(nullptr), m_nInsnLimit(LUA_DEFAULT_INSN_LIMIT), m_bCpuLimited(false), m_nInsnPoll(LUA_ALARM_POLL_INSNS), m_nInsnUsed(0), m_nMemLimit(LUA_DEFAULT_MEM_LIMIT), m_nMemUsed(0), m_nMemPeak(0), m_bMemExceeded(false), m_nCacheMaxSize(LUA_DEFAULT_CACHE_SIZE) { memset(&m_stats, 0, sizeof(m_stats)); g_pLuaMod = this; } MUX_RESULT CLuaMod::FinalConstruct(void) { MUX_RESULT mr; // Acquire logging interface. // mr = mux_CreateInstance(CID_Log, nullptr, UseSameProcess, IID_ILog, reinterpret_cast(&m_pILog)); if (MUX_FAILED(mr)) { return mr; } // Register for server events. // mux_IServerEventsSink *pSink = nullptr; mr = QueryInterface(IID_IServerEventsSink, reinterpret_cast(&pSink)); if (MUX_SUCCEEDED(mr)) { mr = mux_CreateInstance(CID_ServerEventsSource, nullptr, UseSameProcess, IID_IServerEventsControl, reinterpret_cast(&m_pIServerEventsControl)); if (MUX_SUCCEEDED(mr)) { m_pIServerEventsControl->Advise(pSink); } pSink->Release(); } // Acquire core interfaces. // mux_CreateInstance(CID_Notify, nullptr, UseSameProcess, IID_INotify, reinterpret_cast(&m_pINotify)); mux_CreateInstance(CID_ObjectInfo, nullptr, UseSameProcess, IID_IObjectInfo, reinterpret_cast(&m_pIObjectInfo)); mux_CreateInstance(CID_AttributeAccess, nullptr, UseSameProcess, IID_IAttributeAccess, reinterpret_cast(&m_pIAttributeAccess)); mux_CreateInstance(CID_Evaluator, nullptr, UseSameProcess, IID_IEvaluator, reinterpret_cast(&m_pIEvaluator)); mux_CreateInstance(CID_Permissions, nullptr, UseSameProcess, IID_IPermissions, reinterpret_cast(&m_pIPermissions)); // Acquire JIT compile interface (optional — graceful degradation). mux_CreateInstance(CID_JITCompile, nullptr, UseSameProcess, IID_IJITCompile, reinterpret_cast(&m_pIJITCompile)); // Create the Lua state. // if (!CreateLuaState()) { if (nullptr != m_pILog) { bool fStarted; m_pILog->start_log(&fStarted, LOG_ALWAYS, T("INI"), T("ERR")); if (fStarted) { m_pILog->log_text(T("Lua module: failed to create Lua state.")); m_pILog->end_log(); } } return MUX_E_FAIL; } // Log that we are alive. // if (nullptr != m_pILog) { bool fStarted; m_pILog->start_log(&fStarted, LOG_ALWAYS, T("INI"), T("INFO")); if (fStarted) { m_pILog->log_text(T("Lua module loaded (Lua 5.4).")); m_pILog->end_log(); } } return MUX_S_OK; } CLuaMod::~CLuaMod() { CacheClear(); DestroyLuaState(); if (nullptr != m_pILog) { bool fStarted; m_pILog->start_log(&fStarted, LOG_ALWAYS, T("INI"), T("INFO")); if (fStarted) { m_pILog->log_text(T("Lua module unloading.")); m_pILog->end_log(); } m_pILog->Release(); m_pILog = nullptr; } if (nullptr != m_pIServerEventsControl) { m_pIServerEventsControl->Release(); m_pIServerEventsControl = nullptr; } if (nullptr != m_pINotify) { m_pINotify->Release(); m_pINotify = nullptr; } if (nullptr != m_pIObjectInfo) { m_pIObjectInfo->Release(); m_pIObjectInfo = nullptr; } if (nullptr != m_pIAttributeAccess) { m_pIAttributeAccess->Release(); m_pIAttributeAccess = nullptr; } if (nullptr != m_pIEvaluator) { m_pIEvaluator->Release(); m_pIEvaluator = nullptr; } if (nullptr != m_pIPermissions) { m_pIPermissions->Release(); m_pIPermissions = nullptr; } if (nullptr != m_pIJITCompile) { m_pIJITCompile->Release(); m_pIJITCompile = nullptr; } if (g_pLuaMod == this) { g_pLuaMod = nullptr; } } MUX_RESULT CLuaMod::QueryInterface(MUX_IID iid, void **ppv) { if (mux_IID_IUnknown == iid) { *ppv = static_cast(this); } else if (IID_ILuaControl == iid) { *ppv = static_cast(this); } else if (IID_IServerEventsSink == iid) { *ppv = static_cast(this); } else { *ppv = nullptr; return MUX_E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); return MUX_S_OK; } uint32_t CLuaMod::AddRef(void) { return m_cRef.fetch_add(1, std::memory_order_relaxed) + 1; } uint32_t CLuaMod::Release(void) { uint32_t prev = m_cRef.fetch_sub(1, std::memory_order_acq_rel); if (1 == prev) { delete this; return 0; } return prev - 1; } // ========================================================================= // Bytecode cache — LRU keyed by source text. // ========================================================================= // LoadCached: try the cache first, compile on miss. // On success, the compiled chunk is on top of the Lua stack. // Returns true on success, false on compile error (error string on stack). // bool CLuaMod::LoadCached(const char *source, size_t nSource, const char *chunkname) { std::string key(source, nSource); auto it = m_cache.find(key); if (it != m_cache.end()) { // Cache hit — push the cached chunk. // m_stats.cache_hits++; lua_rawgeti(m_L, LUA_REGISTRYINDEX, it->second.lua_ref); // Move to front of LRU. // m_cache_lru.erase(it->second.lru_it); m_cache_lru.push_front(key); it->second.lru_it = m_cache_lru.begin(); return true; } // Cache miss — compile. // m_stats.cache_misses++; int status = luaL_loadbufferx(m_L, source, nSource, chunkname, "t"); if (status != LUA_OK) { return false; // Error string is on top of stack. } // Store in cache: push a copy, get a registry reference. // lua_pushvalue(m_L, -1); // duplicate the chunk int ref = luaL_ref(m_L, LUA_REGISTRYINDEX); // Evict if full. // if (static_cast(m_cache.size()) >= m_nCacheMaxSize) { CacheEvict(); } // Insert. // m_cache_lru.push_front(key); cache_entry entry; entry.lua_ref = ref; entry.lru_it = m_cache_lru.begin(); entry.jit_key = 0; entry.jit_eligible = false; m_cache[key] = entry; return true; } void CLuaMod::CacheEvict(void) { if (m_cache_lru.empty()) { return; } // Remove the least recently used entry (back of list). // const std::string &oldest = m_cache_lru.back(); auto it = m_cache.find(oldest); if (it != m_cache.end()) { luaL_unref(m_L, LUA_REGISTRYINDEX, it->second.lua_ref); if (it->second.jit_key != 0 && nullptr != m_pIJITCompile) { m_pIJITCompile->Invalidate(it->second.jit_key); } m_cache.erase(it); } m_cache_lru.pop_back(); } void CLuaMod::CacheClear(void) { for (auto &pair : m_cache) { if (nullptr != m_L) { luaL_unref(m_L, LUA_REGISTRYINDEX, pair.second.lua_ref); } if (pair.second.jit_key != 0 && nullptr != m_pIJITCompile) { m_pIJITCompile->Invalidate(pair.second.jit_key); } } m_cache.clear(); m_cache_lru.clear(); } // ========================================================================= // lua_dump writer callback — accumulates bytecode into a vector. // ========================================================================= struct dump_buffer { std::vector data; }; static int dump_writer(lua_State *L, const void *p, size_t sz, void *ud) { (void)L; dump_buffer *buf = static_cast(ud); const uint8_t *bytes = static_cast(p); buf->data.insert(buf->data.end(), bytes, bytes + sz); return 0; } // ========================================================================= // TryJIT: attempt to JIT-compile a cached chunk. // The chunk must be on top of the Lua stack. // Returns true if JIT succeeded and result is in pResult. // Returns false on JIT failure (caller should fall through to lua_pcall). // Does NOT pop the chunk from the stack. // ========================================================================= bool CLuaMod::TryJIT(cache_entry &entry, dbref executor, dbref caller, dbref enactor, const UTF8 *pArgs[], int nArgs, UTF8 *pResult, size_t nResultMax, size_t *pnResultLen) { if (nullptr == m_pIJITCompile) return false; // Safety gate (#1309): Lua JIT is off by default until the never-run // lowering/codegen path is green. When off, fall through to the Lua // interpreter — same behavior as before the loader fix made the JIT // reachable. // if (!mudconf.lua_jit) return false; // Already tried and failed? if (entry.jit_eligible) { // Already have a compiled key? Run it. if (entry.jit_key != 0) { // Save Lua stack — ECALL handlers may push tables/functions. int saved_top = lua_gettop(m_L); // Stage the same execution context the interpreter leg gets: // compiled chunks reach the bridge C functions and the per-run // mux fields through ordinary Lua calls now, and without this // they saw a cleared registry and the PREVIOUS run's mux table. lua_exec_ctx jit_ctx; lua_exec_ctx *prev_ctx = lua_setup_exec_context(m_L, jit_ctx, executor, caller, enactor, pArgs, nArgs, true); MUX_RESULT mr = m_pIJITCompile->RunCompiled(entry.jit_key, executor, caller, enactor, pArgs, nArgs, pResult, nResultMax, pnResultLen, m_L); lua_restore_exec_context(m_L, prev_ctx); lua_settop(m_L, saved_top); // restore stack if (MUX_E_NOTFOUND == mr) { // Program gone from cache (e.g. jitstats flush). Clear the // latch and compile again below — pre-entry: nothing ran. // entry.jit_key = 0; entry.jit_eligible = false; } else if (MUX_FAILED(mr)) { // Pre-entry setup failure from RunCompiled (depth, // watermarks, oversize carg, get_dbt). Nothing ran; // interpreter fallback is correct (#1837). Post-entry // failures return MUX_S_OK with pResult already committed. // return false; } else { // #1751 Phase 4: handled run — success or committed // LUA ERROR / POST-ENTRY / CPU LIMITED. Do not re-run. // return true; } } else { return false; // Previously failed to compile (pre-entry). } } // First attempt: dump the chunk to bytecode and try JIT compilation. entry.jit_eligible = true; // lua_dump expects the function on top of stack. We have it there // from LoadCached. Push a copy so we don't consume it. lua_pushvalue(m_L, -1); dump_buffer buf; int dump_status = lua_dump(m_L, dump_writer, &buf, 0); lua_pop(m_L, 1); // pop the copy if (dump_status != 0 || buf.data.empty()) { return false; } // Try to compile. uint64_t key = 0; MUX_RESULT mr = m_pIJITCompile->CompileLuaBytecode( buf.data.data(), buf.data.size(), &key); if (MUX_FAILED(mr) || key == 0) { return false; // JIT doesn't support this bytecode; fall through. } entry.jit_key = key; // Run the compiled program. // Save/restore Lua stack — ECALL handlers for table ops, getglobal, // and generic calls push values onto the Lua stack that must be // cleaned up after JIT execution completes. Execution context staged // exactly as on the cached-key path above. int saved_top = lua_gettop(m_L); lua_exec_ctx jit_ctx; lua_exec_ctx *prev_ctx = lua_setup_exec_context(m_L, jit_ctx, executor, caller, enactor, pArgs, nArgs, true); mr = m_pIJITCompile->RunCompiled(key, executor, caller, enactor, pArgs, nArgs, pResult, nResultMax, pnResultLen, m_L); lua_restore_exec_context(m_L, prev_ctx); lua_settop(m_L, saved_top); // restore stack // Post-entry failures return MUX_S_OK with a committed error string. // Pre-entry setup failure (MUX_E_FAIL, empty result) falls through to // the interpreter — nothing ran (#1837). Keep the compiled key so a // later invocation with short cargs can still take the compiled path. // if (MUX_FAILED(mr)) { return false; } return true; } // ========================================================================= // mux_ILuaControl implementation. // ========================================================================= MUX_RESULT CLuaMod::CallAttr(dbref executor, dbref caller, dbref enactor, dbref obj, const UTF8 *pAttrName, const UTF8 *pArgs[], int nArgs, UTF8 *pResult, size_t nResultMax, size_t *pnResultLen) { if (nullptr == m_L) { return MUX_E_FAIL; } // Read the attribute via COM interface (permission-checked). // if (nullptr == m_pIAttributeAccess) { return MUX_E_FAIL; } UTF8 source[8000]; size_t nSource = 0; MUX_RESULT mr = m_pIAttributeAccess->GetAttribute(executor, obj, pAttrName, source, sizeof(source), &nSource); if (MUX_FAILED(mr)) { size_t n = mux_snprintf(pResult, nResultMax, T("#-1 LUA ERROR: cannot read attribute")); *pnResultLen = n; return MUX_S_OK; } if (nSource == 0) { pResult[0] = '\0'; *pnResultLen = 0; return MUX_S_OK; } m_stats.calls++; // Compile (or load from cache). // char chunkname[128]; mux_snprintf(reinterpret_cast(chunkname), sizeof(chunkname), T("@#%d/%s"), static_cast(obj), pAttrName); if (!LoadCached(reinterpret_cast(source), nSource, chunkname)) { const char *errmsg = lua_tostring(m_L, -1); if (nullptr == errmsg) errmsg = "compile error"; m_stats.errors++; size_t n = mux_snprintf(pResult, nResultMax, T("#-1 LUA ERROR: %s"), errmsg); *pnResultLen = n; lua_pop(m_L, 1); return MUX_S_OK; } // Try JIT execution. The compiled chunk is on top of the Lua stack. // If JIT succeeds, pop the chunk and return. // std::string cache_key(reinterpret_cast(source), nSource); auto cache_it = m_cache.find(cache_key); if (cache_it != m_cache.end()) { if (TryJIT(cache_it->second, executor, caller, enactor, pArgs, nArgs, pResult, nResultMax, pnResultLen)) { lua_pop(m_L, 1); // pop the chunk return MUX_S_OK; } } // Fall through to Lua VM execution. // ExecuteChunk(m_L, executor, caller, enactor, pArgs, nArgs, pResult, nResultMax, pnResultLen); return MUX_S_OK; } MUX_RESULT CLuaMod::Eval(dbref executor, dbref caller, dbref enactor, const UTF8 *pSource, size_t nSource, UTF8 *pResult, size_t nResultMax, size_t *pnResultLen) { if (nullptr == m_L) { return MUX_E_FAIL; } // Wizard-only check. // if (nullptr != m_pIPermissions) { bool bWizard = false; m_pIPermissions->IsWizard(executor, &bWizard); if (!bWizard) { size_t n = mux_snprintf(pResult, nResultMax, T("#-1 LUA ERROR: wizard-only")); *pnResultLen = n; return MUX_S_OK; } } m_stats.calls++; if (!LoadCached(reinterpret_cast(pSource), nSource, "@inline")) { const char *errmsg = lua_tostring(m_L, -1); if (nullptr == errmsg) errmsg = "compile error"; m_stats.errors++; size_t n = mux_snprintf(pResult, nResultMax, T("#-1 LUA ERROR: %s"), errmsg); *pnResultLen = n; lua_pop(m_L, 1); return MUX_S_OK; } // Try JIT execution. // std::string cache_key(reinterpret_cast(pSource), nSource); auto cache_it = m_cache.find(cache_key); if (cache_it != m_cache.end()) { if (TryJIT(cache_it->second, executor, caller, enactor, nullptr, 0, pResult, nResultMax, pnResultLen)) { lua_pop(m_L, 1); // pop the chunk return MUX_S_OK; } } // Fall through to Lua VM execution. // ExecuteChunk(m_L, executor, caller, enactor, nullptr, 0, pResult, nResultMax, pnResultLen); return MUX_S_OK; } MUX_RESULT CLuaMod::GetStats(size_t *pnCalls, size_t *pnErrors, size_t *pnInsnLimitHits, size_t *pnMemLimitHits, size_t *pnBytesUsed, size_t *pnCacheHits, size_t *pnCacheMisses, size_t *pnCacheEntries) { *pnCalls = m_stats.calls; *pnErrors = m_stats.errors; *pnInsnLimitHits = m_stats.insn_limit_hits; *pnMemLimitHits = m_stats.mem_limit_hits; *pnBytesUsed = m_nMemUsed; *pnCacheHits = m_stats.cache_hits; *pnCacheMisses = m_stats.cache_misses; *pnCacheEntries = m_cache.size(); return MUX_S_OK; } MUX_RESULT CLuaMod::SetLimits(int nInsnLimit, int nMemLimit) { if (nInsnLimit > 0) { m_nInsnLimit = nInsnLimit; } if (nMemLimit > 0) { m_nMemLimit = nMemLimit; } return MUX_S_OK; } // ========================================================================= // mux_IServerEventsSink stubs. // ========================================================================= void CLuaMod::startup(void) { } void CLuaMod::presync_database(void) { } void CLuaMod::presync_database_sigsegv(void) { } void CLuaMod::dump_database(int dump_type) { (void)dump_type; } void CLuaMod::dump_complete_signal(void) { } void CLuaMod::shutdown(void) { CacheClear(); DestroyLuaState(); } void CLuaMod::dbck(void) { } void CLuaMod::connect(dbref player, int isnew, int num) { (void)player; (void)isnew; (void)num; } void CLuaMod::disconnect(dbref player, int num) { (void)player; (void)num; } void CLuaMod::data_create(dbref object) { (void)object; } void CLuaMod::data_clone(dbref clone, dbref source) { (void)clone; (void)source; } void CLuaMod::data_free(dbref object) { (void)object; } // ========================================================================= // ========================================================================= // Factory function — called from engine_com.cpp's CLuaModFactory. // ========================================================================= MUX_RESULT lua_mod_create_instance(MUX_IID iid, void **ppv) { CLuaMod *pLuaMod = nullptr; try { pLuaMod = new CLuaMod; } catch (...) { ; } if (nullptr == pLuaMod) return MUX_E_OUTOFMEMORY; MUX_RESULT mr = pLuaMod->FinalConstruct(); if (MUX_SUCCEEDED(mr)) { mr = pLuaMod->QueryInterface(iid, ppv); } pLuaMod->Release(); return mr; }