Skip to content

Commit d4b91f6

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]. Lua API tests can be enabled by CMake option ENABLE_LAPI_TESTS. The option is disabled by default, because necessary dependencies are not installed in OSS Fuzz and thus workflow is failed. 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 d6b190e commit d4b91f6

21 files changed

+673
-22
lines changed

.github/workflows/test.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,15 @@ jobs:
6868
run: |
6969
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
7070
-DUSE_LUA=ON -DENABLE_BUILD_PROTOBUF=OFF \
71-
-DENABLE_INTERNAL_TESTS=ON \
71+
-DENABLE_INTERNAL_TESTS=ON -DENABLE_LAPI_TESTS=ON \
7272
-G Ninja -S . -B build
7373
if: ${{ matrix.LUA == 'lua' }}
7474

7575
- name: Running CMake (LuaJIT -current)
7676
run: |
7777
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
7878
-DUSE_LUAJIT=ON -DENABLE_BUILD_PROTOBUF=OFF \
79-
-DENABLE_INTERNAL_TESTS=ON \
79+
-DENABLE_INTERNAL_TESTS=ON -DENABLE_LAPI_TESTS=ON \
8080
-G Ninja -S . -B build
8181
if: ${{ matrix.LUA == 'luajit' }}
8282

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ option(OSS_FUZZ "Enable support of OSS Fuzz" OFF)
2525
option(ENABLE_BUILD_PROTOBUF "Enable building Protobuf library" ON)
2626
option(ENABLE_BONUS_TESTS "Enable bonus tests" OFF)
2727
option(ENABLE_INTERNAL_TESTS "Enable internal tests" OFF)
28+
option(ENABLE_LAPI_TESTS "Enable Lua API tests" OFF)
2829

2930
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
3031
set(CMAKE_INCLUDE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_INCLUDE_PATH})

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ is LuaJIT-specific.
4949
- `ENABLE_BUILD_PROTOBUF` enables building Protobuf library, otherwise system
5050
library is used.
5151
- `ENABLE_INTERNAL_TESTS` enables internal tests.
52+
- `ENABLE_LAPI_TESTS` enables Lua API tests.
5253

5354
### Running
5455

cmake/BuildLua.cmake

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ macro(build_lua LUA_VERSION)
5656
set(LDFLAGS "${LDFLAGS} -fprofile-instr-generate -fprofile-arcs -fcoverage-mapping -ftest-coverage")
5757
endif (ENABLE_COV)
5858

59+
if(ENABLE_LAPI_TESTS)
60+
# "relocation R_X86_64_PC32 against symbol `lua_isnumber'
61+
# can not be used when making a shared object; recompile
62+
# with -fPIC".
63+
set(CFLAGS "${CFLAGS} -fPIC")
64+
set(CFLAGS "${CFLAGS} -DLUA_USE_DLOPEN")
65+
endif()
5966

6067
include(ExternalProject)
6168

cmake/BuildLuaJIT.cmake

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

83+
if(ENABLE_LAPI_TESTS)
84+
# "relocation R_X86_64_PC32 against symbol `lua_isnumber'
85+
# can not be used when making a shared object; recompile
86+
# with -fPIC".
87+
set(CFLAGS "${CFLAGS} -fPIC")
88+
# CMake option LUAJIT_FRIENDLY_MODE in luzer requires
89+
# LUAJIT_ENABLE_CHECKHOOK.
90+
set(CFLAGS "${CFLAGS} -DLUAJIT_ENABLE_CHECKHOOK")
91+
endif()
8392

8493
include(ExternalProject)
8594

tests/CMakeLists.txt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,23 @@
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+
15+
set(CORPUS_BASE_PATH ${PROJECT_SOURCE_DIR}/corpus)
16+
if(IS_LUAJIT)
17+
set(CORPUS_BASE_PATH ${CORPUS_BASE_PATH}/corpus)
18+
endif()
19+
120
add_subdirectory(capi)
2-
if (ENABLE_BONUS_TESTS)
21+
if(ENABLE_LAPI_TESTS)
322
add_subdirectory(lapi)
423
endif()

tests/capi/CMakeLists.txt

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +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-
71-
set(CORPUS_BASE_PATH ${PROJECT_SOURCE_DIR}/corpus)
72-
if(USE_LUAJIT)
73-
set(CORPUS_BASE_PATH ${CORPUS_BASE_PATH}/corpus)
74-
endif()
75-
7657
function(create_test)
7758
cmake_parse_arguments(
7859
FUZZ

tests/lapi/CMakeLists.txt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
set(dict_path ${CORPUS_BASE_PATH}/${test_name}.dict)
32+
set(corpus_path ${CORPUS_BASE_PATH}/${test_prefix})
33+
if(IS_LUAJIT)
34+
set(corpus_path ${CORPUS_BASE_PATH}/${test_name})
35+
endif()
36+
if (EXISTS ${dict_path})
37+
set(LIBFUZZER_OPTS "${LIBFUZZER_OPTS} -dict=${dict_path}")
38+
endif ()
39+
if (EXISTS ${corpus_path})
40+
set(LIBFUZZER_OPTS "${LIBFUZZER_OPTS} ${corpus_path}")
41+
endif ()
42+
add_test(NAME ${test_name}
43+
COMMAND ${SHELL} -c "${LUA_EXECUTABLE} ${FUZZ_FILENAME} ${LIBFUZZER_OPTS}"
44+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
45+
)
46+
set_tests_properties(${test_name} PROPERTIES
47+
LABELS "lapi"
48+
ENVIRONMENT "LUA_PATH=${LUA_PATH};LUA_CPATH=${LUA_CPATH};ASAN_OPTIONS=detect_odr_violation=0;LD_DYNAMIC_WEAK=1"
49+
DEPENDS ${LUA_EXECUTABLE} ${LUZER_LIBRARY}
50+
)
51+
endfunction()
52+
53+
message(STATUS "Add Lua API test suite")
54+
file(GLOB tests LIST_DIRECTORIES false ${CMAKE_CURRENT_SOURCE_DIR}/*_test.lua)
55+
foreach(filename ${tests})
56+
create_test(FILENAME ${filename})
57+
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)

0 commit comments

Comments
 (0)