fix: address Miri UB in Arc, ThinArc, green types, and cursor#211
fix: address Miri UB in Arc, ThinArc, green types, and cursor#211avrabe wants to merge 3 commits into
Conversation
Comprehensive fixes for Miri UB under both stacked and tree borrows: Arc: - clone/drop/is_unique: access refcount via ptr::addr_of! on raw pointer instead of through &ArcInner reference (avoids provenance narrowing) - ThinArc::clone/drop: operate on refcount directly via raw pointer instead of going through with_arc → transient Arc Green types: - GreenNodeData wraps fat Repr (unsized HeaderSlice<H, [T]>) so &GreenNodeData has provenance covering the full slice - GreenNode/GreenToken::deref transmute from fat refs (correct provenance) - GreenNode/GreenToken::into_raw extract pointer from ThinArc directly instead of going through Deref → &Data → NonNull::from - GreenTokenData::text() uses raw pointer arithmetic for slice access - thin_to_thick made pub(crate), HeaderSlice fields made pub(crate) Cursor: - Cell<NonNull<GreenNodeData>> accessed via as_ptr().read() instead of get() to preserve allocation provenance through the Cell Status: ALL tests pass under -Zmiri-tree-borrows (4/4 non-mutable tests). Under stacked borrows, only the mutable tree path (clone_for_update) still fails due to Cell provenance limitations inherent to that model. Upstream: rust-analyzer#192, rust-analyzer#163, rust-analyzer#108
Make elided lifetimes explicit in return types where &self borrows: - GreenChild::as_ref() -> GreenElementRef<'_> - NodeData::green_siblings() -> slice::Iter<'_, GreenChild> These warnings caused CI failure with RUSTFLAGS="-D warnings".
1203c31 to
82c082c
Compare
82c082c to
dcbece4
Compare
… cy-yrz (rowan 0.15→0.16 bump) cy-h9n (PR #42): workspace-wide rename to all cyrs-*; user-facing identifiers (cypher-project.toml, // cypher-fmt: off, C ABI, Python module, LSP language ID, tree-sitter dir, fuzz dict, three binaries) intentionally kept. cy-934: 5-year-old upstream UB (issues #163, #108, #192) has an in-flight competing fix at rust-analyzer/rowan#211 (untouched 1mo, no reviews). Plan: test #211 against our rehash repro, comment with cyrs CI links, file complementary PR if #211 doesn't cover the green/node_cache.rs path. cy-yrz: latest stable rowan is 0.16.1; we pin "0.15". Port the cy-pom fix to a 0.16 branch on phall1/rowan, bump cyrs, point patch.crates-io at it, then drop the cfg(not(miri)) gates from cy-eu2 + cy-863. Blocks on cy-934 so we can use the upstream branch if it lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
tested this against a NodeCache rehash repro from cyrs (cypher-frontend). arc/cursor changes here help, but cyrs CI hits it twice deterministically:
opened #212 with the same DST promotion for tokens. with both stacked the cyrs repro passes. self-contained miri test: https://github.com/phall1/rowan/blob/fix/sb-node-cache-rehash/tests/miri_node_cache_rehash.rs one more pre-existing SB hit on this branch for the record: |
…fixes; drop cy-eu2 miri gates (#43) * WIP: cy-yrz — rowan 0.16 + patched fork; partial miri gate removal bumps rowan 0.15 -> 0.16 (zero API breaks observed) and wires [patch.crates-io] at phall1/rowan branch fix/sb-node-cache-rehash (stacks rust-analyzer/rowan#211 + our #212; branch is already on 0.16.2, no rebase needed). removed the cfg(not(miri)) gates on: - cyrs-fmt::tests::idempotent_around_fmt_off_directive (cy-eu2) - cyrs-fmt::tests::fixpoint_on_assorted_fixtures (cy-eu2) both pass under `cargo +nightly miri test -p cyrs-fmt` with the patched rowan in place. the NodeCache rehash UB those tests were exposing is fixed by the #211+#212 stack. removed the cfg(not(miri)) gate on: - cyrs-plan::lower::tests::lower_statement_no_panic_on_unresolved_inside_patternpredicate_text (cy-863) this one still fails under miri — but with a *different* SB violation. not the NodeCache rehash bug; this is in `rowan::cursor::free` -> `Box::<NodeData>::from_raw` while a sibling SyntaxNodeChildren iterator still holds a SharedReadOnly retag of the same allocation. Strongly-protected. Reproduces on the patched fork; #211+#212 don't touch cursor.rs so this was never going to fix it. per cy-yrz instructions: bailing rather than re-adding the gate as a bandaid. WIP committed for inspection. Need a follow-up bead to diagnose the cursor::free SB issue (likely needs a third PR upstream, or a refactor of how cursor refcounts NodeData). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(plan): keep cy-863 miri gate, point at cy-208 (cursor::free SB) Removing the gate exposes a third distinct upstream rowan SB violation in cursor::free (Box::from_raw races a SyntaxNodeChildren iterator's retag). Filed cy-208; gate stays until the upstream fix lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(miri): add -Zmiri-disable-isolation so insta snapshot tests can open .snap files With the rowan SB rehash bug now fixed via the patched fork (this PR), miri progresses past the SB block and hits the next failure: insta opens .snap files at runtime, which miri's default isolation rejects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Patrick Hall <phall@macbookpro.mynetworksettings.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hi — we use rowan in two safety-critical projects (rivet, spar) and ran into the Miri UB documented in #192, #163, #108. We took a shot at fixing it and would appreciate your feedback on the approach.
What this changes
The core issue is that Miri's borrow models (stacked and tree borrows) flag provenance violations when:
HeaderSlice<H, [T; 0]>) are used to access data beyond the thin layout&NodeDatacontaining Cell fields freezes the allocation tag, preventing later deallocationCell::get()or&ArcInnercreates references that narrow provenanceThe fix touches 4 files (+152/-63 lines):
&ArcInnerreferences; ThinArc clone/drop without transient ArcGreenNodeDatawraps fatRepr(unsized) so provenance covers the full slice;into_rawextracts pointer from ThinArc directlytext()via raw pointer arithmetic;into_raw/derefvia ThinArc pointerUnsafeCell;NodeData::self_allocfield preserves original allocation provenance for deallocation; refcount decrement via raw*mut u32What we DON'T know
self_allocfield adds 8 bytes per NodeData. The UnsafeCell wrapper and raw pointer access patterns might affect optimization. We'd really appreciate if you could run your benchmarks.clone_for_updatetests (ensure_mut_*) still fail under Miri. Our fix focuses on the immutable tree path. The mutable path has additional provenance issues in the singly-linked list infrastructure.Testing
cargo test --lib— 6/6 passcargo +nightly miri test --lib -- --skip ensure_mut— 4/4 pass under-Zmiri-tree-borrowsHappy to iterate on this based on your feedback. Thank you for maintaining rowan — it's a great library.