Skip to content

Commit

Permalink
Remove extra build stage by handling unwinds in try directly in Rust
Browse files Browse the repository at this point in the history
Uses the same implementation as `std::panic::catch_unwind`, with small modifications to extract the exception object from Objective-C.
  • Loading branch information
madsmtm committed Jun 2, 2021
1 parent 31f34a9 commit a351a8a
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 70 deletions.
5 changes: 0 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,3 @@ documentation = "http://ssheldon.github.io/rust-objc/objc_exception/"
license = "MIT"

exclude = [".gitignore"]

build = "build.rs"

[build-dependencies]
cc = "1"
7 changes: 0 additions & 7 deletions build.rs

This file was deleted.

18 changes: 0 additions & 18 deletions extern/exception.m

This file was deleted.

103 changes: 63 additions & 40 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
#![no_std]
#![feature(c_unwind)]
#![feature(core_intrinsics)]

#[cfg(test)]
extern crate alloc;

use core::ffi::c_void;
use core::mem;
use core::ptr;
use core::intrinsics;
use core::mem::ManuallyDrop;

#[link(name = "objc", kind = "dylib")]
extern "C-unwind" {
Expand All @@ -17,9 +18,14 @@ extern "C-unwind" {
// fn objc_exception_rethrow();
}

extern {
fn RustObjCExceptionTryCatch(r#try: extern fn(*mut c_void),
context: *mut c_void, error: *mut *mut c_void) -> u8; // std::os::raw::c_uchar
#[link(name = "objc", kind = "dylib")]
extern "C" {
// Header marks this with _Nonnull, but LLVM output shows otherwise
fn objc_begin_catch(exc_buf: *mut c_void) -> *mut c_void;
fn objc_end_catch();

// Doesn't belong in this library, but is required to return the exception
fn objc_retain(value: *mut c_void) -> *mut c_void;
}

/// An opaque type representing any Objective-C object thrown as an exception.
Expand All @@ -34,31 +40,6 @@ pub unsafe fn throw(exception: *mut Exception) -> ! {
objc_exception_throw(exception as *mut _)
}

unsafe fn try_no_ret<F>(closure: F) -> Result<(), *mut Exception>
where F: FnOnce() {
extern fn try_objc_execute_closure<F>(closure: &mut Option<F>)
where F: FnOnce() {
// This is always passed Some, so it's safe to unwrap
let closure = closure.take().unwrap();
closure();
}

let f: extern fn(&mut Option<F>) = try_objc_execute_closure;
let f: extern fn(*mut c_void) = mem::transmute(f);
// Wrap the closure in an Option so it can be taken
let mut closure = Some(closure);
let context = &mut closure as *mut _ as *mut c_void;

let mut exception = ptr::null_mut();
let success = RustObjCExceptionTryCatch(f, context, &mut exception);

if success == 0 {
Ok(())
} else {
Err(exception as *mut _)
}
}

/// Tries to execute the given closure and catches an Objective-C exception
/// if one is thrown.
///
Expand All @@ -68,17 +49,59 @@ unsafe fn try_no_ret<F>(closure: F) -> Result<(), *mut Exception>
///
/// Unsafe because this encourages unwinding through the closure from
/// Objective-C, which is not safe.
pub unsafe fn r#try<F, R>(closure: F) -> Result<R, *mut Exception>
where F: FnOnce() -> R {
let mut value = None;
let result = {
let value_ref = &mut value;
try_no_ret(move || {
*value_ref = Some(closure());
})
pub unsafe fn r#try<F: FnOnce() -> R, R>(f: F) -> Result<R, *mut Exception> {
// Our implementation is just a copy of `std::panicking::r#try`:
// https://github.com/rust-lang/rust/blob/1.52.1/library/std/src/panicking.rs#L299-L408
union Data<F, R> {
f: ManuallyDrop<F>,
r: ManuallyDrop<R>,
p: ManuallyDrop<*mut Exception>,
}

let mut data = Data { f: ManuallyDrop::new(f) };

let data_ptr = &mut data as *mut _ as *mut u8;

return if intrinsics::r#try(do_call::<F, R>, data_ptr, do_catch::<F, R>) == 0 {
Ok(ManuallyDrop::into_inner(data.r))
} else {
Err(ManuallyDrop::into_inner(data.p))
};
// If the try succeeded, this was set so it's safe to unwrap
result.map(|_| value.unwrap())

/// Only function that we've changed
#[cold]
unsafe fn cleanup(payload: *mut u8) -> *mut Exception {
// We let Objective-C process the unwind payload, and hand us the
// exception object. Everything between this and `objc_end_catch` is
// treated as a `@catch` block.
let obj = objc_begin_catch(payload as *mut c_void);
// We retain the exception since it might have been autoreleased.
// This cannot unwind, so we don't need extra guards here.
let obj = objc_retain(obj) as *mut Exception;
// End the `@catch` block.
objc_end_catch();
obj
}

#[inline]
fn do_call<F: FnOnce() -> R, R>(data: *mut u8) {
unsafe {
let data = data as *mut Data<F, R>;
let data = &mut (*data);
let f = ManuallyDrop::take(&mut data.f);
data.r = ManuallyDrop::new(f());
}
}

#[inline]
fn do_catch<F: FnOnce() -> R, R>(data: *mut u8, payload: *mut u8) {
unsafe {
let data = data as *mut Data<F, R>;
let data = &mut (*data);
let obj = cleanup(payload);
data.p = ManuallyDrop::new(obj);
}
}
}

#[cfg(test)]
Expand Down

0 comments on commit a351a8a

Please sign in to comment.