Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove extra build stage by handling unwinds in catch directly in Rust #72

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion objc-sys/src/exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub type objc_uncaught_exception_handler = unsafe extern "C" fn(exception: *mut
pub type objc_exception_handler =
unsafe extern "C" fn(unused: *mut objc_object, context: *mut c_void);

extern "C" {
extern "C-unwind" {
pub fn objc_begin_catch(exc_buf: *mut c_void) -> *mut objc_object;
pub fn objc_end_catch();
/// See [`objc-exception.h`][objc-exception].
Expand Down
1 change: 1 addition & 0 deletions objc-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![doc(html_root_url = "https://docs.rs/objc-sys/0.0.0")]
#![feature(c_unwind)]

// TODO: Replace `extern "C"` with `extern "C-unwind"` where applicable.
// See https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html.
Expand Down
4 changes: 2 additions & 2 deletions objc2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ exclude = [
build = "build.rs"

[features]
# Enables `objc2::exception::throw` and `objc2::exception::catch`
exception = ["cc"]
# Deprecated; does nothing
exception = []

# Wrap every `objc2::msg_send` call in a `@try/@catch` block
catch_all = ["exception"]
Expand Down
14 changes: 0 additions & 14 deletions objc2/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,4 @@ fn main() {

let runtime = env::var("DEP_OBJC_RUNTIME").unwrap();
println!("cargo:rustc-cfg={}", runtime);

#[cfg(feature = "exception")]
{
println!("cargo:rerun-if-changed=extern/exception.m");

let mut builder = cc::Build::new();
builder.file("extern/exception.m");

for flag in env::var("DEP_OBJC_CC_ARGS").unwrap().split(' ') {
builder.flag(flag);
}

builder.compile("librust_objc_try_catch_exception.a");
}
}
23 changes: 0 additions & 23 deletions objc2/extern/exception.m

This file was deleted.

108 changes: 57 additions & 51 deletions objc2/src/exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,15 @@
//! - <https://llvm.org/docs/ExceptionHandling.html>

use core::ffi::c_void;
use core::mem;
use core::intrinsics;
use core::mem::ManuallyDrop;
use core::ptr;
use core::ptr::NonNull;
use std::os::raw::c_uchar;

use crate::rc::{Id, Shared};
use crate::runtime::Object;

use objc_sys::{objc_exception_throw, objc_object};

extern "C" {
fn rust_objc_try_catch_exception(
f: extern "C" fn(*mut c_void),
context: *mut c_void,
error: *mut *mut objc_object,
) -> c_uchar;
}
use objc_sys::{objc_begin_catch, objc_end_catch, objc_exception_throw, objc_object};

/// Throws an Objective-C exception.
///
Expand All @@ -51,38 +43,6 @@ pub unsafe fn throw(exception: Option<&Id<Object, Shared>>) -> ! {
unsafe { objc_exception_throw(exception) }
}

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

let f: extern "C" fn(&mut Option<F>) = try_objc_execute_closure;
let f: extern "C" fn(*mut c_void) = unsafe { 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 = unsafe { rust_objc_try_catch_exception(f, context, &mut exception) };

if success == 0 {
Ok(())
} else {
// SAFETY:
// The exception is always a valid object (or NULL, but that has been
// checked).
//
// The ownership is safe as Shared; Objective-C code throwing an
// exception knows that they don't hold sole access to that exception
// instance any more, and Rust code is forbidden by requiring a Shared
// Id in `throw` (instead of just a shared reference, which could have
// come from an Owned Id).
Err(NonNull::new(exception as *mut Object).map(|e| unsafe { Id::new(e) }))
}
}

/// Tries to execute the given closure and catches an Objective-C exception
/// if one is thrown.
///
Expand All @@ -99,15 +59,61 @@ unsafe fn try_no_ret<F: FnOnce()>(closure: F) -> Result<(), Option<Id<Object, Sh
/// undefined behaviour until `C-unwind` is stabilized, see [RFC-2945].
///
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
pub unsafe fn catch<R>(closure: impl FnOnce() -> R) -> Result<R, Option<Id<Object, Shared>>> {
let mut value = None;
let value_ref = &mut value;
let closure = move || {
*value_ref = Some(closure());
pub unsafe fn catch<R, F: FnOnce() -> R>(f: F) -> Result<R, Option<Id<Object, Shared>>> {
// 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<Option<Id<Object, Shared>>>,
}

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

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

return if unsafe { intrinsics::r#try(do_call::<F, R>, data_ptr, do_catch::<F, R>) } == 0 {
Ok(ManuallyDrop::into_inner(unsafe { data.r }))
} else {
Err(ManuallyDrop::into_inner(unsafe { data.p }))
};
let result = unsafe { try_no_ret(closure) };
// 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) -> Option<Id<Object, Shared>> {
// 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 = unsafe { 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 = NonNull::new(obj.cast::<Object>()).map(|ptr| unsafe { Id::retain(ptr) });
// End the `@catch` block.
unsafe { 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
3 changes: 2 additions & 1 deletion objc2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ The bindings can be used on Linux or *BSD utilizing the
feature = "unstable_autoreleasesafe",
feature(negative_impls, auto_traits)
)]
#![feature(c_unwind)]
#![feature(core_intrinsics)]
#![warn(elided_lifetimes_in_paths)]
#![warn(missing_docs)]
#![deny(non_ascii_idents)]
Expand Down Expand Up @@ -92,7 +94,6 @@ mod bool;
mod cache;
pub mod declare;
mod encode;
#[cfg(feature = "exception")]
pub mod exception;
mod message;
pub mod rc;
Expand Down