Skip to content

tests/lapi: add string tests #124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmake/BuildLua.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ macro(build_lua LUA_VERSION)
set(CFLAGS "${CFLAGS} -DLUA_USE_DLOPEN")
endif()

# `io.popen()` is not supported by default, it is enabled
# by `LUA_USE_POSIX` flag. Required by a function `random_locale()`.
set(CFLAGS "${CFLAGS} -DLUA_USE_POSIX")

include(ExternalProject)

set(LUA_LIBRARY ${PROJECT_BINARY_DIR}/lua-${LUA_VERSION}/source/liblua.a)
Expand Down
16 changes: 16 additions & 0 deletions tests/lapi/lib.lua
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ local MIN_INT64 = math.mininteger or -0x8000000000000000
local MAX_INT = 0x7fffffff
local MIN_INT = -0x80000000

local MAX_STR_LEN = 4096

local function bitwise_op(op_name)
return function(...)
local n = select("#", ...)
Expand Down Expand Up @@ -84,6 +86,16 @@ local function approx_equal(a, b, epsilon)
return abs(a - b) <= ((abs(a) < abs(b) and abs(b) or abs(a)) * epsilon)
end

local function random_locale(fdp)
local locales = {}
local locale_it = io.popen("locale -a"):read("*a"):gmatch("([^\n]*)\n?")
for locale in locale_it do
table.insert(locales, locale)
end

return fdp:oneof(locales)
end

return {
approx_equal = approx_equal,
bitwise_op = bitwise_op,
Expand All @@ -95,4 +107,8 @@ return {
MIN_INT64 = MIN_INT64,
MAX_INT = MAX_INT,
MIN_INT = MIN_INT,
MAX_STR_LEN = MAX_STR_LEN,

-- FDP.
random_locale = random_locale,
}
37 changes: 37 additions & 0 deletions tests/lapi/string_byte_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--[[
SPDX-License-Identifier: ISC
Copyright (c) 2023-2025, Sergey Bronnikov.

6.4 – String Manipulation
https://www.lua.org/manual/5.3/manual.html#6.4

string.byte gets confused with some out-of-range negative indices,
https://www.lua.org/bugs.html#5.1.3-9
]]

-- Synopsis: string.byte(s [, i [, j]])

local luzer = require("luzer")
local test_lib = require("lib")

local function TestOneInput(buf, _size)
local fdp = luzer.FuzzedDataProvider(buf)
os.setlocale(test_lib.random_locale(fdp), "all")
local str = fdp:consume_string(test_lib.MAX_STR_LEN)
local i = fdp:consume_integer(0, test_lib.MAX_INT)
local j = fdp:consume_integer(0, test_lib.MAX_INT)
-- `string.byte()` is the same as `str:byte()`.
assert(string.byte(str, i, j) == str:byte(i, j))
local char_code = string.byte(str, i, j)
if char_code then
assert(type(char_code) == "number")
local byte = string.char(char_code)
assert(byte)
assert(byte == str)
end
end

local args = {
artifact_prefix = "string_byte_",
}
luzer.Fuzz(TestOneInput, nil, args)
41 changes: 41 additions & 0 deletions tests/lapi/string_char_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--[[
SPDX-License-Identifier: ISC
Copyright (c) 2023-2025, Sergey Bronnikov.

6.4 – String Manipulation
https://www.lua.org/manual/5.3/manual.html#6.4

string.char bug,
https://github.com/LuaJIT/LuaJIT/issues/375

Fix string.char() recording with no arguments,
https://github.com/LuaJIT/LuaJIT/commit/dfa692b7

Synopsis: string.char(...)
]]

local luzer = require("luzer")
local test_lib = require("lib")

local unpack = unpack or table.unpack

local function TestOneInput(buf, _size)
local fdp = luzer.FuzzedDataProvider(buf)
os.setlocale(test_lib.random_locale(fdp), "all")
-- `n` must be less than UINT_MAX and there are at least extra
-- free stack slots in the stack, otherwise an error
-- "too many results to unpack" is raised, see <ltablib.c>.
local MAX_CHARS_NUM = 1024
local n = fdp:consume_integer(1, MAX_CHARS_NUM)
local CHAR_MAX = 255
local chs = fdp:consume_integers(0, CHAR_MAX, n)
local str = string.char(unpack(chs))
-- Returns a string with length equal to the number of
-- arguments.
assert(#str == n)
end

local args = {
artifact_prefix = "string_char_",
}
luzer.Fuzz(TestOneInput, nil, args)
40 changes: 40 additions & 0 deletions tests/lapi/string_dump_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--[[
SPDX-License-Identifier: ISC
Copyright (c) 2023-2025, Sergey Bronnikov.

6.4 – String Manipulation
https://www.lua.org/manual/5.3/manual.html#6.4

string.dump(table.foreach) will trigger an assert,
https://github.com/LuaJIT/LuaJIT/issues/1038

An emergency collection when handling an error while loading the upvalues of a function can cause a segfault,
https://github.com/lua/lua/commit/422ce50d2e8856ed789d1359c673122dbb0088ea

Synopsis: string.dump(function [, strip])
]]

local luzer = require("luzer")
local test_lib = require("lib")

local function TestOneInput(buf, _size)
local fdp = luzer.FuzzedDataProvider(buf)
os.setlocale(test_lib.random_locale(fdp), "all")
local str = fdp:consume_string(test_lib.MAX_STR_LEN)
local strip = fdp:consume_boolean()
local ok, func = pcall(loadstring, str)
if not ok or func == nil then
return
end
local res = string.dump(func, strip)
assert(#res ~= 0)
end

local args = {
artifact_prefix = "string_dump_",
}
-- LuaJIT ASSERT lj_bcread.c:123: bcread_byte: buffer read overflow.
if test_lib.lua_version() == "LuaJIT" then
args["only_ascii"] = 1
end
luzer.Fuzz(TestOneInput, nil, args)
48 changes: 48 additions & 0 deletions tests/lapi/string_find_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
--[=[[
SPDX-License-Identifier: ISC
Copyright (c) 2023-2025, Sergey Bronnikov.

6.4 – String Manipulation
https://www.lua.org/manual/5.3/manual.html#6.4

Bug in "Don't use STRREF for pointer diff in string.find().",
https://github.com/LuaJIT/LuaJIT/issues/540

Some patterns can overflow the C stack, due to recursion,
https://www.lua.org/bugs.html#5.2.1-1

Properly fix pointer diff in string.find(),
https://github.com/LuaJIT/LuaJIT/commit/0bee44c9

Synopsis: string.find(s, pattern [, init [, plain]])
]]=]

local luzer = require("luzer")
local test_lib = require("lib")

local function TestOneInput(buf, _size)
local fdp = luzer.FuzzedDataProvider(buf)
os.setlocale(test_lib.random_locale(fdp), "all")
local str = fdp:consume_string(test_lib.MAX_STR_LEN)
local pattern = fdp:consume_string(test_lib.MAX_STR_LEN)
local init = fdp:consume_integer(0, test_lib.MAX_INT)
local plain = fdp:consume_boolean()
-- Avoid errors like "malformed pattern (missing ']')".
local ok, _ = pcall(string.find, str, pattern, init, plain)
if not ok then
return
end
local begin_pos, end_pos = string.find(str, pattern, init, plain)
-- `string.format()` returns two numbers or "fail".
assert((type(begin_pos) == "number" and type(end_pos) == "number") or
(begin_pos == nil or end_pos == nil) or
begin_pos == "fail")
-- `string.format()` and `string:format()` is the same.
assert(string.find(str, pattern, init, plain) ==
str:find(pattern, init, plain))
end

local args = {
artifact_prefix = "string_find_",
}
luzer.Fuzz(TestOneInput, nil, args)
83 changes: 83 additions & 0 deletions tests/lapi/string_format_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
--[[
SPDX-License-Identifier: ISC
Copyright (c) 2023-2025, Sergey Bronnikov.

6.4 – String Manipulation
https://www.lua.org/manual/5.3/manual.html#6.4

stack-buffer-overflow in lj_strfmt_wfnum,
https://github.com/LuaJIT/LuaJIT/issues/1149
string.format("%7g",0x1.144399609d407p+401)

string.format %c bug,
https://github.com/LuaJIT/LuaJIT/issues/378

string.format doesn't take current locale decimal separator into account,
https://github.com/LuaJIT/LuaJIT/issues/673

string.format("%f") can cause a buffer overflow (only when
'lua_Number' is long double!),
https://www.lua.org/bugs.html#5.3.0-1

string.format may get buffer as an argument when there are missing
arguments and format string is too long,
https://www.lua.org/bugs.html#5.1.4-7

string.format("%") may read past the string,
https://www.lua.org/bugs.html#5.1.1-3

Option '%q' in string.formatE does not handle '\r' correctly,
https://www.lua.org/bugs.html#5.1-4

FFI: Support FFI numbers in string.format() and buf:putf(),
https://github.com/LuaJIT/LuaJIT/commit/1b7171c3

[0014] CRASH detected in lj_ir_kgc due to a fault at or
near 0x00007ff7f3274008 leading to SIGSEGV,
https://github.com/LuaJIT/LuaJIT/issues/1203

Synopsis: string.format(formatstring, ...)
]]


local luzer = require("luzer")
local test_lib = require("lib")

local specifiers = {
"a",
"A",
"c",
"d",
"e",
"E",
"f",
"g",
"G",
"i",
"o",
"p",
"q",
"s",
"u",
"x",
"X",
}

local function TestOneInput(buf, _size)
local fdp = luzer.FuzzedDataProvider(buf)
local spec = fdp:oneof(specifiers)
local format_string = ("%%%s"):format(spec)
local str = fdp:consume_string(test_lib.MAX_STR_LEN)

os.setlocale(test_lib.random_locale(fdp), "all")
local ok, res = pcall(string.format, format_string, str)
assert(type(res) == "string")
if ok then
assert((format_string):format(str) == string.format(format_string, str))
end
end

local args = {
artifact_prefix = "string_format_",
}
luzer.Fuzz(TestOneInput, nil, args)
33 changes: 33 additions & 0 deletions tests/lapi/string_gmatch_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--[[
SPDX-License-Identifier: ISC
Copyright (c) 2023-2025, Sergey Bronnikov.

6.4 – String Manipulation
https://www.lua.org/manual/5.3/manual.html#6.4

GC64: string.gmatch crash,
https://github.com/LuaJIT/LuaJIT/issues/300

gmatch iterator fails when called from a coroutine different from
the one that created it,
https://www.lua.org/bugs.html#5.3.2-3

Synopsis: string.gmatch(s, pattern [, init])
]]

local luzer = require("luzer")
local test_lib = require("lib")

local function TestOneInput(buf, _size)
local fdp = luzer.FuzzedDataProvider(buf)
os.setlocale(test_lib.random_locale(fdp), "all")
local s = fdp:consume_string(test_lib.MAX_STR_LEN)
local pattern = fdp:consume_string(test_lib.MAX_STR_LEN)
local init = fdp:consume_integer(0, test_lib.MAX_INT)
string.gmatch(s, pattern, init)
end

local args = {
artifact_prefix = "string_gmatch_",
}
luzer.Fuzz(TestOneInput, nil, args)
42 changes: 42 additions & 0 deletions tests/lapi/string_gsub_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--[[
SPDX-License-Identifier: ISC
Copyright (c) 2023-2025, Sergey Bronnikov.

6.4 – String Manipulation
https://www.lua.org/manual/5.3/manual.html#6.4

Performance issue for reg expression with "$",
https://github.com/LuaJIT/LuaJIT/issues/118

LuaJIT's gsub does not work with zero bytes in the pattern string,
https://github.com/LuaJIT/LuaJIT/issues/860

gsub may go wild when wrongly called without its third argument
and with a large subject,
https://www.lua.org/bugs.html#5.1.2-9

Synopsis: string.gsub(s, pattern, repl [, n])
]]

local luzer = require("luzer")
local test_lib = require("lib")

local function TestOneInput(buf, _size)
local fdp = luzer.FuzzedDataProvider(buf)
local str = fdp:consume_string(test_lib.MAX_STR_LEN)
local pattern = fdp:consume_string(test_lib.MAX_STR_LEN)
local repl = fdp:consume_string(test_lib.MAX_STR_LEN)
local n = fdp:consume_integer(0, test_lib.MAX_INT)

os.setlocale(test_lib.random_locale(fdp), "all")
-- Avoid errors like "malformed pattern (missing ']')".
local ok, res = pcall(string.gsub, str, pattern, repl, n)
if ok then
assert(type(res) == "string")
end
end

local args = {
artifact_prefix = "string_gsub_",
}
luzer.Fuzz(TestOneInput, nil, args)
Loading