Restore reserved vectors after panic
This commit is contained in:
+11
-2
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user