Skip to content

Commit 09ab31b

Browse files
committed
Auto merge of rust-lang#61224 - aloucks:drain_filter, r=Gankro
Prevent Vec::drain_filter from double dropping on panic Fixes: rust-lang#60977 The changes in this PR prevent leaking and double-panicking in addition to double-drop. Tracking issue: rust-lang#43244
2 parents 78ca1bd + df5b32e commit 09ab31b

File tree

2 files changed

+169
-9
lines changed

2 files changed

+169
-9
lines changed

src/liballoc/tests/vec.rs

+109
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,115 @@ fn drain_filter_complex() {
944944
}
945945
}
946946

947+
#[test]
948+
#[cfg(not(miri))] // Miri does not support catching panics
949+
fn drain_filter_consumed_panic() {
950+
use std::rc::Rc;
951+
use std::sync::Mutex;
952+
953+
struct Check {
954+
index: usize,
955+
drop_counts: Rc<Mutex<Vec<usize>>>,
956+
};
957+
958+
impl Drop for Check {
959+
fn drop(&mut self) {
960+
self.drop_counts.lock().unwrap()[self.index] += 1;
961+
println!("drop: {}", self.index);
962+
}
963+
}
964+
965+
let check_count = 10;
966+
let drop_counts = Rc::new(Mutex::new(vec![0_usize; check_count]));
967+
let mut data: Vec<Check> = (0..check_count)
968+
.map(|index| Check { index, drop_counts: Rc::clone(&drop_counts) })
969+
.collect();
970+
971+
let _ = std::panic::catch_unwind(move || {
972+
let filter = |c: &mut Check| {
973+
if c.index == 2 {
974+
panic!("panic at index: {}", c.index);
975+
}
976+
// Verify that if the filter could panic again on another element
977+
// that it would not cause a double panic and all elements of the
978+
// vec would still be dropped exactly once.
979+
if c.index == 4 {
980+
panic!("panic at index: {}", c.index);
981+
}
982+
c.index < 6
983+
};
984+
let drain = data.drain_filter(filter);
985+
986+
// NOTE: The DrainFilter is explictly consumed
987+
drain.for_each(drop);
988+
});
989+
990+
let drop_counts = drop_counts.lock().unwrap();
991+
assert_eq!(check_count, drop_counts.len());
992+
993+
for (index, count) in drop_counts.iter().cloned().enumerate() {
994+
assert_eq!(1, count, "unexpected drop count at index: {} (count: {})", index, count);
995+
}
996+
}
997+
998+
#[test]
999+
#[cfg(not(miri))] // Miri does not support catching panics
1000+
fn drain_filter_unconsumed_panic() {
1001+
use std::rc::Rc;
1002+
use std::sync::Mutex;
1003+
1004+
struct Check {
1005+
index: usize,
1006+
drop_counts: Rc<Mutex<Vec<usize>>>,
1007+
};
1008+
1009+
impl Drop for Check {
1010+
fn drop(&mut self) {
1011+
self.drop_counts.lock().unwrap()[self.index] += 1;
1012+
println!("drop: {}", self.index);
1013+
}
1014+
}
1015+
1016+
let check_count = 10;
1017+
let drop_counts = Rc::new(Mutex::new(vec![0_usize; check_count]));
1018+
let mut data: Vec<Check> = (0..check_count)
1019+
.map(|index| Check { index, drop_counts: Rc::clone(&drop_counts) })
1020+
.collect();
1021+
1022+
let _ = std::panic::catch_unwind(move || {
1023+
let filter = |c: &mut Check| {
1024+
if c.index == 2 {
1025+
panic!("panic at index: {}", c.index);
1026+
}
1027+
// Verify that if the filter could panic again on another element
1028+
// that it would not cause a double panic and all elements of the
1029+
// vec would still be dropped exactly once.
1030+
if c.index == 4 {
1031+
panic!("panic at index: {}", c.index);
1032+
}
1033+
c.index < 6
1034+
};
1035+
let _drain = data.drain_filter(filter);
1036+
1037+
// NOTE: The DrainFilter is dropped without being consumed
1038+
});
1039+
1040+
let drop_counts = drop_counts.lock().unwrap();
1041+
assert_eq!(check_count, drop_counts.len());
1042+
1043+
for (index, count) in drop_counts.iter().cloned().enumerate() {
1044+
assert_eq!(1, count, "unexpected drop count at index: {} (count: {})", index, count);
1045+
}
1046+
}
1047+
1048+
#[test]
1049+
fn drain_filter_unconsumed() {
1050+
let mut vec = vec![1, 2, 3, 4];
1051+
let drain = vec.drain_filter(|&mut x| x % 2 != 0);
1052+
drop(drain);
1053+
assert_eq!(vec, [2, 4]);
1054+
}
1055+
9471056
#[test]
9481057
fn test_reserve_exact() {
9491058
// This is all the same as test_reserve

src/liballoc/vec.rs

+60-9
Original file line numberDiff line numberDiff line change
@@ -2152,6 +2152,7 @@ impl<T> Vec<T> {
21522152
del: 0,
21532153
old_len,
21542154
pred: filter,
2155+
panic_flag: false,
21552156
}
21562157
}
21572158
}
@@ -2779,10 +2780,20 @@ pub struct DrainFilter<'a, T, F>
27792780
where F: FnMut(&mut T) -> bool,
27802781
{
27812782
vec: &'a mut Vec<T>,
2783+
/// The index of the item that will be inspected by the next call to `next`.
27822784
idx: usize,
2785+
/// The number of items that have been drained (removed) thus far.
27832786
del: usize,
2787+
/// The original length of `vec` prior to draining.
27842788
old_len: usize,
2789+
/// The filter test predicate.
27852790
pred: F,
2791+
/// A flag that indicates a panic has occured in the filter test prodicate.
2792+
/// This is used as a hint in the drop implmentation to prevent consumption
2793+
/// of the remainder of the `DrainFilter`. Any unprocessed items will be
2794+
/// backshifted in the `vec`, but no further items will be dropped or
2795+
/// tested by the filter predicate.
2796+
panic_flag: bool,
27862797
}
27872798

27882799
#[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")]
@@ -2793,20 +2804,23 @@ impl<T, F> Iterator for DrainFilter<'_, T, F>
27932804

27942805
fn next(&mut self) -> Option<T> {
27952806
unsafe {
2796-
while self.idx != self.old_len {
2807+
while self.idx < self.old_len {
27972808
let i = self.idx;
2798-
self.idx += 1;
27992809
let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len);
2800-
if (self.pred)(&mut v[i]) {
2810+
self.panic_flag = true;
2811+
let drained = (self.pred)(&mut v[i]);
2812+
self.panic_flag = false;
2813+
// Update the index *after* the predicate is called. If the index
2814+
// is updated prior and the predicate panics, the element at this
2815+
// index would be leaked.
2816+
self.idx += 1;
2817+
if drained {
28012818
self.del += 1;
28022819
return Some(ptr::read(&v[i]));
28032820
} else if self.del > 0 {
28042821
let del = self.del;
28052822
let src: *const T = &v[i];
28062823
let dst: *mut T = &mut v[i - del];
2807-
// This is safe because self.vec has length 0
2808-
// thus its elements will not have Drop::drop
2809-
// called on them in the event of a panic.
28102824
ptr::copy_nonoverlapping(src, dst, 1);
28112825
}
28122826
}
@@ -2824,9 +2838,46 @@ impl<T, F> Drop for DrainFilter<'_, T, F>
28242838
where F: FnMut(&mut T) -> bool,
28252839
{
28262840
fn drop(&mut self) {
2827-
self.for_each(drop);
2828-
unsafe {
2829-
self.vec.set_len(self.old_len - self.del);
2841+
struct BackshiftOnDrop<'a, 'b, T, F>
2842+
where
2843+
F: FnMut(&mut T) -> bool,
2844+
{
2845+
drain: &'b mut DrainFilter<'a, T, F>,
2846+
}
2847+
2848+
impl<'a, 'b, T, F> Drop for BackshiftOnDrop<'a, 'b, T, F>
2849+
where
2850+
F: FnMut(&mut T) -> bool
2851+
{
2852+
fn drop(&mut self) {
2853+
unsafe {
2854+
if self.drain.idx < self.drain.old_len && self.drain.del > 0 {
2855+
// This is a pretty messed up state, and there isn't really an
2856+
// obviously right thing to do. We don't want to keep trying
2857+
// to execute `pred`, so we just backshift all the unprocessed
2858+
// elements and tell the vec that they still exist. The backshift
2859+
// is required to prevent a double-drop of the last successfully
2860+
// drained item prior to a panic in the predicate.
2861+
let ptr = self.drain.vec.as_mut_ptr();
2862+
let src = ptr.add(self.drain.idx);
2863+
let dst = src.sub(self.drain.del);
2864+
let tail_len = self.drain.old_len - self.drain.idx;
2865+
src.copy_to(dst, tail_len);
2866+
}
2867+
self.drain.vec.set_len(self.drain.old_len - self.drain.del);
2868+
}
2869+
}
2870+
}
2871+
2872+
let backshift = BackshiftOnDrop {
2873+
drain: self
2874+
};
2875+
2876+
// Attempt to consume any remaining elements if the filter predicate
2877+
// has not yet panicked. We'll backshift any remaining elements
2878+
// whether we've already panicked or if the consumption here panics.
2879+
if !backshift.drain.panic_flag {
2880+
backshift.drain.for_each(drop);
28302881
}
28312882
}
28322883
}

0 commit comments

Comments
 (0)