I'm rolling out my own LUA debugger for a C++ & LUA project and I just hit a bit of a hurdle. I'm trying to follow MobDebug's implementation for setting expression watches, which after removing all the bells and whistles essentially boils down to string-concatenating the expression you want to watch inside a return(...)
statement and running that. The exact line is:
local func, res = mobdebug.loadstring("return(" .. exp .. ")")
This works perfectly as long as you're just debugging a single script and most of your variables are just in the global environment, but for my own purposes I'm trying to restrict / scope down the evaluation of these return(...)
expressions to certain tables and their fields.
More specifically, I'm working with some LUA "classes" using the setmetatable / __index
pattern, and, given access to the table (let's call it SomeClass
) I'd like to be able to evaluate expressions such as
self.mSomeVariable + self.mSomeOtherVariable - self:someOperation(...)
where self
is referring to SomeClass
(i.e. there exists SomeClass:someOperation
, etc).
I'm really at my wits end on how to approach this. At first I tried the following, and it most definitely didn't work.
> SomeClass = {}
> SomeClass.DebugEval = function(self, chunk_str) return load("return(" .. chunk_str .. ")")() end
> SomeClass.DebugEval(SomeClass, "print(1)") // 1 nil
> SomeClass.DebugEval(SomeClass, "print(SomeClass)") // table: 0000000000CBB5C0 nil
> SomeClass.DebugEval(SomeClass, "print(self)") // nil nil
The fact that I cannot even reference the "class" via self
by passing it in directly to the wrapping function's argument makes me suspicious that this might be a upvalue
problem. That is, load(...)
is creating a closure for the execution of this chunk that has no way of reaching the self
argument... but why? It's quite literally there???
In any case, my debugger backend is already on C++, so I thought "no problem, I'll just manually set the upvalue
. Again, I tried doing the following using lua_setupvalue
, but this also didn't work. I'm getting a runtime error on the pcall
.
luaL_dostring(L, "SomeClass = {}"); // just for convenience; I could've done this manually
luaL_loadstring(L, "print(1)"); // [ ... , loadstring_closure ]
lua_getglobal(L, "SomeClass"); // [ ... , loadstring_closure, reference table]
lua_setupvalue(L, -2, 1); // [ ... , loadstring_closure] this returns '_ENV'
lua_pcall(L, 0, 0, 0); // getting LUA_ERRRUN
What am I missing here? Perhaps I'm approaching this in a totally incorrect way? My end goal is simply being able to execute these debugger watches but restricted to certain class instances, so I can enable my watches on a per-instance basis and inspect them. And yes, I absolutely need to roll out my own debugger. It's a long story. Any and all help is appreciated.
The doc to load
and lua_load
functions in the Lua manual mentions:
load: When you load a main chunk, the resulting function will always have exactly one upvalue, the _ENV variable.
lua_load: When loading main chunks, this upvalue will be the _ENV variable.
In summary, the loaded chunk only has one upvalue, which is _ENV
.
Since Lua 5.2, SomeClass = {}
is equivalent to _ENV.SomeClass = {}
, and print(self)
is equivalent to print(_ENV.self)
. Because SomeClass
is set in the global scope, the loaded chunk can also see this entry in the _ENV
table, but there is no place will automatically set _ENV.self
.
You may expect the function to work like this:
function expect(self)
return (function()
return print(self)
end)()
end
In fact it will work like this:
function fact(self)
return (function()
return print(_ENV.self)
end)()
end
For the "expect" function, the lua compiler will add self
as an upvalue for you, but for the "fact" function, it will do nothing.
And your C++ code completely overwritten _ENV
with SomeClass
, which is going too far.