Skip to content

Commit 17975d4

Browse files
committed
tests/lapi: add string tests
The patch adds a fuzzing tests for Lua string functions.
1 parent 2a1da10 commit 17975d4

18 files changed

+595
-0
lines changed

tests/lapi/lib.lua

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,33 @@ local function random_number(fdp)
4343
return math.floor(num)
4444
end
4545

46+
local function oneof(fdp, tbl)
47+
assert(type(tbl) == "table")
48+
assert(next(tbl) ~= nil)
49+
50+
local n = table.getn(tbl)
51+
local idx = fdp:consume_integer(1, n)
52+
return tbl[idx], idx
53+
end
54+
55+
local function random_locale(fdp)
56+
local locales = {}
57+
local locale_it = io.popen("locale -a"):read("*a"):gmatch("([^\n]*)\n?")
58+
for locale in locale_it do
59+
table.insert(locales, locale)
60+
end
61+
62+
return oneof(fdp, locales)
63+
end
64+
4665
return {
4766
-- FDP.
4867
random_number = random_number,
68+
random_locale = random_locale,
4969

70+
oneof = oneof,
5071
version = version,
5172
bitwise_op = bitwise_op,
73+
74+
MAX_INT = 2^51,
5275
}

tests/lapi/string_byte_test.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
6.4 – String Manipulation
6+
https://www.lua.org/manual/5.3/manual.html#6.4
7+
8+
string.byte gets confused with some out-of-range negative indices,
9+
https://www.lua.org/bugs.html#5.1.3-9
10+
]]
11+
12+
-- Synopsis: string.byte (s [, i [, j]])
13+
14+
local luzer = require("luzer")
15+
local test_lib = require("lib")
16+
17+
local function TestOneInput(buf, _size)
18+
local fdp = luzer.FuzzedDataProvider(buf)
19+
os.setlocale(test_lib.random_locale(fdp), "all")
20+
local len = fdp:consume_integer(0, test_lib.MAX_INT)
21+
local b = fdp:consume_string(len)
22+
local char_code = string.byte(b)
23+
if char_code then
24+
assert(type(char_code) == "number")
25+
local byte = string.char(char_code)
26+
assert(byte)
27+
-- FIXME
28+
-- assert(byte == b)
29+
end
30+
end
31+
32+
local args = {
33+
max_len = 4096,
34+
artifact_prefix = "string_byte_",
35+
}
36+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/string_char_test.lua

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
6.4 – String Manipulation
6+
https://www.lua.org/manual/5.3/manual.html#6.4
7+
8+
string.char bug,
9+
https://github.com/LuaJIT/LuaJIT/issues/375
10+
11+
Fix string.char() recording with no arguments,
12+
https://github.com/LuaJIT/LuaJIT/commit/dfa692b7
13+
14+
Synopsis: string.char (···)
15+
]]
16+
17+
local luzer = require("luzer")
18+
local test_lib = require("lib")
19+
20+
local function TestOneInput(buf, _size)
21+
local fdp = luzer.FuzzedDataProvider(buf)
22+
os.setlocale(test_lib.random_locale(fdp), "all")
23+
local c = fdp:consume_integer(0, test_lib.MAX_INT)
24+
string.char(c)
25+
end
26+
27+
local args = {
28+
max_len = 4096,
29+
artifact_prefix = "string_char_",
30+
}
31+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/string_dump_test.lua

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
6.4 – String Manipulation
6+
https://www.lua.org/manual/5.3/manual.html#6.4
7+
8+
string.dump(table.foreach) will trigger an assert,
9+
https://github.com/LuaJIT/LuaJIT/issues/1038
10+
11+
An emergency collection when handling an error while loading the upvalues of a function can cause a segfault,
12+
https://github.com/lua/lua/commit/422ce50d2e8856ed789d1359c673122dbb0088ea
13+
14+
Synopsis: string.dump (function [, strip])
15+
]]
16+
17+
local luzer = require("luzer")
18+
local test_lib = require("lib")
19+
20+
local function TestOneInput(buf, _size)
21+
local fdp = luzer.FuzzedDataProvider(buf)
22+
os.setlocale(test_lib.random_locale(fdp), "all")
23+
local len = fdp:consume_integer(0, test_lib.MAX_INT)
24+
local str = fdp:consume_string(len)
25+
loadstring(string.dump(function() return str end))
26+
end
27+
28+
local args = {
29+
max_len = 4096,
30+
artifact_prefix = "string_dump_",
31+
}
32+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/string_find_test.lua

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
6.4 – String Manipulation
6+
https://www.lua.org/manual/5.3/manual.html#6.4
7+
8+
Bug in "Don't use STRREF for pointer diff in string.find().",
9+
https://github.com/LuaJIT/LuaJIT/issues/540
10+
11+
Some patterns can overflow the C stack, due to recursion,
12+
https://www.lua.org/bugs.html#5.2.1-1
13+
14+
Properly fix pointer diff in string.find(),
15+
https://github.com/LuaJIT/LuaJIT/commit/0bee44c9
16+
]]
17+
18+
-- Synopsis: string.find (s, pattern [, init [, plain]])
19+
20+
local luzer = require("luzer")
21+
local test_lib = require("lib")
22+
23+
local function TestOneInput(buf, _size)
24+
local fdp = luzer.FuzzedDataProvider(buf)
25+
os.setlocale(test_lib.random_locale(fdp), "all")
26+
local len1 = fdp:consume_integer(0, test_lib.MAX_INT)
27+
local len2 = fdp:consume_integer(0, test_lib.MAX_INT)
28+
local str1 = fdp:consume_string(len1)
29+
local str2 = fdp:consume_string(len2)
30+
-- FIXME
31+
local str = string.format("%s - %s", str1, str2)
32+
local begin_pos, end_pos = str:find(str1)
33+
assert(begin_pos == 1)
34+
assert(end_pos == #str1)
35+
end
36+
37+
local args = {
38+
max_len = 4096,
39+
artifact_prefix = "string_find_",
40+
}
41+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/string_format_test.lua

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
6.4 – String Manipulation
6+
https://www.lua.org/manual/5.3/manual.html#6.4
7+
8+
stack-buffer-overflow in lj_strfmt_wfnum,
9+
https://github.com/LuaJIT/LuaJIT/issues/1149
10+
string.format("%7g",0x1.144399609d407p+401)
11+
12+
string.format %c bug,
13+
https://github.com/LuaJIT/LuaJIT/issues/378
14+
15+
string.format doesn't take current locale decimal separator into account,
16+
https://github.com/LuaJIT/LuaJIT/issues/673
17+
18+
string.format("%f") can cause a buffer overflow (only when
19+
'lua_Number' is long double!),
20+
https://www.lua.org/bugs.html#5.3.0-1
21+
22+
string.format may get buffer as an argument when there are missing
23+
arguments and format string is too long,
24+
https://www.lua.org/bugs.html#5.1.4-7
25+
26+
string.format("%") may read past the string,
27+
https://www.lua.org/bugs.html#5.1.1-3
28+
29+
Option '%q' in string.formatE does not handle '\r' correctly,
30+
https://www.lua.org/bugs.html#5.1-4
31+
32+
FFI: Support FFI numbers in string.format() and buf:putf(),
33+
https://github.com/LuaJIT/LuaJIT/commit/1b7171c3
34+
35+
[0014] CRASH detected in lj_ir_kgc due to a fault at or
36+
near 0x00007ff7f3274008 leading to SIGSEGV,
37+
https://github.com/LuaJIT/LuaJIT/issues/1203
38+
39+
Synopsis: string.format (formatstring, ···)
40+
]]
41+
42+
-- q: booleans, nil, numbers, and strings.
43+
-- A, a, E, e, f, G, and g: number.
44+
-- c, d, i, o, u, X, and x: integer.
45+
-- A and a (hexadecimal floats) do not support modifiers.
46+
-- s: string
47+
-- p: pointer returned by lua_topointer
48+
49+
local luzer = require("luzer")
50+
local test_lib = require("lib")
51+
52+
local function TestOneInput(buf, _size)
53+
local fdp = luzer.FuzzedDataProvider(buf)
54+
os.setlocale(test_lib.random_locale(fdp), "all")
55+
local len = fdp:consume_number(0, test_lib.MAX_INT)
56+
local formatstring = fdp:consume_string(len)
57+
local values = {} -- FIXME
58+
assert((formatstring):format(unpack(values)) ==
59+
string.format(formatstring, unpack(values)))
60+
end
61+
62+
local args = {
63+
max_len = 4096,
64+
artifact_prefix = "string_format_",
65+
}
66+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/string_gmatch_test.lua

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
6.4 – String Manipulation
6+
https://www.lua.org/manual/5.3/manual.html#6.4
7+
8+
GC64: string.gmatch crash,
9+
https://github.com/LuaJIT/LuaJIT/issues/300
10+
11+
gmatch iterator fails when called from a coroutine different from
12+
the one that created it,
13+
https://www.lua.org/bugs.html#5.3.2-3
14+
15+
Synopsis: string.gmatch (s, pattern [, init])
16+
]]
17+
18+
local luzer = require("luzer")
19+
local test_lib = require("lib")
20+
21+
local function TestOneInput(buf, _size)
22+
local fdp = luzer.FuzzedDataProvider(buf)
23+
os.setlocale(test_lib.random_locale(fdp), "all")
24+
local len = fdp:consume_integer(0, test_lib.MAX_INT)
25+
local str1 = fdp:consume_string(len)
26+
local str2 = fdp:consume_string(len)
27+
string.gmatch(str1, str2)
28+
end
29+
30+
local args = {
31+
max_len = 4096,
32+
artifact_prefix = "string_gmatch_",
33+
}
34+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/string_gsub_test.lua

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
6.4 – String Manipulation
6+
https://www.lua.org/manual/5.3/manual.html#6.4
7+
8+
Performance issue for reg expression with "$",
9+
https://github.com/LuaJIT/LuaJIT/issues/118
10+
11+
LuaJIT's gsub does not work with zero bytes in the pattern string,
12+
https://github.com/LuaJIT/LuaJIT/issues/860
13+
14+
gsub may go wild when wrongly called without its third argument
15+
and with a large subject,
16+
https://www.lua.org/bugs.html#5.1.2-9
17+
18+
Synopsis: string.gsub (s, pattern, repl [, n])
19+
]]
20+
21+
local luzer = require("luzer")
22+
local test_lib = require("lib")
23+
24+
local string_gsub = string.gsub
25+
26+
local function TestOneInput(buf, _size)
27+
local fdp = luzer.FuzzedDataProvider(buf)
28+
local str = fdp:consume_string(0, test_lib.MAX_INT)
29+
local pattern = fdp:consume_string(0, test_lib.MAX_INT)
30+
local repl = fdp:consume_string(0, test_lib.MAX_INT)
31+
local n = fdp:consume_integer(0, test_lib.MAX_INT)
32+
33+
os.setlocale(test_lib.random_locale(fdp), "all")
34+
-- FIXME: malformed pattern (missing ']')
35+
string_gsub(str, pattern, repl, n)
36+
end
37+
38+
local args = {
39+
max_len = 4096,
40+
artifact_prefix = "string_gsub_",
41+
}
42+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/string_len_test.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
6.4 – String Manipulation
6+
https://www.lua.org/manual/5.3/manual.html#6.4
7+
8+
Synopsis: string.len (s)
9+
]]
10+
11+
local luzer = require("luzer")
12+
local test_lib = require("lib")
13+
14+
local function TestOneInput(buf, _size)
15+
local fdp = luzer.FuzzedDataProvider(buf)
16+
os.setlocale(test_lib.random_locale(fdp), "all")
17+
local str_len = fdp:consume_integer(0, test_lib.MAX_INT)
18+
local str = fdp:consume_string(str_len)
19+
assert(string.len(str) == #str)
20+
end
21+
22+
local args = {
23+
max_len = 4096,
24+
artifact_prefix = "string_len_",
25+
}
26+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/string_lower_test.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
6.4 – String Manipulation
6+
https://www.lua.org/manual/5.3/manual.html#6.4
7+
8+
Synopsis: string.lower (s)
9+
]]
10+
11+
local luzer = require("luzer")
12+
local test_lib = require("lib")
13+
14+
local function TestOneInput(buf, _)
15+
local fdp = luzer.FuzzedDataProvider(buf)
16+
os.setlocale(test_lib.random_locale(fdp), "all")
17+
local ch_uppercase = string.char(fdp:consume_number(65, 65 + 25))
18+
local ch = string.upper(string.lower(ch_uppercase))
19+
assert(ch == ch_uppercase)
20+
end
21+
22+
local args = {
23+
max_len = 4096,
24+
artifact_prefix = "string_lower_",
25+
}
26+
luzer.Fuzz(TestOneInput, nil, args)

0 commit comments

Comments
 (0)