Skip to content

Commit ed8f828

Browse files
authored
Merge pull request #57 from madsmtm/exception-merge
Move `objc2_exception` into `objc2::exception`
2 parents 854711e + ff865df commit ed8f828

File tree

16 files changed

+169
-210
lines changed

16 files changed

+169
-210
lines changed

.github/workflows/apple.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,4 @@ jobs:
7676
with:
7777
command: test
7878
# Not using --all-features because some features are nightly-only
79-
args: --verbose --no-fail-fast --features block,exception,verify_message
79+
args: --verbose --no-fail-fast --features block,exception,catch_all,verify_message

.github/workflows/gnustep.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,4 @@ jobs:
114114
with:
115115
command: test
116116
# Not using --all-features because some features are nightly-only
117-
args: --verbose --no-fail-fast --features gnustep-1-9,block,exception,verify_message
117+
args: --verbose --no-fail-fast --features gnustep-1-9,block,exception,catch_all,verify_message

.travis-disabled.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323
script:
2424
- cargo test --workspace --verbose
2525
- # TODO: cargo test --workspace --verbose --all-features
26-
- # objc2_exception doesn't work on 32bit?
27-
cargo test --workspace --exclude objc2_exception --verbose -Z build-std --target i686-apple-darwin
26+
- # exception doesn't work on 32bit?
27+
cargo test --workspace --verbose -Z build-std --target i686-apple-darwin
2828
- # TODO: cargo test --workspace --verbose --all-features -Z build-std --target i686-apple-darwin
2929

3030
- name: MacOS 11.3

Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ members = [
44
"objc2_block",
55
"objc2_block_sys",
66
"objc2_encode",
7-
"objc2_exception",
87
"objc2_foundation",
98
"objc2_sys",
109
"objc2_test_utils",

objc2/Cargo.toml

+8-2
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,18 @@ exclude = [
2424
build = "build.rs"
2525

2626
[features]
27-
exception = ["objc2_exception"]
27+
# Enables `objc2::exception::throw` and `objc2::exception::catch`
28+
exception = ["cc"]
29+
30+
# Wrap every `objc2::msg_send` call in a `@try/@catch` block
31+
catch_all = ["exception"]
2832
verify_message = []
2933
unstable_autoreleasesafe = []
3034

3135
[dependencies]
3236
malloc_buf = "1.0"
3337
objc2_sys = { path = "../objc2_sys" }
3438
objc2_encode = { path = "../objc2_encode" }
35-
objc2_exception = { path = "../objc2_exception", optional = true }
39+
40+
[build-dependencies]
41+
cc = { version = "1", optional = true }

objc2/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ decl.register();
9494

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

objc2/build.rs

+14
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,18 @@ fn main() {
66

77
let runtime = env::var("DEP_OBJC_RUNTIME").unwrap();
88
println!("cargo:rustc-cfg={}", runtime);
9+
10+
#[cfg(feature = "exception")]
11+
{
12+
println!("cargo:rerun-if-changed=extern/exception.m");
13+
14+
let mut builder = cc::Build::new();
15+
builder.file("extern/exception.m");
16+
17+
for flag in env::var("DEP_OBJC_CC_ARGS").unwrap().split(' ') {
18+
builder.flag(flag);
19+
}
20+
21+
builder.compile("librust_objc_try_catch_exception.a");
22+
}
923
}

objc2_exception/extern/exception.m renamed to objc2/extern/exception.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Don't include any headers, cross compilation is difficult to set up
22
// properly in such situations.
33

4-
/// We're linking to `libobjc` so this should be available.
4+
/// We're linking to `libobjc` in build.rs, so this should be available.
55
///
66
/// See <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-retain>.
77
id objc_retain(id value);

objc2/src/exception.rs

+134-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,87 @@
1+
//! Objective-C's @throw and @try/@catch.
2+
//!
3+
//! This is only available when the `exception` feature is enabled.
4+
//!
5+
//! See the following links for more information:
6+
//! - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Tasks/HandlingExceptions.html>
7+
//! - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocExceptionHandling.html>
8+
//! - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html>
9+
//! - <https://llvm.org/docs/ExceptionHandling.html>
10+
11+
use core::ffi::c_void;
12+
use core::mem;
13+
use core::ptr;
114
use core::ptr::NonNull;
15+
use std::os::raw::c_uchar;
216

317
use crate::rc::{Id, Shared};
418
use crate::runtime::Object;
5-
use objc2_exception::r#try;
619

7-
// Comment copied from `objc2_exception`
20+
use objc2_sys::{objc_exception_throw, objc_object};
21+
22+
extern "C" {
23+
fn rust_objc_try_catch_exception(
24+
f: extern "C" fn(*mut c_void),
25+
context: *mut c_void,
26+
error: *mut *mut objc_object,
27+
) -> c_uchar;
28+
}
29+
30+
/// Throws an Objective-C exception.
31+
///
32+
/// The argument must be a pointer to an Objective-C object.
33+
///
34+
/// # Safety
35+
///
36+
/// This unwinds from Objective-C, and the exception must be caught using an
37+
/// Objective-C exception handler like [`catch`] (and specifically not
38+
/// [`catch_unwind`]).
39+
///
40+
/// This also invokes undefined behaviour until `C-unwind` is stabilized, see
41+
/// [RFC-2945].
42+
///
43+
/// [`catch_unwind`]: std::panic::catch_unwind
44+
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
45+
#[inline]
46+
pub unsafe fn throw(exception: Option<&Id<Object, Shared>>) -> ! {
47+
let exception = match exception {
48+
Some(id) => &**id as *const Object as *mut objc_object,
49+
None => ptr::null_mut(),
50+
};
51+
objc_exception_throw(exception)
52+
}
53+
54+
unsafe fn try_no_ret<F: FnOnce()>(closure: F) -> Result<(), Option<Id<Object, Shared>>> {
55+
extern "C" fn try_objc_execute_closure<F: FnOnce()>(closure: &mut Option<F>) {
56+
// This is always passed Some, so it's safe to unwrap
57+
let closure = closure.take().unwrap();
58+
closure();
59+
}
60+
61+
let f: extern "C" fn(&mut Option<F>) = try_objc_execute_closure;
62+
let f: extern "C" fn(*mut c_void) = mem::transmute(f);
63+
// Wrap the closure in an Option so it can be taken
64+
let mut closure = Some(closure);
65+
let context = &mut closure as *mut _ as *mut c_void;
66+
67+
let mut exception = ptr::null_mut();
68+
let success = rust_objc_try_catch_exception(f, context, &mut exception);
69+
70+
if success == 0 {
71+
Ok(())
72+
} else {
73+
// SAFETY:
74+
// The exception is always a valid object (or NULL, but that has been
75+
// checked).
76+
//
77+
// The ownership is safe as Shared; Objective-C code throwing an
78+
// exception knows that they don't hold sole access to that exception
79+
// instance any more, and Rust code is forbidden by requiring a Shared
80+
// Id in `throw` (instead of just a shared reference, which could have
81+
// come from an Owned Id).
82+
Err(NonNull::new(exception as *mut Object).map(|e| Id::new(e)))
83+
}
84+
}
885

986
/// Tries to execute the given closure and catches an Objective-C exception
1087
/// if one is thrown.
@@ -15,14 +92,64 @@ use objc2_exception::r#try;
1592
///
1693
/// # Safety
1794
///
18-
/// The given closure must not panic.
95+
/// The given closure must not panic (e.g. normal Rust unwinding into this
96+
/// causes undefined behaviour).
1997
///
2098
/// Additionally, this unwinds through the closure from Objective-C, which is
2199
/// undefined behaviour until `C-unwind` is stabilized, see [RFC-2945].
22100
///
23101
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
24-
pub unsafe fn catch_exception<R>(
25-
closure: impl FnOnce() -> R,
26-
) -> Result<R, Option<Id<Object, Shared>>> {
27-
r#try(closure).map_err(|e| NonNull::new(e).map(|e| Id::new(e.cast())))
102+
pub unsafe fn catch<R>(closure: impl FnOnce() -> R) -> Result<R, Option<Id<Object, Shared>>> {
103+
let mut value = None;
104+
let result = {
105+
let value_ref = &mut value;
106+
try_no_ret(move || {
107+
*value_ref = Some(closure());
108+
})
109+
};
110+
// If the try succeeded, this was set so it's safe to unwrap
111+
result.map(|_| value.unwrap())
112+
}
113+
114+
#[cfg(test)]
115+
mod tests {
116+
use alloc::string::ToString;
117+
118+
use super::*;
119+
120+
#[test]
121+
fn test_catch() {
122+
let mut s = "Hello".to_string();
123+
let result = unsafe {
124+
catch(move || {
125+
s.push_str(", World!");
126+
s
127+
})
128+
};
129+
assert_eq!(result.unwrap(), "Hello, World!");
130+
}
131+
132+
#[test]
133+
fn test_throw_catch_none() {
134+
let s = "Hello".to_string();
135+
let result = unsafe {
136+
catch(move || {
137+
if !s.is_empty() {
138+
throw(None);
139+
}
140+
s.len()
141+
})
142+
};
143+
assert!(result.unwrap_err().is_none());
144+
}
145+
146+
#[test]
147+
fn test_throw_catch_object() {
148+
let obj: Id<Object, Shared> = unsafe { Id::new(msg_send![class!(NSObject), new]) };
149+
150+
let result = unsafe { catch(|| throw(Some(&obj))) };
151+
let e = result.unwrap_err().unwrap();
152+
// Compare pointers
153+
assert_eq!(&*e as *const Object, &*obj as *const Object);
154+
}
28155
}

objc2/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ the [`declare`](declare/index.html) module.
3232
3333
By default, if the `msg_send!` macro causes an exception to be thrown, this
3434
will unwind into Rust resulting in unsafe, undefined behavior.
35-
However, this crate has an `"exception"` feature which, when enabled, wraps
35+
However, this crate has an `"catch_all"` feature which, when enabled, wraps
3636
each `msg_send!` in a `@try`/`@catch` and panics if an exception is caught,
3737
preventing Objective-C from unwinding into Rust.
3838
@@ -90,7 +90,7 @@ mod cache;
9090
pub mod declare;
9191
mod encode;
9292
#[cfg(feature = "exception")]
93-
mod exception;
93+
pub mod exception;
9494
mod message;
9595
pub mod rc;
9696
pub mod runtime;

objc2/src/macros.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Variadic arguments are not currently supported.
7878
7979
# Panics
8080
81-
Panics if the `exception` feature is enabled and the Objective-C method throws
81+
Panics if the `catch_all` feature is enabled and the Objective-C method throws
8282
an exception.
8383
8484
And panics if the `verify_message` feature is enabled and the Objective-C

objc2/src/message/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ use crate::rc::{Id, Ownership};
88
use crate::runtime::{Class, Imp, Object, Sel};
99
use crate::{Encode, EncodeArguments, RefEncode};
1010

11-
#[cfg(feature = "exception")]
11+
#[cfg(feature = "catch_all")]
1212
unsafe fn conditional_try<R: Encode>(f: impl FnOnce() -> R) -> Result<R, MessageError> {
1313
use alloc::borrow::ToOwned;
14-
crate::exception::catch_exception(f).map_err(|exception| {
14+
crate::exception::catch(f).map_err(|exception| {
1515
if let Some(exception) = exception {
1616
MessageError(alloc::format!("Uncaught exception {:?}", exception))
1717
} else {
@@ -20,7 +20,7 @@ unsafe fn conditional_try<R: Encode>(f: impl FnOnce() -> R) -> Result<R, Message
2020
})
2121
}
2222

23-
#[cfg(not(feature = "exception"))]
23+
#[cfg(not(feature = "catch_all"))]
2424
#[inline(always)]
2525
unsafe fn conditional_try<R: Encode>(f: impl FnOnce() -> R) -> Result<R, MessageError> {
2626
Ok(f())
@@ -300,7 +300,7 @@ An error encountered while attempting to send a message.
300300
301301
Currently, an error may be returned in two cases:
302302
303-
* an Objective-C exception is thrown and the `exception` feature is enabled
303+
* an Objective-C exception is thrown and the `catch_all` feature is enabled
304304
* the encodings of the arguments do not match the encoding of the method
305305
and the `verify_message` feature is enabled
306306
*/

objc2_exception/Cargo.toml

-26
This file was deleted.

objc2_exception/README.md

-9
This file was deleted.

objc2_exception/build.rs

-14
This file was deleted.

0 commit comments

Comments
 (0)