每个人都知道Lua中的变量,如果没有明确定义为“本地”,将是全局的。这有时会导致问题,例如重写库函数,或意外地为另一个具有相同名称的全局变量提供值。因此,如果有一种方法可以找到在单个Lua代码文件中定义的所有全局变量,那么它应该非常有用。
但是,我没有找到任何关于这个看似非常受欢迎的问题的线索。我可以在线获得的最佳答案是使用_G打印环境中的所有全局变量,这没有多大帮助。我目前正在使用Emmylua在Intellij Idea中编写Lua,这是一个可以以特殊风格显示全局变量的强大工具,它可以轻松地将全局变量跟踪到其定义;但是当代码变得很长时,这也无济于事。
基本上,我只想得到一个给定Lua代码文件中定义的全局变量列表。无论是使用工具还是具有出色的功能。如果它可以使事情变得更容易,我们可以假设代码文件是一个模块。如果它可以进一步打印这些全局变量的定义位置,那就更好了。有人能帮助我吗?
Lua没有办法知道引入全局的时间和地点。
在值是函数的特殊情况下,debug.getinfo
可以通过告诉您函数的定义位置来帮助(通常但不总是将函数设置为全局的相同位置)。
您可以在引入全局时捕获所需的信息。这可以通过在全局表上使用__newindex
方法设置元表来完成。引入新全局时将调用此方法(但是在覆盖现有全局时不会调用此方法)。在这种方法中,您可以通过debug.getinfo
找出调用者的来源。还要注意,如果你的其他代码试图在全局环境中使用metatable,你必须很好地使用它。 (它只能有一个元数据。)
您还可以避免使用全局表。这样做的一种中间方式是覆盖环境。在Lua 5.2和Lua 5.3中,这是通过声明一个名为_ENV
的本地表来完成的 - 所有对全局表的访问都将访问该表。 (实际上,全局访问总是使用_ENV
和_ENV
默认是_G
。)你可以通过给这个_ENV
转发访问_G
(或任何其他环境)的metatable来使这个看不见。这里的区别是,即使__newindex
中存在绑定,_G
仍将被调用,因此此方法可以检测覆盖。
使用_ENV
虽然本质上是范围的本地(例如,每个文件都需要覆盖它)。这样的钩子也可以全局安装。如果使用load
函数手动加载模块(不太可能),您只需提供自定义_ENV
作为参数。如果你使用require
,可以通过覆盖(或猴子修补)package.searchers[2]
中的Lua搜索器来获取加载文件。这是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
请注意,此处的钩子仅适用于运行此代码后所需的文件,并且基本上仅适用于通过内置require
包含的Lua文件(不是C libs)。它没有在全局环境中设置metatable,因此不会在那里发生冲突,但如果文件直接访问_G
(或者例如在他们自己的_G
表中设置访问_ENV
而不是_ENV
),它可以被规避。这样的事情也可以解释,但它可能是一个兔子洞,取决于你需要这个补丁的“隐形”。
在Lua 5.1,而不是_ENV
,你有setfenv
,我相信可以用于类似的效果。
另请注意,我概述的所有方法只能检测实际在运行时执行的全局访问。
是。本地与全局是一个约束性问题,主要在编译时建立。当然,在编译时设置变量是很好的。
Lua提供了luac
编译器,它将参数-l
作为列表。
在Lua 5.1中,有操作码SETGLOBAL
。列表示语句的行号,注释表示全局的名称。
在5.2及更高版本中,有操作码SETTABUP
。列表示语句的行号,注释表示表和键的名称。 “全球”在_ENV
upvalue引用的表格中。
因此,您可以使用Lua提供的工具轻松找到设置全局变量的任何语句的行号。
BTW-在许多模块系统下,模块脚本不会设置任何全局变量。