diff --git a/oscars/src/alloc/arena2/alloc.rs b/oscars/src/alloc/arena2/alloc.rs index 53c4abf..3da34b5 100644 --- a/oscars/src/alloc/arena2/alloc.rs +++ b/oscars/src/alloc/arena2/alloc.rs @@ -115,7 +115,7 @@ impl TaggedPtr { } fn as_ptr(&self) -> *mut T { - self.0.map_addr(|addr| addr ^ MASK) + self.0.map_addr(|addr| addr & !MASK) } } diff --git a/oscars/src/alloc/arena2/tests.rs b/oscars/src/alloc/arena2/tests.rs index 9bdaf9b..48179fa 100644 --- a/oscars/src/alloc/arena2/tests.rs +++ b/oscars/src/alloc/arena2/tests.rs @@ -88,3 +88,34 @@ fn arc_drop() { assert_eq!(allocator.arenas_len(), 0); } + +// === test for TaggedPtr::as_ptr === // + +// `TaggedPtr::as_ptr` must use `addr & !MASK` to unconditionally clear the high +// bit rather than XORing it out. The XOR approach worked for tagged items +// but incorrectly flipped the bit on untagged items, corrupting the pointer. +#[test] +fn as_ptr_clears_not_flips_tag_bit() { + let mut allocator = ArenaAllocator::default(); + + let mut ptr_a = allocator.try_alloc(1u64).unwrap().as_ptr(); + let mut ptr_b = allocator.try_alloc(2u64).unwrap().as_ptr(); + let _ptr_c = allocator.try_alloc(3u64).unwrap().as_ptr(); + assert_eq!(allocator.arenas_len(), 1); + + // Mark B and C as dropped, leave A live. + unsafe { + ptr_b.as_mut().mark_dropped(); + } + + let states = allocator.arena_drop_states(); + assert_eq!( + states[0].as_slice(), + &[false, true, false], + "item_drop_states must correctly report live/dropped status for all nodes" + ); + + unsafe { + ptr_a.as_mut().mark_dropped(); + } +}