如何在Lua 5.2中重新创建setfenv
的功能?我很难理解你应该如何使用新的_ENV
环境变量。
在Lua 5.1中,您可以使用setfenv
轻松地对任何功能进行沙盒化。
--# Lua 5.1
print('_G', _G) -- address of _G
local foo = function()
print('env', _G) -- address of sandbox _G
bar = 1
end
-- create a simple sandbox
local env = { print = print }
env._G = env
-- set the environment and call the function
setfenv(foo, env)
foo()
-- we should have global in our environment table but not in _G
print(bar, env.bar)
运行此示例显示输出:
_G table: 0x62d6b0
env table: 0x635d00
nil 1
我想在Lua 5.2中重新创建这个简单的例子。以下是我的尝试,但它不像上面的例子那样有用。
--# Lua 5.2
local function setfenv(f, env)
local _ENV = env or {} -- create the _ENV upvalue
return function(...)
print('upvalue', _ENV) -- address of _ENV upvalue
return f(...)
end
end
local foo = function()
print('_ENV', _ENV) -- address of function _ENV
bar = 1
end
-- create a simple sandbox
local env = { print = print }
env._G = env
-- set the environment and call the function
foo_env = setfenv(foo, env)
foo_env()
-- we should have global in our envoirnment table but not in _G
print(bar, env.bar)
运行此示例显示输出:
upvalue table: 0x637e90
_ENV table: 0x6305f0
1 nil
我知道关于这个主题的其他几个问题,但它们似乎主要是处理加载动态代码(文件或字符串),使用Lua 5.2中提供的新load
函数可以很好地工作。在这里,我特别要求在沙箱中运行任意函数的解决方案。我想在不使用debug
库的情况下执行此操作。根据Lua documentation,我们不应该依赖它。
答案 0 :(得分:17)
如果不使用Lua 5.2中的Lua调试库,则无法更改函数的环境。一旦创建了一个函数,那就是它所拥有的环境。修改此环境的唯一方法是修改其第一个upvalue,这需要调试库。
Lua 5.2中环境的一般概念是环境应该被认为是在欺骗之外不可变的(即:调试库)。您在环境中创建一个函数;一旦在那里创造,那就是它所拥有的环境。永远。
这就是Lua 5.1中经常使用的环境,但是通过随意的函数调用来修改任何环境很容易并且受到制裁。如果您的Lua解释器已删除setfenv
(以防止用户破坏沙箱),则用户代码无法在内部为其自己的功能设置环境。所以外面的世界得到了一个沙箱,但是内部世界不能在沙箱中有沙箱
Lua 5.2机制使得在创建函数后更难修改环境,但它确实允许您在创建期间设置环境。这使你可以在沙箱内沙箱。
所以你真正想要的是重新安排你的代码:
local foo;
do
local _ENV = { print = print }
function foo()
print('env', _ENV)
bar = 1
end
end
foo
现在已经沙箱化了。现在,有人打破沙箱要困难得多。
你可以想象,这引起了Lua开发者的一些争论。
答案 1 :(得分:12)
它有点贵,但如果它对你很重要......
为什么不使用 string.dump ,并将该功能重新加载到合适的环境中?
function setfenv(f, env)
return load(string.dump(f), nil, nil, env)
end
function foo()
herp(derp)
end
setfenv(foo, {herp = print, derp = "Hello, world!"})()
答案 2 :(得分:11)
要在Lua 5.2中重新创建setfenv/getfenv
,您可以执行以下操作:
if not setfenv then -- Lua 5.2
-- based on http://lua-users.org/lists/lua-l/2010-06/msg00314.html
-- this assumes f is a function
local function findenv(f)
local level = 1
repeat
local name, value = debug.getupvalue(f, level)
if name == '_ENV' then return level, value end
level = level + 1
until name == nil
return nil end
getfenv = function (f) return(select(2, findenv(f)) or _G) end
setfenv = function (f, t)
local level = findenv(f)
if level then debug.setupvalue(f, level, t) end
return f end
end
RPFeltz的答案(load(string.dump(f)...)
)是一个聪明的答案,可能适合你,但它不涉及具有upvalues的函数(除了_ENV)。
还有compat-env模块在Lua 5.2中实现了Lua 5.1功能,反之亦然。
答案 3 :(得分:4)
在Lua5.2中,sandboxeable函数需要指定它自己。您可以使用的一个简单模式是让它接收_ENV
作为参数
function(_ENV)
...
end
或者将其包装在定义env
的内容中local mk_func(_ENV)
return function()
...
end
end
local f = mk_func({print = print})
但是,_ENV
的明确使用对于沙盒不太有用,因为您不能总是假设其他函数将通过_ENV
变量进行合作。在这种情况下,这取决于你做了什么。如果您只想从其他文件加载代码,则load
和loadfile
等函数通常会收到可用于沙盒的可选环境参数。此外,如果您尝试加载的代码是字符串格式,您可以使用字符串操作自己添加_ENV
变量(例如,通过在其周围包含env参数的函数)
local code = 'return function(_ENV) return ' .. their_code .. 'end'
最后,如果您确实需要动态函数环境操作,则可以使用调试库来更改函数_ENV
的内部升值。虽然通常不鼓励使用调试库,但我认为如果所有其他替代方案都不适用是可以接受的(我觉得在这种情况下更改函数的环境已经是深层巫术魔法,因此使用调试库并不是更糟糕)