diff --git a/src/tests.rs b/src/tests.rs index 771c4fd..62bdca3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -302,14 +302,23 @@ fn test_reserve_increases_capacity() { #[test] fn test_reserve_is_unwind_safe() { - let mut erased = TypeErasedVec::new(vec![1_u8]); + let values = vec![1_u8]; + let original_ptr = values.as_ptr(); + let original_capacity = values.capacity(); + let mut erased = TypeErasedVec::new(values); let result = panic::catch_unwind(panic::AssertUnwindSafe(|| { erased.reserve(usize::MAX); })); assert!(result.is_err()); - assert!(erased.is_empty()); + assert_erased_state(&erased, 1, Layout::new::()); + assert_eq!(erased.capacity(), original_capacity); + + // SAFETY: A failed reserve does not change the element type. + let guard = unsafe { erased.cast_type::() }; + assert_eq!(guard.as_slice(), &[1]); + assert_eq!(guard.as_slice().as_ptr(), original_ptr); } #[test] diff --git a/src/vtable.rs b/src/vtable.rs index 6ac9c16..0fb127a 100644 --- a/src/vtable.rs +++ b/src/vtable.rs @@ -1,4 +1,48 @@ use crate::VecParts; +use std::mem::ManuallyDrop; + +/// Temporarily reconstructs a typed vector from erased raw parts and writes its +/// latest raw parts back when dropped. +/// +/// The guard keeps vtable operations unwind-safe while ensuring that the +/// allocation descriptor always has exactly one owner. +struct VecPartsRestoreGuard<'parts, T> { + parts: &'parts mut VecParts, + vec: ManuallyDrop>, +} + +impl<'parts, T> VecPartsRestoreGuard<'parts, T> { + unsafe fn new(parts: &'parts mut VecParts) -> Self { + let original_parts = std::mem::replace(parts, VecParts::from_vec(Vec::::new())); + + // SAFETY: The vtable selects T as the exact type that originally + // produced these raw parts, and ownership was transferred out of + // `parts` before reconstruction. + let vec = unsafe { original_parts.into_vec::() }; + + Self { + parts, + vec: ManuallyDrop::new(vec), + } + } + + fn vec_mut(&mut self) -> &mut Vec { + &mut self.vec + } + + fn length(&self) -> usize { + self.vec.len() + } +} + +impl Drop for VecPartsRestoreGuard<'_, T> { + fn drop(&mut self) { + // SAFETY: `vec` is taken exactly once here and immediately converted + // back to the unique erased allocation descriptor. + let vec = unsafe { ManuallyDrop::take(&mut self.vec) }; + *self.parts = VecParts::from_vec(vec); + } +} pub(super) struct TypeErasedVecVtable { /// Function pointer to reserve additional capacity in the underlying Vec @@ -37,19 +81,13 @@ impl TypeErasedVecVtable { } unsafe fn reserve_vec(parts: &mut VecParts, additional: usize) { - let original_parts = std::mem::replace(parts, VecParts::from_vec(Vec::::new())); + let original_len = parts.len; - // SAFETY: We transferred ownership of the original allocation out of - // `parts` before reconstructing the Vec. If `reserve` unwinds, the Vec - // owns and frees that allocation while `parts` remains a valid empty Vec. - unsafe { - let mut vec = original_parts.into_vec::(); - vec.reserve(additional); - let new_parts = VecParts::from_vec(vec); - debug_assert_eq!(new_parts.len, original_parts.len); - - *parts = new_parts; - } + // SAFETY: This vtable function is selected only for raw parts that + // were produced by Vec. + let mut restore_guard = unsafe { VecPartsRestoreGuard::::new(parts) }; + restore_guard.vec_mut().reserve(additional); + debug_assert_eq!(restore_guard.length(), original_len); } Self {