objc2_foundation/
exception.rs

1use core::fmt;
2use core::hint::unreachable_unchecked;
3use core::panic::{RefUnwindSafe, UnwindSafe};
4
5use objc2::exception::Exception;
6use objc2::rc::Retained;
7use objc2::runtime::{AnyObject, NSObject, NSObjectProtocol};
8use objc2::{extern_methods, msg_send, sel, ClassType};
9
10use crate::{util, NSException};
11
12// SAFETY: Exception objects are immutable data containers, and documented as
13// thread safe.
14unsafe impl Sync for NSException {}
15unsafe impl Send for NSException {}
16
17impl UnwindSafe for NSException {}
18impl RefUnwindSafe for NSException {}
19
20impl NSException {
21    extern_methods!(
22        #[unsafe(method(raise))]
23        unsafe fn raise_raw(&self);
24    );
25}
26
27impl NSException {
28    /// Create a new [`NSException`] object.
29    ///
30    /// Returns `None` if the exception couldn't be created (example: If the
31    /// process is out of memory).
32    #[cfg(all(feature = "NSObjCRuntime", feature = "NSString"))]
33    #[cfg(feature = "NSDictionary")]
34    pub fn new(
35        name: &crate::NSExceptionName,
36        reason: Option<&crate::NSString>,
37        user_info: Option<&crate::NSDictionary>,
38    ) -> Option<Retained<Self>> {
39        use objc2::AnyThread;
40
41        unsafe {
42            objc2::msg_send![
43                Self::alloc(),
44                initWithName: name,
45                reason: reason,
46                userInfo: user_info,
47            ]
48        }
49    }
50
51    /// Raises the exception, causing program flow to jump to the local
52    /// exception handler.
53    ///
54    /// This is equivalent to using `objc2::exception::throw`.
55    pub fn raise(&self) -> ! {
56        // SAFETY: `NSException` is immutable, so it is safe to give to
57        // the place where `@catch` receives it.
58        unsafe { self.raise_raw() };
59        // SAFETY: `raise` will throw an exception, or abort if something
60        // unexpected happened.
61        unsafe { unreachable_unchecked() }
62    }
63
64    /// Convert this into an [`Exception`] object.
65    pub fn into_exception(this: Retained<Self>) -> Retained<Exception> {
66        // SAFETY: Downcasting to "subclass"
67        unsafe { Retained::cast_unchecked(this) }
68    }
69
70    fn is_nsexception(obj: &Exception) -> bool {
71        if obj.class().responds_to(sel!(isKindOfClass:)) {
72            // SAFETY: We only use `isKindOfClass:` on NSObject
73            let obj: *const Exception = obj;
74            let obj = unsafe { obj.cast::<NSObject>().as_ref().unwrap() };
75            obj.isKindOfClass(Self::class())
76        } else {
77            false
78        }
79    }
80
81    /// Create this from an [`Exception`] object.
82    ///
83    /// This should be considered a hint; it may return `Err` in very, very
84    /// few cases where the object is actually an instance of `NSException`.
85    pub fn from_exception(obj: Retained<Exception>) -> Result<Retained<Self>, Retained<Exception>> {
86        if Self::is_nsexception(&obj) {
87            // SAFETY: Just checked the object is an NSException
88            Ok(unsafe { Retained::cast_unchecked::<Self>(obj) })
89        } else {
90            Err(obj)
91        }
92    }
93}
94
95impl fmt::Debug for NSException {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        let obj: &AnyObject = self.as_ref();
98        write!(f, "{obj:?}")?;
99
100        write!(f, " '")?;
101        let name: Retained<NSObject> = unsafe { msg_send![self, name] };
102        // SAFETY: `name` returns `NSExceptionName`, which is `NSString`.
103        unsafe { util::display_string(&name, f)? };
104        write!(f, "'")?;
105
106        write!(f, " reason: ")?;
107        let reason: Option<Retained<NSObject>> = unsafe { msg_send![self, reason] };
108        if let Some(reason) = reason {
109            // SAFETY: `reason` returns `NSString`.
110            unsafe { util::display_string(&reason, f)? };
111        } else {
112            write!(f, "(NULL)")?;
113        }
114        Ok(())
115    }
116}