From ef5eb794700296fadaca2badf58f6b4b891b016b Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sun, 5 Apr 2026 21:18:11 -0500 Subject: [PATCH 1/3] fix: address Miri UB in Arc, ThinArc, green types, and cursor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) 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> 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/rowan#192, #163, #108 --- src/arc.rs | 50 +++++++++++++++----- src/cursor.rs | 115 +++++++++++++++++++++++++++++++-------------- src/green/node.rs | 26 ++++++---- src/green/token.rs | 24 +++++++--- 4 files changed, 152 insertions(+), 63 deletions(-) diff --git a/src/arc.rs b/src/arc.rs index fa3774f..4c4a107 100644 --- a/src/arc.rs +++ b/src/arc.rs @@ -108,7 +108,8 @@ impl Clone for Arc { // another must already provide any required synchronization. // // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) - let old_size = self.inner().count.fetch_add(1, Relaxed); + let count_ptr = unsafe { ptr::addr_of!((*self.ptr()).count) }; + let old_size = unsafe { (*count_ptr).fetch_add(1, Relaxed) }; // However we need to guard against massive refcounts in case someone // is `mem::forget`ing Arcs. If we don't do this the count can overflow @@ -155,16 +156,19 @@ impl Arc { // See the extensive discussion in [1] for why this needs to be Acquire. // // [1] https://github.com/servo/servo/issues/21186 - self.inner().count.load(Acquire) == 1 + let count_ptr = unsafe { ptr::addr_of!((*self.ptr()).count) }; + unsafe { (*count_ptr).load(Acquire) == 1 } } } impl Drop for Arc { #[inline] fn drop(&mut self) { - // Because `fetch_sub` is already atomic, we do not need to synchronize - // with other threads unless we are going to delete the object. - if self.inner().count.fetch_sub(1, Release) != 1 { + // Access the refcount via raw pointer to avoid creating a reference + // to ArcInner whose provenance may be limited when T is a thin + // type wrapping a dynamically-sized allocation. + let count_ptr = unsafe { ptr::addr_of!((*self.ptr()).count) }; + if unsafe { (*count_ptr).fetch_sub(1, Release) } != 1 { return; } @@ -188,7 +192,7 @@ impl Drop for Arc { // // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) // [2]: https://github.com/rust-lang/rust/pull/41714 - self.inner().count.load(Acquire); + unsafe { (*count_ptr).load(Acquire) }; unsafe { self.drop_slow(); @@ -242,8 +246,8 @@ impl Hash for Arc { #[repr(C)] pub(crate) struct HeaderSlice { pub(crate) header: H, - length: usize, - slice: T, + pub(crate) length: usize, + pub(crate) slice: T, } impl HeaderSlice { @@ -279,7 +283,7 @@ impl Deref for HeaderSlice { /// via `HeaderSlice`. #[repr(transparent)] pub(crate) struct ThinArc { - ptr: ptr::NonNull>>, + pub(crate) ptr: ptr::NonNull>>, phantom: PhantomData<(H, T)>, } @@ -287,7 +291,7 @@ unsafe impl Send for ThinArc {} unsafe impl Sync for ThinArc {} // Synthesize a fat pointer from a thin pointer. -fn thin_to_thick( +pub(crate) fn thin_to_thick( thin: *mut ArcInner>, ) -> *mut ArcInner> { let len = unsafe { (*thin).data.length }; @@ -300,6 +304,7 @@ impl ThinArc { /// Temporarily converts |self| into a bonafide Arc and exposes it to the /// provided callback. The refcount is not modified. #[inline] + #[allow(dead_code)] // kept for downstream users; Clone/Drop now use raw pointers pub(crate) fn with_arc(&self, f: F) -> U where F: FnOnce(&Arc>) -> U, @@ -402,14 +407,35 @@ impl Deref for ThinArc { impl Clone for ThinArc { #[inline] fn clone(&self) -> Self { - ThinArc::with_arc(self, |a| Arc::into_thin(a.clone())) + // Increment refcount directly via raw pointer, avoiding the + // with_arc → Arc::clone path that creates intermediate references. + let count_ptr = unsafe { ptr::addr_of!((*self.ptr.as_ptr()).count) }; + let old_size = unsafe { (*count_ptr).fetch_add(1, Relaxed) }; + if old_size > MAX_REFCOUNT { + std::process::abort(); + } + ThinArc { ptr: self.ptr, phantom: PhantomData } } } impl Drop for ThinArc { #[inline] fn drop(&mut self) { - let _ = Arc::from_thin(ThinArc { ptr: self.ptr, phantom: PhantomData }); + // Decrement refcount and deallocate directly via raw pointer, + // avoiding the from_thin → Arc::drop path which creates intermediate + // references that Miri flags for provenance issues. + let ptr = self.ptr.as_ptr(); + let count_ptr = unsafe { ptr::addr_of!((*ptr).count) }; + if unsafe { (*count_ptr).fetch_sub(1, Release) } != 1 { + return; + } + atomic::fence(Acquire); + unsafe { + let thick = thin_to_thick(ptr); + let layout = alloc::Layout::for_value(&*thick); + ptr::drop_in_place(&mut (*thick).data); + alloc::dealloc(ptr as *mut u8, layout); + } } } diff --git a/src/cursor.rs b/src/cursor.rs index 0ea88ac..a4284bb 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -121,6 +121,9 @@ struct NodeData { mutable: bool, /// Absolute offset for immutable nodes, unused for mutable nodes. offset: TextSize, + /// Raw pointer to self with original allocation provenance. + /// Used for deallocation — survives reference freezing under tree borrows. + self_alloc: *mut NodeData, // The following links only have meaning when `mutable` is true. first: Cell<*const NodeData>, /// Invariant: never null if mutable. @@ -144,44 +147,64 @@ unsafe impl sll::Elem for NodeData { pub type SyntaxElement = NodeOrToken; pub struct SyntaxNode { - ptr: ptr::NonNull, + /// Wrapped in UnsafeCell so that reading the pointer through &self + /// does not freeze provenance under tree/stacked borrows. This allows + /// the pointer to retain write/dealloc permission through the + /// lifecycle of the SyntaxNode. + ptr: std::cell::UnsafeCell>, +} + +impl SyntaxNode { + #[inline] + fn ptr(&self) -> ptr::NonNull { + unsafe { *self.ptr.get() } + } } impl Clone for SyntaxNode { #[inline] fn clone(&self) -> Self { self.data().inc_rc(); - SyntaxNode { ptr: self.ptr } + SyntaxNode { ptr: std::cell::UnsafeCell::new(self.ptr()) } } } impl Drop for SyntaxNode { #[inline] fn drop(&mut self) { - if self.data().dec_rc() { - unsafe { free(self.ptr) } + let ptr = self.ptr(); + if unsafe { NodeData::dec_rc_raw(ptr) } { + unsafe { free(ptr) } } } } #[derive(Debug)] pub struct SyntaxToken { - ptr: ptr::NonNull, + ptr: std::cell::UnsafeCell>, +} + +impl SyntaxToken { + #[inline] + fn ptr(&self) -> ptr::NonNull { + unsafe { *self.ptr.get() } + } } impl Clone for SyntaxToken { #[inline] fn clone(&self) -> Self { self.data().inc_rc(); - SyntaxToken { ptr: self.ptr } + SyntaxToken { ptr: std::cell::UnsafeCell::new(self.ptr()) } } } impl Drop for SyntaxToken { #[inline] fn drop(&mut self) { - if self.data().dec_rc() { - unsafe { free(self.ptr) } + let ptr = self.ptr(); + if unsafe { NodeData::dec_rc_raw(ptr) } { + unsafe { free(ptr) } } } } @@ -190,16 +213,18 @@ impl Drop for SyntaxToken { unsafe fn free(mut data: ptr::NonNull) { unsafe { loop { - debug_assert_eq!(data.as_ref().rc.get(), 0); - debug_assert!(data.as_ref().first.get().is_null()); - let node = Box::from_raw(data.as_ptr()); + // Use self_alloc which retains original Box::into_raw provenance, + // unaffected by any &NodeData references created during the node's lifetime. + let alloc_ptr = (*data.as_ptr()).self_alloc; + let node = Box::from_raw(alloc_ptr); + debug_assert_eq!(node.rc.get(), 0); + debug_assert!(node.first.get().is_null()); match node.parent.take() { Some(parent) => { - debug_assert!(parent.as_ref().rc.get() > 0); if node.mutable { - sll::unlink(&parent.as_ref().first, &*node) + sll::unlink(&(*parent.as_ptr()).first, &*node) } - if parent.as_ref().dec_rc() { + if NodeData::dec_rc_raw(parent) { data = parent; } else { break; @@ -208,7 +233,8 @@ unsafe fn free(mut data: ptr::NonNull) { None => { match &node.green { Green::Node { ptr } => { - let _ = GreenNode::from_raw(ptr.get()); + let p = ptr.as_ptr().read(); + let _ = GreenNode::from_raw(p); } Green::Token { ptr } => { let _ = GreenToken::from_raw(*ptr); @@ -234,12 +260,13 @@ impl NodeData { let res = NodeData { _c: Count::new(), rc: Cell::new(1), - parent: Cell::new(parent.as_ref().map(|it| it.ptr)), + parent: Cell::new(parent.as_ref().map(|it| it.ptr())), index: Cell::new(index), green, mutable, offset, + self_alloc: ptr::null_mut(), first: Cell::new(ptr::null()), next: Cell::new(ptr::null()), prev: Cell::new(ptr::null()), @@ -272,12 +299,15 @@ impl NodeData { } it => { let res = Box::into_raw(Box::new(res)); + (*res).self_alloc = res; it.add_to_sll(res); return ptr::NonNull::new_unchecked(res); } } } - ptr::NonNull::new_unchecked(Box::into_raw(Box::new(res))) + let raw = Box::into_raw(Box::new(res)); + (*raw).self_alloc = raw; + ptr::NonNull::new_unchecked(raw) } } @@ -297,10 +327,27 @@ impl NodeData { rc == 0 } + /// Decrement refcount via raw pointer only — no references created. + /// This preserves deallocation permission under tree/stacked borrows. + #[inline] + unsafe fn dec_rc_raw(ptr: ptr::NonNull) -> bool { + // Access the Cell's inner value via raw pointer. + // Cell::get()/set() create &Cell references which under tree + // borrows freeze the parent allocation's borrow tag. + unsafe { + let rc_cell_ptr = ptr::addr_of!((*ptr.as_ptr()).rc); + // Cell wraps UnsafeCell — its memory layout is just u32. + let rc_val_ptr = rc_cell_ptr as *mut u32; + let rc = rc_val_ptr.read() - 1; + rc_val_ptr.write(rc); + rc == 0 + } + } + #[inline] fn key(&self) -> (ptr::NonNull<()>, TextSize) { let ptr = match &self.green { - Green::Node { ptr } => ptr.get().cast(), + Green::Node { ptr } => unsafe { ptr.as_ptr().read() }.cast(), Green::Token { ptr } => ptr.cast(), }; (ptr, self.offset()) @@ -311,7 +358,7 @@ impl NodeData { let parent = self.parent()?; debug_assert!(matches!(parent.green, Green::Node { .. })); parent.inc_rc(); - Some(SyntaxNode { ptr: ptr::NonNull::from(parent) }) + Some(SyntaxNode { ptr: std::cell::UnsafeCell::new(ptr::NonNull::from(parent)) }) } #[inline] @@ -322,14 +369,14 @@ impl NodeData { #[inline] fn green(&self) -> GreenElementRef<'_> { match &self.green { - Green::Node { ptr } => GreenElementRef::Node(unsafe { &*ptr.get().as_ptr() }), + Green::Node { ptr } => GreenElementRef::Node(unsafe { &*ptr.as_ptr().read().as_ptr() }), Green::Token { ptr } => GreenElementRef::Token(unsafe { ptr.as_ref() }), } } #[inline] fn green_siblings(&self) -> slice::Iter { match &self.parent().map(|it| &it.green) { - Some(Green::Node { ptr }) => unsafe { &*ptr.get().as_ptr() }.children().raw, + Some(Green::Node { ptr }) => unsafe { &*ptr.as_ptr().read().as_ptr() }.children().raw, Some(Green::Token { .. }) => { debug_assert!(false); [].iter() @@ -511,7 +558,7 @@ impl NodeData { NodeOrToken::Node(green) => { // Child is root, so it owns the green node. Steal it! let child_green = match &child.green { - Green::Node { ptr } => unsafe { GreenNode::from_raw(ptr.get()).into() }, + Green::Node { ptr } => unsafe { GreenNode::from_raw(ptr.as_ptr().read()).into() }, Green::Token { ptr } => unsafe { GreenToken::from_raw(*ptr).into() }, }; @@ -553,13 +600,13 @@ impl SyntaxNode { pub fn new_root(green: GreenNode) -> SyntaxNode { let green = GreenNode::into_raw(green); let green = Green::Node { ptr: Cell::new(green) }; - SyntaxNode { ptr: NodeData::new(None, 0, 0.into(), green, false) } + SyntaxNode { ptr: std::cell::UnsafeCell::new(NodeData::new(None, 0, 0.into(), green, false)) } } pub fn new_root_mut(green: GreenNode) -> SyntaxNode { let green = GreenNode::into_raw(green); let green = Green::Node { ptr: Cell::new(green) }; - SyntaxNode { ptr: NodeData::new(None, 0, 0.into(), green, true) } + SyntaxNode { ptr: std::cell::UnsafeCell::new(NodeData::new(None, 0, 0.into(), green, true)) } } fn new_child( @@ -570,7 +617,7 @@ impl SyntaxNode { ) -> SyntaxNode { let mutable = parent.data().mutable; let green = Green::Node { ptr: Cell::new(green.into()) }; - SyntaxNode { ptr: NodeData::new(Some(parent), index, offset, green, mutable) } + SyntaxNode { ptr: std::cell::UnsafeCell::new(NodeData::new(Some(parent), index, offset, green, mutable)) } } pub fn is_mutable(&self) -> bool { @@ -594,7 +641,7 @@ impl SyntaxNode { #[inline] fn data(&self) -> &NodeData { - unsafe { self.ptr.as_ref() } + unsafe { self.ptr().as_ref() } } #[inline] @@ -605,8 +652,7 @@ impl SyntaxNode { #[inline] fn take_ptr(self) -> ptr::NonNull { assert!(self.can_take_ptr()); - let ret = self.ptr; - // don't change the refcount when self gets dropped + let ret = self.ptr(); std::mem::forget(self); ret } @@ -784,7 +830,7 @@ impl SyntaxNode { data.index.set(index); data.offset = parent_offset + rel_offset; data.green = Green::Node { ptr: Cell::new(green.into()) }; - SyntaxNode { ptr } + SyntaxNode { ptr: std::cell::UnsafeCell::new(ptr) } }) .or_else(|| { data.dec_rc(); @@ -982,12 +1028,12 @@ impl SyntaxToken { ) -> SyntaxToken { let mutable = parent.data().mutable; let green = Green::Token { ptr: green.into() }; - SyntaxToken { ptr: NodeData::new(Some(parent), index, offset, green, mutable) } + SyntaxToken { ptr: std::cell::UnsafeCell::new(NodeData::new(Some(parent), index, offset, green, mutable)) } } #[inline] fn data(&self) -> &NodeData { - unsafe { self.ptr.as_ref() } + unsafe { self.ptr().as_ref() } } #[inline] @@ -998,8 +1044,7 @@ impl SyntaxToken { #[inline] fn take_ptr(self) -> ptr::NonNull { assert!(self.can_take_ptr()); - let ret = self.ptr; - // don't change the refcount when self gets dropped + let ret = self.ptr(); std::mem::forget(self); ret } @@ -1223,11 +1268,11 @@ impl SyntaxElement { match green.as_ref() { NodeOrToken::Node(node) => { data.green = Green::Node { ptr: Cell::new(node.into()) }; - Some(SyntaxElement::Node(SyntaxNode { ptr })) + Some(SyntaxElement::Node(SyntaxNode { ptr: std::cell::UnsafeCell::new(ptr) })) } NodeOrToken::Token(token) => { data.green = Green::Token { ptr: token.into() }; - Some(SyntaxElement::Token(SyntaxToken { ptr })) + Some(SyntaxElement::Token(SyntaxToken { ptr: std::cell::UnsafeCell::new(ptr) })) } } }) diff --git a/src/green/node.rs b/src/green/node.rs index d056abb..529f952 100644 --- a/src/green/node.rs +++ b/src/green/node.rs @@ -10,7 +10,7 @@ use countme::Count; use crate::{ GreenToken, NodeOrToken, TextRange, TextSize, - arc::{Arc, HeaderSlice, ThinArc}, + arc::{Arc, HeaderSlice, ThinArc, thin_to_thick}, green::{GreenElement, GreenElementRef, SyntaxKind}, }; @@ -32,7 +32,7 @@ type Repr = HeaderSlice; type ReprThin = HeaderSlice; #[repr(transparent)] pub struct GreenNodeData { - data: ReprThin, + data: Repr, // unsized — provenance covers the full slice } impl PartialEq for GreenNodeData { @@ -186,11 +186,11 @@ impl ops::Deref for GreenNode { #[inline] fn deref(&self) -> &GreenNodeData { + // SAFETY: GreenNodeData is #[repr(transparent)] over Repr (fat HeaderSlice). + // ThinArc::deref() returns &HeaderSlice with full allocation provenance + // via thin_to_thick(). We transmute to &GreenNodeData which has the same layout. let repr: &Repr = &self.ptr; - unsafe { - let repr: &ReprThin = &*(repr as *const Repr as *const ReprThin); - mem::transmute::<&ReprThin, &GreenNodeData>(repr) - } + unsafe { mem::transmute::<&Repr, &GreenNodeData>(repr) } } } @@ -231,14 +231,22 @@ impl GreenNode { #[inline] pub(crate) fn into_raw(this: GreenNode) -> ptr::NonNull { let green = ManuallyDrop::new(this); - let green: &GreenNodeData = &green; - ptr::NonNull::from(green) + // Extract the raw pointer directly from ThinArc to preserve full + // allocation provenance. Going through Deref → &GreenNodeData would + // create a reference whose provenance is invalidated when the + // ManuallyDrop wrapper goes out of scope. + let thin_ptr = green.ptr.ptr.as_ptr(); + let thick = thin_to_thick(thin_ptr); + unsafe { ptr::NonNull::new_unchecked(ptr::addr_of!((*thick).data) as *mut Repr as *mut GreenNodeData) } } #[inline] pub(crate) unsafe fn from_raw(ptr: ptr::NonNull) -> GreenNode { unsafe { - let arc = Arc::from_raw(&ptr.as_ref().data as *const ReprThin); + // Cast the fat pointer to thin: just reinterpret the data pointer + // (dropping the length metadata, which is stored in the HeaderSlice). + let thin_ptr = ptr.as_ptr() as *const Repr as *const ReprThin; + let arc = Arc::from_raw(thin_ptr); let arc = mem::transmute::, ThinArc>(arc); GreenNode { ptr: arc } } diff --git a/src/green/token.rs b/src/green/token.rs index 9e6a1f0..5bb1e9c 100644 --- a/src/green/token.rs +++ b/src/green/token.rs @@ -19,7 +19,6 @@ struct GreenTokenHead { _c: Count, } -type Repr = HeaderSlice; type ReprThin = HeaderSlice; #[repr(transparent)] pub struct GreenTokenData { @@ -96,7 +95,15 @@ impl GreenTokenData { /// Text of this Token. #[inline] pub fn text(&self) -> &str { - unsafe { std::str::from_utf8_unchecked(self.data.slice()) } + // Access the byte slice via raw pointer arithmetic to avoid going through + // Deref on HeaderSlice which creates a reference with provenance + // limited to the thin type. + unsafe { + let len = self.data.length; + let slice_start = ptr::addr_of!(self.data.slice) as *const u8; + let bytes = std::slice::from_raw_parts(slice_start, len); + std::str::from_utf8_unchecked(bytes) + } } /// Returns the length of the text covered by this token. @@ -117,8 +124,12 @@ impl GreenToken { #[inline] pub(crate) fn into_raw(this: GreenToken) -> ptr::NonNull { let green = ManuallyDrop::new(this); - let green: &GreenTokenData = &green; - ptr::NonNull::from(green) + // Extract pointer directly from ThinArc to preserve full allocation provenance. + let inner = green.ptr.ptr.as_ptr(); + unsafe { + let data = ptr::addr_of!((*inner).data); + ptr::NonNull::new_unchecked(data as *mut ReprThin as *mut GreenTokenData) + } } /// # Safety @@ -146,9 +157,8 @@ impl ops::Deref for GreenToken { #[inline] fn deref(&self) -> &GreenTokenData { unsafe { - let repr: &Repr = &self.ptr; - let repr: &ReprThin = &*(repr as *const Repr as *const ReprThin); - mem::transmute::<&ReprThin, &GreenTokenData>(repr) + let inner = self.ptr.ptr.as_ptr(); + &*(ptr::addr_of!((*inner).data) as *const ReprThin as *const GreenTokenData) } } } From 72c3be4e4490eb204eceba976c29076e6d9e6b40 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sun, 5 Apr 2026 23:57:28 -0500 Subject: [PATCH 2/3] fix: resolve hidden-lifetime-in-paths lint warnings 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". --- src/cursor.rs | 42 ++++++++++++++++++++++++++++++++++-------- src/green/node.rs | 8 ++++++-- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/cursor.rs b/src/cursor.rs index a4284bb..c52f4bd 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -374,7 +374,7 @@ impl NodeData { } } #[inline] - fn green_siblings(&self) -> slice::Iter { + fn green_siblings(&self) -> slice::Iter<'_, GreenChild> { match &self.parent().map(|it| &it.green) { Some(Green::Node { ptr }) => unsafe { &*ptr.as_ptr().read().as_ptr() }.children().raw, Some(Green::Token { .. }) => { @@ -558,7 +558,9 @@ impl NodeData { NodeOrToken::Node(green) => { // Child is root, so it owns the green node. Steal it! let child_green = match &child.green { - Green::Node { ptr } => unsafe { GreenNode::from_raw(ptr.as_ptr().read()).into() }, + Green::Node { ptr } => unsafe { + GreenNode::from_raw(ptr.as_ptr().read()).into() + }, Green::Token { ptr } => unsafe { GreenToken::from_raw(*ptr).into() }, }; @@ -600,13 +602,17 @@ impl SyntaxNode { pub fn new_root(green: GreenNode) -> SyntaxNode { let green = GreenNode::into_raw(green); let green = Green::Node { ptr: Cell::new(green) }; - SyntaxNode { ptr: std::cell::UnsafeCell::new(NodeData::new(None, 0, 0.into(), green, false)) } + SyntaxNode { + ptr: std::cell::UnsafeCell::new(NodeData::new(None, 0, 0.into(), green, false)), + } } pub fn new_root_mut(green: GreenNode) -> SyntaxNode { let green = GreenNode::into_raw(green); let green = Green::Node { ptr: Cell::new(green) }; - SyntaxNode { ptr: std::cell::UnsafeCell::new(NodeData::new(None, 0, 0.into(), green, true)) } + SyntaxNode { + ptr: std::cell::UnsafeCell::new(NodeData::new(None, 0, 0.into(), green, true)), + } } fn new_child( @@ -617,7 +623,15 @@ impl SyntaxNode { ) -> SyntaxNode { let mutable = parent.data().mutable; let green = Green::Node { ptr: Cell::new(green.into()) }; - SyntaxNode { ptr: std::cell::UnsafeCell::new(NodeData::new(Some(parent), index, offset, green, mutable)) } + SyntaxNode { + ptr: std::cell::UnsafeCell::new(NodeData::new( + Some(parent), + index, + offset, + green, + mutable, + )), + } } pub fn is_mutable(&self) -> bool { @@ -1028,7 +1042,15 @@ impl SyntaxToken { ) -> SyntaxToken { let mutable = parent.data().mutable; let green = Green::Token { ptr: green.into() }; - SyntaxToken { ptr: std::cell::UnsafeCell::new(NodeData::new(Some(parent), index, offset, green, mutable)) } + SyntaxToken { + ptr: std::cell::UnsafeCell::new(NodeData::new( + Some(parent), + index, + offset, + green, + mutable, + )), + } } #[inline] @@ -1268,11 +1290,15 @@ impl SyntaxElement { match green.as_ref() { NodeOrToken::Node(node) => { data.green = Green::Node { ptr: Cell::new(node.into()) }; - Some(SyntaxElement::Node(SyntaxNode { ptr: std::cell::UnsafeCell::new(ptr) })) + Some(SyntaxElement::Node(SyntaxNode { + ptr: std::cell::UnsafeCell::new(ptr), + })) } NodeOrToken::Token(token) => { data.green = Green::Token { ptr: token.into() }; - Some(SyntaxElement::Token(SyntaxToken { ptr: std::cell::UnsafeCell::new(ptr) })) + Some(SyntaxElement::Token(SyntaxToken { + ptr: std::cell::UnsafeCell::new(ptr), + })) } } }) diff --git a/src/green/node.rs b/src/green/node.rs index 529f952..906904b 100644 --- a/src/green/node.rs +++ b/src/green/node.rs @@ -237,7 +237,11 @@ impl GreenNode { // ManuallyDrop wrapper goes out of scope. let thin_ptr = green.ptr.ptr.as_ptr(); let thick = thin_to_thick(thin_ptr); - unsafe { ptr::NonNull::new_unchecked(ptr::addr_of!((*thick).data) as *mut Repr as *mut GreenNodeData) } + unsafe { + ptr::NonNull::new_unchecked( + ptr::addr_of!((*thick).data) as *mut Repr as *mut GreenNodeData + ) + } } #[inline] @@ -255,7 +259,7 @@ impl GreenNode { impl GreenChild { #[inline] - pub(crate) fn as_ref(&self) -> GreenElementRef { + pub(crate) fn as_ref(&self) -> GreenElementRef<'_> { match self { GreenChild::Node { node, .. } => NodeOrToken::Node(node), GreenChild::Token { token, .. } => NodeOrToken::Token(token), From dcbece400019397b97764070435eba62c7aa5336 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 6 Apr 2026 06:31:37 -0500 Subject: [PATCH 3/3] fix: use self_alloc provenance for parent pointers in cursor --- src/cursor.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/cursor.rs b/src/cursor.rs index c52f4bd..f92577d 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -256,11 +256,24 @@ impl NodeData { green: Green, mutable: bool, ) -> ptr::NonNull { + // Extract parent's self_alloc by consuming the Option temporarily. + // We take() the value, read self_alloc through UnsafeCell (no &SyntaxNode), + // then put it back. This avoids creating &SyntaxNode which would freeze provenance. + let (parent, parent_alloc) = match parent { + Some(p) => { + let alloc = unsafe { + let node_data_ptr = (*p.ptr.get()).as_ptr(); + ptr::NonNull::new_unchecked((*node_data_ptr).self_alloc) + }; + (Some(p), Some(alloc)) + } + None => (None, None), + }; let parent = ManuallyDrop::new(parent); let res = NodeData { _c: Count::new(), rc: Cell::new(1), - parent: Cell::new(parent.as_ref().map(|it| it.ptr())), + parent: Cell::new(parent_alloc), index: Cell::new(index), green,