Skip to content

Commit 251b4ca

Browse files
Rollup merge of rust-lang#156588 - Jules-Bertholet:fix-panicky-mapwindows-clone, r=Mark-Simulacrum
Don't drop uninit memory when `MapWindows::clone` panics Fixes rust-lang#156501, using the approach suggested in @bjorn3's comment rust-lang#156517 (comment)
2 parents 0786711 + 4a647a5 commit 251b4ca

2 files changed

Lines changed: 66 additions & 12 deletions

File tree

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

Lines changed: 20 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,17 @@ 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`.
3743
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`.
4144
buffer: [[MaybeUninit<T>; N]; 2],
4245
start: usize,
4346
}
@@ -194,12 +197,18 @@ impl<T, const N: usize> Buffer<T, N> {
194197

195198
impl<T: Clone, const N: usize> Clone for Buffer<T, N> {
196199
fn clone(&self) -> Self {
197-
let mut buffer = Buffer {
200+
// Use `ManuallyDrop` until buffer is fully written to avoid dropping uninitialized elements on panic.
201+
// (See `Buffer` rustdoc for safety invariant)
202+
let mut buffer = ManuallyDrop::new(Buffer {
198203
buffer: [[const { MaybeUninit::uninit() }; N], [const { MaybeUninit::uninit() }; N]],
199204
start: self.start,
200-
};
205+
});
206+
207+
// `clone()` could panic; `ManuallyDrop` guards against that.
201208
buffer.as_uninit_array_mut().write(self.as_array_ref().clone());
202-
buffer
209+
210+
// We initialized the buffer above, so we are good now
211+
ManuallyDrop::into_inner(buffer)
203212
}
204213
}
205214

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)