【问题标题】:Is there an easy way to get all global variables defined in a Lua code file?有没有一种简单的方法来获取在 Lua 代码文件中定义的所有全局变量?
【发布时间】:2019-08-18 17:58:23
【问题描述】:

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

但是,对于这个看似非常流行的问题,我没有找到任何线索。我可以上网的最佳答案是使用 _G 打印环境中的所有全局变量,这没有多大帮助。我目前正在使用 Emmylua 在 Intellij Idea 中编写 Lua,这是一个强大的工具,可以以特殊样式显示全局变量,并且可以轻松地将全局变量追溯到其定义;但是当代码变得很长时,这也无济于事。

所以基本上,我只想获取在给定 Lua 代码文件中定义的全局变量列表。要么用工具,要么用奇妙的功能。如果它能让事情变得更简单,我们可以假设代码文件是一个模块。如果它可以进一步打印这些全局变量的定义位置,那就更好了。有人可以帮帮我吗?

【问题讨论】:

  • lua show_globals.lua < your_script.lua 工具是here
  • @EgorSkriptunoff 非常感谢,先生!它真的很好用!但是有一个小缺陷,因为当我测试它时,它说“print”是一个全局变量,但我只是使用它,没有定义它。希望有办法解决。
  • @lhf 这似乎更容易理解,但我应该如何使用它?我现在用的是Windows系统。
  • 试试 luacheck,它会告诉你什么时候设置了你不应该设置的东西

标签: lua global-variables


【解决方案1】:

Lua 无法判断何时何地引入了全局变量。

在值是函数的特殊情况下,debug.getinfo 可能会告诉您函数的定义位置(通常但并不总是与函数全局化的相同位置)来提供帮助。

您可以在引入全局时捕获所需的信息。这可以通过在全局表上使用__newindex 方法设置元表来完成。此方法将在引入新全局时调用(但不是在覆盖现有全局时)。在这种方法中,您可以通过debug.getinfo 找出调用者来自哪里。还要注意,如果您的任何其他代码试图在全局环境中使用元表,您必须很好地使用它。 (它只能有一个元表。)

您也可以避免使用全局表。这样做的一种中间方法是覆盖环境。在 Lua 5.2 和 Lua 5.3 中,这是通过声明一个名为 _ENV 的本地表来完成的——所有对全局表的访问都将访问这个表。 (实际上,全局访问始终使用_ENV,而_ENV 默认为_G。)您可以通过为_ENV 提供一个将访问转发到_G(或任何其他环境)的元表来使其几乎不可见。这里的区别在于,即使_G 中存在绑定,__newindex 仍然会被调用,因此该方法可以检测覆盖。

使用_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 库)。它不会在全局环境中设置元表,因此不会在那里发生冲突,但如果文件直接访问_G(或者例如在他们自己的_ENV 表中设置对_G 而不是_ENV 的访问权限,则可以绕过它)。这样的事情也可以考虑,但它可能是一个兔子洞,这取决于你需要这个补丁有多“不可见”。

在 Lua 5.1 中,您使用 setfenv 而不是 _ENV,我相信它可以用于类似的效果。

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

【讨论】:

    【解决方案2】:

    是的。本地与全局是一个绑定问题,主要在编译时确定。当然,设置变量在编译时就已经确定了。

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

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

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

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

    顺便说一句——在许多模块系统下,模块脚本不会设置任何全局变量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-28
      • 1970-01-01
      • 2022-11-10
      相关资源
      最近更新 更多