【发布时间】:2019-07-23 09:48:20
【问题描述】:
注意:我使用的是 Lua 5.3 版。
这个问题是由Lua 编程(第 4 版)的练习 25.1(第 264 页)引起的。该练习内容如下:
练习 25.1: 调整
getvarvalue(清单 25.1)以使用不同的协程(如debug库中的函数)。
练习引用的函数getvarvalue逐字复制如下。
-- Listing 25.1 (p. 256) of *Programming in Lua* (4th ed.)
function getvarvalue (name, level, isenv)
local value
local found = false
level = (level or 1) + 1
-- try local variables
for i = 1, math.huge do
local n, v = debug.getlocal(level, i)
if not n then break end
if n == name then
value = v
found = true
end
end
if found then return "local", value end
-- try non-local variables
local func = debug.getinfo(level, "f").func
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then break end
if n == name then return "upvalue", v end
end
if isenv then return "noenv" end -- avoid loop
-- not found; get value from the environment
local _, env = getvarvalue("_ENV", level, true)
if env then
return "global", env[name]
else -- no _ENV available
return "noenv"
end
end
下面是我对该函数的增强版,它实现了练习中指定的附加功能。这个版本接受一个可选的thread 参数,预计是一个协程。此增强版与原始getvarvalue 之间的唯一区别是:
- 额外可选
thread参数的处理; -
level参数的特殊设置取决于thread参数是否与正在运行的协程相同;和 - 在对
debug.getlocal和debug.getinfo的调用以及递归调用中传递thread参数。
(我在源代码中通过编号的cmets标记了这些差异。)
function getvarvalue_enhanced (thread, name, level, isenv)
-- 1
if type(thread) ~= "thread" then
-- (thread, name, level, isenv)
-- (name, level, isenv)
isenv = level
level = name
name = thread
thread = coroutine.running()
end
local value
local found = false
-- 2
level = level or 1
if thread == coroutine.running() then
level = level + 1
end
-- try local variables
for i = 1, math.huge do
local n, v = debug.getlocal(thread, level, i) -- 3
if not n then break end
if n == name then
value = v
found = true
end
end
if found then return "local", value end
-- try non-local variables
local func = debug.getinfo(thread, level, "f").func -- 3
for i = 1, math.huge do
local n, v = debug.getupvalue(func, i)
if not n then break end
if n == name then return "upvalue", v end
end
if isenv then return "noenv" end -- avoid loop
-- not found; get value from the environment
local _, env = getvarvalue_enhanced(thread, "_ENV", level, true) -- 3
if env then
return "global", env[name]
else
return "noenv"
end
end
这个函数工作得相当好,但我发现了一个奇怪的情况1它失败了。下面的函数make_nasty生成一个协程,getvarvalue_enhanced找不到_ENV变量;即它返回"noenv"。 (作为nasty 基础的函数是闭包closure_B,它又调用闭包closure_A。然后产生的是closure_A。)
function make_nasty ()
local function closure_A () coroutine.yield() end
local function closure_B ()
closure_A()
end
local thread = coroutine.create(closure_B)
coroutine.resume(thread)
return thread
end
nasty = make_nasty()
print(getvarvalue_enhanced(nasty, "_ENV", 2))
-- noenv
相比之下,几乎相同的函数make_nice 生成一个协程,getvarvalue_enhanced 成功地为其找到了一个_ENV 变量。
function make_nice ()
local function closure_A () coroutine.yield() end
local function closure_B ()
local _ = one_very_much_non_existent_global_variable -- only difference!
closure_A()
end
local thread = coroutine.create(closure_B)
coroutine.resume(thread)
return thread
end
nice = make_nice()
print(getvarvalue_enhanced(nice, "_ENV", 2))
-- upvalue table: 0x558a2633c930
make_nasty 和 make_nice 之间的唯一区别在于,在后者中,闭包 closure_B 引用了一个不存在的全局变量(并且对它没有任何作用)。
问:如何修改getvarvalue_enhanced,使其能够像nice 那样为nasty 定位_ENV?
编辑:更改了 make_nasty 和 make_nice 中的闭包名称。
EDIT2:练习 25.3(同一页)的措辞可能与此处相关(我的重点):
练习 25.3: 编写一个版本的
getvarvalue(清单 25.1),它返回一个表格,其中包含所有变量在调用函数中可见。 (返回的表不应包含环境变量;而应从原始环境继承它们。)
这个问题意味着应该有一种方法可以从函数中获取仅可见的变量,函数是否使用它们。这些变量肯定包括_ENV。 (作者是 Lua 的创造者之一,所以他知道他在说什么。)
1 我相信,对这个例子中发生的事情有更好理解的人将能够想出一种不那么复杂的方法来引发相同的行为。我在这里展示的示例是我偶然发现的情况的最小形式。
【问题讨论】:
-
我会说这是安全的:不要在生产中使用这样的代码。一旦您在没有调试符号的情况下预编译代码,它就会中断。它只能用于临时调试或可选调试组件。
-
@DarkWiiPlayer:谢谢你的建议。要清楚,您指的是哪个代码?帖子里的所有代码?
-
一般情况下,不要依赖
debug,但更准确地说,如果在使用luac编译时去掉调试符号,所有依赖变量名的东西都会完全中断。只有当您可以控制使用的代码时,它才真正可行,因此在编写要发布的库时根本不可行。
标签: debugging reflection lua coroutine introspection