代码之家  ›  专栏  ›  技术社区  ›  Phrogz

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

lua
  •  1
  • Phrogz  · 技术社区  · 6 年前

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

    例如:

    卢阿福 (不允许修改 t:foo() )

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

    图书馆 (可以在这里更改任何内容以使其生效)

    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
    
    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() 已经不知道如何修改现有运行的环境,无法修改源代码。 _环境 似乎功能非常有限,但我认为我缺少了如何使用它的知识。

    2 回复  |  直到 6 年前
        1
  •  1
  •   lhf    6 年前

    这对我有用。对你有用吗?

    卢阿福

    local L = require('lib')
    local print=print
    _ENV=L
    
    local b = 17
    function t:foo()
        local a = 42
        print(a + b + c())
    end
    
    t()
    

    图书馆

    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
    
        2
  •  1
  •   Phrogz    6 年前

    明白了,顺便说一句 this article 它为 getfenv setfenv (依赖于 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