Lua 5.2 C API中不同对象的沙盒化

4
考虑以下使用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处报错:尝试调用表值

当我将所有对at2的引用/调用都注释掉时,at1中的init()lua_getfield(_lua, 1, "onInit")处出现错误:PANIC:在调用Lua API时未受保护的错误(尝试索引空值)

我感觉我在处理沙箱环境方面缺少了一些基本的东西。我已经尽力遵循我在网上找到的一些Lua 5.2沙盒示例,但迄今为止没有什么帮助。

1个回答

4

我自己研究了一下代码,终于将其修复了,错误似乎只是来自几个问题。

  • lua_pcall 从栈中弹出被调用的函数,但在你的代码中,两种情况都假设函数在 lua_pcall 后仍然在栈上。这导致了错误的栈操作。
  • 在构造函数中,你显然尝试存储块(函数)的引用,而不是环境表。这甚至无法工作,因为该函数已经被弹出了。即使它能工作,init() 中的 lua_getfield 调用也无法正常工作,因为块没有名为 onInit 的字段,而环境表有。

修复构造函数需要以相反的顺序创建环境表并加载块(函数),这样在函数调用后环境表会留在栈上:

        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和luaL_dostring弄清楚了如何做到这一点,但我仍然不确定我的C代码哪里出了问题,但这解释了一切。非常感谢! - Addy
1
@Addy 很高兴能帮忙!特别是lua_type()函数非常有用,可以验证您对堆栈上内容的假设。我在代码中添加了这个函数,并立即发现在构造函数中的lua_setglobal之前堆栈为空。从那里开始,一切都开始变得清晰明了。 - cdhowie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接