Skip to content

Commit b76694f

Browse files
committed
Add UnwindSafe and RefUnwindSafe impls for Foundation types
1 parent 4a073e0 commit b76694f

File tree

12 files changed

+112
-37
lines changed

12 files changed

+112
-37
lines changed

objc2-foundation/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## Unreleased - YYYY-MM-DD
88

9+
### Added
10+
* Implement `UnwindSafe` and `RefUnwindSafe` for all objects.
11+
912
### Removed
1013
* `NSObject::hash_code`, `NSObject::is_equal` and `NSObject::description` in
1114
favour of just having the trait implementations `Hash`, `PartiqalEq` and

objc2-foundation/src/array.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use alloc::vec::Vec;
22
use core::marker::PhantomData;
33
use core::ops::{Index, Range};
4+
use core::panic::{RefUnwindSafe, UnwindSafe};
45

56
use objc2::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
67
use objc2::runtime::{Class, Object};
@@ -28,6 +29,7 @@ __inner_extern_class! {
2829
#[derive(Debug, PartialEq, Eq, Hash)]
2930
unsafe pub struct NSArray<T, O: Ownership>: NSObject {
3031
item: PhantomData<Id<T, O>>,
32+
notunwindsafe: PhantomData<&'static mut ()>,
3133
}
3234
}
3335

@@ -39,6 +41,11 @@ unsafe impl<T: Sync + Send> Send for NSArray<T, Shared> {}
3941
unsafe impl<T: Sync> Sync for NSArray<T, Owned> {}
4042
unsafe impl<T: Send> Send for NSArray<T, Owned> {}
4143

44+
// Also same as Id<T, O>
45+
impl<T: RefUnwindSafe, O: Ownership> RefUnwindSafe for NSArray<T, O> {}
46+
impl<T: RefUnwindSafe> UnwindSafe for NSArray<T, Shared> {}
47+
impl<T: UnwindSafe> UnwindSafe for NSArray<T, Owned> {}
48+
4249
pub(crate) unsafe fn from_refs<T: Message + ?Sized>(cls: &Class, refs: &[&T]) -> *mut Object {
4350
let obj: *mut Object = unsafe { msg_send![cls, alloc] };
4451
unsafe {

objc2-foundation/src/attributed_string.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::panic::{RefUnwindSafe, UnwindSafe};
2+
13
use objc2::rc::{DefaultId, Id, Shared};
24
use objc2::runtime::Object;
35
use objc2::{msg_send, msg_send_id};
@@ -29,6 +31,10 @@ extern_class! {
2931
unsafe impl Sync for NSAttributedString {}
3032
unsafe impl Send for NSAttributedString {}
3133

34+
// Same reasoning as `NSString`.
35+
impl UnwindSafe for NSAttributedString {}
36+
impl RefUnwindSafe for NSAttributedString {}
37+
3238
/// Attributes that you can apply to text in an attributed string.
3339
pub type NSAttributedStringKey = NSString;
3440

objc2-foundation/src/data.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
use alloc::vec::Vec;
33
use core::ffi::c_void;
44
use core::ops::Index;
5+
use core::panic::{RefUnwindSafe, UnwindSafe};
56
use core::slice::{self, SliceIndex};
67

78
use objc2::rc::{DefaultId, Id, Shared};
@@ -25,6 +26,9 @@ extern_class! {
2526
unsafe impl Sync for NSData {}
2627
unsafe impl Send for NSData {}
2728

29+
impl UnwindSafe for NSData {}
30+
impl RefUnwindSafe for NSData {}
31+
2832
impl NSData {
2933
pub fn new() -> Id<Self, Shared> {
3034
unsafe { msg_send_id![Self::class(), new].unwrap() }

objc2-foundation/src/dictionary.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use alloc::vec::Vec;
22
use core::cmp::min;
33
use core::marker::PhantomData;
44
use core::ops::Index;
5+
use core::panic::{RefUnwindSafe, UnwindSafe};
56
use core::ptr;
67

78
use objc2::rc::{DefaultId, Id, Owned, Shared, SliceId};
@@ -22,6 +23,10 @@ __inner_extern_class! {
2223
unsafe impl<K: Sync + Send, V: Sync> Sync for NSDictionary<K, V> {}
2324
unsafe impl<K: Sync + Send, V: Send> Send for NSDictionary<K, V> {}
2425

26+
// Approximately same as `NSArray<T, Shared>`
27+
impl<K: UnwindSafe, V: UnwindSafe> UnwindSafe for NSDictionary<K, V> {}
28+
impl<K: RefUnwindSafe, V: RefUnwindSafe> RefUnwindSafe for NSDictionary<K, V> {}
29+
2530
impl<K: Message, V: Message> NSDictionary<K, V> {
2631
pub fn new() -> Id<Self, Shared> {
2732
unsafe { msg_send_id![Self::class(), new].unwrap() }

objc2-foundation/src/lib.rs

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -118,45 +118,65 @@ mod zone;
118118

119119
#[cfg(test)]
120120
mod tests {
121+
use core::panic::{RefUnwindSafe, UnwindSafe};
121122
use objc2::rc::{Id, Owned, Shared};
122123

123124
use super::*;
124125

125-
fn assert_send_sync<T: Send + Sync>() {}
126+
// We expect most Foundation types to be UnwindSafe and RefUnwindSafe,
127+
// since they follow Rust's usual mutability rules (&T = immutable).
128+
//
129+
// A _lot_ of Objective-C code out there would be subtly broken if e.g.
130+
// `NSString` wasn't exception safe!
131+
// As an example: -[NSArray objectAtIndex:] can throw, but it is still
132+
// perfectly valid to access the array after that!
133+
//
134+
// Note that e.g. `&mut NSMutableString` is still not exception safe, but
135+
// that is the entire idea of `UnwindSafe` (that if the object could have
136+
// been mutated, it is not exception safe).
137+
//
138+
// Also note that this is still just a speed bump, not actually part of
139+
// any unsafe contract; we really protect against it if something is not
140+
// exception safe, since `UnwindSafe` is a safe trait.
141+
fn assert_unwindsafe<T: UnwindSafe + RefUnwindSafe>() {}
142+
143+
fn assert_auto_traits<T: Send + Sync + UnwindSafe + RefUnwindSafe>() {
144+
assert_unwindsafe::<T>();
145+
}
126146

127147
#[test]
128-
fn send_sync() {
129-
assert_send_sync::<NSArray<NSString, Shared>>();
130-
assert_send_sync::<NSArray<NSString, Owned>>();
131-
assert_send_sync::<Id<NSArray<NSString, Shared>, Shared>>();
132-
assert_send_sync::<Id<NSArray<NSString, Owned>, Shared>>();
133-
assert_send_sync::<Id<NSArray<NSString, Shared>, Owned>>();
134-
assert_send_sync::<Id<NSArray<NSString, Owned>, Owned>>();
135-
136-
assert_send_sync::<NSAttributedString>();
137-
assert_send_sync::<NSComparisonResult>();
138-
assert_send_sync::<NSData>();
139-
assert_send_sync::<NSDictionary<NSString, Shared>>();
140-
// TODO: Figure out if safe?
141-
// assert_send_sync::<NSEnumerator<NSString>>();
142-
// assert_send_sync::<NSFastEnumerator<NSArray<NSString, Shared>>>();
143-
assert_send_sync::<NSException>();
144-
assert_send_sync::<CGFloat>();
145-
assert_send_sync::<NSPoint>();
146-
assert_send_sync::<NSRect>();
147-
assert_send_sync::<NSSize>();
148-
assert_send_sync::<NSMutableArray<NSString, Shared>>();
149-
assert_send_sync::<NSMutableAttributedString>();
150-
assert_send_sync::<NSMutableData>();
151-
assert_send_sync::<NSMutableString>();
152-
// assert_send_sync::<NSObject>(); // Intentional
153-
assert_send_sync::<NSProcessInfo>();
154-
assert_send_sync::<NSRange>();
155-
assert_send_sync::<NSString>();
156-
// assert_send_sync::<MainThreadMarker>(); // Intentional
157-
assert_send_sync::<NSThread>();
158-
assert_send_sync::<NSUUID>();
159-
assert_send_sync::<NSValue<i32>>();
160-
assert_send_sync::<NSZone>();
148+
fn send_sync_unwindsafe() {
149+
assert_auto_traits::<NSArray<NSString, Shared>>();
150+
assert_auto_traits::<NSArray<NSString, Owned>>();
151+
assert_auto_traits::<Id<NSArray<NSString, Shared>, Shared>>();
152+
assert_auto_traits::<Id<NSArray<NSString, Owned>, Shared>>();
153+
assert_auto_traits::<Id<NSArray<NSString, Shared>, Owned>>();
154+
assert_auto_traits::<Id<NSArray<NSString, Owned>, Owned>>();
155+
156+
assert_auto_traits::<NSAttributedString>();
157+
assert_auto_traits::<NSComparisonResult>();
158+
assert_auto_traits::<NSData>();
159+
assert_auto_traits::<NSDictionary<NSString, Shared>>();
160+
// TODO: Figure out if Send + Sync is safe?
161+
// assert_auto_traits::<NSEnumerator<NSString>>();
162+
// assert_auto_traits::<NSFastEnumerator<NSArray<NSString, Shared>>>();
163+
assert_auto_traits::<NSException>();
164+
assert_auto_traits::<CGFloat>();
165+
assert_auto_traits::<NSPoint>();
166+
assert_auto_traits::<NSRect>();
167+
assert_auto_traits::<NSSize>();
168+
assert_auto_traits::<NSMutableArray<NSString, Shared>>();
169+
assert_auto_traits::<NSMutableAttributedString>();
170+
assert_auto_traits::<NSMutableData>();
171+
assert_auto_traits::<NSMutableString>();
172+
// assert_auto_traits::<NSObject>(); // Intentional
173+
assert_auto_traits::<NSProcessInfo>();
174+
assert_auto_traits::<NSRange>();
175+
assert_auto_traits::<NSString>();
176+
assert_unwindsafe::<MainThreadMarker>(); // Intentional
177+
assert_auto_traits::<NSThread>();
178+
assert_auto_traits::<NSUUID>();
179+
assert_auto_traits::<NSValue<i32>>();
180+
assert_unwindsafe::<NSZone>(); // Intentional
161181
}
162182
}

objc2-foundation/src/process_info.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::panic::{RefUnwindSafe, UnwindSafe};
2+
13
use objc2::msg_send_id;
24
use objc2::rc::{Id, Shared};
35

@@ -18,6 +20,9 @@ extern_class! {
1820
unsafe impl Send for NSProcessInfo {}
1921
unsafe impl Sync for NSProcessInfo {}
2022

23+
impl UnwindSafe for NSProcessInfo {}
24+
impl RefUnwindSafe for NSProcessInfo {}
25+
2126
impl NSProcessInfo {
2227
pub fn process_info() -> Id<NSProcessInfo, Shared> {
2328
// currentThread is @property(strong), what does that mean?

objc2-foundation/src/string.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ extern_class! {
4646
unsafe impl Sync for NSString {}
4747
unsafe impl Send for NSString {}
4848

49+
// Even if an exception occurs inside a string method, the state of the string
50+
// (should) still be perfectly safe to access.
4951
impl UnwindSafe for NSString {}
5052
impl RefUnwindSafe for NSString {}
5153

objc2-foundation/src/thread.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use core::marker::PhantomData;
2+
use core::panic::{RefUnwindSafe, UnwindSafe};
23

34
use objc2::rc::{Id, Shared};
45
use objc2::{msg_send, msg_send_bool, msg_send_id};
@@ -16,6 +17,9 @@ extern_class! {
1617
unsafe impl Send for NSThread {}
1718
unsafe impl Sync for NSThread {}
1819

20+
impl UnwindSafe for NSThread {}
21+
impl RefUnwindSafe for NSThread {}
22+
1923
impl NSThread {
2024
/// Returns the [`NSThread`] object representing the current thread.
2125
pub fn current() -> Id<Self, Shared> {

objc2-foundation/src/uuid.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::panic::{RefUnwindSafe, UnwindSafe};
2+
13
use objc2::rc::{Id, Shared};
24
use objc2::{msg_send, msg_send_id, Encode, Encoding, RefEncode};
35

@@ -29,6 +31,9 @@ unsafe impl RefEncode for UuidBytes {
2931
unsafe impl Sync for NSUUID {}
3032
unsafe impl Send for NSUUID {}
3133

34+
impl UnwindSafe for NSUUID {}
35+
impl RefUnwindSafe for NSUUID {}
36+
3237
impl NSUUID {
3338
// TODO: `nil` method?
3439

0 commit comments

Comments
 (0)