Skip to content

Commit ff865df

Browse files
committed
Make objc2::exception throw and catch a bit safer
1 parent 12ac1c1 commit ff865df

File tree

1 file changed

+33
-8
lines changed

1 file changed

+33
-8
lines changed

objc2/src/exception.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,21 @@ extern "C" {
3434
/// # Safety
3535
///
3636
/// This unwinds from Objective-C, and the exception must be caught using an
37-
/// Objective-C exception handler.
37+
/// Objective-C exception handler like [`catch`] (and specifically not
38+
/// [`catch_unwind`]).
3839
///
3940
/// This also invokes undefined behaviour until `C-unwind` is stabilized, see
4041
/// [RFC-2945].
4142
///
43+
/// [`catch_unwind`]: std::panic::catch_unwind
4244
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
4345
#[inline]
44-
pub unsafe fn throw(exception: *mut Object) -> ! {
45-
objc_exception_throw(exception as *mut objc_object)
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)
4652
}
4753

4854
unsafe fn try_no_ret<F: FnOnce()>(closure: F) -> Result<(), Option<Id<Object, Shared>>> {
@@ -64,6 +70,15 @@ unsafe fn try_no_ret<F: FnOnce()>(closure: F) -> Result<(), Option<Id<Object, Sh
6470
if success == 0 {
6571
Ok(())
6672
} 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).
6782
Err(NonNull::new(exception as *mut Object).map(|e| Id::new(e)))
6883
}
6984
}
@@ -77,7 +92,8 @@ unsafe fn try_no_ret<F: FnOnce()>(closure: F) -> Result<(), Option<Id<Object, Sh
7792
///
7893
/// # Safety
7994
///
80-
/// The given closure must not panic.
95+
/// The given closure must not panic (e.g. normal Rust unwinding into this
96+
/// causes undefined behaviour).
8197
///
8298
/// Additionally, this unwinds through the closure from Objective-C, which is
8399
/// undefined behaviour until `C-unwind` is stabilized, see [RFC-2945].
@@ -98,9 +114,8 @@ pub unsafe fn catch<R>(closure: impl FnOnce() -> R) -> Result<R, Option<Id<Objec
98114
#[cfg(test)]
99115
mod tests {
100116
use alloc::string::ToString;
101-
use core::ptr;
102117

103-
use super::{catch, throw};
118+
use super::*;
104119

105120
#[test]
106121
fn test_catch() {
@@ -115,16 +130,26 @@ mod tests {
115130
}
116131

117132
#[test]
118-
fn test_throw_catch() {
133+
fn test_throw_catch_none() {
119134
let s = "Hello".to_string();
120135
let result = unsafe {
121136
catch(move || {
122137
if !s.is_empty() {
123-
throw(ptr::null_mut());
138+
throw(None);
124139
}
125140
s.len()
126141
})
127142
};
128143
assert!(result.unwrap_err().is_none());
129144
}
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+
}
130155
}

0 commit comments

Comments
 (0)