Nanfeng

Notes on software development, code, and curious ideas

Reflection and Introspection with Lua's Debug Library

Dynamic features such as type, pairs, load, and environments already give Lua a degree of reflection. The debug library goes further: it can inspect stack frames, local variables, upvalues, and execution events.

This power has a cost. Debug operations can be slow and can break normal lexical boundaries, so application logic should not depend on them unless there is a strong reason.

Function and stack information

1
2
3
4
5
6
7
8
9
10
local function describe(fn)
local info = debug.getinfo(fn, "Snu")
return {
source = info.short_src,
firstLine = info.linedefined,
parameters = info.nparams,
upvalues = info.nups,
isVararg = info.isvararg
}
end

Passing a number instead of a function inspects a stack level. Request only the fields needed through the option string, because collecting line or function data has extra cost.

Locals and upvalues

debug.getlocal(level, index) returns the name and value of a local variable. debug.setlocal can replace it. For a function’s captured variables, use debug.getupvalue and debug.setupvalue:

1
2
3
4
5
6
7
8
9
local function findUpvalue(fn, wanted)
local i = 1
while true do
local name, value = debug.getupvalue(fn, i)
if not name then return nil end
if name == wanted then return value, i end
i = i + 1
end
end

Most inspection functions can also receive a coroutine as their first argument, allowing a suspended coroutine’s stack to be examined.

Hooks and profiling

debug.sethook registers a callback for calls, returns, line changes, or an instruction count:

1
2
3
4
5
6
7
8
9
local calls = 0
debug.sethook(function(event)
if event == "call" then calls = calls + 1 end
end, "c")

-- code being measured

debug.sethook()
print("calls", calls)

Hooks are the foundation of debuggers, tracers, profilers, and instruction-budget guards. They add overhead, and an instruction limit is only one part of sandboxing.

Security boundary

Do not expose the debug library to untrusted scripts. It can inspect or mutate values that ordinary Lua code cannot reach and can undermine an otherwise restricted environment. A sandbox should start from a small allowlist of functions, exclude debug, restrict loading and I/O, and enforce resource limits outside the script where possible.

+