#[cfg(feature = "exception")]
use core::ffi::c_void;
use core::fmt;
#[cfg(feature = "exception")]
use core::mem;
use core::ops::Deref;
use core::panic::RefUnwindSafe;
use core::panic::UnwindSafe;
#[cfg(feature = "exception")]
use core::ptr;
use objc2_encode::Encoding;
use objc2_encode::RefEncode;
use std::error::Error;
#[cfg(feature = "exception")]
use crate::ffi;
#[cfg(feature = "exception")]
use crate::rc::{Id, Shared};
use crate::runtime::Object;
use crate::Message;
#[repr(transparent)]
pub struct Exception(Object);
unsafe impl RefEncode for Exception {
const ENCODING_REF: Encoding = Encoding::Object;
}
unsafe impl Message for Exception {}
impl Deref for Exception {
type Target = Object;
#[inline]
fn deref(&self) -> &Object {
&self.0
}
}
impl AsRef<Object> for Exception {
#[inline]
fn as_ref(&self) -> &Object {
self
}
}
impl fmt::Debug for Exception {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "exception ")?;
#[cfg(feature = "foundation")]
if crate::foundation::NSException::is_nsexception(self) {
let obj: *const Self = self;
let obj = unsafe {
obj.cast::<crate::foundation::NSException>()
.as_ref()
.unwrap()
};
return write!(f, "{:?}", obj);
}
write!(f, "{:?}", self.0)
}
}
impl fmt::Display for Exception {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "foundation")]
if crate::foundation::NSException::is_nsexception(self) {
let obj: *const Self = self;
let obj = unsafe {
obj.cast::<crate::foundation::NSException>()
.as_ref()
.unwrap()
};
if let Some(reason) = obj.reason() {
return write!(f, "{}", reason);
}
}
write!(f, "unknown exception")
}
}
impl Error for Exception {}
impl UnwindSafe for Exception {}
impl RefUnwindSafe for Exception {}
#[inline]
#[cfg(feature = "exception")] pub unsafe fn throw(exception: Id<Exception, Shared>) -> ! {
let ptr = exception.0.as_ptr() as *mut ffi::objc_object;
unsafe { ffi::objc_exception_throw(ptr) }
}
#[cfg(feature = "exception")]
unsafe fn try_no_ret<F: FnOnce()>(closure: F) -> Result<(), Option<Id<Exception, Shared>>> {
#[cfg(not(feature = "unstable-c-unwind"))]
let f = {
extern "C" fn try_objc_execute_closure<F>(closure: &mut Option<F>)
where
F: FnOnce(),
{
let closure = closure.take().unwrap();
closure();
}
let f: extern "C" fn(&mut Option<F>) = try_objc_execute_closure;
let f: extern "C" fn(*mut c_void) = unsafe { mem::transmute(f) };
f
};
#[cfg(feature = "unstable-c-unwind")]
let f = {
extern "C-unwind" fn try_objc_execute_closure<F>(closure: &mut Option<F>)
where
F: FnOnce(),
{
let closure = closure.take().unwrap();
closure();
}
let f: extern "C-unwind" fn(&mut Option<F>) = try_objc_execute_closure;
let f: extern "C-unwind" fn(*mut c_void) = unsafe { mem::transmute(f) };
f
};
let mut closure = Some(closure);
let context: *mut Option<F> = &mut closure;
let context = context.cast();
let mut exception = ptr::null_mut();
let success = unsafe { ffi::rust_objc_sys_0_2_try_catch_exception(f, context, &mut exception) };
if success == 0 {
Ok(())
} else {
Err(unsafe { Id::new(exception.cast()) })
}
}
#[cfg(feature = "exception")]
pub unsafe fn catch<R>(
closure: impl FnOnce() -> R + UnwindSafe,
) -> Result<R, Option<Id<Exception, Shared>>> {
let mut value = None;
let value_ref = &mut value;
let closure = move || {
*value_ref = Some(closure());
};
let result = unsafe { try_no_ret(closure) };
result.map(|()| value.unwrap())
}
#[cfg(test)]
#[cfg(feature = "exception")]
mod tests {
use alloc::format;
use alloc::string::ToString;
use super::*;
use crate::{class, msg_send_id};
#[test]
fn test_catch() {
let mut s = "Hello".to_string();
let result = unsafe {
catch(move || {
s.push_str(", World!");
s
})
};
assert_eq!(result.unwrap(), "Hello, World!");
}
#[test]
#[cfg_attr(
all(feature = "apple", target_os = "macos", target_arch = "x86"),
ignore = "`NULL` exceptions are invalid on 32-bit / w. fragile runtime"
)]
fn test_catch_null() {
let s = "Hello".to_string();
let result = unsafe {
catch(move || {
if !s.is_empty() {
ffi::objc_exception_throw(ptr::null_mut())
}
s.len()
})
};
assert!(result.unwrap_err().is_none());
}
#[test]
fn test_throw_catch_object() {
let obj: Id<Exception, Shared> = unsafe { msg_send_id![class!(NSObject), new] };
let _obj2 = obj.clone();
let ptr: *const Exception = &*obj;
let result = unsafe { catch(|| throw(obj)) };
let obj = result.unwrap_err().unwrap();
assert_eq!(
format!("{:?}", obj),
format!("exception <NSObject: {:p}>", ptr)
);
assert!(ptr::eq(&*obj, ptr));
}
}