Rollup merge of #83629 - the8472:fix-inplace-panic-on-drop, r=m-ou-se
Fix double-drop in `Vec::from_iter(vec.into_iter())` specialization when items drop during panic This fixes the double-drop but it leaves a behavioral difference compared to the default implementation intact: In the default implementation the source and the destination vec are separate objects, so they get dropped separately. Here they share an allocation and the latter only exists as a pointer into the former. So if dropping the former panics then this fix will leak more items than the default implementation would. Is this acceptable or should the specialization also mimic the default implementation's drops-during-panic behavior? Fixes #83618 `@rustbot` label T-libs-impl
This commit is contained in:
commit
542f441d44
@ -85,20 +85,29 @@ impl<T, A: Allocator> IntoIter<T, A> {
|
||||
ptr::slice_from_raw_parts_mut(self.ptr as *mut T, self.len())
|
||||
}
|
||||
|
||||
pub(super) fn drop_remaining(&mut self) {
|
||||
unsafe {
|
||||
ptr::drop_in_place(self.as_mut_slice());
|
||||
}
|
||||
self.ptr = self.end;
|
||||
}
|
||||
/// Drops remaining elements and relinquishes the backing allocation.
|
||||
///
|
||||
/// This is roughly equivalent to the following, but more efficient
|
||||
///
|
||||
/// ```
|
||||
/// # let mut into_iter = Vec::<u8>::with_capacity(10).into_iter();
|
||||
/// (&mut into_iter).for_each(core::mem::drop);
|
||||
/// unsafe { core::ptr::write(&mut into_iter, Vec::new().into_iter()); }
|
||||
/// ```
|
||||
pub(super) fn forget_allocation_drop_remaining(&mut self) {
|
||||
let remaining = self.as_raw_mut_slice();
|
||||
|
||||
/// Relinquishes the backing allocation, equivalent to
|
||||
/// `ptr::write(&mut self, Vec::new().into_iter())`
|
||||
pub(super) fn forget_allocation(&mut self) {
|
||||
// overwrite the individual fields instead of creating a new
|
||||
// struct and then overwriting &mut self.
|
||||
// this creates less assembly
|
||||
self.cap = 0;
|
||||
self.buf = unsafe { NonNull::new_unchecked(RawVec::NEW.ptr()) };
|
||||
self.ptr = self.buf.as_ptr();
|
||||
self.end = self.buf.as_ptr();
|
||||
|
||||
unsafe {
|
||||
ptr::drop_in_place(remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,9 +69,9 @@ where
|
||||
}
|
||||
|
||||
// drop any remaining values at the tail of the source
|
||||
src.drop_remaining();
|
||||
// but prevent drop of the allocation itself once IntoIter goes out of scope
|
||||
src.forget_allocation();
|
||||
// if the drop panics then we also leak any elements collected into dst_buf
|
||||
src.forget_allocation_drop_remaining();
|
||||
|
||||
let vec = unsafe { Vec::from_raw_parts(dst_buf, len, cap) };
|
||||
|
||||
|
@ -1027,7 +1027,7 @@ fn test_from_iter_specialization_head_tail_drop() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_iter_specialization_panic_drop() {
|
||||
fn test_from_iter_specialization_panic_during_iteration_drops() {
|
||||
let drop_count: Vec<_> = (0..=2).map(|_| Rc::new(())).collect();
|
||||
let src: Vec<_> = drop_count.iter().cloned().collect();
|
||||
let iter = src.into_iter();
|
||||
@ -1050,6 +1050,42 @@ fn test_from_iter_specialization_panic_drop() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_iter_specialization_panic_during_drop_leaks() {
|
||||
static mut DROP_COUNTER: usize = 0;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Droppable {
|
||||
DroppedTwice(Box<i32>),
|
||||
PanicOnDrop,
|
||||
}
|
||||
|
||||
impl Drop for Droppable {
|
||||
fn drop(&mut self) {
|
||||
match self {
|
||||
Droppable::DroppedTwice(_) => {
|
||||
unsafe {
|
||||
DROP_COUNTER += 1;
|
||||
}
|
||||
println!("Dropping!")
|
||||
}
|
||||
Droppable::PanicOnDrop => {
|
||||
if !std::thread::panicking() {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
let v = vec![Droppable::DroppedTwice(Box::new(123)), Droppable::PanicOnDrop];
|
||||
let _ = v.into_iter().take(0).collect::<Vec<_>>();
|
||||
}));
|
||||
|
||||
assert_eq!(unsafe { DROP_COUNTER }, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cow_from() {
|
||||
let borrowed: &[_] = &["borrowed", "(slice)"];
|
||||
|
Loading…
x
Reference in New Issue
Block a user