diff --git a/.github/workflows/CITest.yml b/.github/workflows/CITest.yml index dd3f5b547c..1b9d5d1bd8 100644 --- a/.github/workflows/CITest.yml +++ b/.github/workflows/CITest.yml @@ -54,17 +54,17 @@ jobs: build_type: 'Debug' } - { - name: 'ubuntu-22.04 x64 release - assert warn', + name: 'ubuntu-22.04 x64 debug - assert warn', os: ubuntu-22.04, arch: x64, build-system: 'cmake', diet-build: 'OFF', enable-asan: 'OFF', - build_type: 'Release', + build_type: 'Debug', build_options: '-DCAPSTONE_ASSERTION_WARNINGS=ON' } - { - name: 'ubuntu-22.04 x64 release - no asserts', + name: 'ubuntu-22.04 x64 release - no assert warnings', os: ubuntu-22.04, arch: x64, build-system: 'cmake', @@ -73,6 +73,16 @@ jobs: build_type: 'Release', build_options: '-DCAPSTONE_ASSERTION_WARNINGS=OFF' } + - { + name: 'ubuntu-22.04 x64 release - assert warn', + os: ubuntu-22.04, + arch: x64, + build-system: 'cmake', + diet-build: 'OFF', + enable-asan: 'OFF', + build_type: 'Release', + build_options: '-DCAPSTONE_ASSERTION_WARNINGS=ON' + } - { name: 'ubuntu-24.04 x64 ASAN', os: ubuntu-24.04, @@ -112,10 +122,10 @@ jobs: run: | mkdir build && cd build # build static library - cmake -DCAPSTONE_INSTALL=1 -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_ASAN=${asan} -DCAPSTONE_BUILD_DIET=${diet_build} ${build_option} .. + cmake -DCMAKE_BUILD_TYPE=${build_type} -DCAPSTONE_INSTALL=1 -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_ASAN=${asan} -DCAPSTONE_BUILD_DIET=${diet_build} ${build_option} .. cmake --build . --config ${build_type} # build shared library - cmake -DCAPSTONE_INSTALL=1 -DCAPSTONE_BUILD_SHARED_LIBS=1 -DCMAKE_INSTALL_PREFIX=/usr -DCAPSTONE_BUILD_CSTEST=ON -DENABLE_ASAN=${asan} ${build_option} .. + cmake -DCMAKE_BUILD_TYPE=${build_type} -DCAPSTONE_INSTALL=1 -DCAPSTONE_BUILD_SHARED_LIBS=1 -DCMAKE_INSTALL_PREFIX=/usr -DCAPSTONE_BUILD_CSTEST=ON -DENABLE_ASAN=${asan} ${build_option} .. sudo cmake --build . --config ${build_type} --target install - name: Lower number of KASL randomized address bits diff --git a/CMakeLists.txt b/CMakeLists.txt index a6e2dd627a..718115043b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ option(CAPSTONE_USE_DEFAULT_ALLOC "Use default memory allocation functions" ON) option(CAPSTONE_USE_ARCH_REGISTRATION "Use explicit architecture registration" OFF) option(CAPSTONE_ARCHITECTURE_DEFAULT "Whether architectures are enabled by default" ON) option(CAPSTONE_DEBUG "Whether to enable extra debug assertions (enabled with CMAKE_BUILD_TYPE=Debug)" OFF) -option(CAPSTONE_ASSERTION_WARNINGS "Warns about hit assertions in release builds." OFF) +option(CAPSTONE_ASSERTION_WARNINGS "Disables some asserts and warns instead when hit. Does not disable all asserts." OFF) option(CAPSTONE_INSTALL "Generate install target" ${PROJECT_IS_TOP_LEVEL}) option(ENABLE_ASAN "Enable address sanitizer" OFF) option(ENABLE_COVERAGE "Enable test coverage" OFF) diff --git a/cs_priv.h b/cs_priv.h index 34eaefbbd7..a5ada1c659 100644 --- a/cs_priv.h +++ b/cs_priv.h @@ -107,11 +107,28 @@ extern cs_realloc_t cs_mem_realloc; extern cs_free_t cs_mem_free; extern cs_vsnprintf_t cs_vsnprintf; -/// By defining CAPSTONE_DEBUG assertions can be used. -/// For the release build the @expr is not included. -#ifdef CAPSTONE_DEBUG +/// Capstone assert macros. They can be configured to print warnings +/// when the `expr` is false. +/// This can be enabled by defining CAPSTONE_ASSERTION_WARNINGS. +/// Debug builds will always include an `assert(expr)` and hard fail +/// if `!expr`. +/// Release builds will not have `assert(expr)` code. + +/// An simple assert. +#if defined(CAPSTONE_DEBUG) && !defined(CAPSTONE_ASSERTION_WARNINGS) #define CS_ASSERT(expr) assert(expr) -#elif CAPSTONE_ASSERTION_WARNINGS +#elif defined(CAPSTONE_DEBUG) && defined(CAPSTONE_ASSERTION_WARNINGS) +#define CS_ASSERT(expr) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, \ + "Capstone hit the assert: \"" #expr \ + "\": %s:%" PRIu32 "\n", \ + __FILE__, __LINE__); \ + assert(expr) \ + } \ + } while (0) +#elif defined(CAPSTONE_ASSERTION_WARNINGS) #define CS_ASSERT(expr) \ do { \ if (!(expr)) { \ @@ -125,42 +142,44 @@ extern cs_vsnprintf_t cs_vsnprintf; #define CS_ASSERT(expr) #endif -/// If compiled in debug mode it will assert(@expr). -/// In the release build it will check the @expr and return @val if false. -#ifdef CAPSTONE_DEBUG +/// An assert which returns the value in release builds if `!expr`. +#if defined(CAPSTONE_DEBUG) && !defined(CAPSTONE_ASSERTION_WARNINGS) #define CS_ASSERT_RET_VAL(expr, val) assert(expr) -#elif CAPSTONE_ASSERTION_WARNINGS +#elif defined(CAPSTONE_ASSERTION_WARNINGS) #define CS_ASSERT_RET_VAL(expr, val) \ do { \ if (!(expr)) { \ - fprintf(stderr, \ - "Capstone hit the assert: \"" #expr \ - "\": %s:%" PRIu32 "\n", \ - __FILE__, __LINE__); \ + CS_ASSERT(expr); \ return val; \ } \ } while (0) #else -#define CS_ASSERT_RET_VAL(expr, val) +#define CS_ASSERT_RET_VAL(expr, val) \ + do { \ + if (!(expr)) { \ + return val; \ + } \ + } while (0) #endif -/// If compiled in debug mode it will assert(@expr). -/// In the release build it will check the @expr and return if false. -#ifdef CAPSTONE_DEBUG +/// An assert which returns in release builds if `!expr`. +#if defined(CAPSTONE_DEBUG) && !defined(CAPSTONE_ASSERTION_WARNINGS) #define CS_ASSERT_RET(expr) assert(expr) -#elif CAPSTONE_ASSERTION_WARNINGS +#elif defined(CAPSTONE_ASSERTION_WARNINGS) #define CS_ASSERT_RET(expr) \ do { \ if (!(expr)) { \ - fprintf(stderr, \ - "Capstone hit the assert: \"" #expr \ - "\": %s:%" PRIu32 "\n", \ - __FILE__, __LINE__); \ + CS_ASSERT(expr); \ return; \ } \ } while (0) #else -#define CS_ASSERT_RET(expr) +#define CS_ASSERT_RET(expr) \ + do { \ + if (!(expr)) { \ + return; \ + } \ + } while (0) #endif #endif diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt index 6e0d41a973..c853e75224 100644 --- a/tests/integration/CMakeLists.txt +++ b/tests/integration/CMakeLists.txt @@ -34,9 +34,13 @@ add_test(NAME integration_compat_headers ) set(INTEGRATION_TEST_SRC test_litbase.c) +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set(INTEGRATION_TEST_SRC ${INTEGRATION_TEST_SRC} test_assert_macros.c) +endif() + foreach(TSRC ${INTEGRATION_TEST_SRC}) - string(REGEX REPLACE ".c$" "" TBIN ${TSRC}) - add_executable(${TBIN} "${TESTS_INTEGRATION_DIR}/${TSRC}") - target_link_libraries(${TBIN} PRIVATE capstone) - add_test(NAME "integration_${TBIN}" COMMAND ${TBIN}) - endforeach() + string(REGEX REPLACE ".c$" "" TBIN ${TSRC}) + add_executable(${TBIN} "${TESTS_INTEGRATION_DIR}/${TSRC}") + target_link_libraries(${TBIN} PRIVATE capstone) + add_test(NAME "integration_${TBIN}" COMMAND ${TBIN}) +endforeach() diff --git a/tests/integration/test_assert_macros.c b/tests/integration/test_assert_macros.c new file mode 100644 index 0000000000..02cf2c3e50 --- /dev/null +++ b/tests/integration/test_assert_macros.c @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 Rot127 +// SPDX-License-Identifier: LGPL-3.0-only + +// Has to be built as Release build or with CAPSTONE_ASSERTION_WARNINGS. + +#include +#include + +// Test: https://github.com/capstone-engine/capstone/issues/2791 +static bool test_cs_reg_null_case() +{ + csh handle; + if (cs_open(CS_ARCH_AARCH64, (cs_mode)0, &handle) != CS_ERR_OK) { + printf("Open failed\n"); + return false; + } + // Invalid register id 0 should return NULL. + if (cs_reg_name(handle, 0) != NULL) { + printf("NULL check failed\n"); + return false; + } + cs_close(&handle); + return true; +} + +int main() +{ + bool result = true; + result &= test_cs_reg_null_case(); + + return result ? 0 : -1; +}