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

Move objc2_exception into objc2::exception #57

Merged
merged 3 commits into from
Nov 2, 2021
Merged
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 .github/workflows/apple.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@ jobs:
with:
command: test
# Not using --all-features because some features are nightly-only
args: --verbose --no-fail-fast --features block,exception,verify_message
args: --verbose --no-fail-fast --features block,exception,catch_all,verify_message
2 changes: 1 addition & 1 deletion .github/workflows/gnustep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,4 @@ jobs:
with:
command: test
# Not using --all-features because some features are nightly-only
args: --verbose --no-fail-fast --features gnustep-1-9,block,exception,verify_message
args: --verbose --no-fail-fast --features gnustep-1-9,block,exception,catch_all,verify_message
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
10 changes: 8 additions & 2 deletions objc2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ exclude = [
build = "build.rs"

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

# Wrap every `objc2::msg_send` call in a `@try/@catch` block
catch_all = ["exception"]
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 }
2 changes: 1 addition & 1 deletion objc2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ decl.register();

By default, if the `msg_send!` macro causes an exception to be thrown, this
will unwind into Rust resulting in unsafe, undefined behavior.
However, this crate has an `"exception"` feature which, when enabled, wraps
However, this crate has an `"catch_all"` feature which, when enabled, wraps
each `msg_send!` in a `@try`/`@catch` and panics if an exception is caught,
preventing Objective-C from unwinding into Rust.

Expand Down
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
141 changes: 134 additions & 7 deletions objc2/src/exception.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,87 @@
//! 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 like [`catch`] (and specifically not
/// [`catch_unwind`]).
///
/// This also invokes undefined behaviour until `C-unwind` is stabilized, see
/// [RFC-2945].
///
/// [`catch_unwind`]: std::panic::catch_unwind
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
#[inline]
pub unsafe fn throw(exception: Option<&Id<Object, Shared>>) -> ! {
let exception = match exception {
Some(id) => &**id as *const Object as *mut objc_object,
None => ptr::null_mut(),
};
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) = 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 {
// 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| Id::new(e)))
}
}

/// Tries to execute the given closure and catches an Objective-C exception
/// if one is thrown.
Expand All @@ -15,14 +92,64 @@ use objc2_exception::r#try;
///
/// # Safety
///
/// The given closure must not panic.
/// The given closure must not panic (e.g. normal Rust unwinding into this
/// causes undefined behaviour).
///
/// Additionally, this unwinds through the closure from Objective-C, which is
/// 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 super::*;

#[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_none() {
let s = "Hello".to_string();
let result = unsafe {
catch(move || {
if !s.is_empty() {
throw(None);
}
s.len()
})
};
assert!(result.unwrap_err().is_none());
}

#[test]
fn test_throw_catch_object() {
let obj: Id<Object, Shared> = unsafe { Id::new(msg_send![class!(NSObject), new]) };

let result = unsafe { catch(|| throw(Some(&obj))) };
let e = result.unwrap_err().unwrap();
// Compare pointers
assert_eq!(&*e as *const Object, &*obj as *const Object);
}
}
4 changes: 2 additions & 2 deletions objc2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ the [`declare`](declare/index.html) module.

By default, if the `msg_send!` macro causes an exception to be thrown, this
will unwind into Rust resulting in unsafe, undefined behavior.
However, this crate has an `"exception"` feature which, when enabled, wraps
However, this crate has an `"catch_all"` feature which, when enabled, wraps
each `msg_send!` in a `@try`/`@catch` and panics if an exception is caught,
preventing Objective-C from unwinding into Rust.

Expand Down 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/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Variadic arguments are not currently supported.

# Panics

Panics if the `exception` feature is enabled and the Objective-C method throws
Panics if the `catch_all` feature is enabled and the Objective-C method throws
an exception.

And panics if the `verify_message` feature is enabled and the Objective-C
Expand Down
8 changes: 4 additions & 4 deletions objc2/src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ use crate::rc::{Id, Ownership};
use crate::runtime::{Class, Imp, Object, Sel};
use crate::{Encode, EncodeArguments, RefEncode};

#[cfg(feature = "exception")]
#[cfg(feature = "catch_all")]
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 All @@ -20,7 +20,7 @@ unsafe fn conditional_try<R: Encode>(f: impl FnOnce() -> R) -> Result<R, Message
})
}

#[cfg(not(feature = "exception"))]
#[cfg(not(feature = "catch_all"))]
#[inline(always)]
unsafe fn conditional_try<R: Encode>(f: impl FnOnce() -> R) -> Result<R, MessageError> {
Ok(f())
Expand Down Expand Up @@ -300,7 +300,7 @@ An error encountered while attempting to send a message.

Currently, an error may be returned in two cases:

* an Objective-C exception is thrown and the `exception` feature is enabled
* an Objective-C exception is thrown and the `catch_all` feature is enabled
* the encodings of the arguments do not match the encoding of the method
and the `verify_message` feature is enabled
*/
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