From 5740fbf5f88db16bc551cc36106f4fa4da4f1fc5 Mon Sep 17 00:00:00 2001 From: tanglearncode Date: Mon, 14 Sep 2026 00:31:09 +0800 Subject: [PATCH] Run cargo careful and sanitizers in CI Miri cannot run shimforge, because it rewrites machine code at run time. These jobs check the unsafe code while the real patching runs instead, with the tools crossbeam uses for the same purpose: - cargo careful on Linux, Windows and macOS, which rebuilds std with debug assertions and runs with -Z randomize-layout. - AddressSanitizer and ThreadSanitizer on x86_64 Linux, through the x86_64-unknown-linux-gnuasan and gnutsan targets; ThreadSanitizer rebuilds std with -Z build-std. Each job runs the shimforge test suite with one test thread, then tests/parallel.rs with eight. Builds stay unoptimized, unlike crossbeam's, because shimforge needs opt-level 0. A trial on nightly-2026-08-15 found no memory errors, data races or failed std preconditions. It needed three adjustments, all applied here: - LeakSanitizer reports the route entry that routing::install_replacement keeps on purpose after a failed patch write. One unit test injects that failure, and scripts/lsan-suppressions.txt suppresses leaks from that test only. The job finds llvm-symbolizer so the suppression can match. - cargo careful cannot start the shimforge-macros test binary, which links std dynamically, so the job tests the shimforge package only. - Nightlies since 2026-08-22 use the next-generation trait solver, which rejects the current mock! signature check. The jobs pin nightly-2026-09-12 and pass -Znext-solver=coherence until that check is changed. The jobs are not required checks yet; that can only be set once this workflow is on main. --- .github/workflows/ci.yml | 76 +++++++++++++++++++++++++++++++++++ scripts/lsan-suppressions.txt | 6 +++ 2 files changed, 82 insertions(+) create mode 100644 scripts/lsan-suppressions.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a700ca..63a0542 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,3 +103,79 @@ jobs: with: toolchain: '1.85.0' - run: cargo check --workspace --all-targets --locked + + # Runtime checks for undefined behaviour, set up like crossbeam's CI. They need a + # pinned nightly. -Znext-solver=coherence keeps the previous trait solver, which + # older mock! expansions need; drop it once the signature check no longer does. + careful: + name: cargo careful (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, windows-2022, macos-15] + env: + RUSTFLAGS: -Z randomize-layout -Znext-solver=coherence + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + persist-credentials: false + - uses: taiki-e/install-action@5b4d68e2e660441203ab128a23676f1e4faf1532 # v2.86.3 + with: + tool: rust@nightly-2026-09-12+rust-src,cargo-careful + fallback: none + # shimforge-macros is left out: its test binary cannot load the shared std that + # cargo careful builds, and its code only runs inside the compiler. + - name: Test suite + shell: bash + run: cargo careful test -p shimforge --locked --no-fail-fast -- --test-threads=1 + - name: Parallel tests + if: success() || failure() + shell: bash + run: cargo careful test -p shimforge --locked --test parallel -- --test-threads=8 + + sanitizer: + name: ${{ matrix.name }} + runs-on: ubuntu-24.04 + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - name: AddressSanitizer + target: x86_64-unknown-linux-gnuasan + build-std: '' + - name: ThreadSanitizer + target: x86_64-unknown-linux-gnutsan + build-std: -Z build-std + env: + RUSTFLAGS: -Znext-solver=coherence + ASAN_OPTIONS: detect_stack_use_after_return=1 + LSAN_OPTIONS: suppressions=${{ github.workspace }}/scripts/lsan-suppressions.txt + TSAN_OPTIONS: enable_adaptive_delay=1 + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + persist-credentials: false + - uses: taiki-e/install-action@5b4d68e2e660441203ab128a23676f1e4faf1532 # v2.86.3 + with: + tool: rust@nightly-2026-09-12+rust-src+${{ matrix.target }} + fallback: none + # Leak suppressions match function names, so reports must be symbolized. + - name: Find a symbolizer + run: | + path=$(command -v llvm-symbolizer || true) + if [ -z "$path" ]; then + path=$(ls /usr/bin/llvm-symbolizer-* 2>/dev/null | sort -V | tail -n 1 || true) + fi + if [ -z "$path" ]; then + sudo apt-get update && sudo apt-get install -y llvm + path=$(command -v llvm-symbolizer) + fi + echo "ASAN_SYMBOLIZER_PATH=$path" >> "$GITHUB_ENV" + - name: Test suite + run: cargo test ${{ matrix.build-std }} -p shimforge --locked --no-fail-fast --target ${{ matrix.target }} --tests -- --test-threads=1 + - name: Parallel tests + if: success() || failure() + run: cargo test ${{ matrix.build-std }} -p shimforge --locked --target ${{ matrix.target }} --test parallel -- --test-threads=8 diff --git a/scripts/lsan-suppressions.txt b/scripts/lsan-suppressions.txt new file mode 100644 index 0000000..1b42a29 --- /dev/null +++ b/scripts/lsan-suppressions.txt @@ -0,0 +1,6 @@ +# LeakSanitizer suppressions for the AddressSanitizer job in .github/workflows/ci.yml. +# +# This unit test injects a failed patch write. routing::install_replacement then keeps +# the half-built route entry allocated on purpose, because another thread may still be +# reading it, so the entry and its two byte buffers are reported at exit. +leak:local_installation_errors_can_be_retried_and_cleanup_does_not_write_code