Skip to content

Commit

Permalink
Move objc2_exception into objc2::exception
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Oct 30, 2021
1 parent 9143778 commit 8d87a8d
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 201 deletions.
4 changes: 2 additions & 2 deletions .travis-disabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ jobs:
script:
- cargo test --workspace --verbose
- # TODO: cargo test --workspace --verbose --all-features
- # objc2_exception doesn't work on 32bit?
cargo test --workspace --exclude objc2_exception --verbose -Z build-std --target i686-apple-darwin
- # exception doesn't work on 32bit?
cargo test --workspace --verbose -Z build-std --target i686-apple-darwin
- # TODO: cargo test --workspace --verbose --all-features -Z build-std --target i686-apple-darwin

- name: MacOS 11.3
Expand Down
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ members = [
"objc2_block",
"objc2_block_sys",
"objc2_encode",
"objc2_exception",
"objc2_foundation",
"objc2_sys",
"objc2_test_utils",
Expand Down
7 changes: 5 additions & 2 deletions objc2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ exclude = [
build = "build.rs"

[features]
exception = ["objc2_exception"]
# Enables `objc2::exception::throw` and `objc2::exception::catch`
exception = ["cc"]
verify_message = []
unstable_autoreleasesafe = []

[dependencies]
malloc_buf = "1.0"
objc2_sys = { path = "../objc2_sys" }
objc2_encode = { path = "../objc2_encode" }
objc2_exception = { path = "../objc2_exception", optional = true }

[build-dependencies]
cc = { version = "1", optional = true }
14 changes: 14 additions & 0 deletions objc2/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,18 @@ 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");
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Don't include any headers, cross compilation is difficult to set up
// properly in such situations.

/// We're linking to `libobjc` so this should be available.
/// We're linking to `libobjc` in build.rs, so this should be available.
///
/// See <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-retain>.
id objc_retain(id value);
Expand Down
114 changes: 108 additions & 6 deletions objc2/src/exception.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,72 @@
//! Objective-C's @throw and @try/@catch.
//!
//! This is only available when the `exception` feature is enabled.
//!
//! See the following links for more information:
//! - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Tasks/HandlingExceptions.html>
//! - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocExceptionHandling.html>
//! - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html>
//! - <https://llvm.org/docs/ExceptionHandling.html>
use core::ffi::c_void;
use core::mem;
use core::ptr;
use core::ptr::NonNull;
use std::os::raw::c_uchar;

use crate::rc::{Id, Shared};
use crate::runtime::Object;
use objc2_exception::r#try;

// Comment copied from `objc2_exception`
use objc2_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;
}

/// Throws an Objective-C exception.
///
/// The argument must be a pointer to an Objective-C object.
///
/// # Safety
///
/// This unwinds from Objective-C, and the exception must be caught using an
/// Objective-C exception handler.
///
/// This also invokes undefined behaviour until `C-unwind` is stabilized, see
/// [RFC-2945].
///
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
#[inline]
pub unsafe fn throw(exception: *mut Object) -> ! {
objc_exception_throw(exception as *mut objc_object)
}

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) = 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 = rust_objc_try_catch_exception(f, context, &mut exception);

if success == 0 {
Ok(())
} else {
Err(NonNull::new(exception as *mut Object).map(|e| Id::new(e)))
}
}

/// Tries to execute the given closure and catches an Objective-C exception
/// if one is thrown.
Expand All @@ -21,8 +83,48 @@ use objc2_exception::r#try;
/// 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_exception<R>(
closure: impl FnOnce() -> R,
) -> Result<R, Option<Id<Object, Shared>>> {
r#try(closure).map_err(|e| NonNull::new(e).map(|e| Id::new(e.cast())))
pub unsafe fn catch<R>(closure: impl FnOnce() -> R) -> Result<R, Option<Id<Object, Shared>>> {
let mut value = None;
let result = {
let value_ref = &mut value;
try_no_ret(move || {
*value_ref = Some(closure());
})
};
// If the try succeeded, this was set so it's safe to unwrap
result.map(|_| value.unwrap())
}

#[cfg(test)]
mod tests {
use alloc::string::ToString;
use core::ptr;

use super::{catch, throw};

#[test]
fn test_catch() {
let mut s = "Hello".to_string();
let result = unsafe {
catch(move || {
s.push_str(", World!");
s
})
};
assert_eq!(result.unwrap(), "Hello, World!");
}

#[test]
fn test_throw_catch() {
let s = "Hello".to_string();
let result = unsafe {
catch(move || {
if !s.is_empty() {
throw(ptr::null_mut());
}
s.len()
})
};
assert!(result.unwrap_err().is_none());
}
}
2 changes: 1 addition & 1 deletion objc2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ mod cache;
pub mod declare;
mod encode;
#[cfg(feature = "exception")]
mod exception;
pub mod exception;
mod message;
pub mod rc;
pub mod runtime;
Expand Down
2 changes: 1 addition & 1 deletion objc2/src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{Encode, EncodeArguments, RefEncode};
#[cfg(feature = "exception")]
unsafe fn conditional_try<R: Encode>(f: impl FnOnce() -> R) -> Result<R, MessageError> {
use alloc::borrow::ToOwned;
crate::exception::catch_exception(f).map_err(|exception| {
crate::exception::catch(f).map_err(|exception| {
if let Some(exception) = exception {
MessageError(alloc::format!("Uncaught exception {:?}", exception))
} else {
Expand Down
26 changes: 0 additions & 26 deletions objc2_exception/Cargo.toml

This file was deleted.

9 changes: 0 additions & 9 deletions objc2_exception/README.md

This file was deleted.

14 changes: 0 additions & 14 deletions objc2_exception/build.rs

This file was deleted.

Loading

0 comments on commit 8d87a8d

Please sign in to comment.