是否有一种简单的方法来获取Lua代码文件中定义的所有全局变量?

问题描述 投票:2回答:2

每个人都知道Lua中的变量,如果没有明确定义为“本地”,将是全局的。这有时会导致问题,例如重写库函数,或意外地为另一个具有相同名称的全局变量提供值。因此,如果有一种方法可以找到在单个Lua代码文件中定义的所有全局变量,那么它应该非常有用。

但是,我没有找到任何关于这个看似非常受欢迎的问题的线索。我可以在线获得的最佳答案是使用_G打印环境中的所有全局变量,这没有多大帮助。我目前正在使用Emmylua在Intellij Idea中编写Lua,这是一个可以以特殊风格显示全局变量的强大工具,它可以轻松地将全局变量跟踪到其定义;但是当代码变得很长时,这也无济于事。

基本上,我只想得到一个给定Lua代码文件中定义的全局变量列表。无论是使用工具还是具有出色的功能。如果它可以使事情变得更容易,我们可以假设代码文件是一个模块。如果它可以进一步打印这些全局变量的定义位置,那就更好了。有人能帮助我吗?

lua global-variables
2个回答
0
投票

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,我相信可以用于类似的效果。

另请注意,我概述的所有方法只能检测实际在运行时执行的全局访问。


0
投票

是。本地与全局是一个约束性问题,主要在编译时建立。当然,在编译时设置变量是很好的。

Lua提供了luac编译器,它将参数-l作为列表。

在Lua 5.1中,有操作码SETGLOBAL。列表示语句的行号,注释表示全局的名称。

在5.2及更高版本中,有操作码SETTABUP。列表示语句的行号,注释表示表和键的名称。 “全球”在_ENV upvalue引用的表格中。

因此,您可以使用Lua提供的工具轻松找到设置全局变量的任何语句的行号。

BTW-在许多模块系统下,模块脚本不会设置任何全局变量。

© www.soinside.com 2019 - 2024. All rights reserved.