diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index d46f8f8..7113c07 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -67,34 +67,6 @@ jobs: fail_on_failure: 'false' - run: echo "🍏 This job's status is ${{ job.status }}." - thread_sanitizer: - runs-on: ubuntu-latest - steps: - - name: Check out repository code - uses: actions/checkout@v4 - - name: dependencies - run: | - sudo apt-get update -y - sudo apt-get install -y cmake gcc moreutils - # The thread tests assert exclusive ownership of pool slots and reserved - # ranges, which is checkable without tooling and runs in the job above. - # This is the run that proves there is no data race underneath them. - # libtsan arrives with gcc (libgcc-N-dev depends on it); the script - # disables ASLR because TSan aborts on kernels with vm.mmap_rnd_bits > 28. - - name: thread sanitizer - run: | - scripts/thread_test.sh build/tsan --output-junit "$(pwd)/tsan-junit.xml" - - name: publish thread sanitizer results - if: always() - uses: mikepenz/action-junit-report@v4 - with: - report_paths: 'tsan-junit.xml' - annotate_only: true - detailed_summary: true - include_passed: true - fail_on_failure: 'true' - - run: echo "🍏 This job's status is ${{ job.status }}." - mutation_test: runs-on: ubuntu-latest steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index 69dbe30..4ea8499 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,19 @@ if(AKERR_SANITIZE AND NOT CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") "AKERR_SANITIZE requires GCC or Clang, not ${CMAKE_C_COMPILER_ID}") endif() +if(AKERR_SANITIZE STREQUAL "thread" AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + # ThreadSanitizer reserves a fixed, very large virtual-address range. Linux + # ASLR can put a loader mapping inside it before the runtime starts, which + # makes every test fail with "unexpected memory mapping" before main(). + # Run the instrumented tests under the normal util-linux ASLR wrapper. + find_program(AKERR_SETARCH_EXECUTABLE setarch) + if(NOT AKERR_SETARCH_EXECUTABLE) + message(FATAL_ERROR + "AKERR_SANITIZE=thread on Linux requires setarch (util-linux) to " + "start tests with ASLR disabled.") + endif() +endif() + function(akerr_instrument_for_sanitizers _target) if(AKERR_SANITIZE) target_compile_options(${_target} PRIVATE @@ -252,7 +265,13 @@ foreach(_test IN LISTS AKERR_TESTS) target_link_libraries(test_${_test} PRIVATE Threads::Threads) endif() akerr_instrument_for_sanitizers(test_${_test}) - add_test(NAME ${_test} COMMAND test_${_test}) + if(AKERR_SANITIZE STREQUAL "thread" AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_test(NAME ${_test} + COMMAND ${AKERR_SETARCH_EXECUTABLE} ${CMAKE_SYSTEM_PROCESSOR} + -R $) + else() + add_test(NAME ${_test} COMMAND test_${_test}) + endif() # A sanitizer report is a test failure. Without halt_on_error the runtime # prints and continues, which leaves a race to be noticed in the log by # somebody reading it -- and under a race storm the reporting itself is slow @@ -263,6 +282,14 @@ foreach(_test IN LISTS AKERR_TESTS) endif() endforeach() +# HANDLE_GROUP deliberately enters the next case label. Keep that public macro +# compiling with the warning enabled, so a future macro edit cannot restore the +# warning for consumers which adopt -Wextra. +if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(test_err_handle_group PRIVATE + -Werror=implicit-fallthrough) +endif() + set_tests_properties( ${AKERR_WILL_FAIL_TESTS} PROPERTIES WILL_FAIL TRUE diff --git a/include/akerror.tmpl.h b/include/akerror.tmpl.h index dad4c45..ede8fcc 100644 --- a/include/akerror.tmpl.h +++ b/include/akerror.tmpl.h @@ -391,6 +391,18 @@ akerr_ErrorContext AKERR_NOIGNORE *__akerr_copy_string(char *destination, int ca * Defines for the ATTEMPT/CATCH/CLEANUP/PROCESS/HANDLE/FINISH process */ +/* + * HANDLE_GROUP deliberately enters the next case label. GCC and clang both + * understand this spelling in the C modes supported by libakerror. Other + * compilers keep the established control flow without receiving an attribute + * they do not implement. + */ +#if defined(__GNUC__) || defined(__clang__) +#define AKERR_FALLTHROUGH __attribute__((fallthrough)); +#else +#define AKERR_FALLTHROUGH +#endif + #define ATTEMPT \ switch ( 0 ) { \ case 0: \ @@ -443,6 +455,7 @@ akerr_ErrorContext AKERR_NOIGNORE *__akerr_copy_string(char *destination, int ca __err_context->handled = true; #define HANDLE_GROUP(__err_context, __err_status) \ + AKERR_FALLTHROUGH \ case __err_status: \ __err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \ __err_context->handled = true;