c++luaembedded-language

Lua 5.2 Sandboxing in different objects with C API


Consider the following C++ code using the Lua C API:

#include <string>
#include <cassert>

#include <lua/lua.hpp>

class AwesomeThing
{
    lua_State* _lua;
    std::string _name;

public:
    AwesomeThing(lua_State* L, const std::string& name, const std::string& luafile)
        : _lua{ L },
          _name{ name }
    {
        assert(luaL_loadfile(_lua, luafile.c_str()) == 0); // 1:chunk

        lua_newtable(_lua); // 1:chunk, 2:tbl
        lua_newtable(_lua); // 1:chunk, 2:tbl, 3:tbl(mt)
        lua_getglobal(_lua, "_G"); // 1:chunk, 2: tbl, 3:tbl(mt), 4:_G
        lua_setfield(_lua, 3, "__index"); // 1:chunk, 2: tbl, 3:tbl(mt)
        lua_setmetatable(_lua, 2); // 1:chunk, 2: tbl

        lua_setupvalue(_lua, -2, 1); // 1:chunk
        if (lua_pcall(_lua, 0, 0, 0) != 0) // compiled chunk
        {
            auto error = lua_tostring(_lua, -1);
            throw std::runtime_error(error);
        }

        lua_setglobal(_lua, _name.c_str()); // empty stack
    }

    void init()
    {
        lua_getglobal(_lua, _name.c_str()); // 1:env
        assert(lua_isnil(_lua, 1) == 0);

        lua_getfield(_lua, 1, "onInit"); // 1:env, 2:func
        assert(lua_isnil(_lua, 2) == 0);
        assert(lua_isfunction(_lua, 2) == 1);

        assert(lua_pcall(_lua, 0, LUA_MULTRET, 0) == 0); // 1:env, 2:retval

        lua_pop(_lua, 1); // -1:env
        lua_pop(_lua, 1); // empty stack
        assert(lua_gettop(_lua) == 0);
    }
};

int main()
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

    AwesomeThing at1(L, "thing1", "file1.lua");
    AwesomeThing at2(L, "thing2", "file2.lua");

    at1.init();
    at2.init();

    return 0;
}

With two very basic Lua files:

file1.lua

function onInit()
    print("init file1")
end

file2.lua

function onInit()
    print("init file2")
end

As is, I get an error in at2's constructor call at lua_pcall: attempt to call table value

When I comment out all references/calls to at2, I instead get an error in at1's init() at lua_getfield(_lua, 1, "onInit"): PANIC: unprotected error in call to Lua API (attempt to index a nil value)

I feel like there's something fundamental I'm missing in the way I'm handling the sandboxing. I've tried my best to follow a few other Lua 5.2 sandboxing examples I've found online, but so far nothing has helped.


Solution

  • After messing around with the code myself, I was able to fix it and the errors seem to come from just a few errors.

    Fixing the constructor involves creating the environment table and loading the chunk in the opposite order, so that the environment table is left on the stack after the function call:

            lua_newtable(_lua); // 1:tbl
    
            assert(luaL_loadfile(_lua, luafile.c_str()) == 0); // 1:tbl, 2:chunk
    
            lua_newtable(_lua); // 1:tbl, 2:chunk, 3:tbl(mt)
            lua_getglobal(_lua, "_G"); // 1:tbl, 2:chunk, 3:tbl(mt), 4:_G
            lua_setfield(_lua, 3, "__index"); // 1:tbl, 2:chunk, 3:tbl(mt)
            lua_setmetatable(_lua, 1); // 1:tbl, 2:chunk
            lua_pushvalue(_lua, 1); // 1:tbl, 2:chunk, 3:tbl
    
            lua_setupvalue(_lua, -2, 1); // 1:tbl, 2:chunk
            if (lua_pcall(_lua, 0, 0, 0) != 0) // compiled chunk
            {
                auto error = lua_tostring(_lua, -1);
                throw std::runtime_error(error);
            }
    
            // 1:tbl
    
            lua_setglobal(_lua, _name.c_str()); // empty stack
    

    Then in init(), since you use LUA_MULTRET, just clear the stack by replacing both pop calls with lua_settop(_lua, 0).