如何从Lua脚本中获取有关函数调用的信息?

我有一个用Lua 5.1编写的脚本,导入了第三方模块并从中调用了一些函数。我想要获取一个带有参数列表(当它们在执行之前已知时)的模块内函数调用列表。

因此,我需要编写另一个脚本,它接收我第一个脚本的源代码,对其进行解析,并从其中提取信息。

请考虑以下最小示例。

我有以下模块:

local mod = {}

function mod.foo(a, ...)
    print(a, ...)
end

return mod

和以下驱动程序代码:

local M = require "mod"
M.foo('a', 1)
M.foo('b')

有什么更好的方法可以检索带有“使用”'M.foo'函数的数据?

理想情况下,我想获取被调用的函数名和其参数值的信息。从上面的示例代码中,只需要得到像这样的映射:{'foo':[('a',1),('b')]}

我不确定Lua是否有用于反射检索此信息的函数。因此,可能需要使用Lua的现有解析器之一来获取完整的AST,并查找我感兴趣的函数调用。

还有其他建议吗?

点赞
用户7396148
用户7396148

如果无法修改文件,则可以将文件读取为字符串,然后解析 mod 文件并查找其中所有函数,然后使用该信息来解析目标文件以查找 mod 库的所有用法。

functions = {}

for func in modFile:gmatch("function mod%.(%w+)") do
    functions[func] = {}
end

for func, call in targetFile:gmatch("M%.(%w+)%(([^%)]+)%)") do
    args = {}
    for arg in string.gmatch(call, "([^,]+)") do
        table.insert(args, arg)
    end

    table.insert(functions[func], args)
end

最终的表可以序列化成如下格式:

['foo'] = {{"'a'", " 1"}, {"'b'"}}

有三个可能会出现问题的地方:

  1. M 不是一个非常唯一的名字,可能会匹配到另一个库的意想不到的函数调用。
  2. 该示例无法处理参数列表中调用函数的情况,例如 myfunc(getStuff(), true)
  3. 结果表不知道参数的类型,因此它们都以字符串表示保存。

如果可以修改目标文件,则可以在您的必需模块周围创建一个包装器。

function log(mod)
    local calls = {}
    local wrapper = {
        __index = function(_, k)
            if mod[k] then
                return function(...)
                    calls[k] = calls[k] or {}
                    table.insert(calls[k], {...})

                    return mod[k](...)
                end
            end
        end,
    }

    return setmetatable({},wrapper), calls
end

然后您可以像这样使用此函数。

local M, calls = log(require("mod"))
M.foo('a', 1)
M.foo('b')

如果您的模块不仅仅是 function,则需要在包装器中处理它,该包装器假定所有索引都是函数。

在所有调用之后,可以将 calls 表序列化以获取所有调用的历史记录。对于示例代码,表如下所示:

{
    ['foo'] = {{'a', 1}, {'b'}}
}
2021-03-18 14:33:16