about summary refs log tree commit diff
path: root/src/liballoc
diff options
context:
space:
mode:
authorJonas Schievink <jonasschievink@gmail.com>2019-12-13 16:41:19 +0100
committerJonas Schievink <jonasschievink@gmail.com>2020-01-19 20:23:07 +0100
commit3e5eb2634cbb356b626e028a4be1305d4a44a023 (patch)
tree8154f8e4fd2506207f18ea5db0d57fda185a2305 /src/liballoc
parenta859ca5c87ddfaa635fb4cacf8a41e04fd9b02e8 (diff)
downloadrust-3e5eb2634cbb356b626e028a4be1305d4a44a023.tar.gz
rust-3e5eb2634cbb356b626e028a4be1305d4a44a023.zip
Fix `VecDeque::truncate` leak on drop panic
Diffstat (limited to 'src/liballoc')
-rw-r--r--src/liballoc/collections/vec_deque.rs17
-rw-r--r--src/liballoc/tests/vec_deque.rs35
2 files changed, 50 insertions, 2 deletions
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<T> VecDeque<T> {
     /// ```
     #[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<T> VecDeque<T> {
                 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);
+}