Make erased vector operations unwind-safe

This commit is contained in:
2026-07-31 02:09:28 +02:00
parent 0fd5d615aa
commit 5b0d2cab9d
2 changed files with 59 additions and 9 deletions
+41
View File
@@ -209,6 +209,47 @@ fn test_reserve_increases_capacity() {
assert!(erased.capacity() >= 100);
}
#[test]
fn test_reserve_is_unwind_safe() {
let mut erased = TypeErasedVec::new(vec![1_u8]);
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
erased.reserve(usize::MAX);
}));
assert!(result.is_err());
assert!(erased.is_empty());
}
#[test]
fn test_clear_is_unwind_safe_when_element_drop_panics() {
struct PanicOnFirstDrop {
has_panicked: Rc<Cell<bool>>,
}
impl Drop for PanicOnFirstDrop {
fn drop(&mut self) {
assert!(
self.has_panicked.replace(true),
"simulated panic while clearing an element"
);
}
}
let has_panicked = Rc::new(Cell::new(false));
let mut erased = TypeErasedVec::new(vec![PanicOnFirstDrop {
has_panicked: has_panicked.clone(),
}]);
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
erased.clear();
}));
assert!(result.is_err());
assert!(has_panicked.get());
assert!(erased.is_empty());
}
#[test]
fn test_cast_type_success_and_mutation() {
let mut vec = Vec::<i32>::with_capacity(10);
+18 -9
View File
@@ -17,27 +17,36 @@ impl TypeErasedVecVtable {
}
unsafe fn clear_vec<T>(parts: &mut VecParts) {
// SAFETY: We reconstruct the Vec to call clear, dropping only the elements.
// Using into_raw_parts ensures we don't accidentally drop the allocation itself.
let original_parts = std::mem::replace(parts, VecParts::from_vec(Vec::<T>::new()));
// SAFETY: We transferred ownership of the original allocation out of
// `parts` before reconstructing the Vec. If `clear` unwinds, the Vec
// owns and frees that allocation while `parts` remains a valid empty Vec.
unsafe {
let mut vec = parts.into_vec::<T>();
let mut vec = original_parts.into_vec::<T>();
vec.clear();
let new_parts = VecParts::from_vec(vec);
debug_assert!(std::ptr::eq(new_parts.ptr.as_ptr(), parts.ptr.as_ptr()));
debug_assert_eq!(new_parts.cap, parts.cap);
debug_assert!(std::ptr::eq(
new_parts.ptr.as_ptr(),
original_parts.ptr.as_ptr()
));
debug_assert_eq!(new_parts.cap, original_parts.cap);
debug_assert_eq!(new_parts.len, 0);
*parts = new_parts;
}
}
unsafe fn reserve_vec<T>(parts: &mut VecParts, additional: usize) {
// SAFETY: We reconstruct the Vec to trigger a reserve.
// Capacity might change upon reallocation.
let original_parts = std::mem::replace(parts, VecParts::from_vec(Vec::<T>::new()));
// 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 = parts.into_vec::<T>();
let mut vec = original_parts.into_vec::<T>();
vec.reserve(additional);
let new_parts = VecParts::from_vec(vec);
debug_assert_eq!(new_parts.len, parts.len);
debug_assert_eq!(new_parts.len, original_parts.len);
*parts = new_parts;
}