Skip to content

Commit 4821b4f

Browse files
committed
Implement unwind safety
1 parent 52e40e6 commit 4821b4f

File tree

3 files changed

+96
-73
lines changed

3 files changed

+96
-73
lines changed

src/lib.rs

+50-34
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ assert!(nums[0] == "2");
4444

4545
#![warn(missing_docs)]
4646

47+
use std::convert::TryFrom;
4748
use std::error::Error;
4849
use std::fmt;
49-
use std::mem;
5050
use std::os::raw::c_void;
51+
use std::panic::AssertUnwindSafe;
5152
use std::time::Duration;
5253

5354
use crate::ffi::*;
@@ -79,68 +80,83 @@ impl fmt::Display for WaitTimeout {
7980
impl Error for WaitTimeout {}
8081

8182
fn time_after_delay(delay: Duration) -> dispatch_time_t {
82-
delay
83-
.as_secs()
84-
.checked_mul(1_000_000_000)
85-
.and_then(|i| i.checked_add(delay.subsec_nanos() as u64))
86-
.and_then(|i| {
87-
if i < (i64::max_value() as u64) {
88-
Some(i as i64)
89-
} else {
90-
None
91-
}
92-
})
93-
.map_or(DISPATCH_TIME_FOREVER, |i| unsafe {
94-
dispatch_time(DISPATCH_TIME_NOW, i)
95-
})
83+
i64::try_from(delay.as_nanos()).map_or(DISPATCH_TIME_FOREVER, |i| unsafe {
84+
dispatch_time(DISPATCH_TIME_NOW, i)
85+
})
9686
}
9787

9888
fn context_and_function<F>(closure: F) -> (*mut c_void, dispatch_function_t)
9989
where
100-
F: FnOnce(),
90+
F: 'static + FnOnce(),
10191
{
102-
extern "C" fn work_execute_closure<F>(context: Box<F>)
92+
extern "C" fn work_execute_closure<F>(context: *mut c_void)
10393
where
10494
F: FnOnce(),
10595
{
106-
(*context)();
96+
let closure: Box<F> = unsafe { Box::from_raw(context as *mut _) };
97+
if std::panic::catch_unwind(AssertUnwindSafe(closure)).is_err() {
98+
// Abort to prevent unwinding across FFI
99+
std::process::abort();
100+
}
107101
}
108102

109103
let closure = Box::new(closure);
110-
let func: extern "C" fn(Box<F>) = work_execute_closure::<F>;
111-
unsafe { (mem::transmute(closure), mem::transmute(func)) }
104+
let func: dispatch_function_t = work_execute_closure::<F>;
105+
(Box::into_raw(closure) as *mut c_void, func)
112106
}
113107

114-
fn context_and_sync_function<F>(closure: &mut Option<F>) -> (*mut c_void, dispatch_function_t)
108+
fn with_context_and_sync_function<F, T>(
109+
closure: F,
110+
wrapper: impl FnOnce(*mut c_void, dispatch_function_t),
111+
) -> Option<T>
115112
where
116-
F: FnOnce(),
113+
F: FnOnce() -> T,
117114
{
118-
extern "C" fn work_read_closure<F>(context: &mut Option<F>)
115+
#[derive(Debug)]
116+
struct SyncContext<F, T> {
117+
closure: *mut F,
118+
result: Option<std::thread::Result<T>>,
119+
}
120+
121+
extern "C" fn work_execute_closure<F, T>(context: *mut c_void)
119122
where
120-
F: FnOnce(),
123+
F: FnOnce() -> T,
121124
{
122-
// This is always passed Some, so it's safe to unwrap
123-
let closure = context.take().unwrap();
124-
closure();
125+
let sync_context: &mut SyncContext<F, T> = unsafe { &mut *(context as *mut _) };
126+
let closure = unsafe { Box::from_raw(sync_context.closure) };
127+
sync_context.result = Some(std::panic::catch_unwind(AssertUnwindSafe(closure)));
125128
}
126129

127-
let context: *mut Option<F> = closure;
128-
let func: extern "C" fn(&mut Option<F>) = work_read_closure::<F>;
129-
unsafe { (context as *mut c_void, mem::transmute(func)) }
130+
let mut sync_context: SyncContext<F, T> = SyncContext {
131+
closure: Box::into_raw(Box::new(closure)),
132+
result: None,
133+
};
134+
let func: dispatch_function_t = work_execute_closure::<F, T>;
135+
wrapper(&mut sync_context as *mut _ as *mut c_void, func);
136+
137+
// If the closure panicked, resume unwinding
138+
match sync_context.result.transpose() {
139+
Ok(res) => res,
140+
Err(unwind_payload) => std::panic::resume_unwind(unwind_payload),
141+
}
130142
}
131143

132144
fn context_and_apply_function<F>(closure: &F) -> (*mut c_void, extern "C" fn(*mut c_void, usize))
133145
where
134146
F: Fn(usize),
135147
{
136-
extern "C" fn work_apply_closure<F>(context: &F, iter: usize)
148+
extern "C" fn work_apply_closure<F>(context: *mut c_void, iter: usize)
137149
where
138150
F: Fn(usize),
139151
{
140-
context(iter);
152+
let context: &F = unsafe { &*(context as *const _) };
153+
if std::panic::catch_unwind(AssertUnwindSafe(|| context(iter))).is_err() {
154+
// Abort to prevent unwinding across FFI
155+
std::process::abort();
156+
}
141157
}
142158

143159
let context: *const F = closure;
144-
let func: extern "C" fn(&F, usize) = work_apply_closure::<F>;
145-
unsafe { (context as *mut c_void, mem::transmute(func)) }
160+
let func: extern "C" fn(*mut c_void, usize) = work_apply_closure::<F>;
161+
(context as *mut c_void, func)
146162
}

src/once.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::cell::UnsafeCell;
22

3-
use crate::context_and_sync_function;
43
use crate::ffi::*;
4+
use crate::with_context_and_sync_function;
55

66
/// A predicate used to execute a closure only once for the lifetime of an
77
/// application.
@@ -34,11 +34,9 @@ impl Once {
3434
where
3535
F: FnOnce(),
3636
{
37-
let mut work = Some(work);
38-
let (context, work) = context_and_sync_function(&mut work);
39-
unsafe {
37+
with_context_and_sync_function(work, |context, work| unsafe {
4038
dispatch_once_f(predicate, context, work);
41-
}
39+
});
4240
}
4341

4442
unsafe {

src/queue.rs

+43-34
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use std::time::Duration;
66

77
use crate::ffi::*;
88
use crate::{
9-
context_and_apply_function, context_and_function, context_and_sync_function, time_after_delay,
9+
context_and_apply_function, context_and_function, time_after_delay,
10+
with_context_and_sync_function,
1011
};
1112

1213
/// The type of a dispatch queue.
@@ -134,21 +135,10 @@ impl Queue {
134135
F: Send + FnOnce() -> T,
135136
T: Send,
136137
{
137-
let mut result = None;
138-
{
139-
let result_ref = &mut result;
140-
let work = move || {
141-
*result_ref = Some(work());
142-
};
143-
144-
let mut work = Some(work);
145-
let (context, work) = context_and_sync_function(&mut work);
146-
unsafe {
147-
dispatch_sync_f(self.ptr, context, work);
148-
}
149-
}
150-
// This was set so it's safe to unwrap
151-
result.unwrap()
138+
with_context_and_sync_function(work, |context, work| unsafe {
139+
dispatch_sync_f(self.ptr, context, work);
140+
})
141+
.unwrap()
152142
}
153143

154144
/// Submits a closure for asynchronous execution on self and returns
@@ -197,7 +187,7 @@ impl Queue {
197187
{
198188
let slice_ptr = slice.as_mut_ptr();
199189
let work = move |i| unsafe {
200-
work(&mut *slice_ptr.offset(i as isize));
190+
work(&mut *slice_ptr.add(i));
201191
};
202192
let (context, work) = context_and_apply_function(&work);
203193
unsafe {
@@ -221,8 +211,8 @@ impl Queue {
221211
let dest_ptr = dest.as_mut_ptr();
222212

223213
let work = move |i| unsafe {
224-
let result = work(ptr::read(src_ptr.offset(i as isize)));
225-
ptr::write(dest_ptr.offset(i as isize), result);
214+
let result = work(ptr::read(src_ptr.add(i)));
215+
ptr::write(dest_ptr.add(i), result);
226216
};
227217
let (context, work) = context_and_apply_function(&work);
228218
unsafe {
@@ -251,21 +241,10 @@ impl Queue {
251241
F: Send + FnOnce() -> T,
252242
T: Send,
253243
{
254-
let mut result = None;
255-
{
256-
let result_ref = &mut result;
257-
let work = move || {
258-
*result_ref = Some(work());
259-
};
260-
261-
let mut work = Some(work);
262-
let (context, work) = context_and_sync_function(&mut work);
263-
unsafe {
264-
dispatch_barrier_sync_f(self.ptr, context, work);
265-
}
266-
}
267-
// This was set so it's safe to unwrap
268-
result.unwrap()
244+
with_context_and_sync_function(work, |context, work| unsafe {
245+
dispatch_barrier_sync_f(self.ptr, context, work);
246+
})
247+
.unwrap()
269248
}
270249

271250
/// Submits a closure to be executed on self as a barrier and returns
@@ -523,4 +502,34 @@ mod tests {
523502
q.exec_sync(|| ());
524503
assert_eq!(*num.lock().unwrap(), 1);
525504
}
505+
506+
#[test]
507+
#[should_panic(expected = "panic from exec_sync")]
508+
fn test_panic_serial() {
509+
let q = Queue::create("", QueueAttribute::Serial);
510+
511+
q.exec_sync(|| panic!("panic from exec_sync"));
512+
513+
panic!("exec_sync did NOT panic");
514+
}
515+
516+
#[test]
517+
#[should_panic(expected = "panic from exec_sync")]
518+
fn test_panic_concurrent() {
519+
let q = Queue::create("", QueueAttribute::Concurrent);
520+
521+
q.exec_sync(|| panic!("panic from exec_sync"));
522+
523+
panic!("exec_sync did NOT panic");
524+
}
525+
526+
#[test]
527+
#[should_panic(expected = "panic from barrier_sync")]
528+
fn test_panic_barrier() {
529+
let q = Queue::create("", QueueAttribute::Serial);
530+
531+
q.barrier_sync(|| panic!("panic from barrier_sync"));
532+
533+
panic!("barrier_sync did NOT panic");
534+
}
526535
}

0 commit comments

Comments
 (0)