Skip to content

Commit f24d00d

Browse files
authored
Rollup merge of #101642 - SkiFire13:fix-inplace-collection-leak, r=the8472
Fix in-place collection leak when remaining element destructor panic Fixes #101628 cc `@the8472` I went for the drop guard route, placing it immediately before the `forget_allocation_drop_remaining` call and after the comment, as to signal they are closely related. I also updated the test to check for the leak, though the only change really needed was removing the leak clean up for miri since now that's no longer leaked.
2 parents c1d4003 + 1750c7b commit f24d00d

File tree

5 files changed

+67
-36
lines changed

5 files changed

+67
-36
lines changed

library/alloc/src/vec/in_place_collect.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555
//! This is handled by the [`InPlaceDrop`] guard for sink items (`U`) and by
5656
//! [`vec::IntoIter::forget_allocation_drop_remaining()`] for remaining source items (`T`).
5757
//!
58+
//! If dropping any remaining source item (`T`) panics then [`InPlaceDstBufDrop`] will handle dropping
59+
//! the already collected sink items (`U`) and freeing the allocation.
60+
//!
5861
//! [`vec::IntoIter::forget_allocation_drop_remaining()`]: super::IntoIter::forget_allocation_drop_remaining()
5962
//!
6063
//! # O(1) collect
@@ -138,7 +141,7 @@ use core::iter::{InPlaceIterable, SourceIter, TrustedRandomAccessNoCoerce};
138141
use core::mem::{self, ManuallyDrop, SizedTypeProperties};
139142
use core::ptr::{self};
140143

141-
use super::{InPlaceDrop, SpecFromIter, SpecFromIterNested, Vec};
144+
use super::{InPlaceDrop, InPlaceDstBufDrop, SpecFromIter, SpecFromIterNested, Vec};
142145

143146
/// Specialization marker for collecting an iterator pipeline into a Vec while reusing the
144147
/// source allocation, i.e. executing the pipeline in place.
@@ -191,14 +194,17 @@ where
191194
);
192195
}
193196

194-
// Drop any remaining values at the tail of the source but prevent drop of the allocation
195-
// itself once IntoIter goes out of scope.
196-
// If the drop panics then we also leak any elements collected into dst_buf.
197+
// The ownership of the allocation and the new `T` values is temporarily moved into `dst_guard`.
198+
// This is safe because `forget_allocation_drop_remaining` immediately forgets the allocation
199+
// before any panic can occur in order to avoid any double free, and then proceeds to drop
200+
// any remaining values at the tail of the source.
197201
//
198202
// Note: This access to the source wouldn't be allowed by the TrustedRandomIteratorNoCoerce
199203
// contract (used by SpecInPlaceCollect below). But see the "O(1) collect" section in the
200204
// module documenttation why this is ok anyway.
205+
let dst_guard = InPlaceDstBufDrop { ptr: dst_buf, len, cap };
201206
src.forget_allocation_drop_remaining();
207+
mem::forget(dst_guard);
202208

203209
let vec = unsafe { Vec::from_raw_parts(dst_buf, len, cap) };
204210

library/alloc/src/vec/in_place_drop.rs

+15
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,18 @@ impl<T> Drop for InPlaceDrop<T> {
2222
}
2323
}
2424
}
25+
26+
// A helper struct for in-place collection that drops the destination allocation and elements,
27+
// to avoid leaking them if some other destructor panics.
28+
pub(super) struct InPlaceDstBufDrop<T> {
29+
pub(super) ptr: *mut T,
30+
pub(super) len: usize,
31+
pub(super) cap: usize,
32+
}
33+
34+
impl<T> Drop for InPlaceDstBufDrop<T> {
35+
#[inline]
36+
fn drop(&mut self) {
37+
unsafe { super::Vec::from_raw_parts(self.ptr, self.len, self.cap) };
38+
}
39+
}

library/alloc/src/vec/into_iter.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,16 @@ impl<T, A: Allocator> IntoIter<T, A> {
9595
}
9696

9797
/// Drops remaining elements and relinquishes the backing allocation.
98+
/// This method guarantees it won't panic before relinquishing
99+
/// the backing allocation.
98100
///
99101
/// This is roughly equivalent to the following, but more efficient
100102
///
101103
/// ```
102104
/// # let mut into_iter = Vec::<u8>::with_capacity(10).into_iter();
105+
/// let mut into_iter = std::mem::replace(&mut into_iter, Vec::new().into_iter());
103106
/// (&mut into_iter).for_each(core::mem::drop);
104-
/// unsafe { core::ptr::write(&mut into_iter, Vec::new().into_iter()); }
107+
/// std::mem::forget(into_iter);
105108
/// ```
106109
///
107110
/// This method is used by in-place iteration, refer to the vec::in_place_collect
@@ -118,6 +121,8 @@ impl<T, A: Allocator> IntoIter<T, A> {
118121
self.ptr = self.buf.as_ptr();
119122
self.end = self.buf.as_ptr();
120123

124+
// Dropping the remaining elements can panic, so this needs to be
125+
// done only after updating the other fields.
121126
unsafe {
122127
ptr::drop_in_place(remaining);
123128
}

library/alloc/src/vec/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ use self::set_len_on_drop::SetLenOnDrop;
125125
mod set_len_on_drop;
126126

127127
#[cfg(not(no_global_oom_handling))]
128-
use self::in_place_drop::InPlaceDrop;
128+
use self::in_place_drop::{InPlaceDrop, InPlaceDstBufDrop};
129129

130130
#[cfg(not(no_global_oom_handling))]
131131
mod in_place_drop;

library/alloc/tests/vec.rs

+35-30
Original file line numberDiff line numberDiff line change
@@ -1191,48 +1191,53 @@ fn test_from_iter_specialization_panic_during_iteration_drops() {
11911191
}
11921192

11931193
#[test]
1194-
fn test_from_iter_specialization_panic_during_drop_leaks() {
1195-
static mut DROP_COUNTER: usize = 0;
1194+
fn test_from_iter_specialization_panic_during_drop_doesnt_leak() {
1195+
static mut DROP_COUNTER_OLD: [usize; 5] = [0; 5];
1196+
static mut DROP_COUNTER_NEW: [usize; 2] = [0; 2];
11961197

11971198
#[derive(Debug)]
1198-
enum Droppable {
1199-
DroppedTwice(Box<i32>),
1200-
PanicOnDrop,
1201-
}
1199+
struct Old(usize);
12021200

1203-
impl Drop for Droppable {
1201+
impl Drop for Old {
12041202
fn drop(&mut self) {
1205-
match self {
1206-
Droppable::DroppedTwice(_) => {
1207-
unsafe {
1208-
DROP_COUNTER += 1;
1209-
}
1210-
println!("Dropping!")
1211-
}
1212-
Droppable::PanicOnDrop => {
1213-
if !std::thread::panicking() {
1214-
panic!();
1215-
}
1216-
}
1203+
unsafe {
1204+
DROP_COUNTER_OLD[self.0] += 1;
1205+
}
1206+
1207+
if self.0 == 3 {
1208+
panic!();
12171209
}
1210+
1211+
println!("Dropped Old: {}", self.0);
12181212
}
12191213
}
12201214

1221-
let mut to_free: *mut Droppable = core::ptr::null_mut();
1222-
let mut cap = 0;
1215+
#[derive(Debug)]
1216+
struct New(usize);
1217+
1218+
impl Drop for New {
1219+
fn drop(&mut self) {
1220+
unsafe {
1221+
DROP_COUNTER_NEW[self.0] += 1;
1222+
}
1223+
1224+
println!("Dropped New: {}", self.0);
1225+
}
1226+
}
12231227

12241228
let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
1225-
let mut v = vec![Droppable::DroppedTwice(Box::new(123)), Droppable::PanicOnDrop];
1226-
to_free = v.as_mut_ptr();
1227-
cap = v.capacity();
1228-
let _ = v.into_iter().take(0).collect::<Vec<_>>();
1229+
let v = vec![Old(0), Old(1), Old(2), Old(3), Old(4)];
1230+
let _ = v.into_iter().map(|x| New(x.0)).take(2).collect::<Vec<_>>();
12291231
}));
12301232

1231-
assert_eq!(unsafe { DROP_COUNTER }, 1);
1232-
// clean up the leak to keep miri happy
1233-
unsafe {
1234-
drop(Vec::from_raw_parts(to_free, 0, cap));
1235-
}
1233+
assert_eq!(unsafe { DROP_COUNTER_OLD[0] }, 1);
1234+
assert_eq!(unsafe { DROP_COUNTER_OLD[1] }, 1);
1235+
assert_eq!(unsafe { DROP_COUNTER_OLD[2] }, 1);
1236+
assert_eq!(unsafe { DROP_COUNTER_OLD[3] }, 1);
1237+
assert_eq!(unsafe { DROP_COUNTER_OLD[4] }, 1);
1238+
1239+
assert_eq!(unsafe { DROP_COUNTER_NEW[0] }, 1);
1240+
assert_eq!(unsafe { DROP_COUNTER_NEW[1] }, 1);
12361241
}
12371242

12381243
// regression test for issue #85322. Peekable previously implemented InPlaceIterable,

0 commit comments

Comments
 (0)