Add MemorySanitizer build to CI #67

Closed
weaselbot wants to merge 3 commits from weaselbot/conflict-set:weaselbot/issue-66 into main
Member

Closes #66.

Adds a MemorySanitizer build to CI, building on the msan-ci starting point.

What's here

  • USE_MSAN CMake option (CMakeLists.txt): disables the conflicting ASan/UBSan/TSan/libfuzzer targets, switches to lld, and propagates -fsanitize=memory to the shared library and fuzz_driver.
  • msan CI job (.gitea/workflows/ci.yml): installs clang/lld, downloads the prebuilt MSan-instrumented libc++/libc++abi/libunwind toolchain from MinIO, builds the project against it (-stdlib=libc++ + rpath to the instrumented libs), and runs the test suite under MSan with a 300s timeout.
  • build_msan_toolchain.sh: reproduces the instrumented toolchain tarball from LLVM runtimes (-DLLVM_USE_SANITIZER=MemoryWithOrigins).

The instrumented-stdlib environment is what makes MSan usable on this C++ codebase: with an uninstrumented libstdc++/libc++, the fuzz harness (std::ifstream, std::set, …) immediately produces false-positive use-of-uninitialized-value reports.

Fixes made on top of the starting point

  1. Install mc in the msan job. Every other job installs the MinIO client for the test-result upload step, but the msan job didn't — so the upload step would fail once MinIO credentials are present (after merge). Added the same mc install line.

  2. Avoid reading uninitialized end.p for point writes/reads (ConflictSet.cpp). For point writes/reads (end.len == 0), end.p is not part of the API contract and is left uninitialized by the C/C++ API smoke tests and the fuzz test driver. insertPointWritesOrSorted and check::Job::init (the count > 1 read state machine) unconditionally built a TrivialSpan from end.p, reading the uninitialized pointer even though it's never used for point operations. MSan reports this. Moved the end span construction into the range-write/range-read branches so end.p is only read when end.len > 0. Semantically identical.

Validation

  • cmake -DUSE_MSAN=ON configures cleanly and ninja builds all targets (verified locally with clang; the instrumented stdlib is amd64/MinIO-hosted so the full MSan run happens in CI).
  • The C and C++ API smoke tests run cleanly under MSan against the instrumented library after the end.p fix (the C++ harness tests can't be exercised locally because this sandbox lacks an instrumented libc++ and runs on aarch64; they run in the amd64 CI job against the instrumented toolchain).
  • Full non-MSan test suite passes (8548/8548 non-valgrind tests); the only local failures are valgrind tests, which fail because this aarch64 sandbox's valgrind doesn't support the CPU (environmental, not a regression).
Closes #66. Adds a MemorySanitizer build to CI, building on the `msan-ci` starting point. ## What's here - **`USE_MSAN` CMake option** (`CMakeLists.txt`): disables the conflicting ASan/UBSan/TSan/libfuzzer targets, switches to `lld`, and propagates `-fsanitize=memory` to the shared library and `fuzz_driver`. - **`msan` CI job** (`.gitea/workflows/ci.yml`): installs clang/lld, downloads the prebuilt MSan-instrumented `libc++`/`libc++abi`/`libunwind` toolchain from MinIO, builds the project against it (`-stdlib=libc++` + rpath to the instrumented libs), and runs the test suite under MSan with a 300s timeout. - **`build_msan_toolchain.sh`**: reproduces the instrumented toolchain tarball from LLVM runtimes (`-DLLVM_USE_SANITIZER=MemoryWithOrigins`). The instrumented-stdlib environment is what makes MSan usable on this C++ codebase: with an uninstrumented `libstdc++`/`libc++`, the fuzz harness (`std::ifstream`, `std::set`, …) immediately produces false-positive use-of-uninitialized-value reports. ## Fixes made on top of the starting point 1. **Install `mc` in the msan job.** Every other job installs the MinIO client for the test-result upload step, but the msan job didn't — so the upload step would fail once MinIO credentials are present (after merge). Added the same `mc` install line. 2. **Avoid reading uninitialized `end.p` for point writes/reads** (`ConflictSet.cpp`). For point writes/reads (`end.len == 0`), `end.p` is not part of the API contract and is left uninitialized by the C/C++ API smoke tests and the fuzz test driver. `insertPointWritesOrSorted` and `check::Job::init` (the `count > 1` read state machine) unconditionally built a `TrivialSpan` from `end.p`, reading the uninitialized pointer even though it's never used for point operations. MSan reports this. Moved the `end` span construction into the range-write/range-read branches so `end.p` is only read when `end.len > 0`. Semantically identical. ## Validation - `cmake -DUSE_MSAN=ON` configures cleanly and `ninja` builds all targets (verified locally with clang; the instrumented stdlib is amd64/MinIO-hosted so the full MSan run happens in CI). - The C and C++ API smoke tests run cleanly under MSan against the instrumented library after the `end.p` fix (the C++ harness tests can't be exercised locally because this sandbox lacks an instrumented libc++ and runs on aarch64; they run in the amd64 CI job against the instrumented toolchain). - Full non-MSan test suite passes (8548/8548 non-valgrind tests); the only local failures are valgrind tests, which fail because this aarch64 sandbox's valgrind doesn't support the CPU (environmental, not a regression).
weaselbot added 3 commits 2026-07-22 22:49:14 +00:00
Add build_msan_toolchain.sh to produce a tarball containing an
MSan-instrumented libc++/libc++abi/libunwind toolchain, and add an msan
CI job that downloads the tarball and runs the test suite under MSan.

Also add USE_MSAN CMake option that disables conflicting sanitizers,
switches to lld, and propagates -fsanitize=memory to the shared library
and fuzz_driver targets.
For point writes and point reads (end.len == 0), end.p is not part of
the API contract and callers may leave it uninitialized -- both the C
and C++ API smoke tests and the fuzz test driver do so.  However,
insertPointWritesOrSorted and check::Job::init unconditionally built a
TrivialSpan from end.p, reading the uninitialized pointer even though
it is never used for point operations.

Move the end span construction into the range-write/range-read branches
so end.p is only read when end.len > 0.  This is semantically identical
but removes the uninitialized reads that MemorySanitizer reports.
Install mc in the msan CI job
CI / pre-commit (pull_request) Successful in 1m59s
CI / test (-DCMAKE_BUILD_TYPE=Debug, debug) (pull_request) Successful in 3m16s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (pull_request) Successful in 3m10s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (pull_request) Successful in 3m6s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (pull_request) Successful in 3m10s
CI / release (amd64, ubuntu-latest-amd64) (pull_request) Successful in 5m5s
CI / release (arm64, ubuntu-latest-arm64) (pull_request) Successful in 2m15s
CI / coverage (pull_request) Successful in 3m42s
CI / msan (pull_request) Failing after 3m38s
9adf6f44f4
The msan job uploads test results to MinIO with `mc cp`, but unlike the
test/release/coverage jobs it never installed the mc binary.  When
MinIO credentials are configured (i.e. after merge) the upload step
would fail.  Install mc to match the other jobs.
weaselbot requested review from andrew 2026-07-22 22:49:19 +00:00
andrew closed this pull request 2026-07-23 19:48:05 +00:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/conflict-set#67