Skip to content

Commit 8b46ef9

Browse files
committed
test: regression tests for unwind safety
Cover both fixes added in the series: - `[pin_]init_array_from_fn`: a panic or error from element `i`'s initializer drops the previously initialized elements `0..i`. - `[pin_]chain`: a panic or error from the chained closure drops the value initialized by the first stage. Also assert no double-drop on the success paths. Signed-off-by: Mirko Adzic <adzicmirko97@gmail.com>
1 parent ad982f4 commit 8b46ef9

1 file changed

Lines changed: 266 additions & 0 deletions

File tree

tests/unwind_safety.rs

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
//! Regression tests for:
2+
//!
3+
//! 1. the unwind-safety fix in `[pin_]init_array_from_fn`: a panic or error during
4+
//! element `i` initialization must drop the previously initialized elements `0..i`.
5+
//! 2. the unwind-safety fix in `[pin_]chain`: a panic or error from the chained
6+
//! closure must drop the value initialized by the first stage.
7+
//!
8+
//! For more information, see: https://github.com/Rust-for-Linux/pin-init/issues/136.
9+
10+
#![cfg(any(feature = "std", feature = "alloc"))]
11+
#![cfg_attr(feature = "alloc", feature(allocator_api))]
12+
13+
use core::pin::Pin;
14+
use core::sync::atomic::{AtomicUsize, Ordering};
15+
use std::panic::{catch_unwind, AssertUnwindSafe};
16+
17+
use pin_init::*;
18+
19+
#[allow(unused_attributes)]
20+
#[path = "../examples/error.rs"]
21+
mod error;
22+
use error::Error;
23+
24+
struct Counted<'a>(&'a AtomicUsize);
25+
26+
impl<'a> Drop for Counted<'a> {
27+
fn drop(&mut self) {
28+
self.0.fetch_add(1, Ordering::Relaxed);
29+
}
30+
}
31+
32+
fn maybe_panicking_init(
33+
count: &AtomicUsize,
34+
should_panic: bool,
35+
) -> impl Init<Counted<'_>, core::convert::Infallible> {
36+
// SAFETY: on `Ok(())` we have written a valid `Counted` into `slot`;
37+
// on panic we never wrote, so `slot` is left uninitialized as required.
38+
unsafe {
39+
init_from_closure(move |slot: *mut Counted| {
40+
assert!(!should_panic);
41+
slot.write(Counted(count));
42+
Ok(())
43+
})
44+
}
45+
}
46+
47+
fn maybe_panicking_pin_init(
48+
count: &AtomicUsize,
49+
should_panic: bool,
50+
) -> impl PinInit<Counted<'_>, core::convert::Infallible> {
51+
// SAFETY: on `Ok(())` we have written a valid `Counted` into `slot`;
52+
// on panic we never wrote, so `slot` is left uninitialized as required.
53+
//
54+
// `Counted: Unpin`, so pinning invariants are trivial.
55+
unsafe {
56+
pin_init_from_closure(move |slot: *mut Counted| {
57+
assert!(!should_panic);
58+
slot.write(Counted(count));
59+
Ok(())
60+
})
61+
}
62+
}
63+
64+
fn fallible_init(count: &AtomicUsize, should_error: bool) -> impl Init<Counted<'_>, Error> {
65+
// SAFETY: on `Ok(())` we have written a valid `Counted` into `slot`;
66+
// on `Err(Error)` we never wrote, so `slot` is left uninitialized as required.
67+
unsafe {
68+
init_from_closure(move |slot: *mut Counted| {
69+
if should_error {
70+
Err(Error)
71+
} else {
72+
slot.write(Counted(count));
73+
Ok(())
74+
}
75+
})
76+
}
77+
}
78+
79+
fn fallible_pin_init(count: &AtomicUsize, should_error: bool) -> impl PinInit<Counted<'_>, Error> {
80+
// SAFETY: on `Ok(())` we have written a valid `Counted` into `slot`;
81+
// on `Err(Error)` we never wrote, so `slot` is left uninitialized as required.
82+
//
83+
// `Counted: Unpin`, so pinning invariants are trivial.
84+
unsafe {
85+
pin_init_from_closure(move |slot: *mut Counted| {
86+
if should_error {
87+
Err(Error)
88+
} else {
89+
slot.write(Counted(count));
90+
Ok(())
91+
}
92+
})
93+
}
94+
}
95+
96+
#[test]
97+
fn init_array_from_fn_drops_initialized_prefix_on_panic() {
98+
const N: usize = 10;
99+
100+
for panic_at in [0, 5, N - 1] {
101+
let drops = AtomicUsize::new(0);
102+
let func = AssertUnwindSafe(|| {
103+
let init = init_array_from_fn(|i| {
104+
let should_panic = i == panic_at;
105+
maybe_panicking_init(&drops, should_panic)
106+
});
107+
let _array: Result<Box<[Counted; N]>, _> = Box::init(init);
108+
});
109+
let result = catch_unwind(func);
110+
assert!(result.is_err());
111+
assert_eq!(drops.load(Ordering::Relaxed), panic_at);
112+
}
113+
}
114+
115+
#[test]
116+
fn pin_init_array_from_fn_drops_initialized_prefix_on_panic() {
117+
const N: usize = 10;
118+
119+
for panic_at in [0, 5, N - 1] {
120+
let drops = AtomicUsize::new(0);
121+
let func = AssertUnwindSafe(|| {
122+
let init = pin_init_array_from_fn(|i| {
123+
let should_panic = i == panic_at;
124+
maybe_panicking_pin_init(&drops, should_panic)
125+
});
126+
let _array: Result<Pin<Box<[Counted; N]>>, _> = Box::pin_init(init);
127+
});
128+
let result = catch_unwind(func);
129+
assert!(result.is_err());
130+
assert_eq!(drops.load(Ordering::Relaxed), panic_at);
131+
}
132+
}
133+
134+
#[test]
135+
fn init_array_from_fn_drops_initialized_prefix_on_error() {
136+
const N: usize = 10;
137+
138+
for error_at in [0, 5, N - 1] {
139+
let drops = AtomicUsize::new(0);
140+
let init = init_array_from_fn(|i| {
141+
let should_error = i == error_at;
142+
fallible_init(&drops, should_error)
143+
});
144+
let result: Result<Box<[Counted; N]>, _> = Box::try_init(init);
145+
assert!(result.is_err());
146+
assert_eq!(drops.load(Ordering::Relaxed), error_at);
147+
}
148+
}
149+
150+
#[test]
151+
fn pin_init_array_from_fn_drops_initialized_prefix_on_error() {
152+
const N: usize = 10;
153+
154+
for error_at in [0, 5, N - 1] {
155+
let drops = AtomicUsize::new(0);
156+
let init = pin_init_array_from_fn(|i| {
157+
let should_error = i == error_at;
158+
fallible_pin_init(&drops, should_error)
159+
});
160+
let result: Result<Pin<Box<[Counted; N]>>, _> = Box::try_pin_init(init);
161+
assert!(result.is_err());
162+
assert_eq!(drops.load(Ordering::Relaxed), error_at);
163+
}
164+
}
165+
166+
#[test]
167+
fn init_array_from_fn_no_double_drop_on_success() {
168+
const N: usize = 8;
169+
170+
let drops = AtomicUsize::new(0);
171+
{
172+
let init = init_array_from_fn(|_| maybe_panicking_init(&drops, false));
173+
let result: Result<Box<[Counted; N]>, _> = Box::init(init);
174+
assert!(result.is_ok());
175+
assert_eq!(drops.load(Ordering::Relaxed), 0);
176+
}
177+
assert_eq!(drops.load(Ordering::Relaxed), N);
178+
}
179+
180+
#[test]
181+
fn pin_init_array_from_fn_no_double_drop_on_success() {
182+
const N: usize = 8;
183+
184+
let drops = AtomicUsize::new(0);
185+
{
186+
let pin_init = pin_init_array_from_fn(|_| maybe_panicking_pin_init(&drops, false));
187+
let result: Result<Pin<Box<[Counted; N]>>, _> = Box::pin_init(pin_init);
188+
assert!(result.is_ok());
189+
assert_eq!(drops.load(Ordering::Relaxed), 0);
190+
}
191+
assert_eq!(drops.load(Ordering::Relaxed), N);
192+
}
193+
194+
#[test]
195+
fn chain_init_drops_first_stage_on_panic() {
196+
let drops = AtomicUsize::new(0);
197+
let func = AssertUnwindSafe(|| {
198+
let init = maybe_panicking_init(&drops, false).chain(
199+
|_| -> Result<(), core::convert::Infallible> {
200+
panic!();
201+
},
202+
);
203+
let _: Result<Box<Counted>, _> = Box::init(init);
204+
});
205+
let result = catch_unwind(func);
206+
assert!(result.is_err());
207+
assert_eq!(drops.load(Ordering::Relaxed), 1);
208+
}
209+
210+
#[test]
211+
fn chain_pin_init_drops_first_stage_on_panic() {
212+
let drops = AtomicUsize::new(0);
213+
let func = AssertUnwindSafe(|| {
214+
let init = maybe_panicking_pin_init(&drops, false).pin_chain(
215+
|_| -> Result<(), core::convert::Infallible> {
216+
panic!();
217+
},
218+
);
219+
let _: Result<Pin<Box<Counted>>, _> = Box::pin_init(init);
220+
});
221+
let result = catch_unwind(func);
222+
assert!(result.is_err());
223+
assert_eq!(drops.load(Ordering::Relaxed), 1);
224+
}
225+
226+
#[test]
227+
fn chain_init_drops_first_stage_on_error() {
228+
let drops = AtomicUsize::new(0);
229+
let init = fallible_init(&drops, false).chain(|_| Err(Error));
230+
let result: Result<Box<Counted>, _> = Box::try_init(init);
231+
assert!(result.is_err());
232+
assert_eq!(drops.load(Ordering::Relaxed), 1);
233+
}
234+
235+
#[test]
236+
fn chain_pin_init_drops_first_stage_on_error() {
237+
let drops = AtomicUsize::new(0);
238+
let init = fallible_pin_init(&drops, false).pin_chain(|_| Err(Error));
239+
let result: Result<Pin<Box<Counted>>, _> = Box::try_pin_init(init);
240+
assert!(result.is_err());
241+
assert_eq!(drops.load(Ordering::Relaxed), 1);
242+
}
243+
244+
#[test]
245+
fn chain_init_no_double_drop_on_success() {
246+
let drops = AtomicUsize::new(0);
247+
{
248+
let init = maybe_panicking_init(&drops, false).chain(|_| Ok(()));
249+
let result: Result<Box<Counted>, _> = Box::init(init);
250+
assert!(result.is_ok());
251+
assert_eq!(drops.load(Ordering::Relaxed), 0);
252+
}
253+
assert_eq!(drops.load(Ordering::Relaxed), 1);
254+
}
255+
256+
#[test]
257+
fn chain_pin_init_no_double_drop_on_success() {
258+
let drops = AtomicUsize::new(0);
259+
{
260+
let init = maybe_panicking_pin_init(&drops, false).pin_chain(|_| Ok(()));
261+
let result: Result<Pin<Box<Counted>>, _> = Box::pin_init(init);
262+
assert!(result.is_ok());
263+
assert_eq!(drops.load(Ordering::Relaxed), 0);
264+
}
265+
assert_eq!(drops.load(Ordering::Relaxed), 1);
266+
}

0 commit comments

Comments
 (0)