diff --git a/src/liballoc/collections/vec_deque.rs b/src/liballoc/collections/vec_deque.rs index 2cc450bb68a..7307aad8a9e 100644 --- a/src/liballoc/collections/vec_deque.rs +++ b/src/liballoc/collections/vec_deque.rs @@ -866,6 +866,18 @@ impl VecDeque { /// ``` #[stable(feature = "deque_extras", since = "1.16.0")] pub fn truncate(&mut self, len: usize) { + /// Runs the destructor for all items in the slice when it gets dropped (normally or + /// during unwinding). + struct Dropper<'a, T>(&'a mut [T]); + + impl<'a, T> Drop for Dropper<'a, T> { + fn drop(&mut self) { + unsafe { + ptr::drop_in_place(self.0); + } + } + } + // Safe because: // // * Any slice passed to `drop_in_place` is valid; the second case has @@ -888,8 +900,11 @@ impl VecDeque { let drop_back = back as *mut _; let drop_front = front.get_unchecked_mut(len..) as *mut _; self.head = self.wrap_sub(self.head, num_dropped); + + // Make sure the second half is dropped even when a destructor + // in the first one panics. + let _back_dropper = Dropper(&mut *drop_back); ptr::drop_in_place(drop_front); - ptr::drop_in_place(drop_back); } } } diff --git a/src/liballoc/tests/vec_deque.rs b/src/liballoc/tests/vec_deque.rs index 1ab3694a3ca..2dc50d0c70e 100644 --- a/src/liballoc/tests/vec_deque.rs +++ b/src/liballoc/tests/vec_deque.rs @@ -2,7 +2,7 @@ use std::collections::TryReserveError::*; use std::collections::{vec_deque::Drain, VecDeque}; use std::fmt::Debug; use std::mem::size_of; -use std::panic::catch_unwind; +use std::panic::{AssertUnwindSafe, catch_unwind}; use std::{isize, usize}; use crate::hash; @@ -1573,3 +1573,36 @@ fn test_try_rfold_moves_iter() { assert_eq!(iter.try_rfold(0_i8, |acc, &x| acc.checked_add(x)), None); assert_eq!(iter.next_back(), Some(&70)); } + +#[test] +fn truncate_leak() { + static mut DROPS: i32 = 0; + + struct D(bool); + + impl Drop for D { + fn drop(&mut self) { + unsafe { + DROPS += 1; + } + + if self.0 { + panic!("panic in `drop`"); + } + } + } + + let mut q = VecDeque::new(); + q.push_back(D(false)); + q.push_back(D(false)); + q.push_back(D(false)); + q.push_back(D(false)); + q.push_back(D(false)); + q.push_front(D(true)); + q.push_front(D(false)); + q.push_front(D(false)); + + catch_unwind(AssertUnwindSafe(|| q.truncate(1))).ok(); + + assert_eq!(unsafe { DROPS }, 7); +}