From 5124304ac62156ac99ebc2b232bef23f3f6488a8 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Thu, 19 Dec 2024 16:29:08 +0300 Subject: [PATCH 1/5] cmake: print testsuite name only once --- tests/capi/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/capi/CMakeLists.txt b/tests/capi/CMakeLists.txt index 012a4b77..2d8aaa9e 100644 --- a/tests/capi/CMakeLists.txt +++ b/tests/capi/CMakeLists.txt @@ -27,6 +27,8 @@ target_link_libraries( > ) +message(STATUS "Add Lua C API test suite") + # The following condition looks unnecessary, it was added to fix # an issue for Sydr: in the Lua external project in the build # system the explicit build command is used: @@ -108,7 +110,6 @@ function(create_test) add_test(NAME ${test_name} COMMAND ${SHELL} -c "$ ${LIBFUZZER_OPTS}" ) - message(STATUS "Creating Lua C API test ${test_name}") if (USE_LUA) set_tests_properties(${test_name} PROPERTIES ENVIRONMENT "ASAN_OPTIONS='detect_invalid_pointer_pairs=2'" From 3d801e690056eb09b2209169941b21db5bc44a38 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Thu, 19 Dec 2024 16:19:52 +0300 Subject: [PATCH 2/5] cmake: initial version of luzer module The patch add a CMake module that builds a luzer [1], a coverage-guided, native Lua fuzzing engine. Needed for the following commit. 1. https://github.com/ligurio/luzer --- cmake/BuildLuzer.cmake | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 cmake/BuildLuzer.cmake diff --git a/cmake/BuildLuzer.cmake b/cmake/BuildLuzer.cmake new file mode 100644 index 00000000..a55e7ce3 --- /dev/null +++ b/cmake/BuildLuzer.cmake @@ -0,0 +1,61 @@ +set(LUZER_DIR ${PROJECT_BINARY_DIR}/luzer) +set(LUZER_BUILD_DIR ${LUZER_DIR}/build) +set(LUZER_LIBRARY_PATH ${LUZER_BUILD_DIR}/luzer) +list(APPEND LUZER_LIBRARIES + ${LUZER_LIBRARY_PATH}/luzer/luzer.so + ${LUZER_LIBRARY_PATH}/luzer/libcustom_mutator.so +) + +include(ExternalProject) + +get_target_property(LUA_LIBRARIES_LOCATION ${LUA_LIBRARIES} LOCATION) + +list(APPEND LUZER_CMAKE_FLAGS + "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" + "-DLUA_INCLUDE_DIR=${LUA_INCLUDE_DIR}" + "-DLUA_LIBRARIES=${LUA_LIBRARIES_LOCATION}" +) +if(USE_LUAJIT) + list(APPEND LUZER_CMAKE_FLAGS + "-DLUAJIT_FRIENDLY_MODE=ON" + "-DLUA_HAS_JIT=ON" + ) +endif() + +ExternalProject_Add(bundled-luzer + GIT_REPOSITORY https://github.com/ligurio/luzer + GIT_TAG 82d41c5f350296ca351e785a24c914165a0e8033 + GIT_PROGRESS TRUE + GIT_SHALLOW FALSE + SOURCE_DIR ${LUZER_DIR}/source + BINARY_DIR ${LUZER_BUILD_DIR} + TMP_DIR ${LUZER_DIR}/tmp + STAMP_DIR ${LUZER_DIR}/stamp + CONFIGURE_COMMAND + ${CMAKE_COMMAND} -B -S + -G ${CMAKE_GENERATOR} ${LUZER_CMAKE_FLAGS} + BUILD_COMMAND cd && ${CMAKE_MAKE_PROGRAM} + INSTALL_COMMAND "" + CMAKE_GENERATOR ${CMAKE_GENERATOR} + BUILD_BYPRODUCTS ${LUZER_LIBRARIES} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) + +add_library(luzer-library STATIC IMPORTED GLOBAL) +set_target_properties(luzer-library PROPERTIES + IMPORTED_LOCATION "${LUZER_LIBRARIES}") +add_dependencies(luzer-library bundled-luzer) +add_dependencies(bundled-luzer bundled-liblua) + +set(LUZER_LUA_PATH ${LUZER_DIR}/source/?/init.lua) +set(LUZER_LUA_PATH ${LUZER_LUA_PATH} PARENT_SCOPE) + +set(LUZER_LUA_CPATH ${LUZER_LIBRARY_PATH}/?${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(LUZER_LUA_CPATH ${LUZER_LUA_CPATH} PARENT_SCOPE) + +set(LUZER_LIBRARY luzer-library PARENT_SCOPE) + +unset(LUZER_DIR) +unset(LUZER_BUILD_DIR) From 55fbeb46ab49da14540c6f86a4270be9dbb60e15 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Wed, 19 Mar 2025 11:54:22 +0300 Subject: [PATCH 3/5] cmake: introduce MakeLuaPath.cmake helper While extending tests it is often required to append additional path where Lua or Lua C auxiliary modules are located to LUA_PATH or LUA_CPATH environment variables. Due to insane semicolon interpolation in CMake strings (that converts such string to a list as a result), we need to escape semicolon in LUA_PATH/LUA_CPATH strings while building the resulting value. The patch introduce MakeLuaPath.cmake module to make LUA_PATH and LUA_CPATH definition convenient with helper. This function takes all paths given as a variable list argument, joins them in a reverse order by a semicolon and yields the resulting string to a specified CMake variable. Needed for the following commit. --- cmake/MakeLuaPath.cmake | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 cmake/MakeLuaPath.cmake diff --git a/cmake/MakeLuaPath.cmake b/cmake/MakeLuaPath.cmake new file mode 100644 index 00000000..f8cb74a8 --- /dev/null +++ b/cmake/MakeLuaPath.cmake @@ -0,0 +1,55 @@ +# lapi_tests_make_lua_path provides a convenient way to define +# LUA_PATH and LUA_CPATH variables. +# +# Example usage: +# +# lapi_tests_make_lua_path(LUA_PATH +# PATH +# ./?.lua +# ${CMAKE_BINARY_DIR}/?.lua +# ${CMAKE_CURRENT_SOURCE_DIR}/?.lua +# ) +# +# This will give you the string: +# "./?.lua;${CMAKE_BINARY_DIR}/?.lua;${CMAKE_CURRENT_SOURCE_DIR}/?.lua;;" +# +# Source: https://github.com/tarantool/luajit/blob/441405c398d625fda304b16bb10f119bfd822696/cmake/MakeLuaPath.cmake + +function(lapi_tests_make_lua_path path) + set(prefix ARG) + set(noValues) + set(singleValues) + set(multiValues PATHS) + + include(CMakeParseArguments) + cmake_parse_arguments(${prefix} + "${noValues}" + "${singleValues}" + "${multiValues}" + ${ARGN}) + + set(_MAKE_LUA_PATH_RESULT "") + + foreach(inc ${ARG_PATHS}) + # XXX: If one joins two strings with the semicolon, the value + # automatically becomes a list. I found a single working + # solution to make result variable be a string via "escaping" + # the semicolon right in string interpolation. + set(_MAKE_LUA_PATH_RESULT "${_MAKE_LUA_PATH_RESULT}${inc}\;") + endforeach() + + if("${_MAKE_LUA_PATH_RESULT}" STREQUAL "") + message(FATAL_ERROR + "No paths are given to helper.") + endif() + + # XXX: This is the sentinel semicolon having special meaning + # for LUA_PATH and LUA_CPATH variables. For more info, see the + # link below: + # https://www.lua.org/manual/5.1/manual.html#pdf-LUA_PATH + set(${path} "${_MAKE_LUA_PATH_RESULT}\;" PARENT_SCOPE) + # XXX: Unset the internal variable to not spoil CMake cache. + # Study the case in CheckIPOSupported.cmake, that affected this + # module: https://gitlab.kitware.com/cmake/cmake/-/commit/4b82977 + unset(_MAKE_LUA_PATH_RESULT) +endfunction() From 54a92f6c8d6e658189b162f2a0fafcbfeb2a96b4 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 18 Apr 2025 17:08:59 +0300 Subject: [PATCH 4/5] cmake: fix using USE_LUAJIT --- tests/capi/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/capi/CMakeLists.txt b/tests/capi/CMakeLists.txt index 2d8aaa9e..586ea9c5 100644 --- a/tests/capi/CMakeLists.txt +++ b/tests/capi/CMakeLists.txt @@ -98,7 +98,7 @@ function(create_test) endif () set(dict_path ${CORPUS_BASE_PATH}/${test_name}.dict) set(corpus_path ${CORPUS_BASE_PATH}/${test_prefix}) - if(USE_LUAJIT) + if(IS_LUAJIT) set(corpus_path ${CORPUS_BASE_PATH}/${test_name}) endif() if (EXISTS ${dict_path}) From 57150058d3f049726b761521b56da2e8902d280d Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Wed, 1 Mar 2023 17:57:38 +0300 Subject: [PATCH 5/5] 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. https://github.com/ligurio/luzer/commit/4ce52a64234a932bfe4d8169338508252055a674 5. https://github.com/LuaJIT/LuaJIT/commit/69bbf3c1 6. https://en.wikipedia.org/wiki/Bitwise_operation#Boolean_algebra --- .github/workflows/test.yaml | 4 +- CMakeLists.txt | 1 + README.md | 1 + cmake/BuildLua.cmake | 7 +++ cmake/BuildLuaJIT.cmake | 9 ++++ tests/CMakeLists.txt | 21 ++++++++- tests/capi/CMakeLists.txt | 19 -------- tests/lapi/CMakeLists.txt | 57 +++++++++++++++++++++++ tests/lapi/bitop_arshift_test.lua | 37 +++++++++++++++ tests/lapi/bitop_band_test.lua | 59 ++++++++++++++++++++++++ tests/lapi/bitop_bnot_test.lua | 35 +++++++++++++++ tests/lapi/bitop_bor_test.lua | 68 ++++++++++++++++++++++++++++ tests/lapi/bitop_bswap_test.lua | 30 +++++++++++++ tests/lapi/bitop_bxor_test.lua | 75 +++++++++++++++++++++++++++++++ tests/lapi/bitop_lshift_test.lua | 33 ++++++++++++++ tests/lapi/bitop_rol_test.lua | 39 ++++++++++++++++ tests/lapi/bitop_ror_test.lua | 37 +++++++++++++++ tests/lapi/bitop_rshift_test.lua | 36 +++++++++++++++ tests/lapi/bitop_tobit_test.lua | 30 +++++++++++++ tests/lapi/bitop_tohex_test.lua | 31 +++++++++++++ tests/lapi/lib.lua | 66 +++++++++++++++++++++++++++ 21 files changed, 673 insertions(+), 22 deletions(-) create mode 100644 tests/lapi/CMakeLists.txt create mode 100644 tests/lapi/bitop_arshift_test.lua create mode 100644 tests/lapi/bitop_band_test.lua create mode 100644 tests/lapi/bitop_bnot_test.lua create mode 100644 tests/lapi/bitop_bor_test.lua create mode 100644 tests/lapi/bitop_bswap_test.lua create mode 100644 tests/lapi/bitop_bxor_test.lua create mode 100644 tests/lapi/bitop_lshift_test.lua create mode 100644 tests/lapi/bitop_rol_test.lua create mode 100644 tests/lapi/bitop_ror_test.lua create mode 100644 tests/lapi/bitop_rshift_test.lua create mode 100644 tests/lapi/bitop_tobit_test.lua create mode 100644 tests/lapi/bitop_tohex_test.lua create mode 100644 tests/lapi/lib.lua diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 305ea36f..e2629e32 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -68,7 +68,7 @@ jobs: run: | cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ -DUSE_LUA=ON -DENABLE_BUILD_PROTOBUF=OFF \ - -DENABLE_INTERNAL_TESTS=ON \ + -DENABLE_INTERNAL_TESTS=ON -DENABLE_LAPI_TESTS=ON \ -G Ninja -S . -B build if: ${{ matrix.LUA == 'lua' }} @@ -76,7 +76,7 @@ jobs: run: | cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ -DUSE_LUAJIT=ON -DENABLE_BUILD_PROTOBUF=OFF \ - -DENABLE_INTERNAL_TESTS=ON \ + -DENABLE_INTERNAL_TESTS=ON -DENABLE_LAPI_TESTS=ON \ -G Ninja -S . -B build if: ${{ matrix.LUA == 'luajit' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a0cbb4e..cede604f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ option(OSS_FUZZ "Enable support of OSS Fuzz" OFF) option(ENABLE_BUILD_PROTOBUF "Enable building Protobuf library" ON) option(ENABLE_BONUS_TESTS "Enable bonus tests" OFF) option(ENABLE_INTERNAL_TESTS "Enable internal tests" OFF) +option(ENABLE_LAPI_TESTS "Enable Lua API tests" OFF) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) set(CMAKE_INCLUDE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_INCLUDE_PATH}) diff --git a/README.md b/README.md index afc46653..c8a3fde6 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ is LuaJIT-specific. - `ENABLE_BUILD_PROTOBUF` enables building Protobuf library, otherwise system library is used. - `ENABLE_INTERNAL_TESTS` enables internal tests. +- `ENABLE_LAPI_TESTS` enables Lua API tests. ### Running diff --git a/cmake/BuildLua.cmake b/cmake/BuildLua.cmake index 8d275f15..97186f65 100644 --- a/cmake/BuildLua.cmake +++ b/cmake/BuildLua.cmake @@ -56,6 +56,13 @@ macro(build_lua LUA_VERSION) set(LDFLAGS "${LDFLAGS} -fprofile-instr-generate -fprofile-arcs -fcoverage-mapping -ftest-coverage") endif (ENABLE_COV) + if(ENABLE_LAPI_TESTS) + # "relocation R_X86_64_PC32 against symbol `lua_isnumber' + # can not be used when making a shared object; recompile + # with -fPIC". + set(CFLAGS "${CFLAGS} -fPIC") + set(CFLAGS "${CFLAGS} -DLUA_USE_DLOPEN") + endif() include(ExternalProject) diff --git a/cmake/BuildLuaJIT.cmake b/cmake/BuildLuaJIT.cmake index 1e2419d1..021b3d5d 100644 --- a/cmake/BuildLuaJIT.cmake +++ b/cmake/BuildLuaJIT.cmake @@ -80,6 +80,15 @@ macro(build_luajit LJ_VERSION) set(LDFLAGS "${LDFLAGS} -fprofile-instr-generate -fprofile-arcs -fcoverage-mapping -ftest-coverage") endif (ENABLE_COV) + if(ENABLE_LAPI_TESTS) + # "relocation R_X86_64_PC32 against symbol `lua_isnumber' + # can not be used when making a shared object; recompile + # with -fPIC". + set(CFLAGS "${CFLAGS} -fPIC") + # CMake option LUAJIT_FRIENDLY_MODE in luzer requires + # LUAJIT_ENABLE_CHECKHOOK. + set(CFLAGS "${CFLAGS} -DLUAJIT_ENABLE_CHECKHOOK") + endif() include(ExternalProject) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9dd70ade..09cb6127 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,23 @@ +set(DEFAULT_RUNS_NUMBER 5) + +string(JOIN " " LIBFUZZER_OPTS + -mutate_depth=20 + -print_final_stats=1 + -print_pcs=1 + -reduce_inputs=1 + -reload=1 + -report_slow_units=5 + -runs=$\{RUNS:-${DEFAULT_RUNS_NUMBER}\} + -use_value_profile=1 + -workers=${CMAKE_BUILD_PARALLEL_LEVEL} +) + +set(CORPUS_BASE_PATH ${PROJECT_SOURCE_DIR}/corpus) +if(IS_LUAJIT) + set(CORPUS_BASE_PATH ${CORPUS_BASE_PATH}/corpus) +endif() + add_subdirectory(capi) -if (ENABLE_BONUS_TESTS) +if(ENABLE_LAPI_TESTS) add_subdirectory(lapi) endif() diff --git a/tests/capi/CMakeLists.txt b/tests/capi/CMakeLists.txt index 586ea9c5..58f28cfe 100644 --- a/tests/capi/CMakeLists.txt +++ b/tests/capi/CMakeLists.txt @@ -54,25 +54,6 @@ if (ENABLE_COV) AppendFlags(LDFLAGS -fprofile-instr-generate -fcoverage-mapping) endif() -set(DEFAULT_RUNS_NUMBER 5) - -string(JOIN " " LIBFUZZER_OPTS - -mutate_depth=20 - -print_final_stats=1 - -print_pcs=1 - -reduce_inputs=1 - -reload=1 - -report_slow_units=5 - -workers=${CMAKE_BUILD_PARALLEL_LEVEL} - -runs=$\{RUNS:-${DEFAULT_RUNS_NUMBER}\} - -use_value_profile=1 -) - -set(CORPUS_BASE_PATH ${PROJECT_SOURCE_DIR}/corpus) -if(USE_LUAJIT) - set(CORPUS_BASE_PATH ${CORPUS_BASE_PATH}/corpus) -endif() - function(create_test) cmake_parse_arguments( FUZZ diff --git a/tests/lapi/CMakeLists.txt b/tests/lapi/CMakeLists.txt new file mode 100644 index 00000000..e5f35a72 --- /dev/null +++ b/tests/lapi/CMakeLists.txt @@ -0,0 +1,57 @@ +include(BuildLuzer) +include(MakeLuaPath) + +if(NOT LUA_EXECUTABLE) + message(FATAL_ERROR "${LUA_EXECUTABLE} is not found.") +endif() + +lapi_tests_make_lua_path(LUA_CPATH + PATHS + ${LUZER_LUA_CPATH} +) + +lapi_tests_make_lua_path(LUA_PATH + PATHS + ${LUZER_LUA_PATH} + ${CMAKE_CURRENT_SOURCE_DIR}/?.lua +) + +function(create_test) + cmake_parse_arguments( + FUZZ + "" + "FILENAME" + "" + ${ARGN} + ) + get_filename_component(test_name ${FUZZ_FILENAME} NAME_WE) + string(REPLACE "_test" "" test_prefix ${test_name}) + set(dict_path ${PROJECT_SOURCE_DIR}/corpus/${test_prefix}.dict) + set(corpus_path ${PROJECT_SOURCE_DIR}/corpus/${test_prefix}) + set(dict_path ${CORPUS_BASE_PATH}/${test_name}.dict) + set(corpus_path ${CORPUS_BASE_PATH}/${test_prefix}) + if(IS_LUAJIT) + set(corpus_path ${CORPUS_BASE_PATH}/${test_name}) + endif() + if (EXISTS ${dict_path}) + set(LIBFUZZER_OPTS "${LIBFUZZER_OPTS} -dict=${dict_path}") + endif () + if (EXISTS ${corpus_path}) + set(LIBFUZZER_OPTS "${LIBFUZZER_OPTS} ${corpus_path}") + endif () + add_test(NAME ${test_name} + COMMAND ${SHELL} -c "${LUA_EXECUTABLE} ${FUZZ_FILENAME} ${LIBFUZZER_OPTS}" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties(${test_name} PROPERTIES + LABELS "lapi" + ENVIRONMENT "LUA_PATH=${LUA_PATH};LUA_CPATH=${LUA_CPATH};ASAN_OPTIONS=detect_odr_violation=0;LD_DYNAMIC_WEAK=1" + DEPENDS ${LUA_EXECUTABLE} ${LUZER_LIBRARY} + ) +endfunction() + +message(STATUS "Add Lua API test suite") +file(GLOB tests LIST_DIRECTORIES false ${CMAKE_CURRENT_SOURCE_DIR}/*_test.lua) +foreach(filename ${tests}) + create_test(FILENAME ${filename}) +endforeach() diff --git a/tests/lapi/bitop_arshift_test.lua b/tests/lapi/bitop_arshift_test.lua new file mode 100644 index 00000000..b3f75bfe --- /dev/null +++ b/tests/lapi/bitop_arshift_test.lua @@ -0,0 +1,37 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Synopsis: bit.arshift(x, n) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +if test_lib.lua_version() ~= "LuaJIT" then + print("Unsupported version.") + os.exit(0) +end + +local arshift = bit.arshift +local bxor = bit.bxor + +local function is_opposite_sign(a, b) + return bxor(a, b) < 0 +end + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local n = fdp:consume_integer(MIN_INT, MAX_INT) + local res = arshift(x, n) + assert(type(res) == "number") + assert(is_opposite_sign(x, res) == false) +end + +local args = { + artifact_prefix = "bitop_arshift_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_band_test.lua b/tests/lapi/bitop_band_test.lua new file mode 100644 index 00000000..22eff0d9 --- /dev/null +++ b/tests/lapi/bitop_band_test.lua @@ -0,0 +1,59 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Wrong code generation for constants in bitwise operations, +https://github.com/lua/lua/commit/c764ca71a639f5585b5f466bea25dc42b855a4b0 + +Inconsistent behaviour of bit ops in DUALNUM mode, +https://github.com/LuaJIT/LuaJIT/issues/1273 + +Synopsis: bit.band(x1 [,x2...]) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local band +if test_lib.lua_version() == "LuaJIT" then + band = bit.band +else + band = test_lib.bitwise_op("&") +end + +local unpack = unpack or table.unpack + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local y = fdp:consume_integer(MIN_INT, MAX_INT) + local z = fdp:consume_integer(MIN_INT, MAX_INT) + + -- Commutative law. + assert(band(x, y) == band(y, x)) + + assert(band(x, band(y, z)) == band(band(x, y), z)) + assert(band(x, 0) == 0) + assert(band(x, x) == x) + assert(band(x, -1) == x) + + -- Multiple arguments. + -- `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 . + local n = fdp:consume_integer(2, 1024) + local band_args = fdp:consume_integers(0, MAX_INT, n) + local res = band(unpack(band_args)) + assert(type(res) == "number") + + -- Commutative law. + table.sort(band_args) + assert(res == band(unpack(band_args))) +end + +local args = { + artifact_prefix = "bitop_band_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_bnot_test.lua b/tests/lapi/bitop_bnot_test.lua new file mode 100644 index 00000000..6786df70 --- /dev/null +++ b/tests/lapi/bitop_bnot_test.lua @@ -0,0 +1,35 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Synopsis: bit.bnot(x) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local bnot +if test_lib.lua_version() == "LuaJIT" then + bnot = bit.bnot +else + bnot = test_lib.bitwise_op("~") +end + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local res = bnot(x) + assert(type(res) == "number") + + -- For any integer x, the following identity holds [1]: + -- + -- 1. https://www.lua.org/manual/5.2/manual.html + assert(bnot(x) == (-1 - x)) +end + +local args = { + artifact_prefix = "bitop_bnot_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_bor_test.lua b/tests/lapi/bitop_bor_test.lua new file mode 100644 index 00000000..6aa9417a --- /dev/null +++ b/tests/lapi/bitop_bor_test.lua @@ -0,0 +1,68 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +ARM64: Should not fuse sign-extension into logical operands, +can fuse rotations though, +https://github.com/LuaJIT/LuaJIT/issues/1076 + +Wrong code generation for constants in bitwise operations, +https://github.com/lua/lua/commit/c764ca71a639f5585b5f466bea25dc42b855a4b0 + +Synopsis: bit.bor(x1 [,x2...]) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local unpack = unpack or table.unpack + +local bor +if test_lib.lua_version() == "LuaJIT" then + bor = bit.bor +else + bor = test_lib.bitwise_op("|") +end + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local y = fdp:consume_integer(MIN_INT, MAX_INT) + local z = fdp:consume_integer(MIN_INT, MAX_INT) + local res = bor(x, y) + assert(type(res) == "number") + + -- Commutative law. + assert(bor(x, y) == bor(y, x)) + + assert(bor(x, bor(y, z)) == bor(bor(x, y), z)) + assert(bor(x, 0) == x) + assert(bor(x, x) == x) + if test_lib.lua_version() == "LuaJIT" then + local MAX_UINT = bor(test_lib.MAX_INT, test_lib.MIN_INT) + assert(bor(x, MAX_UINT) == MAX_UINT) + else + local MAX_UINT64 = bor(test_lib.MAX_INT64, test_lib.MIN_INT64) + assert(bor(x, MAX_UINT64) == MAX_UINT64) + end + + -- Multiple arguments. + -- `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 . + local n = fdp:consume_integer(2, 1024) + local args = fdp:consume_integers(0, MAX_INT, n) + res = bor(unpack(args)) + assert(type(res) == "number") + + -- Commutative law. + table.sort(args) + assert(res == bor(unpack(args))) +end + +local args = { + artifact_prefix = "bitop_bor_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_bswap_test.lua b/tests/lapi/bitop_bswap_test.lua new file mode 100644 index 00000000..20bd306c --- /dev/null +++ b/tests/lapi/bitop_bswap_test.lua @@ -0,0 +1,30 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Synopsis: bit.swap(x) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +if test_lib.lua_version() ~= "LuaJIT" then + print("Unsupported version.") + os.exit(0) +end + +local bswap = bit.bswap + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local res = bswap(x) + assert(type(res) == "number") +end + +local args = { + artifact_prefix = "bitop_bswap_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_bxor_test.lua b/tests/lapi/bitop_bxor_test.lua new file mode 100644 index 00000000..6a46c2cb --- /dev/null +++ b/tests/lapi/bitop_bxor_test.lua @@ -0,0 +1,75 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Synopsis: bit.bxor(x1 [,x2...]) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local bxor +local band +local bor +local bnot +if test_lib.lua_version() == "LuaJIT" then + bxor = bit.bxor + band = bit.band + bor = bit.bor + bnot = bit.bnot +else + bxor = test_lib.bitwise_op("~") + band = test_lib.bitwise_op("&") + bor = test_lib.bitwise_op("|") + bnot = test_lib.bitwise_op("~") +end + +local unpack = unpack or table.unpack + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local y = fdp:consume_integer(MIN_INT, MAX_INT) + local z = fdp:consume_integer(MIN_INT, MAX_INT) + + -- Commutative law. + assert(bxor(x, y) == bxor(y, x)) + + assert(bxor(x, bxor(y, z)) == bxor(bxor(x, y), z)) + assert(bxor(x, x) == 0) + -- a ^ b = (a | b) & (~a | ~b) + assert(bxor(x, y) == band(bor(x, y), bor(bnot(x), bnot(y)))) + -- a ^ b = (a & ~b) | (~a & b) + assert(bxor(x, y) == bor(band(x, bnot(y)), band(bnot(x), y))) + assert(bxor(x, y, y) == x) + assert(bxor(bxor(x, y), y) == x) + + if test_lib.lua_version() == "LuaJIT" then + local MAX_UINT = bor(test_lib.MAX_INT, test_lib.MIN_INT) + assert(bxor(x, MAX_UINT) == bnot(x)) + else + local MAX_UINT64 = bor(test_lib.MAX_INT64, test_lib.MIN_INT64) + assert(bxor(x, MAX_UINT64) == bnot(x)) + end + assert(bxor(x, 0) == x) + + -- Multiple arguments. + -- `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 . + local n = fdp:consume_integer(2, 1024) + local bxor_args = fdp:consume_integers(0, MAX_INT, n) + local res = bxor(unpack(bxor_args)) + assert(type(res) == "number") + + -- Commutative law. + table.sort(bxor_args) + assert(res == bxor(unpack(bxor_args))) +end + +local args = { + artifact_prefix = "bitop_bxor_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_lshift_test.lua b/tests/lapi/bitop_lshift_test.lua new file mode 100644 index 00000000..f44642f1 --- /dev/null +++ b/tests/lapi/bitop_lshift_test.lua @@ -0,0 +1,33 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Wrong code generation for constants in bitwise operations, +https://github.com/lua/lua/commit/c764ca71a639f5585b5f466bea25dc42b855a4b0 + +Synopsis: bit.lshift(x, n) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local lshift +if test_lib.lua_version() == "LuaJIT" then + lshift = bit.lshift +else + lshift = test_lib.bitwise_op("<<") +end + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local x = fdp:consume_integer(0, MAX_INT) + local n = fdp:consume_integer(1, 32) + local res = lshift(x, n) + assert(type(res) == "number") +end + +local args = { + artifact_prefix = "bitop_lshift_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_rol_test.lua b/tests/lapi/bitop_rol_test.lua new file mode 100644 index 00000000..c7ad87d5 --- /dev/null +++ b/tests/lapi/bitop_rol_test.lua @@ -0,0 +1,39 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Wrong code generation for constants in bitwise operations, +https://github.com/lua/lua/commit/c764ca71a639f5585b5f466bea25dc42b855a4b0 + +Synopsis: bit.rol(x, n) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +if test_lib.lua_version() ~= "LuaJIT" then + print("Unsupported version.") + os.exit(0) +end + +local rol = bit.rol + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local n = fdp:consume_integer(MIN_INT, MAX_INT) + local res = rol(x, n) + assert(type(res) == "number") + + -- For any valid displacement, the following identity holds [1]: + -- + -- 1. https://www.lua.org/manual/5.2/manual.html + assert(rol(x, n) == rol(x, n % 32)) +end + +local args = { + artifact_prefix = "bitop_rol_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_ror_test.lua b/tests/lapi/bitop_ror_test.lua new file mode 100644 index 00000000..cce891ac --- /dev/null +++ b/tests/lapi/bitop_ror_test.lua @@ -0,0 +1,37 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Synopsis: bit.ror(x, n) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +if test_lib.lua_version() ~= "LuaJIT" then + print("Unsupported version.") + os.exit(0) +end + +local ror = bit.ror + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local n = fdp:consume_integer(MIN_INT, MAX_INT) + local res = ror(x, n) + assert(type(res) == "number") + + -- For any valid displacement, the following identity holds + -- [1]: + -- + -- 1. https://www.lua.org/manual/5.2/manual.html + assert(ror(x, n) == ror(x, n % 32)) +end + +local args = { + artifact_prefix = "bitop_ror_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_rshift_test.lua b/tests/lapi/bitop_rshift_test.lua new file mode 100644 index 00000000..6478041b --- /dev/null +++ b/tests/lapi/bitop_rshift_test.lua @@ -0,0 +1,36 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Missing guard for obscure situations with open upvalues aliasing SSA slots, +https://github.com/LuaJIT/LuaJIT/issues/176 + +Negation in macro 'luaV_shiftr' may overflow, +https://github.com/lua/lua/commit/62fb93442753cbfb828335cd172e71471dffd536 + +Synopsis: bit.rshift(x, n) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +local rshift +if test_lib.lua_version() == "LuaJIT" then + rshift = bit.rshift +else + rshift = test_lib.bitwise_op(">>") +end + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local x = fdp:consume_integer(0, MAX_INT) + local n = fdp:consume_integer(1, 32) + local res = rshift(x, n) + assert(type(res) == "number") +end + +local args = { + artifact_prefix = "bitop_rshift_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_tobit_test.lua b/tests/lapi/bitop_tobit_test.lua new file mode 100644 index 00000000..fe08e7de --- /dev/null +++ b/tests/lapi/bitop_tobit_test.lua @@ -0,0 +1,30 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Synopsis: bit.tobit(x) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +if test_lib.lua_version() ~= "LuaJIT" then + print("Unsupported version.") + os.exit(0) +end + +local tobit = bit.tobit + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local res = tobit(x) + assert(type(res) == "number") +end + +local args = { + artifact_prefix = "bitop_tobit_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/bitop_tohex_test.lua b/tests/lapi/bitop_tohex_test.lua new file mode 100644 index 00000000..9c08968c --- /dev/null +++ b/tests/lapi/bitop_tohex_test.lua @@ -0,0 +1,31 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Synopsis: bit.tohex(x [,n]) +]] + +local luzer = require("luzer") +local test_lib = require("lib") + +if test_lib.lua_version() ~= "LuaJIT" then + print("Unsupported version.") + os.exit(0) +end + +local tohex = bit.tohex + +local function TestOneInput(buf) + local fdp = luzer.FuzzedDataProvider(buf) + local MAX_INT = test_lib.MAX_INT + local MIN_INT = test_lib.MIN_INT + local x = fdp:consume_integer(MIN_INT, MAX_INT) + local n = fdp:consume_integer(MIN_INT, MAX_INT) + local res = tohex(x, n) + assert(type(res) == "string") +end + +local args = { + artifact_prefix = "bitop_tohex_", +} +luzer.Fuzz(TestOneInput, nil, args) diff --git a/tests/lapi/lib.lua b/tests/lapi/lib.lua new file mode 100644 index 00000000..a1e6cfee --- /dev/null +++ b/tests/lapi/lib.lua @@ -0,0 +1,66 @@ +--[[ +SPDX-License-Identifier: ISC +Copyright (c) 2023-2025, Sergey Bronnikov. + +Test helpers. +]] + +-- The function determines a Lua version. +local function lua_version() + local is_luajit, _ = pcall(require, "jit") + if is_luajit then + return "LuaJIT" + end + + return _VERSION +end + +-- By default `lua_Integer` is ptrdiff_t in Lua 5.1 and Lua 5.2 +-- and `long long` in Lua 5.3+, (usually a 64-bit two-complement +-- integer), but that can be changed to `long` or `int` (usually a +-- 32-bit two-complement integer), see LUA_INT_TYPE in +-- . Lua 5.3+ has two functions: `math.maxinteger` and +-- `math.mininteger` that returns an integer with the maximum +-- value for an integer and an integer with the minimum value for +-- an integer, see [1] and [2]. + +-- `0x7ffffffffffff` is a maximum integer in `long long`, however +-- this number is not representable in `double` and the nearest +-- number representable in `double` is `0x7ffffffffffffc00`. +-- +-- 1. https://www.lua.org/manual/5.1/manual.html#lua_Integer +-- 2. https://www.lua.org/manual/5.3/manual.html#lua_Integer +local MAX_INT64 = math.maxinteger or 0x7ffffffffffffc00 +local MIN_INT64 = math.mininteger or -0x8000000000000000 +-- 32-bit integers +local MAX_INT = 0x7fffffff +local MIN_INT = -0x80000000 + +local function bitwise_op(op_name) + return function(...) + local n = select("#", ...) + local chunk + -- Bitwise exclusive OR and bitwise NOT have the same + -- operator. + if (op_name == "&" or op_name == "|") then + assert(n > 1) + end + if n == 1 then + local x = ... + chunk = ("return %s %d"):format(op_name, x) + else + local op_name_ws = (" %s "):format(op_name) + chunk = "return " .. table.concat({...}, op_name_ws) + end + return assert(load(chunk))() + end +end + +return { + lua_version = lua_version, + bitwise_op = bitwise_op, + MAX_INT64 = MAX_INT64, + MIN_INT64 = MIN_INT64, + MAX_INT = MAX_INT, + MIN_INT = MIN_INT, +}