Miri #217
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Miri: Rust undefined behavior detector | |
| # Runs tests under the Miri interpreter to catch UB, invalid memory access, | |
| # and unsafe code issues that normal test runs cannot detect. | |
| # | |
| # **Not on pull requests**, since 2026-08-22. It is the longest job in this repo by a wide margin — | |
| # a median of 9 minutes against `ci.yml`'s 1 and `coverage.yml`'s 4 — and running it on the PR *and* | |
| # on the merge meant paying that twice for every change. It has never failed: 92 successes and 6 | |
| # runs cancelled by the rule below, across the last hundred. So it moves to where one run per change | |
| # is enough, and stops being the thing a PR waits on. | |
| # | |
| # A **path filter** was the first proposal and was measured rather than adopted. Skipping Miri for | |
| # changes that cannot affect it does not work in this crate: filtering to the obviously-unsafe | |
| # modules would have to exclude `dbgeng.rs` (23 tests Miri executes) and `pool/**` (81, decoding raw | |
| # kernel structures out of byte slices), which is the most UB-prone code here; and a filter that | |
| # keeps everything Miri's run could depend on — `src/**`, `Cargo.*`, `build.rs` — would have skipped | |
| # 2 of the last 30 merged pull requests. Nearly every change here touches `src/`. | |
| # | |
| # What this costs: a UB regression is found on `main` rather than on the branch. That is a small | |
| # difference in a repo where `main` is not protected and the author merges their own work, and | |
| # `workflow_dispatch` runs it against a branch when a change is one Miri would have something to say | |
| # about. | |
| name: Miri | |
| on: | |
| push: | |
| branches: [ "main" ] | |
| # The toolchain is the other thing that can turn this red, and it moves on its own: Miri ships | |
| # with nightly, and a nightly that tightens a rule finds the same code newly wrong. A weekly run | |
| # separates "the code changed" from "the checker changed", which a push-only schedule cannot. | |
| schedule: | |
| - cron: "0 6 * * 1" | |
| workflow_dispatch: | |
| concurrency: | |
| # A superseded push should cancel its predecessor: two merges landing minutes apart otherwise | |
| # leave the first still checking code nobody has any more. The rule was written for pull | |
| # requests, where five pushes to one PR meant five concurrent Miri runs — most of an hour of | |
| # runner time nobody would read, back when a run took ~28 minutes — and it outlives them because | |
| # the shape is the same and the reasoning never depended on the duration. | |
| # | |
| # Manual dispatches and scheduled runs are deliberately excluded. Both share `github.ref` with | |
| # the branch's own run — `main`, in the scheduled case — so a shared group would have them cancel | |
| # each other: a Monday run would kill the merge that happened to land beside it, or be killed by | |
| # it, and a repeat dispatch would kill the run someone had just asked for. Keying them by run id | |
| # gives each one a group of its own: never cancelled, never cancelling. | |
| group: miri-${{ github.ref }}-${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') && github.run_id || 'auto' }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| miri: | |
| name: Miri | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: microsoft/setup-msbuild@v3 | |
| with: | |
| vs-architecture: x64 | |
| - name: Install nightly Rust with Miri | |
| run: | | |
| rustup toolchain install nightly --component miri,rust-src | |
| rustup override set nightly | |
| # After the toolchain install, never before: rust-cache derives its key from the active | |
| # rustc, so caching earlier would key everything on stable and restore the wrong artifacts. | |
| # A nightly bump changes the key and rebuilds, which is what we want — the Miri sysroot in | |
| # `cache-directories` is toolchain-specific and must never outlive the toolchain that | |
| # produced it. Within one nightly this turns `cargo miri setup` into a no-op. | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: miri | |
| cache-directories: "~/AppData/Local/rust-lang/miri/cache" | |
| - uses: taiki-e/install-action@nextest | |
| - name: Setup Miri sysroot | |
| run: cargo miri setup | |
| # nextest, not `cargo miri test`, because libtest's threads all share one interpreter — | |
| # Miri interleaves them rather than running them in parallel, so a multi-core runner buys | |
| # nothing. nextest puts each test in its own process, which is real parallelism: 27.5s | |
| # against 43.7s locally. The cost is ~1.3s of sysroot load per test process, so this is a | |
| # win only while the suite is short; revisit if a single test starts to dominate again. | |
| - name: Run Miri | |
| env: | |
| MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-ignore-leaks" | |
| run: cargo miri nextest run | |
| # nextest cannot run doctests, and ci.yml is on nextest too — without this step the crate's | |
| # doctests would run nowhere in CI. It is ~0.3s on top of an already-built tree. | |
| - name: Run Miri doctests | |
| env: | |
| MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-ignore-leaks" | |
| run: cargo miri test --doc |