Skip to content

Commit 4d2dcb9

Browse files
Don't drop uninit memory when MapWindows::clone panics
Co-Authored-By: Ralf Jung <post@ralfj.de>
1 parent 9e293ae commit 4d2dcb9

2 files changed

Lines changed: 68 additions & 12 deletions

File tree

library/core/src/iter/adapters/map_windows.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::iter::FusedIterator;
2-
use crate::mem::{MaybeUninit, SizedTypeProperties};
2+
use crate::mem::{ManuallyDrop, MaybeUninit, SizedTypeProperties};
33
use crate::{fmt, ptr};
44

55
/// An iterator over the mapped windows of another iterator.
@@ -30,14 +30,19 @@ struct MapWindowsInner<I: Iterator, const N: usize> {
3030
buffer: Option<Buffer<I::Item, N>>,
3131
}
3232

33-
// `Buffer` uses two times of space to reduce moves among the iterations.
34-
// `Buffer<T, N>` is semantically `[MaybeUninit<T>; 2 * N]`. However, due
35-
// to limitations of const generics, we use this different type. Note that
36-
// it has the same underlying memory layout.
33+
/// `Buffer<T, N>` is semantically `[MaybeUninit<T>; 2 * N]`. This helps
34+
/// reduce moves while iterating. However, due
35+
/// to limitations of const generics, we use this different type. Note that
36+
/// it has the same underlying memory layout.
37+
///
38+
/// # Safety invariant
39+
///
40+
/// `self.buffer[self.start..self.start + N]` must be initialized,
41+
/// with all other elements being uninitialized. This also
42+
/// implies that `self.start <= N`.
43+
//
44+
// FIXME make these unsafe fields once that feature is ready
3745
struct Buffer<T, const N: usize> {
38-
// Invariant: `self.buffer[self.start..self.start + N]` is initialized,
39-
// with all other elements being uninitialized. This also
40-
// implies that `self.start <= N`.
4146
buffer: [[MaybeUninit<T>; N]; 2],
4247
start: usize,
4348
}
@@ -194,12 +199,18 @@ impl<T, const N: usize> Buffer<T, N> {
194199

195200
impl<T: Clone, const N: usize> Clone for Buffer<T, N> {
196201
fn clone(&self) -> Self {
197-
let mut buffer = Buffer {
202+
// Use `ManuallyDrop` until buffer is fully written to avoid dropping uninitialized elements on panic.
203+
// (See `Buffer` rustdoc for safety invariant)
204+
let mut buffer = ManuallyDrop::new(Buffer {
198205
buffer: [[const { MaybeUninit::uninit() }; N], [const { MaybeUninit::uninit() }; N]],
199206
start: self.start,
200-
};
207+
});
208+
209+
// `clone()` could panic; `ManuallyDrop` guards against that.
201210
buffer.as_uninit_array_mut().write(self.as_array_ref().clone());
202-
buffer
211+
212+
// We initialized the buffer above, so we are good now
213+
ManuallyDrop::into_inner(buffer)
203214
}
204215
}
205216

library/coretests/tests/iter/adapters/map_windows.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::sync::atomic::Ordering::SeqCst;
55
mod drop_checks {
66
//! These tests mainly make sure the elements are correctly dropped.
77
8-
use std::sync::atomic::Ordering::SeqCst;
8+
use std::sync::atomic::Ordering::{Relaxed, SeqCst};
99
use std::sync::atomic::{AtomicBool, AtomicUsize};
1010

1111
#[derive(Debug)]
@@ -143,6 +143,51 @@ mod drop_checks {
143143
check::<5>(5, 1);
144144
check::<5>(5, 4);
145145
}
146+
147+
/// Regression test for #156501
148+
#[test]
149+
fn panicking_clone() {
150+
static CLONE_COUNTER: AtomicUsize = AtomicUsize::new(0);
151+
static DROP_COUNTER: AtomicUsize = AtomicUsize::new(0);
152+
153+
struct PanickingClone(u8);
154+
155+
impl Clone for PanickingClone {
156+
fn clone(&self) -> Self {
157+
if CLONE_COUNTER.fetch_add(1, Relaxed) == 3 {
158+
panic!(
159+
"⚞(· <:::> ·)⚟ aaaaaah its the turbofish monster!!! its gonna eat us all!!!1!"
160+
);
161+
}
162+
163+
Self(self.0)
164+
}
165+
}
166+
167+
impl Drop for PanickingClone {
168+
fn drop(&mut self) {
169+
assert_eq!(self.0, 42);
170+
171+
DROP_COUNTER.fetch_add(1, Relaxed);
172+
}
173+
}
174+
175+
let array = [const { PanickingClone(42) }; 17];
176+
let mut iter = array.into_iter().map_windows::<_, (), 17>(|_| ());
177+
178+
// initialize the buffer
179+
iter.next();
180+
181+
let result = std::panic::catch_unwind(|| {
182+
// now do the clones and panic.
183+
// this should't try to drop uninitialized memory
184+
let _ = iter.clone();
185+
});
186+
187+
assert!(result.is_err());
188+
189+
assert_eq!(DROP_COUNTER.load(Relaxed), 3);
190+
}
146191
}
147192

148193
#[test]

0 commit comments

Comments
 (0)