У Lua нет способа узнать, когда или где была введена глобальная переменная.
В особом, что значение является функцией, debug.getinfo
может помочь, сообщив вам, где функцияопределяется (что часто, но не всегда одно и то же место, где функция делается глобальной).
Вы можете получить необходимую информацию во время введения глобальной функции.Это может быть сделано путем установки метатаблицы с помощью метода __newindex
в глобальной таблице.Этот метод будет вызываться, когда вводится новый глобал (но не тогда, когда существующий глобал переопределяется).В этом методе вы можете выяснить, откуда пришел абонент, набрав debug.getinfo
.Также будьте осторожны, если любой другой ваш код пытается использовать метатаблицу в глобальной среде, вы должны хорошо с ней поиграть.(Может иметь только одну метатаблицу.)
Вы также можете избежать использования глобальной таблицы.Один из промежуточных способов сделать это - переопределить окружение.В Lua 5.2 и Lua 5.3 это делается путем объявления локальной таблицы с именем _ENV
- вместо этого все обращения к глобальной таблице будут иметь доступ к этой таблице.(На самом деле, глобальные доступы всегда используют _ENV
, а _ENV
- это _G
по умолчанию.) Вы можете сделать это главным образом невидимым, предоставив метатаблице _ENV
, которая перенаправляет доступ к _G
(или любой другой среде).Разница здесь в том, что __newindex
будет по-прежнему вызываться, даже если в _G
существует привязка, поэтому этот метод может обнаруживать переопределения.
Использование _ENV
, хотя по своей природе локально для области (например, для каждогофайл должен его переопределить).Такой крюк может быть установлен и во всем миреЕсли вы загружаете свои модули вручную с помощью функции load
(маловероятно), вы можете просто указать пользовательский _ENV
в качестве аргумента.Если вы используете require
, можно получить удержание загруженного файла перед его выполнением путем переопределения (или исправления обезьяны) искателя Lua в package.searchers[2]
.Это встроенная функция, которую require
вызывает, чтобы найти файл в вашей файловой системе и затем загрузить его.Возвращаемым значением является загруженная функция, которая затем запускается require
.Таким образом, после загрузки, но до возвращения обратно к require
, вы можете использовать debug.setupvalue
для переопределения значения по умолчанию _ENV
(если есть).
Пример кода (только слегка проверенный):
local global_info = {}
local default_searcher2 = package.searchers[2]
package.searchers[2] = function(...)
local result = default_searcher2(...)
local parent_environment = _G
local my_env = setmetatable({}, {
__index = parent_environment,
__newindex = function(self, k, v)
local new_info = debug.getinfo(2)
-- keeping rich data like this could be a memory leak
-- if some globals are assigned repeatedly, but that
-- may still be okay in a debugging scenario
local history = global_info[k]
if history == nil then
history = {}
global_info[k] = history
end
table.insert(history, {info = new_info, value = v})
parent_environment[k] = v
end,
})
if type(result) == "function" then
debug.setupvalue(result, 1, my_env)
end
return result
end
function gethistory(name)
local history = global_info[name]
if history == nil then
print('"' .. name .. '" has never been defined...')
else
print('History for "' .. name .. '":')
for _, record in ipairs(history) do
print(record.info.short_src .. ": " .. record.info.currentline)
end
end
end
Обратите внимание, что ловушка здесь будет применяться только к файлам, требуемым после запуска этого кода, и в основном применяется только к файлам Lua (не C-библиотекам), которые включаются через встроенный require
,Он не устанавливает метатаблицы в глобальной среде, поэтому не конфликтует, но его можно обойти, если файлы обращаются к _G
напрямую (или, например, настраивают доступ к _G
вместо _ENV
в своих собственных _ENV
таблицах).Такие вещи также могут быть учтены, но это может быть кроличья нора в зависимости от того, насколько «невидимым» должен быть этот патч.
В Lua 5.1 вместо _ENV
у вас есть setfenv
, чтоЯ полагаю, что может быть использован аналогичный эффект.
Также обратите внимание, что все методы, которые я выделяю, могут обнаруживать только глобальные обращения, которые фактически выполняются во время выполнения.