From d81b18d314bd82a01a7c02f7070cf0c8c96886ca Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Wed, 10 Jun 2026 07:46:29 +0200 Subject: [PATCH 1/5] ci(miri): parallelize with cargo-nextest (process-per-test) to use idle cores MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured root cause of the 45-min Miri job + lean-mem contention: `cargo miri test` runs ONE Miri process — a single-threaded interpreter — pinning one core for 45 min while the rest of the (ample) box sits idle. The CPU/mem doesn't vanish into contention (peak 15 concurrent jobs, ~22s queues); Miri just can't use it, and it hogs the scarce lean-mem runner for 45 min so other lean-mem jobs queue behind it. Switch to `cargo miri nextest run`: each test runs in its own process, so the runner's cores are actually used and wall-time collapses toward the slowest single test. `--test-threads 4` bounds concurrent Miri processes under the 24G shadow-memory ceiling (conservative start; raise after a green run shows peak RSS). Same test selection: the libtest `--skip ` list is translated to nextest's `-E 'not (test() | ...)'` (verified locally: 721 run / 412 excluded, identical modules). Adds a resource-print step so the log shows the box's real nproc/free. Refs: REQ-215, #509 Trace: skip --- .github/workflows/ci.yml | 62 +++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22ae788..1ba3231 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -412,7 +412,8 @@ jobs: # ── Miri (undefined behavior, pointer provenance) ─────────────────── miri: name: Miri - # lean-mem: Miri allocates aggressively; benefits from 24G ceiling. + # lean-mem: parallel Miri (nextest, process-per-test) trades RAM for cores — + # the 24G ceiling lets several Miri processes run at once. runs-on: [self-hosted, linux, x64, lean-mem] steps: - uses: actions/checkout@v6 @@ -420,32 +421,39 @@ jobs: with: components: miri - uses: Swatinem/rust-cache@v2 - - name: Run Miri - # Run safety-critical modules under Miri with tree borrows model. - # Uses pulseengine/rowan fork with Miri UB fixes (upstream: rust-analyzer/rowan#210). - # Skip: bazel/db (salsa internals), externals (spawns git), - # export/providers/test_scanner/yaml_edit (not safety-critical, slow under Miri). - # Skip yaml_cst/yaml_hir tests that create multi-item trees: rowan cursor - # deallocation UB with large trees under tree borrows (pulseengine/rowan#211). - # Single-item parser tests (25/26) pass clean. - # Also skip feature_model (constraint parsing builds rowan trees → same UB). - # Also skip doc_check (pulldown-cmark heavy → 30–90s/test under Miri, - # times out the job; business-logic tests, not memory-safety tests). - # Skip sexpr_eval and any test that goes through it (embed query, - # query::execute_sexpr, parse_query): all build rowan trees via - # s-expr parsing and hit the same cursor deallocation UB as - # yaml_cst/feature_model (pulseengine/rowan#211). - run: cargo miri test -p rivet-core --lib -- --skip bazel --skip db --skip externals --skip export --skip providers --skip test_scanner --skip yaml_edit --skip markdown --skip parse_actual_hazards --skip stpa_hazard --skip yaml_hir --skip feature_model --skip doc_check --skip sexpr_eval --skip query_embed --skip parse_query --skip execute_sexpr - # Bumped 15→30 during smithy migration: first run timed out at - # 15 min with the last printed test at the 11-min mark (i.e., - # the slow tests at the tail just ran past the budget on - # smithy's lean-mem class). Hosted may have been fine because - # of different tail-test perf characteristics. - # Bumped 30→45 (2026-06-10): under self-hosted-pool contention - # (clearing the #509 outage backlog) Miri ran past 30 min and - # timed out, failing the run while every required gate was green. - # 45 gives headroom on the lean-mem class without masking a real - # hang (a genuine UB loop still trips the budget). + - uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + - name: Show runner resources + # Make the "where do CPU/mem go" answer visible in the log: the box has + # plenty; single-process `cargo miri test` just used one core of it. + run: | + echo "cores (nproc): $(nproc)" + free -h + - name: Run Miri (nextest — process-per-test parallelism) + # `cargo miri test` ran ONE Miri process: a single-threaded interpreter + # pinning one core for ~45 min while the rest of the box sat idle. + # `cargo miri nextest run` runs each test in its own process, so the + # lean-mem runner's cores are actually used and wall-time collapses + # toward the slowest single test. `--test-threads` bounds concurrent + # Miri processes so total shadow-memory stays under the 24G ceiling — + # start conservative at 4, raise once a green run shows real peak-RSS + # headroom. + # + # SAME test selection as before: the old libtest `--skip ` list is + # translated to nextest's filterset `not (test() | ...)`. Skip + # bazel/db (salsa internals), externals (spawns git), + # export/providers/test_scanner/yaml_edit/markdown (not + # safety-critical, slow under Miri); yaml_hir/feature_model/sexpr_eval + # + its s-expr consumers (query_embed/parse_query/execute_sexpr) build + # multi-item rowan trees → cursor-dealloc UB under tree borrows + # (pulseengine/rowan#211); doc_check (pulldown-cmark heavy); + # parse_actual_hazards/stpa_hazard (slow). + run: | + cargo miri nextest run -p rivet-core --lib --test-threads 4 \ + -E 'not (test(bazel) | test(db) | test(externals) | test(export) | test(providers) | test(test_scanner) | test(yaml_edit) | test(markdown) | test(parse_actual_hazards) | test(stpa_hazard) | test(yaml_hir) | test(feature_model) | test(doc_check) | test(sexpr_eval) | test(query_embed) | test(parse_query) | test(execute_sexpr))' + # Generous headroom for a now-parallel job (was a single-process 45-min + # budget). Tighten once we see the real parallel wall-time. timeout-minutes: 45 env: MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-tree-borrows" From ef158cd9d6b426f295bae1adb4321c06529ff492 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Wed, 10 Jun 2026 15:20:14 +0200 Subject: [PATCH 2/5] =?UTF-8?q?ci(miri):=20use=2024=20of=20the=20runner's?= =?UTF-8?q?=2032=20cores=20(was=204)=20=E2=80=94=20box=20has=20125=20GiB,?= =?UTF-8?q?=20not=2024?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured the runner directly (the new resource-print step): 32 cores, 125 GiB RAM — the "lean-mem" label is a misnomer and memory is a non-constraint. At --test-threads 4 the parallel Miri job still timed out at 45 min (~678 tests × ~20s ÷ 4 ≈ 56 min); 28 of 32 cores sat idle. Raise to 24 threads: ~226 core-min ÷ 24 ≈ ~9-10 min, using ~72 GiB (well under 125). This is where the CPU/mem was "vanishing": into idle cores, because nothing was using them. Refs: REQ-215, #509 Trace: skip --- .github/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ba3231..9bfd209 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -434,11 +434,14 @@ jobs: # `cargo miri test` ran ONE Miri process: a single-threaded interpreter # pinning one core for ~45 min while the rest of the box sat idle. # `cargo miri nextest run` runs each test in its own process, so the - # lean-mem runner's cores are actually used and wall-time collapses - # toward the slowest single test. `--test-threads` bounds concurrent - # Miri processes so total shadow-memory stays under the 24G ceiling — - # start conservative at 4, raise once a green run shows real peak-RSS - # headroom. + # runner's cores are actually used and wall-time collapses toward + # total-work / threads. + # + # MEASURED on this runner (see "Show runner resources"): 32 cores, + # 125 GiB RAM — NOT memory-constrained. ~678 Miri tests at ~20s each + # ≈ 226 core-min of work. At --test-threads 4 that was still ~56 min + # (timed out); at 24 it's ~9-10 min, using ~24 * ~3 GiB ≈ 72 GiB, + # comfortably under 125 GiB. (The "lean-mem" label is a misnomer here.) # # SAME test selection as before: the old libtest `--skip ` list is # translated to nextest's filterset `not (test() | ...)`. Skip @@ -450,7 +453,7 @@ jobs: # (pulseengine/rowan#211); doc_check (pulldown-cmark heavy); # parse_actual_hazards/stpa_hazard (slow). run: | - cargo miri nextest run -p rivet-core --lib --test-threads 4 \ + cargo miri nextest run -p rivet-core --lib --test-threads 24 \ -E 'not (test(bazel) | test(db) | test(externals) | test(export) | test(providers) | test(test_scanner) | test(yaml_edit) | test(markdown) | test(parse_actual_hazards) | test(stpa_hazard) | test(yaml_hir) | test(feature_model) | test(doc_check) | test(sexpr_eval) | test(query_embed) | test(parse_query) | test(execute_sexpr))' # Generous headroom for a now-parallel job (was a single-process 45-min # budget). Tighten once we see the real parallel wall-time. From 72ed134214e4c36638a81e043dac94e96d668867 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Wed, 10 Jun 2026 17:52:44 +0200 Subject: [PATCH 3/5] ci(miri): scope PR gate to the unsafe/CST surface; full sweep nightly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correction to the earlier "24 threads thrash" claim: measured effective concurrency is 23x — parallelism works fine. The real problem is work VOLUME: the 678-test sweep is ~930 test-minutes (mean 104s/test, worst 557s) of mostly safe business logic (regex/glob/HTML) that Miri runs ~100-1000x slower than native and that has no `unsafe` to validate. No thread count fits that in a per-PR budget. So split it (the #498 nightly pattern): - PR/push `miri` → only the real UB surface: the SyntaxKind `transmute`s in the rowan CST parsers (sexpr + yaml_cst), 41 tests, ~3 min. This is what Miri is FOR. - nightly/manual `miri-full` → the full Miri-compatible sweep, 24 threads on the 125 GiB runner, 90-min budget, off the per-PR path. Refs: REQ-215, #509 Trace: skip --- .github/workflows/ci.yml | 74 ++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bfd209..722354c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -410,10 +410,16 @@ jobs: path: coverage-html/ # ── Miri (undefined behavior, pointer provenance) ─────────────────── + # PR/push: Miri over the actual UB SURFACE only. Measured (PR #521): the full + # 678-test sweep is ~930 test-minutes of mostly safe business logic (regex, + # glob, HTML) that Miri runs ~100-1000x slower than native — it can't fit a + # per-PR budget at any thread count, and provides ~no memory-safety value. + # Miri exists to validate `unsafe`; in rivet-core that's the SyntaxKind + # `transmute`s in the rowan-based CST parsers. Scope the PR gate to those + # (sexpr + yaml_cst); the FULL sweep runs nightly (miri-full, #498 pattern). miri: - name: Miri - # lean-mem: parallel Miri (nextest, process-per-test) trades RAM for cores — - # the 24G ceiling lets several Miri processes run at once. + name: Miri (safety surface) + if: github.event_name != 'schedule' runs-on: [self-hosted, linux, x64, lean-mem] steps: - uses: actions/checkout@v6 @@ -424,40 +430,42 @@ jobs: - uses: taiki-e/install-action@v2 with: tool: cargo-nextest - - name: Show runner resources - # Make the "where do CPU/mem go" answer visible in the log: the box has - # plenty; single-process `cargo miri test` just used one core of it. + - name: Run Miri over the unsafe/CST surface + # sexpr.rs + yaml_cst.rs hold the only real `unsafe` in rivet-core + # (`std::mem::transmute` of raw SyntaxKind for the rowan parsers). + # bazel.rs has the third transmute but its tests touch salsa/process + # internals — covered by the nightly full sweep, not the PR gate. The + # multi-item-tree rowan cursor UB (pulseengine/rowan#211) lives in + # yaml_hir/sexpr_eval/feature_model (nightly-skipped); the single-item + # sexpr/yaml_cst tests here pass clean. nextest spreads them across the + # cores (measured 32c/125G) so this is a couple of minutes. run: | - echo "cores (nproc): $(nproc)" - free -h - - name: Run Miri (nextest — process-per-test parallelism) - # `cargo miri test` ran ONE Miri process: a single-threaded interpreter - # pinning one core for ~45 min while the rest of the box sat idle. - # `cargo miri nextest run` runs each test in its own process, so the - # runner's cores are actually used and wall-time collapses toward - # total-work / threads. - # - # MEASURED on this runner (see "Show runner resources"): 32 cores, - # 125 GiB RAM — NOT memory-constrained. ~678 Miri tests at ~20s each - # ≈ 226 core-min of work. At --test-threads 4 that was still ~56 min - # (timed out); at 24 it's ~9-10 min, using ~24 * ~3 GiB ≈ 72 GiB, - # comfortably under 125 GiB. (The "lean-mem" label is a misnomer here.) - # - # SAME test selection as before: the old libtest `--skip ` list is - # translated to nextest's filterset `not (test() | ...)`. Skip - # bazel/db (salsa internals), externals (spawns git), - # export/providers/test_scanner/yaml_edit/markdown (not - # safety-critical, slow under Miri); yaml_hir/feature_model/sexpr_eval - # + its s-expr consumers (query_embed/parse_query/execute_sexpr) build - # multi-item rowan trees → cursor-dealloc UB under tree borrows - # (pulseengine/rowan#211); doc_check (pulldown-cmark heavy); - # parse_actual_hazards/stpa_hazard (slow). + cargo miri nextest run -p rivet-core --lib --test-threads 24 \ + -E 'test(sexpr::) | test(yaml_cst::)' + timeout-minutes: 20 + + # Nightly + manual: the FULL Miri sweep — every Miri-compatible test (all but + # the externals/process-spawning + rowan#211 multi-item failures the suite + # has always skipped). Off the per-PR path (#498) so its ~50-min runtime + # never blocks a PR; 24 threads on the 125 GiB runner, generous timeout. + miri-full: + name: Miri (full, nightly) + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: [self-hosted, linux, x64, lean-mem] + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@nightly + with: + components: miri + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + - name: Run full Miri sweep run: | cargo miri nextest run -p rivet-core --lib --test-threads 24 \ -E 'not (test(bazel) | test(db) | test(externals) | test(export) | test(providers) | test(test_scanner) | test(yaml_edit) | test(markdown) | test(parse_actual_hazards) | test(stpa_hazard) | test(yaml_hir) | test(feature_model) | test(doc_check) | test(sexpr_eval) | test(query_embed) | test(parse_query) | test(execute_sexpr))' - # Generous headroom for a now-parallel job (was a single-process 45-min - # budget). Tighten once we see the real parallel wall-time. - timeout-minutes: 45 + timeout-minutes: 90 env: MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-tree-borrows" From df3b9743911cfcd14db7a78425a8952d21a36fea Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Wed, 10 Jun 2026 19:14:59 +0200 Subject: [PATCH 4/5] ci(miri): restore -Zmiri-tree-borrows on the scoped job (my regression) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When I split Miri into scoped (PR) + full (nightly) I kept MIRIFLAGS on miri-full but dropped it from the scoped job, so it ran under default Stacked Borrows and surfaced rowan's thin-token SharedReadOnly zero-size-retag UB — which I mistakenly reported as a fresh finding. Research (subagent, source- grounded) confirms: rust-analyzer/rowan #210/#211/#212 are all closed UNMERGED (upstream is rewriting rowan, not patching); the maintainer holds that rowan should pass Tree Borrows and the failing Stacked Borrows rule is unlikely to survive Rust's final aliasing model. Our fork pin is sound under Tree Borrows, which the original job already used. Restore `MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-tree-borrows` on the scoped job (matching miri-full and the pre-split job). Update the rowan pin comment in Cargo.toml to reflect the closed-unmerged/rewrite reality. Refs: REQ-215, #509 Trace: skip --- .github/workflows/ci.yml | 16 +++++++++++----- Cargo.toml | 13 ++++++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 722354c..59535f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -434,14 +434,20 @@ jobs: # sexpr.rs + yaml_cst.rs hold the only real `unsafe` in rivet-core # (`std::mem::transmute` of raw SyntaxKind for the rowan parsers). # bazel.rs has the third transmute but its tests touch salsa/process - # internals — covered by the nightly full sweep, not the PR gate. The - # multi-item-tree rowan cursor UB (pulseengine/rowan#211) lives in - # yaml_hir/sexpr_eval/feature_model (nightly-skipped); the single-item - # sexpr/yaml_cst tests here pass clean. nextest spreads them across the - # cores (measured 32c/125G) so this is a couple of minutes. + # internals — covered by the nightly full sweep, not the PR gate. + # + # MUST run under Tree Borrows. rowan's thin-token provenance trips a + # *Stacked Borrows* rule (rust-analyzer/rowan #210/#211/#212, all closed + # UNMERGED — upstream is rewriting rowan, not patching it). The + # maintainer's position: "rowan should pass Tree Borrows; that Stacked + # Borrows rule is unlikely to survive Rust's final aliasing model." Our + # `fix/miri-soundness-v2` fork pin passes under TB. (Without + # `-Zmiri-tree-borrows` this hits a SharedReadOnly zero-size-retag UB.) run: | cargo miri nextest run -p rivet-core --lib --test-threads 24 \ -E 'test(sexpr::) | test(yaml_cst::)' + env: + MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-tree-borrows" timeout-minutes: 20 # Nightly + manual: the FULL Miri sweep — every Miri-compatible test (all but diff --git a/Cargo.toml b/Cargo.toml index 933777a..c370e85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,9 +107,16 @@ quick-xml = { version = "0.37", features = ["serialize", "overlapped-lists"] } wasmtime = { version = "43", features = ["component-model"] } wasmtime-wasi = "43" -# Lossless syntax trees — using fork with Miri UB fixes until upstream merges. -# Upstream issues: rust-analyzer/rowan#192, #163, #108 -# Our PR: rust-analyzer/rowan#210 +# Lossless syntax trees — pinned to our fork's Miri-soundness branch. +# Upstream will NOT take a patch: rust-analyzer/rowan #210/#211/#212 were all +# closed UNMERGED — the maintainer intends a full rowan rewrite (rust-analyzer +# #15710/#18285, "Future Rowan" GSoC) and considers the residual failure a +# Stacked-Borrows-only rule ("rowan should pass Tree Borrows; that SB rule is +# unlikely to survive Rust's final aliasing model"). So this is a permanent +# maintenance fork until that rewrite lands. We run Miri under Tree Borrows +# (`-Zmiri-tree-borrows`, see ci.yml), under which this pin is sound; the +# residual SB token UB (#211) is fixed only on phall1's unmerged #212 branch, +# to adopt if we ever need SB coverage. Upstream issues: rowan#192, #163, #108. rowan = { git = "https://github.com/pulseengine/rowan.git", branch = "fix/miri-soundness-v2" } # Markdown rendering From b4d5cd2689ffbc7296138e547fafd74dfaf03db0 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Wed, 10 Jun 2026 19:42:01 +0200 Subject: [PATCH 5/5] =?UTF-8?q?build(rowan):=20bump=20fork=20pin=20to=20v3?= =?UTF-8?q?=20(phall1=20#212=20token+cursor=20DST)=20=E2=86=92=20Miri=20SB?= =?UTF-8?q?-sound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopts phall1's unmerged rust-analyzer/rowan#212 into our fork as fix/miri-soundness-v3 (= v2 + `48a1b5e` GreenToken-as-DST + `9e7abd1` cursor SB fix). v2 was only sound under Tree Borrows; the residual thin-token SharedReadOnly zero-size-retag UB is now fixed, so rivet's rowan parsers are sound under BOTH Stacked Borrows and Tree Borrows. - Cargo.toml: pin branch v2 → v3; comment rewritten to the closed-unmerged / upstream-rewrite reality + the SB-soundness. - Cargo.lock: rowan git rev → 9e7abd1. - ci.yml: the scoped PR Miri job now gates under STACKED BORROWS (strictest; drop -Zmiri-tree-borrows) since v3 makes it sound. miri-full stays on Tree Borrows for the broader (incl. mutable-path) sweep. Confirmed: rivet-core builds against v3; native sexpr::/yaml_cst:: tests pass; rowan fork v3 builds + carries phall1's miri_node_cache_rehash / miri_cursor_free_sb regression tests; actionlint clean. Refs: REQ-215, #509 Trace: skip --- .github/workflows/ci.yml | 16 ++++++++-------- Cargo.lock | 2 +- Cargo.toml | 19 ++++++++++--------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59535f7..8380516 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -436,18 +436,18 @@ jobs: # bazel.rs has the third transmute but its tests touch salsa/process # internals — covered by the nightly full sweep, not the PR gate. # - # MUST run under Tree Borrows. rowan's thin-token provenance trips a - # *Stacked Borrows* rule (rust-analyzer/rowan #210/#211/#212, all closed - # UNMERGED — upstream is rewriting rowan, not patching it). The - # maintainer's position: "rowan should pass Tree Borrows; that Stacked - # Borrows rule is unlikely to survive Rust's final aliasing model." Our - # `fix/miri-soundness-v2` fork pin passes under TB. (Without - # `-Zmiri-tree-borrows` this hits a SharedReadOnly zero-size-retag UB.) + # Runs under STACKED BORROWS (Miri default, the strictest model). This + # is sound thanks to rowan fork v3 (= v2 + phall1's #212: GreenToken as a + # DST + cursor SB fixes), which widens the token retag past the slice + # tail. The pre-v3 pin hit a SharedReadOnly zero-size-retag UB here and + # needed `-Zmiri-tree-borrows` to dodge it; v3 makes both SB and TB pass, + # so we gate on the stricter one. (rust-analyzer/rowan #210/#211/#212 are + # all closed-unmerged; upstream is rewriting rowan — Cargo.toml.) run: | cargo miri nextest run -p rivet-core --lib --test-threads 24 \ -E 'test(sexpr::) | test(yaml_cst::)' env: - MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-tree-borrows" + MIRIFLAGS: "-Zmiri-disable-isolation" timeout-minutes: 20 # Nightly + manual: the FULL Miri sweep — every Miri-compatible test (all but diff --git a/Cargo.lock b/Cargo.lock index 8bc7d08..ffbbff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2938,7 +2938,7 @@ dependencies = [ [[package]] name = "rowan" version = "0.16.2" -source = "git+https://github.com/pulseengine/rowan.git?branch=fix%2Fmiri-soundness-v2#dcbece400019397b97764070435eba62c7aa5336" +source = "git+https://github.com/pulseengine/rowan.git?branch=fix%2Fmiri-soundness-v3#9e7abd1161634377d278cde0c504c101ac003941" dependencies = [ "countme", "hashbrown 0.15.5", diff --git a/Cargo.toml b/Cargo.toml index c370e85..0bae4e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,17 +107,18 @@ quick-xml = { version = "0.37", features = ["serialize", "overlapped-lists"] } wasmtime = { version = "43", features = ["component-model"] } wasmtime-wasi = "43" -# Lossless syntax trees — pinned to our fork's Miri-soundness branch. +# Lossless syntax trees — pinned to our fork's Miri-soundness branch v3. # Upstream will NOT take a patch: rust-analyzer/rowan #210/#211/#212 were all # closed UNMERGED — the maintainer intends a full rowan rewrite (rust-analyzer -# #15710/#18285, "Future Rowan" GSoC) and considers the residual failure a -# Stacked-Borrows-only rule ("rowan should pass Tree Borrows; that SB rule is -# unlikely to survive Rust's final aliasing model"). So this is a permanent -# maintenance fork until that rewrite lands. We run Miri under Tree Borrows -# (`-Zmiri-tree-borrows`, see ci.yml), under which this pin is sound; the -# residual SB token UB (#211) is fixed only on phall1's unmerged #212 branch, -# to adopt if we ever need SB coverage. Upstream issues: rowan#192, #163, #108. -rowan = { git = "https://github.com/pulseengine/rowan.git", branch = "fix/miri-soundness-v2" } +# #15710/#18285, "Future Rowan" GSoC). So this is a permanent maintenance fork +# until that rewrite lands. +# v3 = v2 (GreenNode DST + cursor parent-provenance) PLUS phall1's unmerged +# #212: GreenToken made a DST (`token.rs`/`arc.rs`) to widen the retag past the +# slice tail, and an SB fix in `cursor.rs` free()/to_next_sibling. With v3 the +# parsers are sound under BOTH Stacked Borrows and Tree Borrows — so CI now +# gates the unsafe/CST surface under Stacked Borrows (the strictest model; see +# ci.yml). Upstream issues: rowan#192, #163, #108. +rowan = { git = "https://github.com/pulseengine/rowan.git", branch = "fix/miri-soundness-v3" } # Markdown rendering pulldown-cmark = { version = "0.12", default-features = false, features = ["html"] }