objc2_exception_helper/
lib.rs

1//! External helper methods for catching Objective-C exceptions.
2//!
3//! This exists as a separate crate to avoid having to compile a `build.rs`
4//! script in `objc2` in most cases, and to properly version the compiled
5//! binary with [the `links` Cargo manifest key][cargo-links].
6//!
7//! You should not need to use this crate directly.
8//!
9//! [cargo-links]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
10#![no_std]
11#![doc(html_root_url = "https://docs.rs/objc2-exception-helper/0.1.1")]
12
13// Forwards-compatibility
14#[cfg(feature = "alloc")]
15extern crate alloc;
16#[cfg(feature = "std")]
17extern crate std;
18
19use core::ffi::c_void;
20
21type TryCatchClosure = extern "C-unwind" fn(*mut c_void);
22
23// `try_catch` is `extern "C-unwind"`, since it does not use `@catch (...)`,
24// but instead let unhandled exceptions pass through.
25extern "C-unwind" {
26    /// Call the given function inside an Objective-C `@try/@catch` block.
27    ///
28    /// Defined in `src/try_catch.m` and compiled in `build.rs`.
29    ///
30    /// Alternatively, we could manually write assembly for this function like
31    /// [`objrs` does][manual-asm] does, that would cut down on a build stage
32    /// (and would probably give us a bit better performance), but it gets
33    /// unwieldy _very_ quickly, so I chose the much more stable option.
34    ///
35    /// Another thing to remember: While Rust's and Objective-C's unwinding
36    /// mechanisms are similar now, Rust's is explicitly unspecified, and they
37    /// may diverge significantly in the future; so handling this in pure Rust
38    /// (using mechanisms like core::intrinsics::r#try) is not an option!
39    ///
40    /// [manual-asm]: https://gitlab.com/objrs/objrs/-/blob/b4f6598696b3fa622e6fddce7aff281770b0a8c2/src/exception.rs
41    ///
42    ///
43    /// # Panics
44    ///
45    /// This panics / continues unwinding if the unwind is not triggered by an
46    /// Objective-C exception (i.e. it was triggered by Rust/C++/...).
47    #[link_name = "objc2_exception_helper_0_1_try_catch"]
48    pub fn try_catch(f: TryCatchClosure, context: *mut c_void, error: *mut *mut c_void) -> u8;
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use core::ptr;
55
56    #[link(name = "objc", kind = "dylib")]
57    extern "C" {}
58
59    #[test]
60    fn context_is_passed_through_correctly() {
61        struct SyncPtr(*mut c_void);
62        unsafe impl Sync for SyncPtr {}
63
64        static VALUE: SyncPtr = SyncPtr(&VALUE.0 as *const *mut c_void as *mut c_void);
65
66        extern "C-unwind" fn check_value(value: *mut c_void) {
67            assert_eq!(VALUE.0, value);
68        }
69
70        let mut error: *mut c_void = ptr::null_mut();
71        let res = unsafe { try_catch(check_value, VALUE.0, &mut error) };
72        assert_eq!(res, 0);
73        assert!(error.is_null());
74    }
75}