diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1d671cb4e..7c8928f9f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,3 +1,4 @@ + name: Linux on: workflow_dispatch: @@ -42,10 +43,10 @@ jobs: - name: Install LLVM and Clang if: matrix.sys.compiler == 'clang' - uses: egor-tensin/setup-clang@v1 + uses: KyleMayes/install-llvm-action@v2 with: version: ${{matrix.sys.version}} - platform: x64 + arch: x64 - name: Install the specified standard library for clang if: matrix.sys.compiler == 'clang' @@ -63,7 +64,7 @@ jobs: cache-downloads: true - name: Configure using CMake - run: cmake -G Ninja -Bbuild ${{matrix.sys.config-flags}} -DCMAKE_BUILD_TYPE:STRING=${{matrix.config.name}} -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX -DUSE_DATE_POLYFILL=${{matrix.sys.date-polyfill}} -DBUILD_TESTS=ON + run: cmake -G Ninja -Bbuild ${{matrix.sys.config-flags}} -DCMAKE_BUILD_TYPE:STRING=${{matrix.config.name}} -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX -DUSE_DATE_POLYFILL=${{matrix.sys.date-polyfill}} -DBUILD_TESTS=ON -DUSE_SANITIZER="address;undefined;leak" - name: Install working-directory: build @@ -76,6 +77,9 @@ jobs: - name: Run tests working-directory: build run: cmake --build . --config ${{matrix.config.name}} --target run_tests_with_junit_report + env: + ASAN_OPTIONS: log_path=${{github.workspace}}/asan.log:alloc_dealloc_mismatch=0:halt_on_error=0:handle_abort=0:exitcode=0 + # UBSAN_OPTIONS: log_path=${{github.workspace}}/ubsan.log:halt_on_error=0:handle_abort=1:exitcode=0 - name: Upload test results uses: actions/upload-artifact@v4 @@ -83,3 +87,10 @@ jobs: with: name: test_sparrow_lib_report_Linux_${{ matrix.sys.compiler }}_${{ matrix.sys.version }}_${{ matrix.sys.stdlib }}_${{ matrix.config.name }}_date-polyfill_${{ matrix.sys.date-polyfill}} path: '**/test_sparrow_lib_report.xml' + + - name: Upload ASAN log + if: always() + uses: actions/upload-artifact@v2 + with: + name: asan-log-${{ matrix.sys.compiler }}-${{ matrix.sys.version }}-${{ matrix.config.name }}-${{ matrix.sys.date-polyfill }} + path: ${{github.workspace}}/asan.log diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index a54752ab3..65e358b0a 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Setup MSVC - if: matrix.sys.compiler == 'msvc' && matrix.build-system != 'Visual Studio 17 2022' + if: matrix.sys.compiler == 'msvc' uses: ilammy/msvc-dev-cmd@v1 - name: Install LLVM and Clang @@ -63,7 +63,9 @@ jobs: ninja - name: Configure using CMake - run: cmake -Bbuild -DCMAKE_BUILD_TYPE:STRING=${{matrix.config.name}} -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX -DBUILD_TESTS=ON -DUSE_DATE_POLYFILL=${{matrix.sys.date-polyfill}} -G "${{matrix.build-system}}" + run: cmake -Bbuild -DCMAKE_BUILD_TYPE:STRING=${{matrix.config.name}} -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX -DBUILD_TESTS=ON -DUSE_DATE_POLYFILL=${{matrix.sys.date-polyfill}} -G "${{matrix.build-system}}" ${{env.CMAKE_SANITIZER}} + env: + CMAKE_SANITIZER: ${{ (matrix.config.name == 'Debug' && matrix.sys.compiler == 'msvc') && '-DUSE_SANITIZER=address' || '' }} - name: Install working-directory: build diff --git a/CMakeLists.txt b/CMakeLists.txt index f8cdfa3d8..2d65f9ab7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ cmake_minimum_required(VERSION 3.27) # so here we go. # This will affects all following targets being defined. set(CMAKE_CXX_EXTENSIONS OFF) +cmake_policy(SET CMP0091 NEW) project(sparrow CXX) @@ -73,6 +74,10 @@ if(ACTIVATE_LINTER) include(cmake/clang-tidy.cmake) endif() +# Sanitizers +# ========== +include(cmake/sanitizers.cmake) + # Dependencies # ============ diff --git a/cmake/sanitizers.cmake b/cmake/sanitizers.cmake new file mode 100644 index 000000000..a8cc2b996 --- /dev/null +++ b/cmake/sanitizers.cmake @@ -0,0 +1,47 @@ +set(AVALAIBLE_SANITIZERS "address;leak;memory;thread;undefined") +set(USE_SANITIZER "" CACHE STRING "Enable sanitizer(s). Options are: ${AVALAIBLE_SANITIZERS}. Case insensitive; multiple options delimited by comma or space possible.") +string(TOLOWER "${USE_SANITIZER}" USE_SANITIZER) + +if((CMAKE_BUILD_TYPE IN_LIST "Debug;RelWithDebInfo") AND USE_SANITIZER) + message(FATAL_ERROR "❌ Sanitizer only supported in Debug and RelWithDebInfo build types.") +endif() + +if(USE_SANITIZER) + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") + + if(USE_SANITIZER MATCHES "address") + list(APPEND SANITIZER_COMPILE_OPTIONS /fsanitize=address /D_DISABLE_VECTOR_ANNOTATION /D_DISABLE_STRING_ANNOTATION) + else() + message(FATAL_ERROR "❌ Sanitizer not supported by MSVC: ${USE_SANITIZER}. It only supports 'address'.") + endif() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + if(USE_SANITIZER MATCHES "address") + list(APPEND SANITIZER_COMPILE_OPTIONS /fsanitize=address /D_DISABLE_VECTOR_ANNOTATION /D_DISABLE_STRING_ANNOTATION) + list(APPEND SANITIZER_LINK_LIBRARIES clang_rt.asan_dynamic-x86_64 clang_rt.asan_dynamic_runtime_thunk-x86_64) + else() + message(FATAL_ERROR "❌ Sanitizer not supported by Clang-MSVC: ${USE_SANITIZER}. It only supports 'address'.") + endif() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + foreach(sanitizer ${USE_SANITIZER}) + if(NOT ${sanitizer} IN_LIST AVALAIBLE_SANITIZERS) + message(FATAL_ERROR "❌ Sanitizer not supported: ${sanitizer}. It should be one of: ${AVALAIBLE_SANITIZERS}.") + endif() + list(APPEND SANITIZER_COMPILE_OPTIONS -fsanitize=${sanitizer}) + list(APPEND SANITIZER_LINK_OPTIONS -fsanitize=${sanitizer}) + if (${sanitizer} MATCHES "memory") + list(APPEND SANITIZER_LINK_LIBRARIES -fsanitize-memory-track-origins -fPIE -pie) + list(APPEND SANITIZER_LINK_OPTIONS -fsanitize-memory-track-origins -fPIE -pie) + endif() + endforeach() + list(APPEND SANITIZER_COMPILE_OPTIONS -fno-omit-frame-pointer) + else() + message(FATAL_ERROR "❌ Sanitizer: Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}") + endif() + + list(REMOVE_DUPLICATES SANITIZER_COMPILE_OPTIONS) + list(REMOVE_DUPLICATES SANITIZER_LINK_OPTIONS) + list(REMOVE_DUPLICATES SANITIZER_LINK_LIBRARIES) + + message(STATUS "🔍 Using sanitizer: ${USE_SANITIZER}") +endif() diff --git a/ignorelist_asan.supp b/ignorelist_asan.supp new file mode 100644 index 000000000..1d7d303ef --- /dev/null +++ b/ignorelist_asan.supp @@ -0,0 +1 @@ +src:*/float16_t.hpp \ No newline at end of file diff --git a/include/sparrow/allocator.hpp b/include/sparrow/allocator.hpp index a1283ab0e..5cb844602 100644 --- a/include/sparrow/allocator.hpp +++ b/include/sparrow/allocator.hpp @@ -53,6 +53,15 @@ namespace sparrow && (std::same_as> || std::same_as>); + template + struct overloaded : Ts... + { + using Ts::operator()...; + }; + // Although not required in C++20, clang needs it to build the code below + template + overloaded(Ts...) -> overloaded; + /* * Type erasure class for allocators. This allows to use any kind of allocator * (standard, polymorphic) without having to expose it as a template parameter. @@ -149,15 +158,6 @@ namespace sparrow return std::forward(alloc); } - template - struct overloaded : Ts... - { - using Ts::operator()...; - }; - // Although not required in C++20, clang needs it to build the code below - template - overloaded(Ts...) -> overloaded; - storage_type copy_storage(const storage_type& rhs) const { return std::visit( @@ -226,18 +226,28 @@ namespace sparrow } template - void any_allocator::deallocate(T* p, std::size_t n) +#if defined(_MSC_VER) && !defined(__clang__) // MSVC + __declspec(no_sanitize_address) +#else +# if defined(__has_feature) +# if __has_feature(address_sanitizer) + __attribute__((no_sanitize("address"))) +# endif +# endif +#endif + void + any_allocator::deallocate(T* p, std::size_t n) { return visit_storage( [n, p](auto& allocator) { #if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmismatched-new-delete" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmismatched-new-delete" #endif return allocator.deallocate(p, n); #if defined(__GNUC__) -#pragma GCC diagnostic pop +# pragma GCC diagnostic pop #endif } ); diff --git a/include/sparrow/data_traits.hpp b/include/sparrow/data_traits.hpp index a03504586..e19c1f4d0 100644 --- a/include/sparrow/data_traits.hpp +++ b/include/sparrow/data_traits.hpp @@ -18,6 +18,8 @@ #include "sparrow/fixed_size_layout.hpp" #include "sparrow/variable_size_binary_layout.hpp" +#include + namespace sparrow { diff --git a/include/sparrow/details/3rdparty/float16_t.hpp b/include/sparrow/details/3rdparty/float16_t.hpp index e9f946186..1fd4eacd5 100644 --- a/include/sparrow/details/3rdparty/float16_t.hpp +++ b/include/sparrow/details/3rdparty/float16_t.hpp @@ -177,7 +177,7 @@ namespace half const std::uint32_t f_m_rounded = ( f_m + f_m_round_offset ); const std::uint32_t f_m_denorm_sa = ( one - f_e_half_bias ); const std::uint32_t f_m_with_hidden = ( f_m_rounded | f_m_hidden_bit ); - const std::uint32_t f_m_denorm = ( f_m_with_hidden >> f_m_denorm_sa ); + const std::uint32_t f_m_denorm = f_m_denorm_sa < 32 ? ( f_m_with_hidden >> f_m_denorm_sa ) : 0; const std::uint32_t h_m_denorm = ( f_m_denorm >> f_h_m_pos_offset ); const std::uint32_t f_m_rounded_overflow = ( f_m_rounded & f_m_hidden_bit ); const std::uint32_t m_nan = ( f_m >> f_h_m_pos_offset ); diff --git a/include/sparrow/dynamic_bitset.hpp b/include/sparrow/dynamic_bitset.hpp index 8281500aa..0a3259d41 100644 --- a/include/sparrow/dynamic_bitset.hpp +++ b/include/sparrow/dynamic_bitset.hpp @@ -536,7 +536,7 @@ namespace sparrow const size_type extra_bits = count_extra_bits(); if (extra_bits > 0) { - m_buffer.data()[old_block_count - 1] |= (value << extra_bits); + m_buffer.data()[old_block_count - 1] |= static_cast(value << extra_bits); } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 68fb2ebaa..c0cf3dbde 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -61,7 +61,11 @@ set(SPARROW_TESTS_SOURCES ) set(test_target "test_sparrow_lib") add_executable(${test_target} ${SPARROW_TESTS_SOURCES}) -target_link_libraries(${test_target} PRIVATE sparrow doctest::doctest) +target_link_libraries(${test_target} + PRIVATE + sparrow + doctest::doctest + $<$:${SANITIZER_LINK_LIBRARIES}>) include(doctest) doctest_discover_tests(${test_target}) @@ -113,6 +117,9 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "C -Wshadow # warn the user if a variable declaration shadows one from a parent context -Wsign-conversion # warn on sign conversions -Wunused # warn on anything being unused + $<$:-Wno-maybe-uninitialized> + $<$:-Wno-array-bounds> + $<$:-Wno-stringop-overread> $<$:-Wduplicated-branches> # warn if if / else branches have duplicated code $<$:-Wduplicated-cond> # warn if if / else chain has duplicated conditions $<$:-Wlogical-op> # warn about logical operations being used where bitwise were probably wanted @@ -121,10 +128,21 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "C ) endif() -target_compile_options(${test_target} PRIVATE ${compiles_options}) +target_compile_options(${test_target} + PRIVATE + ${compiles_options} + $<$:${SANITIZER_COMPILE_OPTIONS}>) + +target_link_options(${test_target} + PRIVATE + $<$:${SANITIZER_LINK_OPTIONS}>) # We do not use non-standard C++ -set_target_properties(${test_target} PROPERTIES CMAKE_CXX_EXTENSIONS OFF) +set_target_properties(${test_target} + PROPERTIES + CMAKE_CXX_EXTENSIONS OFF + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + # ENVIRONMENT "ASAN_OPTIONS=log_path=${CMAKE_SOURCE_DIR}/log_file.txt") target_compile_features(${test_target} PRIVATE cxx_std_20) add_custom_target(run_tests