Skip to content

Commit 1cf7ed3

Browse files
committed
Clean up hack around Exception object
1 parent 32be247 commit 1cf7ed3

File tree

2 files changed

+49
-100
lines changed

2 files changed

+49
-100
lines changed

objc2/src/exception.rs

Lines changed: 30 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
//! - [Exception Programming Topics for Cocoa](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html)
1414
//! - [The Objective-C Programming Language - Exception Handling](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocExceptionHandling.html)
1515
//! - [Exception Handling in LLVM](https://llvm.org/docs/ExceptionHandling.html)
16+
//!
17+
//! [`msg_send!`]: crate::msg_send
1618
1719
// TODO: Test this with panic=abort, and ensure that the code-size is
1820
// reasonable in that case.
1921

20-
use alloc::string::String;
21-
use alloc::string::ToString;
2222
#[cfg(feature = "exception")]
2323
use core::ffi::c_void;
2424
use core::fmt;
@@ -29,41 +29,16 @@ use core::panic::RefUnwindSafe;
2929
use core::panic::UnwindSafe;
3030
#[cfg(feature = "exception")]
3131
use core::ptr;
32-
use core::slice;
3332
use objc2_encode::Encoding;
3433
use objc2_encode::RefEncode;
3534
use std::error::Error;
36-
use std::os::raw::c_char;
3735

3836
#[cfg(feature = "exception")]
3937
use crate::ffi;
40-
use crate::rc::autoreleasepool;
38+
#[cfg(feature = "exception")]
4139
use crate::rc::{Id, Shared};
42-
use crate::runtime::Class;
4340
use crate::runtime::Object;
4441
use crate::Message;
45-
use crate::{msg_send, msg_send_bool, msg_send_id, sel};
46-
47-
/// Unfortunate reimplementation of `objc2::foundation::NSString`.
48-
///
49-
/// I guess this is the price of wanting to do things "right"...
50-
unsafe fn to_string_hack(obj: Id<Object, Shared>) -> String {
51-
#[cfg(feature = "apple")]
52-
const UTF8_ENCODING: usize = 4;
53-
#[cfg(feature = "gnustep-1-7")]
54-
const UTF8_ENCODING: i32 = 4;
55-
56-
autoreleasepool(|_| {
57-
let len: usize = unsafe { msg_send![&obj, lengthOfBytesUsingEncoding: UTF8_ENCODING] };
58-
59-
let bytes: *const c_char = unsafe { msg_send![&obj, UTF8String] };
60-
let bytes: *const u8 = bytes.cast();
61-
let bytes: &[u8] = unsafe { slice::from_raw_parts(bytes, len) };
62-
63-
// Use lossy to avoid panic in error situations
64-
String::from_utf8_lossy(bytes).to_string()
65-
})
66-
}
6742

6843
/// An Objective-C exception.
6944
///
@@ -100,78 +75,45 @@ impl AsRef<Object> for Exception {
10075
// Note: We can't implement `Send` nor `Sync` since the exception could be
10176
// anything!
10277

103-
impl Exception {
104-
/// Checks whether this is an instance of `NSException`.
105-
///
106-
/// This should be considered a hint; it may return `false` in very, very
107-
/// few cases where it is actually `true`, but if it returns `true`, then
108-
/// it is definitely an instance of `NSException`.
109-
fn is_nsexception(&self) -> bool {
110-
// If `NSException` class is present
111-
if let Some(cls) = Class::get("NSException") {
112-
if self.0.class().responds_to(sel!(isKindOfClass:)) {
113-
unsafe { msg_send_bool![self, isKindOfClass: cls] }
114-
} else {
115-
false
116-
}
117-
} else {
118-
false
119-
}
120-
}
121-
122-
// SAFETY: Must ensure that self is NSException
123-
unsafe fn name(&self) -> Option<String> {
124-
let obj: Option<Id<Object, Shared>> = unsafe { msg_send_id![self, name] };
125-
obj.map(|obj| unsafe { to_string_hack(obj) })
126-
}
127-
128-
// SAFETY: Must ensure that self is NSException
129-
unsafe fn reason(&self) -> Option<String> {
130-
let obj: Option<Id<Object, Shared>> = unsafe { msg_send_id![self, reason] };
131-
obj.map(|obj| unsafe { to_string_hack(obj) })
132-
}
133-
}
134-
135-
// This is not in any way efficient, but that's not really the point!
136-
//
137-
// We mostly just want to present a somewhat usable error message when the
138-
// `catch-all` feature is enabled!
13978
impl fmt::Debug for Exception {
14079
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141-
write!(f, "exception {:?}", self.0)?;
142-
143-
// Attempt to provide better error message
144-
if self.is_nsexception() {
145-
// SAFETY: We know that these are safe to call since this is an
146-
// instance of `NSException`.
147-
let name = unsafe { self.name() };
148-
let reason = unsafe { self.reason() };
149-
150-
if let Some(name) = name {
151-
write!(f, " '{}'", name)?;
152-
} else {
153-
write!(f, " (NULL)")?;
154-
}
155-
156-
if let Some(reason) = reason {
157-
write!(f, " reason:{}", reason)?;
158-
} else {
159-
write!(f, " reason:(NULL)")?;
160-
}
80+
write!(f, "exception ")?;
81+
82+
// Attempt to present a somewhat usable error message if the
83+
// `foundation` feature is enabled
84+
#[cfg(feature = "foundation")]
85+
if crate::foundation::NSException::is_nsexception(self) {
86+
// SAFETY: Just checked that object is an NSException
87+
let obj: *const Self = self;
88+
let obj = unsafe {
89+
obj.cast::<crate::foundation::NSException>()
90+
.as_ref()
91+
.unwrap()
92+
};
93+
return write!(f, "{:?}", obj);
16194
}
16295

163-
Ok(())
96+
// Fall back to `Object` Debug
97+
write!(f, "{:?}", self.0)
16498
}
16599
}
166100

167101
impl fmt::Display for Exception {
168102
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169-
if self.is_nsexception() {
170-
// SAFETY: Just checked that this is NSException.
171-
if let Some(reason) = unsafe { self.reason() } {
103+
#[cfg(feature = "foundation")]
104+
if crate::foundation::NSException::is_nsexception(self) {
105+
// SAFETY: Just checked that object is an NSException
106+
let obj: *const Self = self;
107+
let obj = unsafe {
108+
obj.cast::<crate::foundation::NSException>()
109+
.as_ref()
110+
.unwrap()
111+
};
112+
if let Some(reason) = obj.reason() {
172113
return write!(f, "{}", reason);
173114
}
174115
}
116+
175117
write!(f, "unknown exception")
176118
}
177119
}

objc2/src/foundation/exception.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl NSException {
7070
/// [doc]: https://developer.apple.com/documentation/foundation/nsexceptionname?language=objc
7171
pub fn name(&self) -> Id<NSExceptionName, Shared> {
7272
// Nullability not documented, but a name is expected in most places.
73-
unsafe { msg_send_id![self, name].unwrap() }
73+
unsafe { msg_send_id![self, name].expect("unexpected NULL NSException name") }
7474
}
7575

7676
/// A human-readable message summarizing the reason for the exception.
@@ -89,20 +89,27 @@ impl NSException {
8989
unsafe { Id::cast(this) }
9090
}
9191

92-
/// Convert this into an [`Exception`] object.
92+
pub(crate) fn is_nsexception(obj: &Exception) -> bool {
93+
if obj.class().responds_to(sel!(isKindOfClass:)) {
94+
// SAFETY: We only use `isKindOfClass:` on NSObject
95+
let obj: *const Exception = obj;
96+
let obj = unsafe { obj.cast::<NSObject>().as_ref().unwrap() };
97+
obj.is_kind_of(Self::class())
98+
} else {
99+
false
100+
}
101+
}
102+
103+
/// Create this from an [`Exception`] object.
104+
///
105+
/// This should be considered a hint; it may return `Err` in very, very
106+
/// few cases where the object is actually an instance of `NSException`.
93107
pub fn from_exception(
94108
obj: Id<Exception, Shared>,
95109
) -> Result<Id<Self, Shared>, Id<Exception, Shared>> {
96-
if obj.class().responds_to(sel!(isKindOfClass:)) {
97-
// SAFETY: We only use `isKindOfClass:` on NSObject
98-
let obj = unsafe { Id::cast::<NSObject>(obj) };
99-
if obj.is_kind_of(Self::class()) {
100-
// SAFETY: Just checked the object is an NSException
101-
Ok(unsafe { Id::cast::<Self>(obj) })
102-
} else {
103-
// SAFETY: Cast back
104-
Err(unsafe { Id::cast(obj) })
105-
}
110+
if Self::is_nsexception(&obj) {
111+
// SAFETY: Just checked the object is an NSException
112+
Ok(unsafe { Id::cast::<Self>(obj) })
106113
} else {
107114
Err(obj)
108115
}

0 commit comments

Comments
 (0)