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.
After messing around with the code myself, I was able to fix it and the errors seem to come from just a few errors.
lua_pcallpops the called function from the stack, but in both cases in your code you assume the function is still on the stack afterlua_pcall. This results in bad stack manipulation.lua_getfieldcall ininit()wouldn't work as intended since the chunk doesn't have a field namedonInit-- the environment table does.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:
Then in
init(), since you useLUA_MULTRET, just clear the stack by replacing both pop calls withlua_settop(_lua, 0).