Skip to content

Commit 576190c

Browse files
committed
Mark CoreFoundation types as thread-safe
Bit part of #696.
1 parent c9803c0 commit 576190c

File tree

11 files changed

+246
-2
lines changed

11 files changed

+246
-2
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[features]
22
# Uses the nightly derive_coerce_pointee feature to make conversions more ergonomic.
33
unstable-coerce-pointee = []
4+
5+
[dev-dependencies]
6+
static_assertions = "1.1.0"

framework-crates/objc2-core-foundation/Cargo.toml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

framework-crates/objc2-core-foundation/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mod opaque;
4444
mod retained;
4545
#[cfg(feature = "CFString")]
4646
mod string;
47+
mod thread_safety;
4748
#[cfg(feature = "CFTimeZone")]
4849
mod timezone;
4950
mod type_traits;
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
//! Thread-safety.
2+
3+
#[allow(unused_imports)]
4+
use crate::*;
5+
6+
// SAFETY: CFBundle is immutable, and can be retrieved from any thread.
7+
#[cfg(feature = "CFBundle")]
8+
unsafe impl Send for CFBundle {}
9+
#[cfg(feature = "CFBundle")]
10+
unsafe impl Sync for CFBundle {}
11+
12+
// SAFETY: `NSNotificationCenter` and `NSDistributedNotificationCenter` are
13+
// thread-safe, and those build upon this, so it must be thread-safe too.
14+
//
15+
// Additionally, they can be retrieved from any thread.
16+
//
17+
// NOTE: added notification observers are sent to the main thread, so the
18+
// functions for doing that are not necessarily safe.
19+
#[cfg(feature = "CFNotificationCenter")]
20+
unsafe impl Send for CFNotificationCenter {}
21+
#[cfg(feature = "CFNotificationCenter")]
22+
unsafe impl Sync for CFNotificationCenter {}
23+
24+
// SAFETY: CFUUID is immutable and just stores the 16 bytes.
25+
#[cfg(feature = "CFUUID")]
26+
unsafe impl Send for CFUUID {}
27+
#[cfg(feature = "CFUUID")]
28+
unsafe impl Sync for CFUUID {}
29+
30+
// SAFETY: NSNumber is thread-safe, and this is toll-free bridged to that.
31+
#[cfg(feature = "CFNumber")]
32+
unsafe impl Send for CFBoolean {}
33+
#[cfg(feature = "CFNumber")]
34+
unsafe impl Sync for CFBoolean {}
35+
#[cfg(feature = "CFNumber")]
36+
unsafe impl Send for CFNumber {}
37+
#[cfg(feature = "CFNumber")]
38+
unsafe impl Sync for CFNumber {}
39+
40+
// SAFETY: NSDate is thread-safe, and this is toll-free bridged to that.
41+
#[cfg(feature = "CFDate")]
42+
unsafe impl Send for CFDate {}
43+
#[cfg(feature = "CFDate")]
44+
unsafe impl Sync for CFDate {}
45+
46+
// SAFETY: NSError is thread-safe, and this is toll-free bridged to that.
47+
#[cfg(feature = "CFError")]
48+
unsafe impl Send for CFError {}
49+
#[cfg(feature = "CFError")]
50+
unsafe impl Sync for CFError {}
51+
52+
// SAFETY: NSLocale is thread-safe, and this is toll-free bridged to that.
53+
#[cfg(feature = "CFLocale")]
54+
unsafe impl Send for CFLocale {}
55+
#[cfg(feature = "CFLocale")]
56+
unsafe impl Sync for CFLocale {}
57+
58+
// SAFETY: NSNull is thread-safe, and this is toll-free bridged to that.
59+
#[cfg(feature = "CFBase")]
60+
unsafe impl Send for CFNull {}
61+
#[cfg(feature = "CFBase")]
62+
unsafe impl Sync for CFNull {}
63+
64+
// SAFETY: NSTimeZone is thread-safe, and this is toll-free bridged to that.
65+
#[cfg(feature = "CFDate")]
66+
unsafe impl Send for CFTimeZone {}
67+
#[cfg(feature = "CFDate")]
68+
unsafe impl Sync for CFTimeZone {}
69+
70+
// SAFETY: NSURL is thread-safe, and this is toll-free bridged to that.
71+
#[cfg(feature = "CFURL")]
72+
unsafe impl Send for CFURL {}
73+
#[cfg(feature = "CFURL")]
74+
unsafe impl Sync for CFURL {}
75+
76+
#[allow(unused_imports, unused_macros)]
77+
#[cfg(test)]
78+
mod tests {
79+
use super::*;
80+
81+
macro_rules! not_thread_safe {
82+
($($ty:ty),*) => {$(
83+
static_assertions::assert_not_impl_any!($ty: Send, Sync);
84+
)?};
85+
}
86+
87+
macro_rules! thread_safe {
88+
($($ty:ty),*) => {$(
89+
// General assumption: Assumes that the allocator this was created
90+
// with is also thread-safe; that's probably a fair assumption,
91+
// otherwise nothing in CF would be thread-safe.
92+
static_assertions::assert_impl_all!($ty: Send, Sync);
93+
)?};
94+
}
95+
96+
#[test]
97+
fn base() {
98+
// CFType can hold thread-unsafe objects.
99+
// Just like AnyObject and NSObject aren't thread-safe.
100+
#[cfg(feature = "CFBase")]
101+
not_thread_safe!(CFType, CFPropertyList);
102+
103+
// Only the built-ins are thread-safe, other allocators aren't
104+
// guaranteed to be. Usually fine though, since they're placed in
105+
// statics anyhow, and can thus already be accessed from all threads.
106+
#[cfg(feature = "CFBase")]
107+
not_thread_safe!(CFAllocator);
108+
}
109+
110+
/// Collections, mutable types and immutable types with mutable variants
111+
/// aren't thread-safe:
112+
/// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW9>
113+
///
114+
/// Note that things that are known to be the immutable variant are
115+
/// usually thread-safe (which is why e.g. `CFString` in statics is fine).
116+
#[test]
117+
fn mutable_or_mutable_variant() {
118+
#[cfg(feature = "CFBag")]
119+
not_thread_safe!(CFBag<u32>, CFMutableBag<u32>);
120+
#[cfg(feature = "CFBinaryHeap")]
121+
not_thread_safe!(CFBinaryHeap);
122+
#[cfg(feature = "CFBitVector")]
123+
not_thread_safe!(CFBitVector, CFMutableBitVector);
124+
#[cfg(feature = "CFDateFormatter")]
125+
not_thread_safe!(CFDateFormatter); // Explicitly stated to not be thread-safe
126+
#[cfg(feature = "CFFileDescriptor")]
127+
not_thread_safe!(CFFileDescriptor);
128+
#[cfg(feature = "CFNumberFormatter")]
129+
not_thread_safe!(CFNumberFormatter); // Explicitly stated to not be thread-safe
130+
#[cfg(feature = "CFSocket")]
131+
not_thread_safe!(CFSocket);
132+
#[cfg(feature = "CFStringTokenizer")]
133+
not_thread_safe!(CFStringTokenizer);
134+
#[cfg(feature = "CFTree")]
135+
not_thread_safe!(CFTree);
136+
#[cfg(feature = "CFURLEnumerator")]
137+
not_thread_safe!(CFURLEnumerator);
138+
#[cfg(feature = "CFUserNotification")]
139+
not_thread_safe!(CFUserNotification);
140+
141+
// Types are not marked as thread-safe if their toll-free
142+
// bridged type is not thread safe either.
143+
#[cfg(feature = "CFArray")]
144+
not_thread_safe!(CFArray<u32>, CFMutableArray<u32>);
145+
#[cfg(feature = "CFAttributedString")]
146+
not_thread_safe!(CFAttributedString, CFMutableAttributedString);
147+
#[cfg(feature = "CFCalendar")]
148+
not_thread_safe!(CFCalendar);
149+
#[cfg(feature = "CFCharacterSet")]
150+
not_thread_safe!(CFCharacterSet, CFMutableCharacterSet);
151+
#[cfg(feature = "CFData")]
152+
not_thread_safe!(CFData, CFMutableData);
153+
#[cfg(feature = "CFDictionary")]
154+
not_thread_safe!(CFDictionary<u32, u32>, CFMutableDictionary<u32, u32>);
155+
#[cfg(feature = "CFFileSecurity")]
156+
not_thread_safe!(CFFileSecurity);
157+
#[cfg(feature = "CFMachPort")]
158+
not_thread_safe!(CFMachPort);
159+
#[cfg(feature = "CFMessagePort")]
160+
not_thread_safe!(CFMessagePort);
161+
#[cfg(feature = "CFRunLoop")]
162+
not_thread_safe!(CFRunLoopTimer);
163+
#[cfg(feature = "CFSet")]
164+
not_thread_safe!(CFSet<u32>, CFMutableSet<u32>);
165+
#[cfg(all(feature = "CFBase", feature = "CFString"))]
166+
not_thread_safe!(CFString, CFMutableString);
167+
#[cfg(feature = "CFStream")]
168+
not_thread_safe!(CFReadStream, CFWriteStream);
169+
}
170+
171+
/// Immutable CF types are generally thread-safe.
172+
#[test]
173+
fn immutable() {
174+
#[cfg(feature = "CFBundle")]
175+
thread_safe!(CFBundle);
176+
177+
#[cfg(feature = "CFNotificationCenter")]
178+
thread_safe!(CFNotificationCenter);
179+
180+
#[cfg(feature = "CFUUID")]
181+
thread_safe!(CFUUID);
182+
183+
// Types whose toll-free bridged type is thread-safe are also marked
184+
// as thread-safe.
185+
186+
#[cfg(feature = "CFNumber")]
187+
thread_safe!(CFBoolean, CFNumber);
188+
#[cfg(feature = "CFDate")]
189+
thread_safe!(CFDate);
190+
#[cfg(feature = "CFError")]
191+
thread_safe!(CFError);
192+
#[cfg(feature = "CFLocale")]
193+
thread_safe!(CFLocale);
194+
#[cfg(feature = "CFBase")]
195+
thread_safe!(CFNull);
196+
#[cfg(feature = "CFDate")]
197+
thread_safe!(CFTimeZone);
198+
#[cfg(feature = "CFURL")]
199+
thread_safe!(CFURL);
200+
}
201+
202+
#[test]
203+
fn uncertain() {
204+
// Uncertain, so marked as thread-unsafe for now.
205+
#[cfg(feature = "CFBundle")]
206+
not_thread_safe!(CFPlugIn);
207+
#[cfg(feature = "CFPlugIn")]
208+
not_thread_safe!(CFPlugInInstance);
209+
210+
// Under discussion in https://github.com/madsmtm/objc2/issues/696.
211+
#[cfg(feature = "CFRunLoop")]
212+
not_thread_safe!(CFRunLoop, CFRunLoopSource, CFRunLoopObserver);
213+
}
214+
}

framework-crates/objc2-core-foundation/translation-config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ fn.CFGetAllocator.unsafe = false
138138
# CFBundle
139139
fn.CFBundleGetMainBundle.unsafe = false
140140
fn.CFBundleGetBundleWithIdentifier.unsafe = false
141-
fn.CFBundleGetAllBundles.unsafe = false
141+
fn.CFBundleGetAllBundles.unsafe = true # Not thread-safe
142142
fn.CFBundleCreate.unsafe = false
143143
fn.CFBundleCreateBundlesFromDirectory.unsafe = false
144144
fn.CFBundleCopyBundleURL.unsafe = false
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[dev-dependencies]
2+
static_assertions = "1.1.0"

framework-crates/objc2-core-text/Cargo.toml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

framework-crates/objc2-core-text/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extern crate alloc;
1616
extern crate std;
1717

1818
mod generated;
19+
mod thread_safety;
1920

2021
#[allow(unused_imports, unreachable_pub)]
2122
pub use self::generated::*;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#[cfg(test)]
2+
mod tests {
3+
#[allow(unused_imports)]
4+
use crate::*;
5+
6+
// Ensure that toll-free bridged types are not marked as thread-safe.
7+
#[cfg(feature = "CTFont")]
8+
static_assertions::assert_not_impl_any!(CTFont: Send, Sync);
9+
#[cfg(feature = "CTFontCollection")]
10+
static_assertions::assert_not_impl_any!(CTFontCollection: Send, Sync);
11+
#[cfg(feature = "CTFontDescriptor")]
12+
static_assertions::assert_not_impl_any!(CTFontDescriptor: Send, Sync);
13+
#[cfg(feature = "CTGlyphInfo")]
14+
static_assertions::assert_not_impl_any!(CTGlyphInfo: Send, Sync);
15+
}

0 commit comments

Comments
 (0)