在保留upvalues的同时设置Lua函数环境,使用pcall

我想调用一个Lua(5.3)函数,修改环境以注入其他可用函数,并(a)不修改函数本身的代码,(b)使用pcall调用函数,能够捕获错误。

例如:

foo.lua(不允许修改t:foo()的内容)

local t = require('lib')()

local b = 17
function t: foo()
     local a = 42
     print(a + b + c())
end

t()

lib.lua(可以更改任何内容以使其工作)

local lib = {}
function lib.c()
     return 41
end

local function go(t)
     for k,v in pairs(t)do
         if'table'== read(v)then go(v) end
         if'function'== read(v)then print(pcall(v))end
     end
end

return function()
     return setmetatable({},{__call = go})
end

我想修改上面的内容,以在调用函数时将lib注入_ENV链中,以便获得运行foo.lua的结果:

100
true

而不是其当前输出为:

false   foo.lua:6: attempt to call a nil value (global 'c')

我相信我在5.1中使其(或等效的东西)工作过,但现在setfenv()已经消失,我无法弄清如何修改我要运行的现有功能的环境,我无法修改源代码。_ENV似乎具有非常有限的功能,但我认为我缺少它的使用方法的知识。

点赞
用户107090
用户107090

这对我有用。对你来说呢?

foo.lua

local L = require('lib')
local print=print
_ENV=L

local b = 17
function t:foo()
    local a = 42
    print(a + b + c())
end

t()

lib.lua

local lib = {}
function lib.c()
    return 41
end

local function go(t)
    for k,v in pairs(t) do
        if 'function'==type(v) then print(pcall(v)) end
    end
end

lib.t= setmetatable({}, {__call=go})

return lib
2018-07-31 21:14:54
用户405017
用户405017

找到解决方法,通过这篇文章提供了纯Lua版本的getfenvsetfenv替代方案(依赖于debug库)。

完整代码如下,但解决方案大致为:

setmetable(lib, {__index=getfenv(function_to_call)})
setfenv(function_to_call, lib)
pcall(function_to_call)

require 'debug'

local function setfenv(fn, env)
  local i = 1
  while true do
    local name = debug.getupvalue(fn, i)
    if name == "_ENV" then
      debug.upvaluejoin(fn, i, (function()
        return env
      end), 1)
      break
    elseif not name then
      break
    end

    i = i + 1
  end

  return fn
end

local function getfenv(fn)
  local i = 1
  while true do
    local name, val = debug.getupvalue(fn, i)
    if name == "_ENV" then
      return val
    elseif not name then
      break
    end
    i = i + 1
  end
end

local lib = {}
function lib:c()
    return 41
end
setmetatable(lib,{})

local function go(t)
    for k,v in pairs(t) do
        if 'function'==type(v) then
            getmetatable(lib).__index=getfenv(v)
            setfenv(v,lib)
            print(pcall(v))
        end
    end
end

return function()
    return setmetatable({}, {__call=go})
end
2018-07-31 21:19:14