Skip to content

Commit 5a13ad7

Browse files
committed
tests/lapi: add coroutine test
The patch adds a fuzzing tests for Lua coroutine library.
1 parent bfac9d7 commit 5a13ad7

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

tests/lapi/coroutine_test.lua

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
2.6 – Coroutines
6+
https://www.lua.org/manual/5.3/manual.html#2.6
7+
8+
Computation of stack limit when entering a coroutine is wrong,
9+
https://github.com/lua/lua/commit/e1d8770f12542d34a3e32b825c95b93f8a341ee1
10+
11+
C-stack overflow with deep nesting of coroutine.close,
12+
https://www.lua.org/bugs.html#5.4.4-9
13+
14+
C stack overflow (again),
15+
https://github.com/lua/lua/commit/34affe7a63fc5d842580a9f23616d057e17dfe27
16+
17+
When a coroutine tries to resume a non-suspended coroutine,
18+
it can do some mess (and break C assertions) before detecting the error,
19+
https://www.lua.org/bugs.html#5.3.3-4
20+
21+
debug.getlocal on a coroutine suspended in a hook can crash the interpreter,
22+
https://www.lua.org/bugs.html#5.3.0-2
23+
24+
Suspended __le metamethod can give wrong result,
25+
https://www.lua.org/bugs.html#5.3.0-3
26+
27+
Resuming the running coroutine makes it unyieldable,
28+
https://www.lua.org/bugs.html#5.2.2-8
29+
30+
pcall may not restore previous error function when inside coroutines,
31+
https://www.lua.org/bugs.html#5.2.1-2
32+
33+
Wrong handling of nCcalls in coroutines,
34+
https://www.lua.org/bugs.html#5.2.0-4
35+
36+
coroutine.resume pushes element without ensuring stack size,
37+
https://www.lua.org/bugs.html#5.1.3-2
38+
39+
Recursive coroutines may overflow C stack,
40+
https://www.lua.org/bugs.html#5.1.2-4
41+
42+
Stand-alone interpreter shows incorrect error message when the
43+
"message" is a coroutine,
44+
https://www.lua.org/bugs.html#5.1.2-12
45+
46+
Debug hooks may get wrong when mixed with coroutines,
47+
https://www.lua.org/bugs.html#5.1-7
48+
49+
Values held in open upvalues of suspended threads may be
50+
incorrectly collected,
51+
https://www.lua.org/bugs.html#5.0.2-3
52+
53+
Attempt to resume a running coroutine crashes Lua,
54+
https://www.lua.org/bugs.html#5.0-2
55+
56+
debug.getlocal on a coroutine suspended in a hook can crash the interpreter,
57+
https://www.lua.org/bugs.html#5.3.0-2
58+
59+
debug.sethook/gethook may overflow the thread's stack,
60+
https://www.lua.org/bugs.html#5.1.2-13
61+
62+
Memory hoarding when creating Lua hooks for coroutines,
63+
https://www.lua.org/bugs.html#5.2.0-1
64+
65+
Synopsis:
66+
67+
coroutine.close(co)
68+
coroutine.create(f)
69+
coroutine.isyieldable([co])
70+
coroutine.resume(co [, val1, ...])
71+
coroutine.running()
72+
coroutine.status(co)
73+
coroutine.wrap(f)
74+
coroutine.yield(...)
75+
]]
76+
77+
local luzer = require("luzer")
78+
local test_lib = require("lib")
79+
80+
local C = {}
81+
82+
local STATUS_CREATE = "CREATE"
83+
local STATUS_CLOSE = "CLOSE"
84+
local STATUS_YIELD = "YIELD"
85+
local STATUS_RESUME = "RESUME"
86+
local STATUS_DEAD = "DEAD"
87+
88+
local CORO_STATE = {
89+
STATUS_CLOSE,
90+
STATUS_CREATE,
91+
STATUS_RESUME,
92+
STATUS_YIELD,
93+
}
94+
95+
-- Forward declaration.
96+
local coro_loop
97+
98+
local function random_coro(fdp)
99+
local coro, coro_n = fdp:oneof(C)
100+
local coro_status = coroutine.status(coro)
101+
if coro_status == STATUS_DEAD then
102+
table.remove(C, coro_n)
103+
coro = coroutine.create(coro_loop)
104+
table.insert(C, coro)
105+
coroutine.resume(coro, 1)
106+
end
107+
return fdp:oneof(C)
108+
end
109+
110+
local function coro_step(fdp)
111+
local state = fdp:oneof(CORO_STATE)
112+
if state == STATUS_CREATE then
113+
local co = coroutine.create(coro_loop)
114+
local set_hook = fdp:consume_boolean()
115+
local hook_args = {}
116+
if set_hook then
117+
table.insert(hook_args, function () return co end)
118+
table.insert(hook_args, fdp:oneof({'c', 'r', 'l'}))
119+
end
120+
debug.sethook(co, unpack(hook_args))
121+
table.insert(C, co)
122+
coroutine.resume(co, 1)
123+
return
124+
end
125+
126+
state = fdp:oneof(CORO_STATE)
127+
local coro = random_coro(fdp)
128+
local status = coroutine.status(coro)
129+
if status == STATUS_DEAD then
130+
return
131+
end
132+
-- io.write(("[%0.6s] STATE: %s -> %s\n"):format(#C, status, state))
133+
if state == STATUS_CLOSE then
134+
coroutine.close(coro)
135+
elseif state == STATUS_YIELD and coroutine.isyieldable(coro) then
136+
coroutine.yield(coro)
137+
elseif state == STATUS_RESUME then
138+
coroutine.resume(coro)
139+
end
140+
141+
return
142+
end
143+
144+
coro_loop = function(fdp, coro_max_number)
145+
local n = fdp:consume_integer(1, coro_max_number)
146+
for _ = 1, n do
147+
coro_step(fdp)
148+
end
149+
end
150+
151+
local function TestOneInput(buf, _size)
152+
local fdp = luzer.FuzzedDataProvider(buf)
153+
local coro_max_number = fdp:consume_integer(0, test_lib.MAX_INT64)
154+
local co = coroutine.create(coro_loop)
155+
table.insert(C, co)
156+
-- The function `coroutine.resume` starts the execution of
157+
-- a coroutine, changing its state from suspended to running.
158+
coroutine.resume(co, fdp, coro_max_number)
159+
end
160+
161+
local args = {
162+
artifact_prefix = "coroutine_create_",
163+
}
164+
luzer.Fuzz(TestOneInput, nil, args)

0 commit comments

Comments
 (0)