From 9d15af772e9aa25bbb85b58eede35a380dbefd65 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Thu, 23 Jul 2026 15:16:58 -0400 Subject: [PATCH] Run msan in CI. Closes #66 --- .gitea/workflows/ci.yml | 11 ++++++++++- CMakeLists.txt | 36 +++++++++++++++++++++++++++++++++- ConflictSet.cpp | 4 ++-- build_msan_toolchain.sh | 43 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100755 build_msan_toolchain.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index b255702..5b846de 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -116,8 +116,10 @@ jobs: include: - runner: ubuntu-latest-amd64 arch: amd64 + cmake_args: -DMSAN_TOOLCHAIN_PATH=/opt/msan - runner: ubuntu-latest-arm64 arch: arm64 + cmake_args: "" runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@v4 @@ -158,11 +160,18 @@ jobs: restore-keys: | ccache-release-${{ matrix.arch }}- + - name: Download MSan toolchain + if: matrix.arch == 'amd64' + run: | + curl -Ls "https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst" -o /tmp/msan-toolchain.tar.zst + sudo mkdir -p /opt/msan + sudo tar --zstd -xf /tmp/msan-toolchain.tar.zst -C /opt/msan + - name: Build run: | export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" rm -rf build - cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_FLAGS=-DNVALGRIND + cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_FLAGS=-DNVALGRIND ${{ matrix.cmake_args }} ninja -C build ccache -s diff --git a/CMakeLists.txt b/CMakeLists.txt index eabecea..4dfdd73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,13 @@ option(USE_SIMD_FALLBACK option(DISABLE_TSAN "Disable TSAN" OFF) +set(MSAN_TOOLCHAIN_PATH + "" + CACHE + PATH + "Path to an MSan-instrumented libc++ toolchain. When set, an MSan fuzz_driver target is added. Must contain include/c++/v1 and lib. See build_msan_toolchain.sh to produce one." +) + # This is encouraged according to # https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind) @@ -253,7 +260,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING) endif() endif() - # whitebox tests + # whitebox tests asan+ubsan add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp) target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS}) if(NOT CMAKE_CROSSCOMPILING) @@ -268,6 +275,33 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING) add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST}) endforeach() + # whitebox tests msan + if(MSAN_TOOLCHAIN_PATH) + add_executable(fuzz_driver_msan ConflictSet.cpp FuzzTestDriver.cpp) + target_compile_options(fuzz_driver_msan PRIVATE ${TEST_FLAGS}) + if(NOT CMAKE_CROSSCOMPILING) + target_compile_options( + fuzz_driver_msan + PRIVATE -fsanitize=memory -fsanitize-memory-track-origins=2 + -stdlib=libc++ -I${MSAN_TOOLCHAIN_PATH}/include/c++/v1) + target_link_options( + fuzz_driver_msan + PRIVATE + -fsanitize=memory + -fsanitize-memory-track-origins=2 + -stdlib=libc++ + -L${MSAN_TOOLCHAIN_PATH}/lib + LINKER:-rpath,${MSAN_TOOLCHAIN_PATH}/lib) + endif() + target_compile_definitions(fuzz_driver_msan PRIVATE ENABLE_FUZZ) + target_include_directories(fuzz_driver_msan + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) + foreach(TEST ${CORPUS_TESTS}) + get_filename_component(hash ${TEST} NAME) + add_test(NAME conflict_set_msan_${hash} COMMAND fuzz_driver_msan ${TEST}) + endforeach() + endif() + # tsan tests if(NOT CMAKE_CROSSCOMPILING AND NOT DISABLE_TSAN) add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp) diff --git a/ConflictSet.cpp b/ConflictSet.cpp index 200a592..a3b9143 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -1332,8 +1332,8 @@ TaggedNodePointer getChildGeq(Node *self, int child) { TaggedNodePointer getFirstChild(Node0 *) { return nullptr; } TaggedNodePointer getFirstChild(Node3 *self) { - // Improves scan performance - self->children[1].prefetch(); + // Don't prefetch since self->children[1] might be uninitialized and msan + // doesn't like that return self->children[0]; } TaggedNodePointer getFirstChild(Node16 *self) { diff --git a/build_msan_toolchain.sh b/build_msan_toolchain.sh new file mode 100755 index 0000000..bfeb3ea --- /dev/null +++ b/build_msan_toolchain.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -euxo pipefail + +LLVM_VERSION="${LLVM_VERSION:-21}" +MSAN_PREFIX="${MSAN_PREFIX:-$PWD/msan}" +JOBS="${JOBS:-$(nproc)}" + +cd /tmp +rm -rf libcxx-msan +mkdir libcxx-msan +cd libcxx-msan + +git clone --depth=1 "https://github.com/llvm/llvm-project.git" -b "release/${LLVM_VERSION}.x" + +cmake -S llvm-project/runtimes -B build_msan \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER="clang++-${LLVM_VERSION}" \ + -DCMAKE_C_COMPILER="clang-${LLVM_VERSION}" \ + -DCMAKE_INSTALL_PREFIX="${MSAN_PREFIX}" \ + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \ + -DLLVM_USE_SANITIZER=MemoryWithOrigins + +cmake --build build_msan -j"${JOBS}" +cmake --install build_msan + +cmake -S llvm-project/runtimes -B build_nomsan \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER="clang++-${LLVM_VERSION}" \ + -DCMAKE_C_COMPILER="clang-${LLVM_VERSION}" \ + -DCMAKE_INSTALL_PREFIX="${MSAN_PREFIX}" \ + -DLLVM_ENABLE_RUNTIMES="libunwind" + +cmake --build build_nomsan -j"${JOBS}" +cmake --install build_nomsan + +VERSION="$(clang-"${LLVM_VERSION}" --version | head -n1 | sed -E 's/.*clang version ([0-9.]+).*/\1/')" +TARBALL="msan-toolchain-${VERSION}.tar.zst" + +tar --zstd -cf "${TARBALL}" -C "${MSAN_PREFIX}" . + +TARBALL_PATH="$(pwd)/${TARBALL}" +echo "Created: ${TARBALL_PATH}"