This question is motivated by exercise 25.1 (p. 264) of Programming in Lua (4th ed.). That exercise reads as follows:
Exercise 25.1: Adapt
getvarvalue
(Listing 25.1) to work with different coroutines (like the functions from thedebug
library).
The function getvarvalue
that the exercise refers to is copied verbatim below.
-- Listing 25.1 (p. 256) of *Programming in Lua* (4th ed.)
function original_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
Below is my enhanced version of this function, which implements the additional functionality specified in the exercise. This version accepts an optional thread
parameter, expected to be a coroutine. The only differences between this enhanced version and the original are:
- the handling of the additional optional parameter;
- the special setting of the level depending on whether the thread parameter is the same as the running coroutine or not; and
- the passing of the thread argument in the calls to
debug.getlocal
anddebug.getinfo
, and in the recursive call.
(I have marked these differences in the source code through numbered comments.)
function enhanced_getvarvalue (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 = enhanced_getvarvalue(thread, "_ENV", level, true) -- 3
if env then
return "global", env[name]
else
return "noenv"
end
end
This function works reasonably well, but I have found one strange situation1 where it fails. The function make_nasty
below generates a coroutine for which getvarvalue_enhanced
fails to find an _ENV
variable; i.e. it returns "noenv"
. (The function that serves as the basis for nasty
is the closure outer_closure
, which in turn invokes the closure inner_closure
. It is inner_closure
that then yields.)
function make_nasty ()
local function inner_closure () coroutine.yield() end
local function outer_closure ()
inner_closure()
end
local thread = coroutine.create(outer_closure)
coroutine.resume(thread)
return thread
end
nasty = make_nasty()
print(enhanced_getvarvalue(nasty, "_ENV", 2))
-- noenv
In contrast, the almost identical function make_nice
produces a coroutine for which getvarvalue_enhanced
succeeds in finding an _ENV
variable.
function make_nice ()
local function inner_closure () coroutine.yield() end
local function outer_closure ()
local _ = one_very_much_non_existent_global_variable -- only difference!
inner_closure()
end
local thread = coroutine.create(outer_closure)
coroutine.resume(thread)
return thread
end
nice = make_nice()
print(enhanced_getvarvalue(nice, "_ENV", 2))
-- upvalue table: 0x558a2633c930
The only difference between make_nasty
and make_nice
is that, in the latter, the closure outer_closure
references a non-existent global variable (and does nothing with it).
Q: How can I modify getvarvalue_enhanced
so that it is able to locate _ENV
for nasty
, the way it does for nice
?
1 I am sure that someone with a better understanding of what is going on in this example will be able to come up with a less convoluted way to elicit the same behavior. The example I present here is the most minimal form I can come up with for the situation I found by accident.
Aucun commentaire:
Enregistrer un commentaire