Skip to content

Commit b87524b

Browse files
authored
Rollup merge of rust-lang#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 rust-lang#83618 `@rustbot` label T-libs-impl
2 parents d074214 + 5abbea3 commit b87524b

File tree

3 files changed

+57
-12
lines changed

3 files changed

+57
-12
lines changed

alloc/src/vec/into_iter.rs

+18-9
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,29 @@ impl<T, A: Allocator> IntoIter<T, A> {
8585
ptr::slice_from_raw_parts_mut(self.ptr as *mut T, self.len())
8686
}
8787

88-
pub(super) fn drop_remaining(&mut self) {
89-
unsafe {
90-
ptr::drop_in_place(self.as_mut_slice());
91-
}
92-
self.ptr = self.end;
93-
}
88+
/// Drops remaining elements and relinquishes the backing allocation.
89+
///
90+
/// This is roughly equivalent to the following, but more efficient
91+
///
92+
/// ```
93+
/// # let mut into_iter = Vec::<u8>::with_capacity(10).into_iter();
94+
/// (&mut into_iter).for_each(core::mem::drop);
95+
/// unsafe { core::ptr::write(&mut into_iter, Vec::new().into_iter()); }
96+
/// ```
97+
pub(super) fn forget_allocation_drop_remaining(&mut self) {
98+
let remaining = self.as_raw_mut_slice();
9499

95-
/// Relinquishes the backing allocation, equivalent to
96-
/// `ptr::write(&mut self, Vec::new().into_iter())`
97-
pub(super) fn forget_allocation(&mut self) {
100+
// overwrite the individual fields instead of creating a new
101+
// struct and then overwriting &mut self.
102+
// this creates less assembly
98103
self.cap = 0;
99104
self.buf = unsafe { NonNull::new_unchecked(RawVec::NEW.ptr()) };
100105
self.ptr = self.buf.as_ptr();
101106
self.end = self.buf.as_ptr();
107+
108+
unsafe {
109+
ptr::drop_in_place(remaining);
110+
}
102111
}
103112
}
104113

alloc/src/vec/source_iter_marker.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ where
6969
}
7070

7171
// drop any remaining values at the tail of the source
72-
src.drop_remaining();
7372
// but prevent drop of the allocation itself once IntoIter goes out of scope
74-
src.forget_allocation();
73+
// if the drop panics then we also leak any elements collected into dst_buf
74+
src.forget_allocation_drop_remaining();
7575

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

alloc/tests/vec.rs

+37-1
Original file line numberDiff line numberDiff line change
@@ -1027,7 +1027,7 @@ fn test_from_iter_specialization_head_tail_drop() {
10271027
}
10281028

10291029
#[test]
1030-
fn test_from_iter_specialization_panic_drop() {
1030+
fn test_from_iter_specialization_panic_during_iteration_drops() {
10311031
let drop_count: Vec<_> = (0..=2).map(|_| Rc::new(())).collect();
10321032
let src: Vec<_> = drop_count.iter().cloned().collect();
10331033
let iter = src.into_iter();
@@ -1050,6 +1050,42 @@ fn test_from_iter_specialization_panic_drop() {
10501050
);
10511051
}
10521052

1053+
#[test]
1054+
fn test_from_iter_specialization_panic_during_drop_leaks() {
1055+
static mut DROP_COUNTER: usize = 0;
1056+
1057+
#[derive(Debug)]
1058+
enum Droppable {
1059+
DroppedTwice(Box<i32>),
1060+
PanicOnDrop,
1061+
}
1062+
1063+
impl Drop for Droppable {
1064+
fn drop(&mut self) {
1065+
match self {
1066+
Droppable::DroppedTwice(_) => {
1067+
unsafe {
1068+
DROP_COUNTER += 1;
1069+
}
1070+
println!("Dropping!")
1071+
}
1072+
Droppable::PanicOnDrop => {
1073+
if !std::thread::panicking() {
1074+
panic!();
1075+
}
1076+
}
1077+
}
1078+
}
1079+
}
1080+
1081+
let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
1082+
let v = vec![Droppable::DroppedTwice(Box::new(123)), Droppable::PanicOnDrop];
1083+
let _ = v.into_iter().take(0).collect::<Vec<_>>();
1084+
}));
1085+
1086+
assert_eq!(unsafe { DROP_COUNTER }, 1);
1087+
}
1088+
10531089
#[test]
10541090
fn test_cow_from() {
10551091
let borrowed: &[_] = &["borrowed", "(slice)"];

0 commit comments

Comments
 (0)