debug
The debug library of LuaJIT 2.1: stack frames, locals, upvalues, metatables, environments and hooks. debug.debug (the interactive prompt) is removed, and debug.sethook cannot install a hook: the debug hook belongs to the client, which uses it to stop code that runs too long.
At a glance
debug.debug is nil. debug.sethook exists but cannot install a hook, see debug.getuservalue and debug.setuservalue (Lua 5.2) do not exist in this build.
Inspection
debug.traceback(co, message, level): string
debug.traceback(co?: thread, message?: any, level?: number = 1): string
Builds a stack traceback. Often used as the handler of xpcall.
Returns: message, a newline and the traceback.
print(debug.traceback("here"))
debug.getinfo(co, f, what): table | nil
debug.getinfo(co?: thread, f: function | number, what?: string = "flnSu"): table | nil
Describes a function or an active stack level.
Returns: the table, or nil if the level is out of range. The main chunk has source @ plus the script name, a module @lib/util.lua.
local info = debug.getinfo(1, "Sl")
print(info.short_src, info.currentline)
debug.getlocal(co, level, index): string, any
debug.getlocal(co?: thread, level: number | function, index: number): string, any
Returns: the name and value of the local, or nil. With a function, only the name of parameter index.
Errors: bad argument #1 to 'getlocal' (level out of range).
local function show(a, b)
print(debug.getlocal(1, 1), debug.getlocal(1, 2))
end
show(10, 20)
debug.setlocal(co, level, index, value): string | nil
debug.setlocal(co?: thread, level: number, index: number, value: any): string | nil
Returns: the local name, or nil if there is no such local.
Errors: bad argument #1 to 'setlocal' (level out of range).
Upvalues
debug.getupvalue(f, up): string, any
debug.getupvalue(f: function, up: number): string, any
Returns: the name and value of the upvalue, or nothing.
local count = 0
local function bump() count = count + 1 end
print(debug.getupvalue(bump, 1))
debug.setupvalue(f, up, value): string | nil
debug.setupvalue(f: function, up: number, value: any): string | nil
Returns: the upvalue name, or nil if there is none.
debug.upvalueid(f, n): userdata
debug.upvalueid(f: function, n: number): userdata
Returns: an identifier, the same for two closures that share the upvalue.
debug.upvaluejoin(f1, n1, f2, n2)
debug.upvaluejoin(f1: function, n1: number, f2: function, n2: number)
Makes upvalue n1 of f1 refer to upvalue n2 of f2.
Errors: bad argument #2 to 'upvaluejoin' (index out of range), the same for #4.
Metatables and environments
debug.getmetatable(obj): table | nil
debug.getmetatable(obj: any): table | nil
Returns: its metatable, ignoring __metatable.
debug.setmetatable(obj, mt): any
debug.setmetatable(obj: any, mt: table | nil): any
Sets the metatable of any value, numbers, strings, functions and booleans included. A metatable on a type other than table or userdata is shared by every value of that type.
Returns: obj.
debug.getfenv(o): table | nil
debug.getfenv(o: any): table | nil
Returns: its environment table.
debug.setfenv(o, env): any
debug.setfenv(o: any, env: table): any
Returns: o.
debug.getregistry(): table
debug.getregistry(): table
Returns: the Lua registry. The client keeps its own state there (xiao.* keys): changing it can break the VM until the next Clear VM.
Hooks
LuaJIT has one hook for the whole VM, shared by every coroutine, and the client uses it for its time limits. A script cannot install its own hook.
debug.sethook(co, hook, mask, count)
debug.sethook(co?: thread, hook?: function, mask?: string, count?: number)
Changed Kept for compatibility, it never changes the hook:
Errors:
debug.sethook cannot install a hook: the client keeps the debug hook for its time limitsdebug.sethook is not available inside a packet hook or a cmd hook
A hook function runs with the hooks of the VM turned off, so a loop in it could not be stopped by any limit: that is why installing one is refused everywhere.
print(pcall(debug.sethook, function() end, "l"))
debug.gethook(co): function | string | nil, string, number
debug.gethook(co?: thread): function | string | nil, string, number
Returns: nil when no hook is installed, which is the normal case. While a client hook is installed (see below), the string "external hook", its mask and its count.
Client hooks
- When a time limit expires (2000 ms for the main chunk, 100 ms for a frame), the client installs its hook and raises the error
script timeout after 2000 ms(or100 ms) on every instruction; the console showsscript timeout.pcallcannot keep the script running. - During a packet hook or a cmd hook, the client installs a count hook that checks the 2000 us budget every 1000 instructions, at the start of every call, and removes it afterwards. Nothing a script did before can disable it. See Execution contexts.
- At the end of every call the client makes into the VM, the hook is removed.
jit.attachand thejit.profilemodule are removed for the same reason asdebug.sethook: their callbacks run with the hooks turned off.
__gc finalizers
A __gc metamethod (on a newproxy userdata, or set with debug.setmetatable) runs inside the garbage collector, with the hooks turned off: no time limit applies to it. A loop in a finalizer freezes the game. Keep finalizers short, or do not use them.
Complete example
A traceback handler that logs a clean error, with the location of the failing function.
local function run(name, f)
local ok, trace = xpcall(f, debug.traceback)
if not ok then
local info = debug.getinfo(f, "S")
print(name .. " failed (" .. info.short_src .. ":" .. info.linedefined .. "):", trace)
end
return ok
end
run("demo", function()
local info = debug.getinfo(1, "Sl")
print("running at line", info.currentline)
error("boom")
end)