1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//! External helper methods for catching Objective-C exceptions.
//!
//! This exists as a separate crate to avoid having to compile a `build.rs`
//! script in `objc2` in most cases, and to properly version the compiled
//! binary with [the `links` Cargo manifest key][cargo-links].
//!
//! You should not need to use this crate directly.
//!
//! [cargo-links]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
#![no_std]
#![doc(html_root_url = "https://docs.rs/objc2-exception-helper/0.1.0")]

// Forwards-compatibility
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

use core::ffi::c_void;

#[cfg(not(feature = "unstable-c-unwind"))]
type TryCatchClosure = extern "C" fn(*mut c_void);
#[cfg(feature = "unstable-c-unwind")]
type TryCatchClosure = extern "C-unwind" fn(*mut c_void);

// `try_catch` is deliberately `extern "C"`, we just prevented the unwind.
extern "C" {
    /// Call the given function inside an Objective-C `@try/@catch` block.
    ///
    /// Defined in `src/try_catch.m` and compiled in `build.rs`.
    ///
    /// Alternatively, we could manually write assembly for this function like
    /// [`objrs` does][manual-asm] does, that would cut down on a build stage
    /// (and would probably give us a bit better performance), but it gets
    /// unwieldy _very_ quickly, so I chose the much more stable option.
    ///
    /// Another thing to remember: While Rust's and Objective-C's unwinding
    /// mechanisms are similar now, Rust's is explicitly unspecified, and they
    /// may diverge significantly in the future; so handling this in pure Rust
    /// (using mechanisms like core::intrinsics::r#try) is not an option!
    ///
    /// [manual-asm]: https://gitlab.com/objrs/objrs/-/blob/b4f6598696b3fa622e6fddce7aff281770b0a8c2/src/exception.rs
    #[link_name = "objc2_exception_helper_0_2_try_catch_exception"]
    pub fn try_catch(f: TryCatchClosure, context: *mut c_void, error: *mut *mut c_void) -> u8;
}

#[cfg(test)]
mod tests {
    use super::*;
    use core::ptr;

    #[link(name = "objc", kind = "dylib")]
    extern "C" {}

    #[test]
    fn context_is_passed_through_correctly() {
        struct SyncPtr(*mut c_void);
        unsafe impl Sync for SyncPtr {}

        static VALUE: SyncPtr = SyncPtr(&VALUE.0 as *const *mut c_void as *mut c_void);

        #[cfg(not(feature = "unstable-c-unwind"))]
        extern "C" fn check_value(value: *mut c_void) {
            assert_eq!(VALUE.0, value);
        }
        #[cfg(feature = "unstable-c-unwind")]
        extern "C-unwind" fn check_value(value: *mut c_void) {
            assert_eq!(VALUE.0, value);
        }

        let mut error: *mut c_void = ptr::null_mut();
        let res = unsafe { try_catch(check_value, VALUE.0, &mut error) };
        assert_eq!(res, 0);
        assert!(error.is_null());
    }
}