Restore reserved vectors after panic

This commit is contained in:
2026-07-31 03:59:56 +02:00
parent 7ec1695cf9
commit 29dfa1e508
2 changed files with 61 additions and 14 deletions
+11 -2
View File
@@ -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::<u8>());
assert_eq!(erased.capacity(), original_capacity);
// SAFETY: A failed reserve does not change the element type.
let guard = unsafe { erased.cast_type::<u8>() };
assert_eq!(guard.as_slice(), &[1]);
assert_eq!(guard.as_slice().as_ptr(), original_ptr);
}
#[test]
+50 -12
View File
@@ -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<Vec<T>>,
}
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::<T>::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::<T>() };
Self {
parts,
vec: ManuallyDrop::new(vec),
}
}
fn vec_mut(&mut self) -> &mut Vec<T> {
&mut self.vec
}
fn length(&self) -> usize {
self.vec.len()
}
}
impl<T> 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<T>(parts: &mut VecParts, additional: usize) {
let original_parts = std::mem::replace(parts, VecParts::from_vec(Vec::<T>::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::<T>();
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<T>.
let mut restore_guard = unsafe { VecPartsRestoreGuard::<T>::new(parts) };
restore_guard.vec_mut().reserve(additional);
debug_assert_eq!(restore_guard.length(), original_len);
}
Self {