From 7d61526726834c014b4dfa42588788385ff3ffea Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Thu, 17 Sep 2026 03:32:38 +0000 Subject: [PATCH 1/2] Fix zipper_head on a write zipper with a borrowed path as_static_path_zipper asserted the zipper held no borrowed origin path, but write_zipper_at_path always does. Once buffers are prepared the path lives in prefix_buf, so drop the borrowed slice instead. --- src/write_zipper.rs | 4 ++++ src/zipper_head.rs | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/write_zipper.rs b/src/write_zipper.rs index b38eed05..13e6845c 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -1343,6 +1343,10 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC /// Internal method to re-borrow a WriteZipperCore without the `'path` lifetime fn as_static_path_zipper(&mut self) -> &mut WriteZipperCore<'a, 'static, V, A> { self.prepare_buffers(); + //The path is in `prefix_buf` now, so drop the borrowed copy + if self.key.origin_path.len() > 0 { + self.key.origin_path = SliceOrLen::new_owned(self.key.origin_path.len()); + } debug_assert!(!self.key.origin_path.is_slice() || self.key.origin_path.len() == 0); unsafe{ &mut *(self as *mut WriteZipperCore).cast() } } diff --git a/src/zipper_head.rs b/src/zipper_head.rs index cab1cfa9..4de39872 100644 --- a/src/zipper_head.rs +++ b/src/zipper_head.rs @@ -1515,4 +1515,27 @@ mod tests { paths.sort(); assert_eq!(paths, vec![b"ax".to_vec(), b"bx".to_vec(), b"c".to_vec(), b"dx".to_vec()]); } + + /// A `ZipperHead` from a write zipper made with a borrowed path, and the zipper used afterwards + #[test] + fn zipper_head_from_write_zipper_at_borrowed_path() { + let mut map = PathMap::::new(); + map.set_val_at(&[1u8, 2, 3], 7); + { + let path = [1u8, 2]; + let mut wz = map.write_zipper_at_path(&path); + { + let zh = wz.zipper_head(); + let mut child = zh.write_zipper_at_exclusive_path(&[4u8]).unwrap(); + child.set_val(1); + } + assert_eq!(wz.origin_path(), &[1u8, 2]); + wz.descend_to(&[5u8]); + wz.set_val(2); + assert_eq!(wz.origin_path(), &[1u8, 2, 5]); + } + assert_eq!(map.get_val_at(&[1u8, 2, 4]), Some(&1)); + assert_eq!(map.get_val_at(&[1u8, 2, 5]), Some(&2)); + assert_eq!(map.get_val_at(&[1u8, 2, 3]), Some(&7)); + } } From 01958803f790b2f32d88f2085f52080b9b272528 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Thu, 24 Sep 2026 05:32:28 -0600 Subject: [PATCH 2/2] Moving fix to the right spot; fixing warnings in test --- src/write_zipper.rs | 11 ++++++----- src/zipper_head.rs | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/write_zipper.rs b/src/write_zipper.rs index 13e6845c..93f96422 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -1343,11 +1343,10 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC /// Internal method to re-borrow a WriteZipperCore without the `'path` lifetime fn as_static_path_zipper(&mut self) -> &mut WriteZipperCore<'a, 'static, V, A> { self.prepare_buffers(); - //The path is in `prefix_buf` now, so drop the borrowed copy - if self.key.origin_path.len() > 0 { - self.key.origin_path = SliceOrLen::new_owned(self.key.origin_path.len()); - } - debug_assert!(!self.key.origin_path.is_slice() || self.key.origin_path.len() == 0); + debug_assert!( + !self.key.origin_path.is_slice() || self.key.origin_path.len() == 0, + "a prepared zipper must not retain a borrowed origin path" + ); unsafe{ &mut *(self as *mut WriteZipperCore).cast() } } @@ -2788,6 +2787,8 @@ impl<'k> KeyFields<'k> { self.prefix_buf.reserve(path_len.saturating_sub(self.prefix_buf.len())); if was_unallocated { self.prefix_buf.extend(unsafe{ self.origin_path.as_slice_unchecked() }); + //The path now lives in `prefix_buf`; release the borrowed slice. + self.origin_path.make_len(); } } if self.prefix_idx.capacity() < stack_depth { diff --git a/src/zipper_head.rs b/src/zipper_head.rs index e033f96c..796832a6 100644 --- a/src/zipper_head.rs +++ b/src/zipper_head.rs @@ -1541,9 +1541,9 @@ mod tests { wz.set_val(2); assert_eq!(wz.origin_path(), &[1u8, 2, 5]); } - assert_eq!(map.get_val_at(&[1u8, 2, 4]), Some(&1)); - assert_eq!(map.get_val_at(&[1u8, 2, 5]), Some(&2)); - assert_eq!(map.get_val_at(&[1u8, 2, 3]), Some(&7)); + assert_eq!(map.val_at(&[1u8, 2, 4]), Some(&1)); + assert_eq!(map.val_at(&[1u8, 2, 5]), Some(&2)); + assert_eq!(map.val_at(&[1u8, 2, 3]), Some(&7)); } /// An exclusive zipper at the head's own root, requested more than once, from a head whose