Skip to content

Commit 08949ae

Browse files
committed
tests/lapi: add debug tests
The commit adds tests for the following functions in debug library: `debug.getlocal()`, `debug.getupvalue()` and `debug.getinfo()`.
1 parent 8ef72ef commit 08949ae

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed

tests/lapi/debug_torture_test.lua

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
Parameter 'what' of 'debug.getinfo' cannot start with '>',
6+
https://www.lua.org/bugs.html#5.4.2-2
7+
8+
Access to debug information in line hook of stripped function,
9+
https://github.com/lua/lua/commit/ae5b5ba529753c7a653901ffc29b5ea24c3fdf3a
10+
11+
Return hook may not see correct values for active local variables when function returns,
12+
https://www.lua.org/bugs.html#5.3.0-4
13+
14+
The PC out-of-range in lj_debug_frameline(),
15+
https://github.com/LuaJIT/LuaJIT/issues/1369
16+
17+
LuaJIT segfault in debug.getinfo(),
18+
https://github.com/LuaJIT/LuaJIT/issues/509
19+
20+
Negation overflow in getlocal/setlocal,
21+
https://github.com/lua/lua/commit/a585eae6e7ada1ca9271607a4f48dfb17868ab7b
22+
23+
debug.getlocal on a coroutine suspended in a hook can crash the interpreter,
24+
https://www.lua.org/bugs.html#5.3.0-2
25+
26+
lua_getupvalue and lua_setupvalue do not check for index too small,
27+
https://www.lua.org/bugs.html#5.0.2-2
28+
29+
Synopsis: debug.getupvalue (f, up)
30+
Synopsis: debug.getlocal([thread,] level, local)
31+
Synopsis: debug.getinfo([thread,] function [, what])
32+
]]
33+
34+
local luzer = require("luzer")
35+
local test_lib = require("lib")
36+
37+
local what = {
38+
"n", -- Fills in the field `name` and `namewhat`.
39+
"S", -- Fills in the fields `source`, `short_src`,
40+
-- `linedefined`, `lastlinedefined`, and `what`.
41+
"l", -- Fills in the field `currentline`.
42+
"u", -- Fills in the field `nups`.
43+
"f", -- Pushes onto the stack the function that is running at
44+
-- the given level.
45+
"L", -- Pushes onto the stack a table whose indices are the
46+
-- numbers of the lines that are valid on the function.
47+
}
48+
-- Fills in the field `istailcall`.
49+
if test_lib.lua_current_version_ge_than(5, 2) then
50+
table.insert(what, "t")
51+
end
52+
-- Fills in the fields `ftransfer` and `ntransfer`.
53+
if test_lib.lua_current_version_ge_than(5, 4) then
54+
table.insert(what, "r")
55+
end
56+
57+
local hook_mask = {
58+
"c", -- The hook is called every time Lua calls a function.
59+
"r", -- The hook is called every time Lua returns from a
60+
-- function.
61+
"l", -- The hook is called every time Lua enters a new line of
62+
-- code.
63+
}
64+
65+
local loadstring = type(loadstring) == "function" and loadstring or load
66+
67+
local what_modes_str
68+
local what_modes_map
69+
70+
local function check_getinfo(ar)
71+
if what_modes_map.S then
72+
assert(ar.source ~= nil and type(ar.source) == "string")
73+
assert(ar.short_src ~= nil and type(ar.short_src) == "string")
74+
assert(ar.linedefined ~= nil and type(ar.linedefined) == "number")
75+
assert(ar.lastlinedefined ~= nil and
76+
type(ar.lastlinedefined) == "number")
77+
assert(ar.what ~= nil and (ar.what == "Lua" or
78+
ar.what == "C" or
79+
ar.what == "main"))
80+
-- Beware, in PUC Rio Lua `srclen` can be omitted with
81+
-- `S` mode, see <ldebug.c>.
82+
assert(ar.srclen == nil or type(ar.srclen) == "number")
83+
end
84+
85+
if what_modes_map.l then
86+
assert(ar.currentline ~= nil and type(ar.currentline) == "number")
87+
end
88+
89+
if what_modes_map.t then
90+
assert(ar.name == nil or type(ar.name) == "string")
91+
assert(ar.namewhat == "global" or
92+
ar.namewhat == "local" or
93+
ar.namewhat == "method" or
94+
ar.namewhat == "field" or
95+
ar.namewhat == "upvalue" or
96+
ar.namewhat == "" or
97+
-- Undocumented in PUC Rio Lua (5.4+?).
98+
ar.namewhat == "hook" or
99+
ar.namewhat == "metamethod" or
100+
ar.namewhat == nil)
101+
end
102+
103+
if what_modes_map.t then
104+
assert(type(ar.istailcall) == "boolean")
105+
end
106+
107+
if what_modes_map.u then
108+
assert(ar.nups ~= nil and type(ar.nups) == "number")
109+
assert(ar.nparams ~= nil and type(ar.nparams) == "number")
110+
if ar.what == "C" then
111+
assert(ar.nparams == 0)
112+
assert(ar.isvararg == true)
113+
end
114+
assert(type(ar.isvararg) == "boolean")
115+
end
116+
117+
if what_modes_map.r then
118+
assert(ar.ftransfer ~= nil and type(ar.ftransfer) == "number")
119+
assert(ar.ntransfer ~= nil and type(ar.ntransfer) == "number")
120+
end
121+
end
122+
123+
local function touch_upvalues(func, nups)
124+
if not func then return end
125+
for j = 1, nups do
126+
local n, _ = debug.getupvalue(func, j)
127+
if not n then break end
128+
end
129+
end
130+
131+
local function debug_hook()
132+
local level = 0
133+
local ar = debug.getinfo(level, what_modes_str)
134+
while ar do
135+
-- "Touch" fields.
136+
check_getinfo(ar)
137+
138+
-- "Touch" locals.
139+
local i = 1
140+
while true do
141+
local name, _ = debug.getlocal(level, i)
142+
if name == nil then break end
143+
i = i + 1
144+
end
145+
146+
-- "Touch" upvalues.
147+
local func = debug.getinfo(level, what_modes_str).func
148+
local nups = debug.getinfo(level, "u").nups
149+
touch_upvalues(func, nups)
150+
151+
level = level + 1
152+
ar = debug.getinfo(level, what_modes_str)
153+
end
154+
end
155+
156+
local function TestOneInput(buf)
157+
local fdp = luzer.FuzzedDataProvider(buf)
158+
159+
-- Generate a random 'what'.
160+
what_modes_map = {}
161+
local what_modes_array = {}
162+
local count_modes = fdp:consume_integer(0, #what)
163+
for _ = 0, count_modes do
164+
local mode = fdp:oneof(what)
165+
table.insert(what_modes_array, mode)
166+
what_modes_map[mode] = true
167+
end
168+
what_modes_str = table.concat(what_modes_array)
169+
170+
-- Generate a random hook mask.
171+
local n_hook_mask = fdp:consume_integer(0, #hook_mask)
172+
local mask = {}
173+
for _ = 0, n_hook_mask do
174+
table.insert(mask, fdp:oneof(hook_mask))
175+
end
176+
177+
-- Turn on the hook.
178+
debug.sethook(debug_hook, table.concat(mask), 1)
179+
180+
local code = fdp:consume_string(test_lib.MAX_STR_LEN)
181+
local chunk = loadstring(code)
182+
if chunk == nil then
183+
return -1
184+
end
185+
pcall(chunk)
186+
187+
-- Turn off the hook.
188+
debug.sethook()
189+
end
190+
191+
local args = {
192+
artifact_prefix = "debug_torture_",
193+
}
194+
-- lj_bcread.c:123: bcread_byte: buffer read overflow
195+
if test_lib.lua_version() == "LuaJIT" then
196+
args.only_ascii = 1
197+
end
198+
luzer.Fuzz(TestOneInput, nil, args)

0 commit comments

Comments
 (0)