Skip to content

Commit c133fb0

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

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed

tests/lapi/coroutine_test.lua

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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+
Suspended __le metamethod can give wrong result,
22+
https://www.lua.org/bugs.html#5.3.0-3
23+
24+
Resuming the running coroutine makes it unyieldable,
25+
https://www.lua.org/bugs.html#5.2.2-8
26+
27+
pcall may not restore previous error function when inside coroutines,
28+
https://www.lua.org/bugs.html#5.2.1-2
29+
30+
Wrong handling of nCcalls in coroutines,
31+
https://www.lua.org/bugs.html#5.2.0-4
32+
33+
coroutine.resume pushes element without ensuring stack size,
34+
https://www.lua.org/bugs.html#5.1.3-2
35+
36+
Recursive coroutines may overflow C stack,
37+
https://www.lua.org/bugs.html#5.1.2-4
38+
39+
Stand-alone interpreter shows incorrect error message when the
40+
"message" is a coroutine,
41+
https://www.lua.org/bugs.html#5.1.2-12
42+
43+
Debug hooks may get wrong when mixed with coroutines,
44+
https://www.lua.org/bugs.html#5.1-7
45+
46+
Values held in open upvalues of suspended threads may be
47+
incorrectly collected,
48+
https://www.lua.org/bugs.html#5.0.2-3
49+
50+
Attempt to resume a running coroutine crashes Lua,
51+
https://www.lua.org/bugs.html#5.0-2
52+
53+
Synopsis: coroutine.close (co)
54+
Synopsis: coroutine.create (f)
55+
Synopsis: coroutine.isyieldable ([co])
56+
Synopsis: coroutine.resume (co [, val1, ···])
57+
Synopsis: coroutine.running ()
58+
Synopsis: coroutine.status (co)
59+
Synopsis: coroutine.wrap (f)
60+
Synopsis: coroutine.yield (···)
61+
]]
62+
63+
local luzer = require("luzer")
64+
local test_lib = require("lib")
65+
66+
local C = {}
67+
68+
local STATUS_CREATE = "CREATE"
69+
local STATUS_CLOSE = "CLOSE"
70+
local STATUS_YIELD = "YIELD"
71+
local STATUS_RESUME = "RESUME"
72+
local STATUS_DEAD = "DEAD"
73+
74+
local CORO_MAX_NUMBER = 10^53
75+
76+
local CORO_STATE = {
77+
STATUS_CLOSE,
78+
STATUS_CREATE,
79+
STATUS_RESUME,
80+
STATUS_YIELD,
81+
}
82+
83+
-- FIXME: Replace with `fdp:oneof()`.
84+
local function oneof(fdp, tbl)
85+
assert(type(tbl) == "table")
86+
assert(next(tbl) ~= nil)
87+
88+
local n = table.getn(tbl)
89+
local idx = fdp:consume_integer(1, n)
90+
return tbl[idx], idx
91+
end
92+
93+
-- Forward declaration.
94+
local coro_loop
95+
96+
local function random_coro(fdp)
97+
-- FIXME: Replace with `fdp:oneof()`.
98+
local coro, coro_n = oneof(fdp, C)
99+
local coro_status = coroutine.status(coro)
100+
if coro_status == STATUS_DEAD then
101+
table.remove(C, coro_n)
102+
coro = coroutine.create(coro_loop)
103+
table.insert(C, coro)
104+
coroutine.resume(coro, 1)
105+
end
106+
-- FIXME: Replace with `fdp:oneof()`.
107+
return oneof(fdp, C)
108+
end
109+
110+
local function coro_step(fdp)
111+
-- FIXME: Replace with `fdp:oneof()`.
112+
local state = oneof(fdp, CORO_STATE)
113+
if state == STATUS_CREATE then
114+
local co = coroutine.create(coro_loop)
115+
table.insert(C, co)
116+
coroutine.resume(co, 1)
117+
return
118+
end
119+
120+
-- FIXME: Replace with `fdp:oneof()`.
121+
state = oneof(fdp, CORO_STATE)
122+
local coro = random_coro(fdp)
123+
local status = coroutine.status(coro)
124+
if status == STATUS_DEAD then
125+
return
126+
end
127+
io.write(("[%0.6s] STATE: %s -> %s\n"):format(#C, status, state))
128+
if state == STATUS_CLOSE then
129+
coroutine.close(coro)
130+
elseif state == STATUS_YIELD and coroutine.isyieldable(coro) then
131+
coroutine.yield(coro)
132+
elseif state == STATUS_RESUME then
133+
coroutine.resume(coro)
134+
end
135+
136+
return
137+
end
138+
139+
coro_loop = function(fdp, max_n)
140+
local n = fdp:consume_integer(1, max_n)
141+
for _ = 1, n do
142+
coro_step(fdp)
143+
end
144+
end
145+
146+
local function TestOneInput(buf, _size)
147+
local fdp = luzer.FuzzedDataProvider(buf)
148+
local co = coroutine.create(coro_loop)
149+
table.insert(C, co)
150+
-- The function `coroutine.resume` starts the execution of
151+
-- a coroutine, changing its state from suspended to running.
152+
coroutine.resume(co, fdp, CORO_MAX_NUMBER)
153+
end
154+
155+
local args = {
156+
artifact_prefix = "coroutine_create_",
157+
}
158+
luzer.Fuzz(TestOneInput, nil, args)

0 commit comments

Comments
 (0)