diff --git a/.clang-format b/.clang-format index da256fbeb2..9f7bebb34a 100644 --- a/.clang-format +++ b/.clang-format @@ -274,4 +274,5 @@ WhitespaceSensitiveMacros: - STRINGIZE - SEQAN3_WORKAROUND_GCC_BOGUS_MEMCPY_START - SEQAN3_WORKAROUND_GCC_BOGUS_MEMCPY_STOP + - reduction ... diff --git a/.github/workflows/ci_misc.yml b/.github/workflows/ci_misc.yml index 5388fbc8f2..f5a3147081 100644 --- a/.github/workflows/ci_misc.yml +++ b/.github/workflows/ci_misc.yml @@ -60,7 +60,7 @@ jobs: mkdir build && cd build cmake ../test/${{ matrix.build }} -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS="${{ matrix.cxx_flags }}" \ - -DSEQAN3_BENCHMARK_MIN_TIME=0.01 + -DSEQAN3_BENCHMARK_MIN_TIME=0.01s case "${{ matrix.build }}" in snippet) make gtest_main;; performance) make benchmark_main;; diff --git a/.github/workflows/ci_sanitizer.yml b/.github/workflows/ci_sanitizer.yml new file mode 100644 index 0000000000..f5de38bd83 --- /dev/null +++ b/.github/workflows/ci_sanitizer.yml @@ -0,0 +1,113 @@ +# SPDX-FileCopyrightText: 2006-2024, Knut Reinert & Freie Universität Berlin +# SPDX-FileCopyrightText: 2016-2024, Knut Reinert & MPI für molekulare Genetik +# SPDX-License-Identifier: CC0-1.0 + +name: Sanitizer + +on: + schedule: + - cron: "0 6 * * SAT" + workflow_dispatch: + +concurrency: + group: sanitizer-actions + cancel-in-progress: true + +env: + SEQAN3_NO_VERSION_CHECK: 1 + TZ: Europe/Berlin + TSAN_OPTIONS: ignore_noninstrumented_modules=1 + UBSAN_OPTIONS: print_stacktrace=1 + +defaults: + run: + shell: bash -Eeuxo pipefail {0} + +jobs: + build: + name: ${{ matrix.name }} ${{ matrix.build_type }} ${{ matrix.os }} + runs-on: ${{ matrix.os }} + if: github.repository_owner == 'seqan' || github.event_name == 'workflow_dispatch' + env: + ASAN_OPTIONS: strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=${{ contains(matrix.os, 'macos') && '0' || '1' }} + strategy: + fail-fast: false + matrix: + name: [ASan, TSan, UBSan] + os: [ubuntu-latest, macos-14] + build_type: [Release, RelWithDebInfo, Debug] + exclude: + # macOS llvm packages do not contain libarcher, which is required for TSan to handle OpenMP. + # TSan runs on ubuntu with clang. Packages there contain libarcher. + - name: "TSan" + os: macos-14 + + include: + - os: macos-14 + compiler: clang-19 + - os: ubuntu-latest + compiler: gcc-14 + image: ghcr.io/seqan/gcc-14 + + - name: "TSan" + os: ubuntu-latest + compiler: clang-19 + image: ghcr.io/seqan/clang-19 + cxx_flags: "-fsanitize=thread" + ctest_excludes: "-E async_input_buffer_snippet" + + - name: "ASan" + os: ubuntu-latest + cxx_flags: "-fsanitize=address -Wno-maybe-uninitialized" + - name: "ASan" + os: macos-14 + cxx_flags: "-fsanitize=address" + + - name: "UBSan" + os: ubuntu-latest + cxx_flags: "-fsanitize=undefined,float-divide-by-zero -Wno-maybe-uninitialized -Wno-stringop-overflow" + - name: "UBSan" + os: macos-14 + cxx_flags: "-fsanitize=undefined,float-divide-by-zero,local-bounds,nullability" + ctest_excludes: "-E tmp_directory_snippet_cmp_output" + + container: + # If an image is defined for a matrix entry, use it. + # Otherwise, use the "empty"/'' image which means do not use a container at all. + image: ${{ matrix.image || '' }} + volumes: + - /home/runner:/home/runner + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup compiler + if: contains(matrix.os, 'macos') + uses: seqan/actions/setup-compiler@main + with: + compiler: ${{ matrix.compiler }} + + - name: Configure tests + run: | + mkdir build && cd build + cmake ../test/analyse -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DCMAKE_CXX_FLAGS="-fno-omit-frame-pointer ${{ matrix.cxx_flags }} -fno-sanitize-recover=all" \ + -DSEQAN3_BENCHMARK_MIN_TIME=0.01s \ + -DSEQAN3_WITH_SEQAN2_CI=OFF + make gtest_main benchmark_main + + - name: Build tests + working-directory: build + run: make -k + + - name: Run tests + working-directory: build + continue-on-error: true + id: test + run: ctest . -j --output-on-failure --no-tests=error ${{ matrix.ctest_excludes }} + + # Rerun failed tests with **one** thread. Some snippets touch the same file and fail in parallel. + - name: Rerun failed tests + if: steps.test.outcome == 'failure' + working-directory: build + run: ctest . -j1 --output-on-failure --no-tests=error --rerun-failed diff --git a/.github/workflows/cron_avx2.yml b/.github/workflows/cron_avx2.yml index 62bb0343e9..946065b22d 100644 --- a/.github/workflows/cron_avx2.yml +++ b/.github/workflows/cron_avx2.yml @@ -51,7 +51,7 @@ jobs: mkdir build && cd build cmake ../test/${{ matrix.build }} -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS="-mavx2 ${{ matrix.cxx_flags }}" \ - -DSEQAN3_BENCHMARK_MIN_TIME=0.01 + -DSEQAN3_BENCHMARK_MIN_TIME=0.01s case "${{ matrix.build }}" in unit) make gtest_main;; snippet) make gtest_main;; diff --git a/.github/workflows/cron_latest_libraries.yml b/.github/workflows/cron_latest_libraries.yml index 570027fc3a..dee1d27387 100644 --- a/.github/workflows/cron_latest_libraries.yml +++ b/.github/workflows/cron_latest_libraries.yml @@ -59,7 +59,7 @@ jobs: mkdir build && cd build cmake ../test/${{ matrix.build }} -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS="${{ matrix.cxx_flags }}" \ - -DSEQAN3_BENCHMARK_MIN_TIME=0.01 + -DSEQAN3_BENCHMARK_MIN_TIME=0.01s case "${{ matrix.build }}" in unit) make gtest_main;; snippet) make gtest_main;; diff --git a/include/seqan3/core/debug_stream/debug_stream_type.hpp b/include/seqan3/core/debug_stream/debug_stream_type.hpp index 18458972ea..9e08bb3d07 100644 --- a/include/seqan3/core/debug_stream/debug_stream_type.hpp +++ b/include/seqan3/core/debug_stream/debug_stream_type.hpp @@ -28,7 +28,7 @@ namespace seqan3 //!\ingroup core_debug_stream //!\implements seqan3::enum_bitwise_operators //!\sa seqan3::enum_bitwise_operators enables combining enum values. -enum fmtflags2 +enum class fmtflags2 : int8_t { none = 0, //!< No flag is set. utf8 = 1, //!< Enables use of non-ASCII UTF8 characters in formatted output. diff --git a/include/seqan3/io/structure_file/format_vienna.hpp b/include/seqan3/io/structure_file/format_vienna.hpp index 52c99dd41b..60a3a32d9c 100644 --- a/include/seqan3/io/structure_file/format_vienna.hpp +++ b/include/seqan3/io/structure_file/format_vienna.hpp @@ -200,8 +200,14 @@ class format_vienna using alph_type = typename std::ranges::range_value_t::structure_alphabet_type; // We need the structure_length parameter to count the length of the structure while reading // because we cannot infer it from the (already resized) structure_seq object. - auto res = std::ranges::copy(read_structure(stream_view), std::ranges::begin(structure)); + auto range = read_structure(stream_view); + // Use std::views::take to avoid going out of bounds if the structure is longer than the sequence. + auto res = std::ranges::copy(range | std::views::take(std::ranges::distance(seq)), + std::ranges::begin(structure)); structure_length = std::ranges::distance(std::ranges::begin(structure), res.out); + // If the structure is longer than the sequence, there are characters left. + // std::ranges::distance will also consume the characters in the stream. + structure_length += std::ranges::distance(range); if constexpr (!detail::decays_to_ignore_v) detail::bpp_from_rna_structure(bpp, structure); diff --git a/include/seqan3/utility/math.hpp b/include/seqan3/utility/math.hpp index c014ebdca5..32f0acd946 100644 --- a/include/seqan3/utility/math.hpp +++ b/include/seqan3/utility/math.hpp @@ -123,9 +123,25 @@ base_t pow(base_t base, exp_t exp) if (base == 0) return 0; + auto check = [base](base_t result) + { + if (base > 0) + { + return result > std::numeric_limits::max() / base; + } + else if (result < 0) // and base < 0 + { + return result < std::numeric_limits::max() / base; + } + else // base < 0 and result > 0 + { + return base < std::numeric_limits::min() / result; + } + }; + for (exp_t i = 0; i < exp; ++i) { - if ((base < 0 ? std::numeric_limits::min() : std::numeric_limits::max()) / base < result) + if (check(result)) { std::string error_message{"Calculating " + std::to_string(base) + '^' + std::to_string(exp) + " will result in an " diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index bf02cecf29..5de6448254 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -10,7 +10,7 @@ include (../seqan3-test.cmake) CPMGetPackage (benchmark) set (SEQAN3_BENCHMARK_MIN_TIME - "1" + "1s" CACHE STRING "Set --benchmark_min_time= for each bechmark. Timings are unreliable in CI.") macro (seqan3_benchmark benchmark_cpp) diff --git a/test/performance/alignment/global_affine_alignment_parallel_benchmark.cpp b/test/performance/alignment/global_affine_alignment_parallel_benchmark.cpp index fee03de502..c4d94c76fc 100644 --- a/test/performance/alignment/global_affine_alignment_parallel_benchmark.cpp +++ b/test/performance/alignment/global_affine_alignment_parallel_benchmark.cpp @@ -113,7 +113,7 @@ void seqan3_affine_dna4_omp_for(benchmark::State & state) int64_t total = 0; for (auto _ : state) { -# pragma omp parallel for num_threads(std::thread::hardware_concurrency()) schedule(guided) +# pragma omp parallel for num_threads(std::thread::hardware_concurrency()) schedule(guided) reduction(+:total) for (size_t i = 0; i < zip.size(); ++i) { auto rng = align_pairwise(zip[i], affine_cfg | result_t{}); @@ -182,7 +182,7 @@ void seqan2_affine_dna4_omp_for(benchmark::State & state) int64_t total = 0; for (auto _ : state) { -# pragma omp parallel for num_threads(std::thread::hardware_concurrency()) schedule(guided) +# pragma omp parallel for num_threads(std::thread::hardware_concurrency()) schedule(guided) reduction(+:total) for (size_t i = 0; i < seqan2::length(vec1); ++i) { if constexpr (score_only) diff --git a/test/performance/range/container_assignment_benchmark.cpp b/test/performance/range/container_assignment_benchmark.cpp index 0e4d1731b5..6d5e807368 100644 --- a/test/performance/range/container_assignment_benchmark.cpp +++ b/test/performance/range/container_assignment_benchmark.cpp @@ -30,8 +30,8 @@ struct assignment_functor { template requires (id == tag::assignment_operator) - static constexpr void call(container_t & to, container_t const & from) noexcept( - noexcept(std::is_nothrow_assignable_v)) + static constexpr void call(container_t & to, container_t const & from) + noexcept(noexcept(std::is_nothrow_assignable_v)) { benchmark::DoNotOptimize(to = from); } @@ -53,31 +53,18 @@ struct assignment_functor } }; -template - requires requires (container_t v) { v.clear(); } -static constexpr void clear(container_t & container) noexcept(noexcept(std::declval().clear())) -{ - container.clear(); -} - template requires requires (container_t v) { v.resize(1u); } -static constexpr void resize(container_t & container, - size_t const size) noexcept(noexcept(std::declval().resize(1u))) +static constexpr void resize(container_t & container, size_t const size) + noexcept(noexcept(std::declval().resize(1u))) { container.resize(size); } #if SEQAN3_HAS_SEQAN2 template -static constexpr void clear(container_t & container) noexcept(noexcept(seqan2::clear(std::declval()))) -{ - seqan2::clear(container); -} - -template -static constexpr void resize(container_t & container, - size_t const size) noexcept(noexcept(seqan2::resize(std::declval(), 1u))) +static constexpr void resize(container_t & container, size_t const size) + noexcept(noexcept(seqan2::resize(std::declval(), 1u))) { seqan2::resize(container, size); } @@ -107,8 +94,6 @@ static void assign(benchmark::State & state) { fn.call(to, from); benchmark::ClobberMemory(); - clear(to); - benchmark::ClobberMemory(); } } diff --git a/test/snippet/CMakeLists.txt b/test/snippet/CMakeLists.txt index 2ed91aa5fb..898538a837 100644 --- a/test/snippet/CMakeLists.txt +++ b/test/snippet/CMakeLists.txt @@ -40,7 +40,7 @@ macro (seqan3_snippet test_name_prefix snippet snippet_base_path) add_test (NAME "${snippet_compare_test_target}" COMMAND ${CMAKE_COMMAND} -DTARGET_FILE=$ -DSOURCE_FILE=${snippet_base_path}/${snippet} # - -P "${CMAKE_SOURCE_DIR}/compare_snippet_output.cmake") + -P "${CMAKE_CURRENT_SOURCE_DIR}/compare_snippet_output.cmake") # disable version checker, as it interferes with comparing the snippet output set_tests_properties ("${snippet_compare_test_target}" PROPERTIES ENVIRONMENT SEQAN3_NO_VERSION_CHECK=0) diff --git a/test/unit/alignment/scoring/scoring_scheme_test.cpp b/test/unit/alignment/scoring/scoring_scheme_test.cpp index 9ffb661904..a4fb5c1eef 100644 --- a/test/unit/alignment/scoring/scoring_scheme_test.cpp +++ b/test/unit/alignment/scoring/scoring_scheme_test.cpp @@ -59,7 +59,7 @@ TEST(nucleotide_scoring_scheme, template_argument_deduction) } { - std::array, 15> m; + std::array, 15> m{}; seqan3::nucleotide_scoring_scheme scheme{m}; EXPECT_TRUE((std::is_same_v>)); } @@ -83,7 +83,7 @@ TEST(aminoacid_scoring_scheme, template_argument_deduction) } { - std::array, 27> m; + std::array, 27> m{}; seqan3::aminoacid_scoring_scheme scheme{m}; EXPECT_TRUE((std::is_same_v>)); } diff --git a/test/unit/contrib/parallel/buffer_queue_parallel_test.cpp b/test/unit/contrib/parallel/buffer_queue_parallel_test.cpp index 34c8def1a3..37752c2af5 100644 --- a/test/unit/contrib/parallel/buffer_queue_parallel_test.cpp +++ b/test/unit/contrib/parallel/buffer_queue_parallel_test.cpp @@ -137,8 +137,8 @@ void test_buffer_queue_wait_throw(size_t initialCapacity) if constexpr (sequential_pop_t::value) thread_count = writer_count + 1; - // std::cout << "threads: " << thread_count << ‘\n‘; - // std::cout << "writers: " << writer_count << ‘\n‘; + // std::cout << "threads: " << thread_count << '\n'; + // std::cout << "writers: " << writer_count << '\n'; ASSERT_GE(thread_count, 2u); @@ -147,8 +147,8 @@ void test_buffer_queue_wait_throw(size_t initialCapacity) std::vector workers; std::atomic registered_writer = 0; std::atomic registered_reader = 0; - seqan3::contrib::queue_op_status push_status = seqan3::contrib::queue_op_status::success; - seqan3::contrib::queue_op_status pop_status = seqan3::contrib::queue_op_status::success; + std::atomic push_status = seqan3::contrib::queue_op_status::success; + std::atomic pop_status = seqan3::contrib::queue_op_status::success; for (size_t tid = 0; tid < thread_count; ++tid) { workers.push_back(std::thread( diff --git a/test/unit/range/iterator_test_template.hpp b/test/unit/range/iterator_test_template.hpp index 5c0d7ad143..e8a1989d4c 100644 --- a/test/unit/range/iterator_test_template.hpp +++ b/test/unit/range/iterator_test_template.hpp @@ -499,14 +499,14 @@ inline void jump_backward_test(it_begin_t && it_begin, rng_t && rng) // Backward copy it + (-n) for (size_t n = 0; n < sz; ++n) { - expect_iter_equal(pre_end_it + (-1 * n), pre_end_rng_it - n); + expect_iter_equal(pre_end_it + (-static_cast(n)), pre_end_rng_it - n); expect_iter_equal(pre_end_it, pre_end_rng_it); } // Backward copy friend through (-n) + it for (size_t n = 0; n < sz; ++n) { - expect_iter_equal((-1 * n) + pre_end_it, pre_end_rng_it - n); + expect_iter_equal((-static_cast(n)) + pre_end_it, pre_end_rng_it - n); expect_iter_equal(pre_end_it, pre_end_rng_it); } }