Как я могу получить информацию о вызовах функций из сценария Lua?

#reflection #lua #abstract-syntax-tree

Вопрос:

У меня есть скрипт, написанный на 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 и найти интересующие меня вызовы функций.

Есть еще какие-нибудь предложения?

Комментарии:

1. Видеть lua.org/manual/5.1/manual.html#3.8 и lua.org/manual/5.1/manual.html#5.9

Ответ №1:

Если вы не можете изменить файлы, вы можете прочитать файлы в строку, затем проанализировать 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'"}}

 

3 возможные ошибки:

  1. M это не очень уникальное имя и может отличаться, возможно, в соответствии с непреднамеренными вызовами функций в другую библиотеку.
  2. В этом примере не обрабатывается, если внутри списка arg выполняется вызов функции. например myfunc(getStuff(), true)
  3. Результирующая таблица не знает типизации аргументов, поэтому все они сохраняются как строковые представления.

Если вы хотите изменить целевой файл, вы можете создать оболочку вокруг своего required модуля

 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 s, вам нужно будет обработать это в оболочке, эта оболочка предполагает, что все индексы являются функцией.

после всех ваших звонков вы можете сериализовать calls таблицу, чтобы получить историю всех сделанных звонков. Для примера кода таблица выглядит следующим образом

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

Комментарии:

1. Спасибо, это очень хорошее решение. Но в идеале я хотел бы получить те же данные без изменения сценария. Я понимаю , что это невозможно с помощью обертки require , поэтому я пока оставлю вопрос открытым, может быть, у кого-то будут другие идеи.

2. Я добавил решение, которое работает путем чтения целевых файлов.

3. Хм, использование регулярных выражений-еще один вариант избежать использования полного анализатора Lua. Спасибо, у меня есть идея. Я подумал, может быть, в языке есть какие-то специальные библиотеки или инструменты отражения, которые упростили бы эту задачу, но, видимо, мне придется разобрать код.