-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #653.
- Loading branch information
Showing
20 changed files
with
729 additions
and
978 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
use core::ffi::CStr; | ||
use std::sync::OnceLock; | ||
|
||
use crate::ffi::NSInteger; | ||
use crate::rc::{autoreleasepool, Retained}; | ||
use crate::runtime::{AnyClass, NSObject}; | ||
use crate::{msg_send_id, ClassType}; | ||
|
||
// Marked `#[cold]` to tell the optimizer that errors are comparatively rare. | ||
// | ||
// And intentionally not `#[inline]`, we'll let the optimizer figure out if it | ||
// wants to do that or not. | ||
#[cold] | ||
pub(crate) unsafe fn encountered_error<E: ClassType>(err: *mut E) -> Retained<E> { | ||
// SAFETY: Caller ensures that the pointer is valid. | ||
unsafe { Retained::retain(err) }.unwrap_or_else(|| { | ||
let err = null_error(); | ||
assert!(E::IS_NSERROR_COMPATIBLE); | ||
// SAFETY: Just checked (via `const` assertion) that the `E` type is | ||
// either `NSError` or `NSObject`, and hence it is valid to cast the | ||
// `NSObject` that we have here to that. | ||
unsafe { Retained::cast_unchecked(err) } | ||
}) | ||
} | ||
|
||
/// Poor mans string equality in `const`. Implements `a == b`. | ||
const fn is_eq(a: &str, b: &str) -> bool { | ||
let a = a.as_bytes(); | ||
let b = b.as_bytes(); | ||
|
||
if a.len() != b.len() { | ||
return false; | ||
} | ||
|
||
let mut i = 0; | ||
while i < a.len() { | ||
if a[i] != b[i] { | ||
return false; | ||
} | ||
i += 1; | ||
} | ||
|
||
true | ||
} | ||
|
||
// TODO: Use inline `const` once in MSRV (or add proper trait bounds). | ||
trait IsNSError { | ||
const IS_NSERROR_COMPATIBLE: bool; | ||
} | ||
|
||
impl<T: ClassType> IsNSError for T { | ||
const IS_NSERROR_COMPATIBLE: bool = { | ||
if is_eq(T::NAME, "NSError") || is_eq(T::NAME, "NSObject") { | ||
true | ||
} else { | ||
// The post monomorphization error here is not nice, but it's | ||
// better than UB because the user used a type that cannot be | ||
// converted to NSError. | ||
// | ||
// TODO: Add a trait bound or similar instead. | ||
panic!("error parameter must be either `NSError` or `NSObject`") | ||
} | ||
}; | ||
} | ||
|
||
#[cold] // Mark the NULL error branch as cold | ||
fn null_error() -> Retained<NSObject> { | ||
static CACHED_NULL_ERROR: OnceLock<NSErrorWrapper> = OnceLock::new(); | ||
|
||
// We use a OnceLock here, since performance doesn't really matter, and | ||
// using an AtomicPtr would leak under (very) high initialization | ||
// contention. | ||
CACHED_NULL_ERROR.get_or_init(create_null_error).0.clone() | ||
} | ||
|
||
struct NSErrorWrapper(Retained<NSObject>); | ||
|
||
// SAFETY: NSError is immutable and thread safe. | ||
unsafe impl Send for NSErrorWrapper {} | ||
unsafe impl Sync for NSErrorWrapper {} | ||
|
||
#[cold] // Mark the error creation branch as cold | ||
fn create_null_error() -> NSErrorWrapper { | ||
// Wrap creation in an autoreleasepool, since we don't know anything about | ||
// the outside world, and we don't want to appear to leak. | ||
autoreleasepool(|_| { | ||
// TODO: Replace with c string literals once in MSRV. | ||
|
||
// SAFETY: The string is NUL terminated. | ||
let cls = unsafe { CStr::from_bytes_with_nul_unchecked(b"NSString\0") }; | ||
// Intentional dynamic lookup, we don't know if Foundation is linked. | ||
let cls = AnyClass::get(cls).unwrap_or_else(foundation_not_linked); | ||
|
||
// SAFETY: The string is NUL terminated. | ||
let domain = unsafe { CStr::from_bytes_with_nul_unchecked(b"__objc2.missingError\0") }; | ||
// SAFETY: The signate is correct, and the string is UTF-8 encoded and | ||
// NUL terminated. | ||
let domain: Retained<NSObject> = | ||
unsafe { msg_send_id![cls, stringWithUTF8String: domain.as_ptr()] }; | ||
|
||
// SAFETY: The string is valid. | ||
let cls = unsafe { CStr::from_bytes_with_nul_unchecked(b"NSError\0") }; | ||
// Intentional dynamic lookup, we don't know if Foundation is linked. | ||
let cls = AnyClass::get(cls).unwrap_or_else(foundation_not_linked); | ||
|
||
let domain: &NSObject = &domain; | ||
let code: NSInteger = 0; | ||
let user_info: Option<&NSObject> = None; | ||
// SAFETY: The signate is correct. | ||
let err: Retained<NSObject> = | ||
unsafe { msg_send_id![cls, errorWithDomain: domain, code: code, userInfo: user_info] }; | ||
NSErrorWrapper(err) | ||
}) | ||
} | ||
|
||
fn foundation_not_linked() -> &'static AnyClass { | ||
panic!("Foundation must be linked to get a proper error message on NULL errors") | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_is_eq() { | ||
assert!(is_eq("NSError", "NSError")); | ||
assert!(!is_eq("nserror", "NSError")); | ||
assert!(!is_eq("CFError", "NSError")); | ||
assert!(!is_eq("NSErr", "NSError")); | ||
assert!(!is_eq("NSErrorrrr", "NSError")); | ||
} | ||
|
||
#[test] | ||
fn test_create() { | ||
let _ = create_null_error().0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.