Skip to content

Commit afe6dcd

Browse files
committed
tests/lapi: add bitop tests
The patch adds initial infrastructure for Lua API tests and changes existed infrastructure for running these tests and adds a fuzzing tests for bitwise operations in PUC Rio (since 5.2) [1] Lua and bitwise functions in LuaJIT [2][3]. PUC Rio Lua provided auto-coercion of string arguments to numbers by default, but it has been removed from the core language in 5.4. LuaJIT provides auto-coercion of string arguments to numbers by default, but it doesn not tested by proposed tests. The patch requires commit "cmake: allow to set a Lua library outside" [4] in the `luzer` project. The proposed bitop tests are capable to reproduce a LuaJIT issue with bit op coercion for shifts in DUALNUM builds [5]. Rules from boolean algebra [6] has been used as invariants for bitwise expressions. 1. https://www.lua.org/manual/5.2/manual.html#6.7 2. https://bitop.luajit.org/semantics.html 3. https://bitop.luajit.org/api.html 4. ligurio/luzer@4ce52a6 5. LuaJIT/LuaJIT@69bbf3c1 6. https://en.wikipedia.org/wiki/Bitwise_operation#Boolean_algebra
1 parent 5445e2f commit afe6dcd

18 files changed

+650
-17
lines changed

cmake/BuildLua.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ macro(build_lua LUA_VERSION)
55
set(LUA_PATCH_PATH ${PROJECT_SOURCE_DIR}/patches/puc-rio-lua.patch)
66

77
set(CFLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer")
8+
set(CFLAGS "${CMAKE_C_FLAGS} -DLUA_USE_DLOPEN")
89
if (ENABLE_LUA_ASSERT)
910
set(CFLAGS "${CFLAGS} -DLUAI_ASSERT")
1011
endif (ENABLE_LUA_ASSERT)
@@ -56,6 +57,9 @@ macro(build_lua LUA_VERSION)
5657
set(LDFLAGS "${LDFLAGS} -fprofile-instr-generate -fprofile-arcs -fcoverage-mapping -ftest-coverage")
5758
endif (ENABLE_COV)
5859

60+
# "relocation R_X86_64_PC32 against symbol `lua_isnumber' can
61+
# not be used when making a shared object; recompile with -fPIC".
62+
set(CFLAGS "${CFLAGS} -fPIC")
5963

6064
include(ExternalProject)
6165

cmake/BuildLuaJIT.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ macro(build_luajit LJ_VERSION)
8080
set(LDFLAGS "${LDFLAGS} -fprofile-instr-generate -fprofile-arcs -fcoverage-mapping -ftest-coverage")
8181
endif (ENABLE_COV)
8282

83+
# "relocation R_X86_64_PC32 against symbol `lua_isnumber' can
84+
# not be used when making a shared object; recompile with -fPIC".
85+
set(CFLAGS "${CFLAGS} -fPIC")
8386

8487
include(ExternalProject)
8588

tests/CMakeLists.txt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1+
set(DEFAULT_RUNS_NUMBER 5)
2+
3+
string(JOIN " " LIBFUZZER_OPTS
4+
-mutate_depth=20
5+
-print_final_stats=1
6+
-print_pcs=1
7+
-reduce_inputs=1
8+
-reload=1
9+
-report_slow_units=5
10+
-runs=$\{RUNS:-${DEFAULT_RUNS_NUMBER}\}
11+
-use_value_profile=1
12+
-workers=${CMAKE_BUILD_PARALLEL_LEVEL}
13+
)
14+
115
add_subdirectory(capi)
2-
if (ENABLE_BONUS_TESTS)
3-
add_subdirectory(lapi)
4-
endif()
16+
add_subdirectory(lapi)

tests/capi/CMakeLists.txt

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,6 @@ if (ENABLE_COV)
5454
AppendFlags(LDFLAGS -fprofile-instr-generate -fcoverage-mapping)
5555
endif()
5656

57-
set(DEFAULT_RUNS_NUMBER 5)
58-
59-
string(JOIN " " LIBFUZZER_OPTS
60-
-mutate_depth=20
61-
-print_final_stats=1
62-
-print_pcs=1
63-
-reduce_inputs=1
64-
-reload=1
65-
-report_slow_units=5
66-
-workers=${CMAKE_BUILD_PARALLEL_LEVEL}
67-
-runs=$\{RUNS:-${DEFAULT_RUNS_NUMBER}\}
68-
-use_value_profile=1
69-
)
70-
7157
function(create_test)
7258
cmake_parse_arguments(
7359
FUZZ

tests/lapi/CMakeLists.txt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
include(BuildLuzer)
2+
include(MakeLuaPath)
3+
4+
if(NOT LUA_EXECUTABLE)
5+
message(FATAL_ERROR "${LUA_EXECUTABLE} is not found.")
6+
endif()
7+
8+
lapi_tests_make_lua_path(LUA_CPATH
9+
PATHS
10+
${LUZER_LUA_CPATH}
11+
)
12+
13+
lapi_tests_make_lua_path(LUA_PATH
14+
PATHS
15+
${LUZER_LUA_PATH}
16+
${CMAKE_CURRENT_SOURCE_DIR}/?.lua
17+
)
18+
19+
function(create_test)
20+
cmake_parse_arguments(
21+
FUZZ
22+
""
23+
"FILENAME"
24+
""
25+
${ARGN}
26+
)
27+
get_filename_component(test_name ${FUZZ_FILENAME} NAME_WE)
28+
string(REPLACE "_test" "" test_prefix ${test_name})
29+
set(dict_path ${PROJECT_SOURCE_DIR}/corpus/${test_prefix}.dict)
30+
set(corpus_path ${PROJECT_SOURCE_DIR}/corpus/${test_prefix})
31+
if (EXISTS ${dict_path})
32+
set(LIBFUZZER_OPTS "${LIBFUZZER_OPTS} -dict=${dict_path}")
33+
endif ()
34+
if (EXISTS ${corpus_path})
35+
set(LIBFUZZER_OPTS "${LIBFUZZER_OPTS} ${corpus_path}")
36+
endif ()
37+
add_test(NAME ${test_name}
38+
COMMAND ${SHELL} -c "${LUA_EXECUTABLE} ${FUZZ_FILENAME} ${LIBFUZZER_OPTS}"
39+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
40+
)
41+
set_tests_properties(${test_name} PROPERTIES
42+
LABELS "lapi"
43+
ENVIRONMENT "LUA_PATH=${LUA_PATH};LUA_CPATH=${LUA_CPATH};ASAN_OPTIONS=detect_odr_violation=0;LD_DYNAMIC_WEAK=1"
44+
DEPENDS ${LUA_EXECUTABLE} ${LUZER_LIBRARY}
45+
)
46+
endfunction()
47+
48+
message(STATUS "Add Lua API test suite")
49+
file(GLOB tests LIST_DIRECTORIES false ${CMAKE_CURRENT_SOURCE_DIR}/*_test.lua)
50+
foreach(filename ${tests})
51+
create_test(FILENAME ${filename})
52+
endforeach()

tests/lapi/bitop_arshift_test.lua

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
Synopsis: bit.arshift(x, n)
6+
]]
7+
8+
local luzer = require("luzer")
9+
local test_lib = require("lib")
10+
11+
if test_lib.lua_version() ~= "LuaJIT" then
12+
print("Unsupported version.")
13+
os.exit(0)
14+
end
15+
16+
local arshift = bit.arshift
17+
local bxor = bit.bxor
18+
19+
local function is_opposite_sign(a, b)
20+
return bxor(a, b) < 0
21+
end
22+
23+
local function TestOneInput(buf)
24+
local fdp = luzer.FuzzedDataProvider(buf)
25+
local MAX_INT = test_lib.MAX_INT
26+
local MIN_INT = test_lib.MIN_INT
27+
local x = fdp:consume_integer(MIN_INT, MAX_INT)
28+
local n = fdp:consume_integer(MIN_INT, MAX_INT)
29+
local res = arshift(x, n)
30+
assert(type(res) == "number")
31+
assert(is_opposite_sign(x, res) == false)
32+
end
33+
34+
local args = {
35+
artifact_prefix = "bitop_arshift_",
36+
}
37+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/bitop_band_test.lua

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
Wrong code generation for constants in bitwise operations,
6+
https://github.com/lua/lua/commit/c764ca71a639f5585b5f466bea25dc42b855a4b0
7+
8+
Inconsistent behaviour of bit ops in DUALNUM mode,
9+
https://github.com/LuaJIT/LuaJIT/issues/1273
10+
11+
Synopsis: bit.band(x1 [,x2...])
12+
]]
13+
14+
local luzer = require("luzer")
15+
local test_lib = require("lib")
16+
17+
local band
18+
if test_lib.lua_version() == "LuaJIT" then
19+
band = bit.band
20+
else
21+
band = test_lib.bitwise_op("&")
22+
end
23+
24+
local unpack = unpack or table.unpack
25+
26+
local function TestOneInput(buf)
27+
local fdp = luzer.FuzzedDataProvider(buf)
28+
local MAX_INT = test_lib.MAX_INT
29+
local MIN_INT = test_lib.MIN_INT
30+
local x = fdp:consume_integer(MIN_INT, MAX_INT)
31+
local y = fdp:consume_integer(MIN_INT, MAX_INT)
32+
local z = fdp:consume_integer(MIN_INT, MAX_INT)
33+
34+
-- Commutative law.
35+
assert(band(x, y) == band(y, x))
36+
37+
assert(band(x, band(y, z)) == band(band(x, y), z))
38+
assert(band(x, 0) == 0)
39+
assert(band(x, x) == x)
40+
assert(band(x, -1) == x)
41+
42+
-- Multiple arguments.
43+
-- `n` must be less than UINT_MAX and there are at least extra
44+
-- free stack slots in the stack, otherwise an error
45+
-- "too many results to unpack" is raised, see <ltablib.c>.
46+
local n = fdp:consume_integer(2, 1024)
47+
local band_args = fdp:consume_integers(0, MAX_INT, n)
48+
local res = band(unpack(band_args))
49+
assert(type(res) == "number")
50+
51+
-- Commutative law.
52+
table.sort(band_args)
53+
assert(res == band(unpack(band_args)))
54+
end
55+
56+
local args = {
57+
artifact_prefix = "bitop_band_",
58+
}
59+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/bitop_bnot_test.lua

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
Synopsis: bit.bnot(x)
6+
]]
7+
8+
local luzer = require("luzer")
9+
local test_lib = require("lib")
10+
11+
local bnot
12+
if test_lib.lua_version() == "LuaJIT" then
13+
bnot = bit.bnot
14+
else
15+
bnot = test_lib.bitwise_op("~")
16+
end
17+
18+
local function TestOneInput(buf)
19+
local fdp = luzer.FuzzedDataProvider(buf)
20+
local MAX_INT = test_lib.MAX_INT
21+
local MIN_INT = test_lib.MIN_INT
22+
local x = fdp:consume_integer(MIN_INT, MAX_INT)
23+
local res = bnot(x)
24+
assert(type(res) == "number")
25+
26+
-- For any integer x, the following identity holds [1]:
27+
--
28+
-- 1. https://www.lua.org/manual/5.2/manual.html
29+
assert(bnot(x) == (-1 - x))
30+
end
31+
32+
local args = {
33+
artifact_prefix = "bitop_bnot_",
34+
}
35+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/bitop_bor_test.lua

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
ARM64: Should not fuse sign-extension into logical operands,
6+
can fuse rotations though,
7+
https://github.com/LuaJIT/LuaJIT/issues/1076
8+
9+
Wrong code generation for constants in bitwise operations,
10+
https://github.com/lua/lua/commit/c764ca71a639f5585b5f466bea25dc42b855a4b0
11+
12+
Synopsis: bit.bor(x1 [,x2...])
13+
]]
14+
15+
local luzer = require("luzer")
16+
local test_lib = require("lib")
17+
18+
local unpack = unpack or table.unpack
19+
20+
local bor
21+
if test_lib.lua_version() == "LuaJIT" then
22+
bor = bit.bor
23+
else
24+
bor = test_lib.bitwise_op("|")
25+
end
26+
27+
local function TestOneInput(buf)
28+
local fdp = luzer.FuzzedDataProvider(buf)
29+
local MAX_INT = test_lib.MAX_INT
30+
local MIN_INT = test_lib.MIN_INT
31+
local x = fdp:consume_integer(MIN_INT, MAX_INT)
32+
local y = fdp:consume_integer(MIN_INT, MAX_INT)
33+
local z = fdp:consume_integer(MIN_INT, MAX_INT)
34+
local res = bor(x, y)
35+
assert(type(res) == "number")
36+
37+
-- Commutative law.
38+
assert(bor(x, y) == bor(y, x))
39+
40+
assert(bor(x, bor(y, z)) == bor(bor(x, y), z))
41+
assert(bor(x, 0) == x)
42+
assert(bor(x, x) == x)
43+
if test_lib.lua_version() == "LuaJIT" then
44+
local MAX_UINT = bor(test_lib.MAX_INT, test_lib.MIN_INT)
45+
assert(bor(x, MAX_UINT) == MAX_UINT)
46+
else
47+
local MAX_UINT64 = bor(test_lib.MAX_INT64, test_lib.MIN_INT64)
48+
assert(bor(x, MAX_UINT64) == MAX_UINT64)
49+
end
50+
51+
-- Multiple arguments.
52+
-- `n` must be less than UINT_MAX and there are at least extra
53+
-- free stack slots in the stack, otherwise an error
54+
-- "too many results to unpack" is raised, see <ltablib.c>.
55+
local n = fdp:consume_integer(2, 1024)
56+
local args = fdp:consume_integers(0, MAX_INT, n)
57+
res = bor(unpack(args))
58+
assert(type(res) == "number")
59+
60+
-- Commutative law.
61+
table.sort(args)
62+
assert(res == bor(unpack(args)))
63+
end
64+
65+
local args = {
66+
artifact_prefix = "bitop_bor_",
67+
}
68+
luzer.Fuzz(TestOneInput, nil, args)

tests/lapi/bitop_bswap_test.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--[[
2+
SPDX-License-Identifier: ISC
3+
Copyright (c) 2023-2025, Sergey Bronnikov.
4+
5+
Synopsis: bit.swap(x)
6+
]]
7+
8+
local luzer = require("luzer")
9+
local test_lib = require("lib")
10+
11+
if test_lib.lua_version() ~= "LuaJIT" then
12+
print("Unsupported version.")
13+
os.exit(0)
14+
end
15+
16+
local bswap = bit.bswap
17+
18+
local function TestOneInput(buf)
19+
local fdp = luzer.FuzzedDataProvider(buf)
20+
local MAX_INT = test_lib.MAX_INT
21+
local MIN_INT = test_lib.MIN_INT
22+
local x = fdp:consume_integer(MIN_INT, MAX_INT)
23+
local res = bswap(x)
24+
assert(type(res) == "number")
25+
end
26+
27+
local args = {
28+
artifact_prefix = "bitop_bswap_",
29+
}
30+
luzer.Fuzz(TestOneInput, nil, args)

0 commit comments

Comments
 (0)