debug

The LuaJIT debug library, without debug.debug, and a debug.sethook that cannot weaken the client time limits.

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

Group Functions
Inspection debug.traceback, debug.getinfo, debug.getlocal, debug.setlocal
Upvalues debug.getupvalue, debug.setupvalue, debug.upvalueid, debug.upvaluejoin
Metatables and environments debug.getmetatable, debug.setmetatable, debug.getfenv, debug.setfenv, debug.getregistry
Hooks debug.gethook, debug.sethook

debug.debug is nil. debug.sethook exists but cannot install a hook, see Hooks. 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.

Name Type Description
co thread coroutine whose stack is read, default the running one
message any text put before the traceback; a value that is not a string or nil is returned unchanged
level number first stack level shown

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.

Name Type Description
co thread coroutine whose stack is read
f function or number a function, or a level: 0 is getinfo, 1 its caller
what string letters selecting the fields
what Fields
n name, namewhat
S source, short_src, what, linedefined, lastlinedefined
l currentline
u nups
f func
L activelines

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
Name Type Description
co thread coroutine whose stack is read
level number or function stack level, or a function to get only parameter names
index number local number, from 1

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
Name Type Description
co thread coroutine whose stack is changed
level number stack level
index number local number
value any new value

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
Name Type Description
f function closure
up number upvalue number, from 1

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
Name Type Description
f function closure
up number upvalue number
value any new value

Returns: the upvalue name, or nil if there is none.

debug.upvalueid(f, n): userdata

debug.upvalueid(f: function, n: number): userdata
Name Type Description
f function closure
n number upvalue number

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.

Name Type Description
f1, n1 function, number upvalue to redirect
f2, n2 function, number upvalue to share

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
Name Type Description
obj any any value

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.

Name Type Description
obj any any value
mt table or nil new metatable

Returns: obj.

debug.getfenv(o): table | nil

debug.getfenv(o: any): table | nil
Name Type Description
o function, userdata or thread object to inspect

Returns: its environment table.

debug.setfenv(o, env): any

debug.setfenv(o: any, env: table): any
Name Type Description
o function, userdata or thread object to change
env table new environment

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:

Call Where Result
debug.sethook(), debug.sethook(co) main chunk, frame, receiver, timer nothing: there is no script hook to remove
debug.sethook(hook, ...), debug.sethook(co, hook, ...) main chunk, frame, receiver, timer error
any call packet hook or cmd hook error, the callback is removed like any failing hook

Errors:

  • debug.sethook cannot install a hook: the client keeps the debug hook for its time limits
  • debug.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
Name Type Description
co thread ignored

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 (or 100 ms) on every instruction; the console shows script timeout. pcall cannot 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.attach and the jit.profile module are removed for the same reason as debug.sethook: their callbacks run with the hooks turned off.

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)