重新创建Lua 5.2中的setfenv()

如何在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文档 ,我们不应该依赖它。

点赞
用户734069
用户734069

在 Lua 5.2 中,如果没有使用 Lua 的 debug 库,无法更改函数的环境。一旦创建了一个函数,它的环境就确定下来了。想要修改函数环境的唯一方式就是修改其第一个 upvalue,这需要使用 debug 库。

Lua 5.2 中环境的一般概念是,环境应该被视为不可变的,除了通过巧妙手段(例如:debug 库)。你可以在一个环境中创建一个函数,一旦在该环境中创建,它就将一直处于该环境中。

这就是 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 开发者之间的争议。

2013-01-12 05:37:06
用户90511
用户90511

在 Lua5.2 中,一个需要沙盒化的函数需要指定自身。一个简单的方式是将 _ENV 作为参数传入函数中。

function(_ENV)
    ...
end

或者将其包裹在一个定义环境的函数中:

local mk_func(_ENV)
    return function()
        ...
    end
end

local f = mk_func({print = print})

然而,这种显式使用 _ENV 的方式对于沙盒限制并不常用,因为你并不能总是默认其他函数会有一个 _ENV 变量。在这种情况下,就要根据情况而定。如果你只是想从另一个文件中加载代码,那么像 loadloadfile 这样的函数通常会接收一个可选的环境参数,你可以用来进行沙盒限制。此外,如果你要加载的代码是一个字符串形式的,那么你可以使用字符串操作来自己添加 _ENV 变量(比如,将一个带有环境参数的函数包裹在其周围):

local code = 'return function(_ENV) return ' .. their_code .. 'end'

最后,如果你真的需要动态函数环境的操作,你可以使用 debug 库来改变函数内部的 upvalue 变量 _ENV。虽然使用 debug 库通常是不鼓励的,但是我认为如果所有其他选择均不适用(在这种情况下,改变函数的环境已经是一个比较神秘的黑科技了,使用 debug 库并不会更糟糕)。

2013-01-12 14:20:45
用户903234
用户903234

这有点贵,但如果这对你很重要... 为什么不使用string.dump,并将函数重新load到正确的环境中呢?

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!"})()
2013-01-21 22:09:59
用户1442917
用户1442917

要在Lua 5.2中重新创建setfenv / getfenv,可以执行以下操作:

如果不是setfenv,则为Lua 5.2
  - 基于http://lua-users.org/lists/lua-l/2010-06/msg00314.html
  -这假设f是一个函数
  local function findenv(f)
    local level = 1
    重复
      local name,value = debug.getupvalue(f,level)
      如果名称=='_ENV'则返回级别,值
      级别=级别+ 1
    直到名称== nil
    返回空 end
  getfenv = function(f)return(select(2,findenv(f))or _G)end
  setfenv = function(f,t)
    local level = findenv(f)
    如果级别则调试设置值(f,级别,t)
    返回f end
end

RPFeltz的答案(load(string.dump(f)...))很聪明,可能适合您,但它不能处理具有upvalue(除了\ _ENV之外)的函数。

还有一个[compat-env](https://github.com/davidm/lua-compat-env)模块,它实现了Lua 5.1函数在Lua 5.2和反之亦然。

2013-01-28 02:17:44