Answer to the Question
In Lua 5.4 the line hook receives a line argument only when the executing source line is represented in the bytecode’s line‑number table. When a chunk is compiled from a string that has no source name (e.g. loadstring or load(nil)) and the string contains no newline characters, the compiler creates a single line of bytecode that has no line‑number table. Consequently the hook is invoked but the line value is nil. This is intentional behaviour, documented in the reference manual’s discussion of the line hook mask. It is not a defect.
Why Anonymous Strings Get nil
- Anonymous chunks are stored with
source = "=[C]" and linedefined = 0.
- A single‑line anonymous chunk has no line‑number table, so
lua_getinfo reports currentline = 0 and the hook receives nil.
- When the chunk contains multiple lines, the compiler builds a proper line‑number table and the hook receives the correct line numbers.
Detecting Anonymous Chunks in a Hook
When you receive a nil line value, you can determine that the current function is an anonymous chunk by inspecting its debug info:
local info = debug.getinfo(2, "S") -- 2 = caller of the hook
if info.linedefined == 0 and info.lastlinedefined == 0 then
-- anonymous chunk – no line table
end
Alternatively, check info.short_src for the string "=[C]". For a chunk that is anonymous but multi‑line, linedefined will be >0, so the nil line value is only produced for truly single‑line anonymous chunks.
Hook Design to Avoid Noisy Alerts
- Filter by line value: Only generate an alert when
line is a number. If it is nil, skip or log a generic "anonymous chunk" message.
- Use additional context: Combine the hook with
debug.getinfo to attach the chunk’s source (or short_src) to the alert. Even if line is nil, you can still report the source identifier.
- Throttle per‑chunk: For anonymous chunks that execute many times (e.g. a loop inside a single‑line string), consider throttling alerts by counting occurrences per chunk.
- Provide a fallback line: When
line is nil, use info.linedefined (which is 0 for anonymous chunks) or the first line of the chunk if you have access to the source string.
Verification Steps
- Set a line hook:
debug.sethook(function(event, line)
print("hook", event, line)
end, "l")
- Load a single‑line anonymous chunk:
loadstring('print("hello")')()
You will see: hook l nil.
- Load a multi‑line anonymous chunk:
loadstring([[
print("line1")
print("line2")
]])() -- two lines
You will see hook l 1 and hook l 2.
- Use
debug.getinfo(2, "S") inside the hook to confirm the source and line definitions.
Missing Diagnostic Detail
If you are observing nil line values in a context that includes newline characters or a named source, could you share the exact load/loadstring call you use? That will help confirm whether the chunk is truly anonymous or if another factor is at play.