在5.2中对嵌入式Lua进行沙盒隔离 / 为来自lua.file的函数设置环境

7

假设我至少有两个Lua脚本文件。

test1.lua test2.lua

两者都定义了一个init函数和其他类似名称的函数。

我该如何使用C++/C将每个脚本文件加载到单独的环境中,使用Lua 5.2,以便相同的函数名称不会冲突 - 我找到了一个针对5.1的示例代码,但对我无效(因为setenv已经消失,而lua_setuservalue似乎也不起作用)

这里有一个示例 Calling lua functions from .lua's using handles?

基本上,如果我将setenv替换为setuservalue,则会出现访问冲突。


谢谢,但我已经阅读了有关load和loadfile的内容,然而我仍未找到解决方案。 - Steve
当尝试操作环境时,“setuservalue”肯定不是正确的函数。然而,Lua文档似乎对你应该做什么有些不清楚。lua_load 表示,与已加载块关联的单个upvalue被设置为其环境,但没有说明如何从C将upvalue与块相关联。如果你是从Lua内部加载块,则 load 函数的Lua版本看起来应该正确设置环境。 - Rook
我会对建议你调用lua_setupvalue的人持怀疑态度。它在文档中作为调试API的一部分,因此在正常使用lua时不需要触及它。 - Rook
@Rook:如果你对 Lua文档 持怀疑态度(它明确告诉你要这样做),那么你打算相信什么?调试API更适合称为“内省”API;使用它并没有本质上的问题。 - Nicol Bolas
我有所怀疑,因为它没有明确告诉你要做什么。它只是简单地提到了upvalues,并且字符串"lua_setupvalue"在文档的debug库部分之外没有任何引用。5.1手册中说:"在使用这个库时应该小心。这里提供的函数应该专门用于调试和类似的任务,比如性能分析。请不要抵制将它们作为通常的编程工具来使用的诱惑。"我的犹豫并不是没有道理的,尽管在这种情况下它被证明是没有根据的。 - Rook
显示剩余5条评论
2个回答

9

非官方Lua FAQ中有一篇关于Lua沙盒的文章。我猜你可以很容易地将这个逻辑转换到C/C++代码中。

还可以参考lua-users wiki上的LuaFiveTo

更正

事实上,这并不像看起来那么简单。但最终的重点很简单:加载你的代码块,推入_ENV表,使用lua_setupvalue(L,-2,1)。重要的是,这个表应该在栈的顶部。

作为一个小例子,使用两个默认为_G的环境通过元表读取内容:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main(void){
        lua_State *L = luaL_newstate();
        char *file1 = "file1.lua";
        char *file2 = "file2.lua";

        luaL_openlibs(L);

        luaL_loadfile(L,file2); // S: 1
        luaL_loadfile(L,file1); // S: 2
        lua_newtable(L); // ENV for file 1: S: 321
        lua_newtable(L); // ENV for file 2: S: 4321

        //lets have each function have its metatable, where missed lookups are
        //instead looked up in the global table _G

        lua_newtable(L); // metatable S: 54321
        lua_getglobal(L,"_G"); // pushes _G, which will be the __index metatable entry S: 654321

        lua_setfield(L,-2,"__index"); // metatable on top S: 54321
        lua_pushvalue(L,-1); // copy the metatable S: 554321
        lua_setmetatable(L,-3); // set the last copy for env2 S: 54321
        lua_setmetatable(L,-3); // set the original for env1  S: 4321
        // here we end up having 2 tables on the stack for 2 environments
        lua_setupvalue(L,1,1); // first upvalue == _ENV so set it. S: 321
        lua_setupvalue(L,2,1); // set _ENV for file S: 21
        // Remaining on the stack: 2 chunks with env set.
        lua_pcall(L,0,LUA_MULTRET,0);
        lua_pcall(L,0,LUA_MULTRET,0);
        lua_close(L);
        return 0;
}

对于这2个Lua文件:

-- file1.lua
function init()
        A="foo"
        print("Hello from file1")
        print(A)
end
init()

-- file2.lua
-- this shows that stuff defined in file1 will not polute the environment for file2
print("init function is",tostring(init))
function init()
        A="bar"
        print("Hello from file2")
        print(A)
end
init()

谢谢,但我知道这些资源 - 然而我无法在C/C++中复现这个。 - Steve
确实,问题在于loadloadfile的C语言等价物并没有如此直观的接口或文档。lua-users维基页面似乎也有点过时,并包含从未出现在5.2规范中的特性。 - Rook
jpjacobs,我们今天在lua-irc上见面了。正如我所说,我已经找到了解决方案,但再次感谢您提供另一种示例! - Steve

0
首先,为什么这些函数是全局的?它们应该是脚本本地的。如果你要在其他文件中使用它们,它们应该创建并返回一个包含它们想要公开的函数的表。
现代的做法是在需要这些文件时执行以下操作:
local Library = require 'library'

Library.Func1(...)

因此,您不会污染全局Lua命名空间。您使用本地变量。

但是,如果您坚持像这样使用全局变量,您可以按照文档所说的那样:更改编译块的第一个upvalue。

基本上,如果我用setenv替换setuservalue-我会得到访问冲突。

当然会。这不是lua_setuservalue的作用。它用于设置与userdata相关联的值。您想要的是适当称为lua_setupvalue的内容。

使用您引用的示例代码,正确的答案应该是:

lua_setupvalue(L, -2, 1);

我无意中自己找到了解决方法 - lua_setupvalue(L,-2,0); 导致崩溃!- 我成功地使用了lua_setupvalue(L,-2,1); - 我的知识有限,我确切地不知道这个上下文中的1是什么。但它似乎有效 - 现在是否也可能仅将我的对象暴露在此“命名空间”中而不在全局表中?我正在使用luabind来公开我的类,目前我正在使用luabind :: globals(myLuaState)[“myObj”] = myObj - Steve
关于“为什么”函数是全局的 - 我想创建一个事件系统,使用相同的函数签名(例如init)的多个实体的脚本。由于这些脚本通常不是由开发人员创建的,我希望尽可能地减少麻烦,不需要任何库或模块 - 因此,我希望确保每个脚本在其自己的环境中执行 - 它无论如何都不会被源化! - Steve
@Steve:文档没有明确说明lua_setupvalue函数是基于零还是基于一的索引。看起来它们是基于一的。 - Nicol Bolas
基本上 - 我甚至不确定那个参数是什么意思 - 它总是1吗?此外,你知道如何仅将我的C++对象暴露给这个环境吗? - Steve
@Steve:任何类型的函数都可以有0个或多个upvalue。Lua函数(编译后的Lua代码)至少会有一个upvalue。第一个upvalue,索引1,是该函数的环境(这就是为什么每个Lua函数至少有一个的原因)。至于后者,那就取决于你了。将其放入环境表中是一种方法。 - Nicol Bolas
显示剩余2条评论

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