diff --git a/.luacheckrc b/.luacheckrc index 26c1cb7..9707f32 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -5,3 +5,13 @@ files["tests/capi/luaL_loadbuffer_proto/preamble.lua"] = { "211", }, } + +-- The new function introduced in the Lua 5.5, it is not yet +-- supported by the luacheck, see [1]. +-- +-- 1. https://github.com/lunarmodules/luacheck/issues/132 +globals = { + table = { + fields = { "create" } + } +} diff --git a/tests/lapi/lib.lua b/tests/lapi/lib.lua index b0f1a7f..c4bb890 100644 --- a/tests/lapi/lib.lua +++ b/tests/lapi/lib.lua @@ -105,8 +105,23 @@ local function err_handler(ignored_msgs) end end +local function is_nan(v) + return v ~= v +end + +local function arrays_equal(t1, t2) + for i = 1, #t1 do + if t1[i] ~= t2[i] and + not (is_nan(t1[i]) and is_nan(t2[i])) then + return false + end + end + return #t1 == #t2 +end + return { approx_equal = approx_equal, + arrays_equal = arrays_equal, bitwise_op = bitwise_op, err_handler = err_handler, lua_current_version_ge_than = lua_current_version_ge_than, diff --git a/tests/lapi/table_concat_test.lua b/tests/lapi/table_concat_test.lua new file mode 100644 index 0000000..297c0c3 --- /dev/null +++ b/tests/lapi/table_concat_test.lua @@ -0,0 +1,40 @@ +--[=[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +5.5 – Table Manipulation, +https://www.lua.org/manual/5.1/manual.html#5.5 + +Infinite loop on table lookup, +https://github.com/LuaJIT/LuaJIT/issues/494 + +Synopsis: table.concat(list [, sep [, i [, j]]]) +--]=] + +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 tbl_size = #str + if tbl_size == 0 then return -1 end + + -- Split string to a table. + local tbl = {} + str:gsub(".", function(c) + table.insert(tbl, c) + end) + assert(#tbl == tbl_size) + + -- Join table to a string. + local j = fdp:consume_integer(1, tbl_size) + local i = fdp:consume_integer(1, j) + local sep = "" + assert(string.sub(str, i, j) == table.concat(tbl, sep, i, j)) +end + +local args = { + artifact_prefix = "table_concat_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/table_create_test.lua b/tests/lapi/table_create_test.lua new file mode 100644 index 0000000..908f0df --- /dev/null +++ b/tests/lapi/table_create_test.lua @@ -0,0 +1,40 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Bad lookup generated by lj_record_idx in GC64, +https://github.com/LuaJIT/LuaJIT/issues/840 + +Synopsis: + LuaJIT: table.new(narray, nhash) + PUC Rio Lua: table.create(narray, nhash) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local table_create +if test_lib.lua_version() == "LuaJIT" then + table_create = require("table.new") +elseif test_lib.lua_current_version_ge_than(5, 5) then + -- New function 'table.create()' in PUC Rio Lua, + -- https://github.com/lua/lua/commit/3e9dbe143d3338f5f13a5e421ea593adff482da0 + table_create = table.create +else + print("Unsupported version.") + os.exit(0) +end + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + -- Beware, huge number triggers OOM or table overflow. + local MAX_N = 1000 + local narray = fdp:consume_integer(0, MAX_N) + local nhash = fdp:consume_integer(0, MAX_N) + local _ = table_create(narray, nhash) -- luacheck: no unused +end + +local args = { + artifact_prefix = "table_create_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/table_foreach_test.lua b/tests/lapi/table_foreach_test.lua new file mode 100644 index 0000000..2b28634 --- /dev/null +++ b/tests/lapi/table_foreach_test.lua @@ -0,0 +1,39 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +5.4 – Table Manipulation, +https://www.lua.org/manual/5.0/manual.html#5.4 + +Bad loop initialization in table.foreach(), +https://github.com/LuaJIT/LuaJIT/issues/844 + +string.dump(table.foreach) will trigger an assert, +https://github.com/LuaJIT/LuaJIT/issues/1038 + +Synopsis: table.foreach(table, f) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +-- Function `table.foreach` is deprecated in Lua 5.1. +if test_lib.lua_current_version_ge_than(5, 2) then + print("Unsupported version.") + os.exit(0) +end + +local function TestOneInput(buf, _size) + local fdp = luzer.FuzzedDataProvider(buf) + local count = fdp:consume_integer(1, test_lib.MAX_INT) + local tbl = fdp:consume_strings(test_lib.MAX_STR_LEN, count) + local i = 0 + local fn = function(_idx, _v) i = i + 1 end + table.foreach(tbl, fn) + assert(#tbl == i) +end + +local args = { + artifact_prefix = "table_foreach_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/table_foreachi_test.lua b/tests/lapi/table_foreachi_test.lua new file mode 100644 index 0000000..e99e226 --- /dev/null +++ b/tests/lapi/table_foreachi_test.lua @@ -0,0 +1,33 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +5.4 – Table Manipulation, +https://www.lua.org/manual/5.0/manual.html#5.4 + +Synopsis: table.foreachi(table, f) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +-- Function `table.foreach` is deprecated in Lua 5.1. +if test_lib.lua_current_version_ge_than(5, 2) then + print("Unsupported version.") + os.exit(0) +end + +local function TestOneInput(buf, _size) + local fdp = luzer.FuzzedDataProvider(buf) + local count = fdp:consume_integer(1, test_lib.MAX_INT) + local tbl = fdp:consume_strings(test_lib.MAX_STR_LEN, count) + local i = 0 + local fn = function(_idx, _v) i = i + 1 end + table.foreachi(tbl, fn) + assert(#tbl == i) +end + +local args = { + artifact_prefix = "table_foreachi_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/table_insert_test.lua b/tests/lapi/table_insert_test.lua new file mode 100644 index 0000000..60ba429 --- /dev/null +++ b/tests/lapi/table_insert_test.lua @@ -0,0 +1,36 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2024, Sergey Bronnikov. + +5.5 – Table Manipulation, +https://www.lua.org/manual/5.1/manual.html#5.5 + +Synopsis: table.insert(list, [pos,] value) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local MAX_INT = test_lib.MAX_INT + +local function TestOneInput(buf, _size) + local fdp = luzer.FuzzedDataProvider(buf) + local str = fdp:consume_string(test_lib.MAX_STR_LEN) + local tbl = {} + str:gsub(".", function(c) + -- The `pos` value cannot be negative in PUC Rio Lua 5.2+ + -- and in LuaJIT `table.insert()` works too slow with huge + -- `pos` values. + local MAX_POS = #tbl + 1 + if test_lib.lua_version() == "LuaJIT" then + MAX_POS = MAX_INT + end + local pos = fdp:consume_integer(1, MAX_POS) + table.insert(tbl, pos, c) + end) +end + +local args = { + artifact_prefix = "table_insert_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/table_maxn_test.lua b/tests/lapi/table_maxn_test.lua new file mode 100644 index 0000000..d2db502 --- /dev/null +++ b/tests/lapi/table_maxn_test.lua @@ -0,0 +1,30 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +5.5 – Table Manipulation, +https://www.lua.org/manual/5.1/manual.html#5.5 + +Synopsis: table.maxn(table) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +if test_lib.lua_current_version_ge_than(5, 3) then + print("Unsupported version.") + os.exit(0) +end + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local count = fdp:consume_integer(0, test_lib.MAX_INT64) + local tbl = fdp:consume_strings(test_lib.MAX_STR_LEN, count) + local maxn = table.maxn(tbl) + assert(maxn == #tbl) +end + +local args = { + artifact_prefix = "table_maxn_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/table_move_test.lua b/tests/lapi/table_move_test.lua new file mode 100644 index 0000000..4424e6f --- /dev/null +++ b/tests/lapi/table_move_test.lua @@ -0,0 +1,38 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +6.6 – Table Manipulation, +https://www.lua.org/manual/5.3/manual.html#6.6 + +Synopsis: table.move(a1, f, e, t [,a2]) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +if test_lib.lua_current_version_lt_than(5, 3) and + test_lib.lua_version == "PUC Rio Lua" then + print("Unsupported version.") + os.exit(0) +end + +local function TestOneInput(buf, _size) + local fdp = luzer.FuzzedDataProvider(buf) + local count = fdp:consume_integer(0, test_lib.MAX_INT) + local a1 = fdp:consume_strings(test_lib.MAX_STR_LEN, count) + local a2 = {} + -- Move random items from the table `a1` to the table `a2`. + -- Beware, `table.move()` works too slow with huge numbers. + local MAX_N = 1000 + local f = fdp:consume_integer(-MAX_N, MAX_N) + local e = fdp:consume_integer(-MAX_N, MAX_N) + local t = fdp:consume_integer(-MAX_N, MAX_N) + local res = table.move(a1, f, e, t, a2) + assert(test_lib.arrays_equal(a2, res)) +end + +local args = { + artifact_prefix = "table_move_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/table_pack_test.lua b/tests/lapi/table_pack_test.lua new file mode 100644 index 0000000..ab989f2 --- /dev/null +++ b/tests/lapi/table_pack_test.lua @@ -0,0 +1,35 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +6.5 – Table Manipulation, +https://www.lua.org/manual/5.2/manual.html#6.5 + +Synopsis: table.pack(...) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +if test_lib.lua_current_version_lt_than(5, 2) then + print("Unsupported version.") + os.exit(0) +end + +local unpack = unpack or table.unpack + +local function TestOneInput(buf, _size) + local fdp = luzer.FuzzedDataProvider(buf) + -- Beware, huge number triggers 'too many results to unpack'. + local MAX_N = 1000 + local count = fdp:consume_integer(1, MAX_N) + local tbl = fdp:consume_integers(test_lib.MIN_INT, test_lib.MAX_INT, count) + local packed = table.pack(unpack(tbl)) + assert(#packed == #tbl) + assert(test_lib.arrays_equal(packed, tbl)) +end + +local args = { + artifact_prefix = "table_pack_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/table_remove_test.lua b/tests/lapi/table_remove_test.lua new file mode 100644 index 0000000..330d946 --- /dev/null +++ b/tests/lapi/table_remove_test.lua @@ -0,0 +1,34 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +5.5 – Table Manipulation, +https://www.lua.org/manual/5.1/manual.html#5.5 + +table.remove removes last element of a table when given an +out-of-bound index, https://www.lua.org/bugs.html#5.1.2-10 + +Synopsis: table.remove(list [, pos]) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local function TestOneInput(buf, _size) + local fdp = luzer.FuzzedDataProvider(buf) + local count = fdp:consume_integer(0, test_lib.MAX_INT) + local tbl = fdp:consume_strings(test_lib.MAX_STR_LEN, count) + + local indices_count = fdp:consume_integer(0, #tbl) + local indices = fdp:consume_integers(0, count, indices_count) + for _, idx in ipairs(indices) do + local old_v = tbl[idx] + assert(table.remove(tbl, idx) == old_v) + assert(tbl[idx] == nil) + end +end + +local args = { + artifact_prefix = "table_remove_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/table_sort_test.lua b/tests/lapi/table_sort_test.lua new file mode 100644 index 0000000..768e13a --- /dev/null +++ b/tests/lapi/table_sort_test.lua @@ -0,0 +1,51 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +6.6 – Table Manipulation, +https://www.lua.org/manual/5.1/manual.html#5.5 + +'table.sort' does not work for partial orders, +https://github.com/lua/lua/commit/825ac8eca8e384d6ad2538b5670088c31e08a9d7 + +Synopsis: table.sort(list [, comp]) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local MIN_INT = test_lib.MIN_INT64 +local MAX_INT = test_lib.MAX_INT64 + +local function random_table(fdp, n) + local count = fdp:consume_integer(0, n) + local item_type = fdp:oneof({ "number", "string" }) + local tbl + if item_type == "number" then + tbl = fdp:consume_integers(MIN_INT, MAX_INT, count) + elseif item_type == "string" then + tbl = fdp:consume_strings(test_lib.MAX_STR_LEN, count) + else + assert("Unsupported type") + end + return tbl +end + +local function TestOneInput(buf, _size) + local fdp = luzer.FuzzedDataProvider(buf) + -- Beware, huge length leads to slow units. + local MAX_N = 1000 + local t = random_table(fdp, MAX_N) + local len = #t + table.sort(t) + assert(len == #t) + + for i = 1, len - 1 do + assert(t[i] <= t[i + 1]) + end +end + +local args = { + artifact_prefix = "table_sort_", +} +luzer.Fuzz(TestOneInput, nil, args)