Lua 5.2 使用 C 在不同对象中进行沙盒处理 API
Lua 5.2 Sandboxing in different objects with C API
考虑以下使用 Lua C API 的 C++ 代码:
#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;
}
有两个非常基本的 Lua 文件:
file1.lua
function onInit()
print("init file1")
end
file2.lua
function onInit()
print("init file2")
end
照原样,我在 at2
的构造函数调用 lua_pcall
中遇到错误:attempt to call table value
当我注释掉所有 references/calls 到 at2
时,我反而在 lua_getfield(_lua, 1, "onInit")
的 at1
的 init()
中收到错误:PANIC:调用 Lua API 时发生未受保护的错误(尝试索引 nil 值)
我觉得我在处理沙盒的方式中缺少一些基本的东西。我已尽力遵循我在网上找到的其他一些 Lua 5.2 沙盒示例,但到目前为止没有任何帮助。
在我自己修改代码之后,我能够修复它并且错误似乎来自一些错误。
lua_pcall
从堆栈中弹出被调用的函数,但在这两种情况下,在您的代码中您都假设该函数在 lua_pcall
之后仍在堆栈中。这会导致错误的堆栈操作。
- 在构造函数中,您显然试图存储对块(函数)而不是环境的引用 table。但这甚至不起作用,因为该功能已经弹出。如果它确实有效,
init()
中的 lua_getfield
调用将不会按预期工作,因为该块没有名为 onInit
的字段——环境 table 有。
修复构造函数涉及创建环境 table 并以相反的顺序加载块,以便在函数调用后将环境 table 留在堆栈中:
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
然后在 init()
中,由于您使用 LUA_MULTRET
,只需将两个 pop 调用替换为 lua_settop(_lua, 0)
即可清除堆栈。
考虑以下使用 Lua C API 的 C++ 代码:
#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;
}
有两个非常基本的 Lua 文件:
file1.lua
function onInit()
print("init file1")
end
file2.lua
function onInit()
print("init file2")
end
照原样,我在 at2
的构造函数调用 lua_pcall
中遇到错误:attempt to call table value
当我注释掉所有 references/calls 到 at2
时,我反而在 lua_getfield(_lua, 1, "onInit")
的 at1
的 init()
中收到错误:PANIC:调用 Lua API 时发生未受保护的错误(尝试索引 nil 值)
我觉得我在处理沙盒的方式中缺少一些基本的东西。我已尽力遵循我在网上找到的其他一些 Lua 5.2 沙盒示例,但到目前为止没有任何帮助。
在我自己修改代码之后,我能够修复它并且错误似乎来自一些错误。
lua_pcall
从堆栈中弹出被调用的函数,但在这两种情况下,在您的代码中您都假设该函数在lua_pcall
之后仍在堆栈中。这会导致错误的堆栈操作。- 在构造函数中,您显然试图存储对块(函数)而不是环境的引用 table。但这甚至不起作用,因为该功能已经弹出。如果它确实有效,
init()
中的lua_getfield
调用将不会按预期工作,因为该块没有名为onInit
的字段——环境 table 有。
修复构造函数涉及创建环境 table 并以相反的顺序加载块,以便在函数调用后将环境 table 留在堆栈中:
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
然后在 init()
中,由于您使用 LUA_MULTRET
,只需将两个 pop 调用替换为 lua_settop(_lua, 0)
即可清除堆栈。