|
1 |
| -use crate::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush}; |
2 | 1 | use core::ffi::c_void;
|
| 2 | +#[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] |
| 3 | +use std::{cell::RefCell, vec::Vec, thread_local}; |
| 4 | + |
| 5 | +use crate::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush}; |
3 | 6 |
|
4 |
| -// we use a struct to ensure that objc_autoreleasePoolPop during unwinding. |
5 |
| -struct AutoReleaseHelper { |
| 7 | +/// An Objective-C autorelease pool. |
| 8 | +/// |
| 9 | +/// The pool is drained when dropped. |
| 10 | +/// |
| 11 | +/// This is not [`Send`], since `objc_autoreleasePoolPop` must be called on |
| 12 | +/// the same thread. |
| 13 | +/// |
| 14 | +/// And this is not [`Sync`], since you can only autorelease a reference to a |
| 15 | +/// pool on the current thread. |
| 16 | +/// |
| 17 | +/// See [the clang documentation][clang-arc] and [the apple article on memory |
| 18 | +/// management][memory-mgmt] for more information on automatic reference |
| 19 | +/// counting. |
| 20 | +/// |
| 21 | +/// [clang-arc]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html |
| 22 | +/// [memory-mgmt]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html |
| 23 | +pub struct AutoreleasePool { |
6 | 24 | context: *mut c_void,
|
7 | 25 | }
|
8 | 26 |
|
9 |
| -impl AutoReleaseHelper { |
| 27 | +/// ```rust,compile_fail |
| 28 | +/// use objc::rc::AutoreleasePool; |
| 29 | +/// fn needs_sync<T: Send>() {} |
| 30 | +/// needs_sync::<AutoreleasePool>(); |
| 31 | +/// ``` |
| 32 | +/// ```rust,compile_fail |
| 33 | +/// use objc::rc::AutoreleasePool; |
| 34 | +/// fn needs_send<T: Send>() {} |
| 35 | +/// needs_send::<AutoreleasePool>(); |
| 36 | +/// ``` |
| 37 | +#[cfg(doctest)] |
| 38 | +pub struct AutoreleasePoolNotSendNorSync; |
| 39 | + |
| 40 | +#[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] |
| 41 | +thread_local! { |
| 42 | + /// We track the thread's pools to verify that object lifetimes are only |
| 43 | + /// taken from the innermost pool. |
| 44 | + static POOLS: RefCell<Vec<*mut c_void>> = RefCell::new(Vec::new()); |
| 45 | +} |
| 46 | + |
| 47 | +impl AutoreleasePool { |
| 48 | + /// Construct a new autorelease pool. |
| 49 | + /// |
| 50 | + /// Use the [`autoreleasepool`] block for a safe alternative. |
| 51 | + /// |
| 52 | + /// # Safety |
| 53 | + /// |
| 54 | + /// The caller must ensure that when handing out `&'p AutoreleasePool` to |
| 55 | + /// functions that this is the innermost pool. |
| 56 | + /// |
| 57 | + /// Additionally, the pools must be dropped in the same order they were |
| 58 | + /// created. |
| 59 | + #[doc(alias = "objc_autoreleasePoolPush")] |
10 | 60 | unsafe fn new() -> Self {
|
11 |
| - AutoReleaseHelper { |
12 |
| - context: objc_autoreleasePoolPush(), |
13 |
| - } |
| 61 | + // TODO: Make this function pub when we're more certain of the API |
| 62 | + let context = objc_autoreleasePoolPush(); |
| 63 | + #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] |
| 64 | + POOLS.with(|c| c.borrow_mut().push(context)); |
| 65 | + Self { context } |
| 66 | + } |
| 67 | + |
| 68 | + /// Returns a shared reference to the given autoreleased pointer object. |
| 69 | + /// |
| 70 | + /// This is the preferred way to make references from autoreleased |
| 71 | + /// objects, since it binds the lifetime of the reference to the pool, and |
| 72 | + /// does some extra checks when debug assertions are enabled. |
| 73 | + /// |
| 74 | + /// For the mutable counterpart see [`ptr_as_mut`](#method.ptr_as_mut). |
| 75 | + /// |
| 76 | + /// # Safety |
| 77 | + /// |
| 78 | + /// This is equivalent to `&*ptr`, and shares the unsafety of that, except |
| 79 | + /// the lifetime is bound to the pool instead of being unbounded. |
| 80 | + #[cfg_attr( |
| 81 | + all(debug_assertions, not(feature = "unstable_autoreleasesafe")), |
| 82 | + inline |
| 83 | + )] |
| 84 | + pub unsafe fn ptr_as_ref<'p, T>(&'p self, ptr: *const T) -> &'p T { |
| 85 | + #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] |
| 86 | + POOLS.with(|c| { |
| 87 | + assert_eq!( |
| 88 | + c.borrow().last(), |
| 89 | + Some(&self.context), |
| 90 | + "Tried to create shared reference with a lifetime from a pool that was not the innermost pool" |
| 91 | + ) |
| 92 | + }); |
| 93 | + // SAFETY: Checked by the caller |
| 94 | + &*ptr |
| 95 | + } |
| 96 | + |
| 97 | + /// Returns a unique reference to the given autoreleased pointer object. |
| 98 | + /// |
| 99 | + /// This is the preferred way to make mutable references from autoreleased |
| 100 | + /// objects, since it binds the lifetime of the reference to the pool, and |
| 101 | + /// does some extra checks when debug assertions are enabled. |
| 102 | + /// |
| 103 | + /// For the shared counterpart see [`ptr_as_ref`](#method.ptr_as_ref). |
| 104 | + /// |
| 105 | + /// # Safety |
| 106 | + /// |
| 107 | + /// This is equivalent to `&mut *ptr`, and shares the unsafety of that, |
| 108 | + /// except the lifetime is bound to the pool instead of being unbounded. |
| 109 | + #[cfg_attr( |
| 110 | + all(debug_assertions, not(feature = "unstable_autoreleasesafe")), |
| 111 | + inline |
| 112 | + )] |
| 113 | + pub unsafe fn ptr_as_mut<'p, T>(&'p self, ptr: *mut T) -> &'p mut T { |
| 114 | + #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] |
| 115 | + POOLS.with(|c| { |
| 116 | + assert_eq!( |
| 117 | + c.borrow().last(), |
| 118 | + Some(&self.context), |
| 119 | + "Tried to create unique reference with a lifetime from a pool that was not the innermost pool") |
| 120 | + } |
| 121 | + ); |
| 122 | + // SAFETY: Checked by the caller |
| 123 | + &mut *ptr |
14 | 124 | }
|
15 | 125 | }
|
16 | 126 |
|
17 |
| -impl Drop for AutoReleaseHelper { |
| 127 | +impl Drop for AutoreleasePool { |
| 128 | + /// Drains the autoreleasepool. |
| 129 | + /// |
| 130 | + /// The [clang documentation] says that `@autoreleasepool` blocks are not |
| 131 | + /// drained when exceptions occur because: |
| 132 | + /// |
| 133 | + /// > Not draining the pool during an unwind is apparently required by the |
| 134 | + /// > Objective-C exceptions implementation. |
| 135 | + /// |
| 136 | + /// This was true in the past, but since [revision `371`] of |
| 137 | + /// `objc-exception.m` (ships with MacOS 10.5) the exception is now |
| 138 | + /// retained when `@throw` is encountered. |
| 139 | + /// |
| 140 | + /// Hence it is safe to drain the pool when unwinding. |
| 141 | + /// |
| 142 | + /// [clang documentation]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#autoreleasepool |
| 143 | + /// [revision `371`]: https://opensource.apple.com/source/objc4/objc4-371/runtime/objc-exception.m.auto.html |
| 144 | + #[doc(alias = "objc_autoreleasePoolPop")] |
18 | 145 | fn drop(&mut self) {
|
19 | 146 | unsafe { objc_autoreleasePoolPop(self.context) }
|
| 147 | + #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] |
| 148 | + POOLS.with(|c| { |
| 149 | + assert_eq!( |
| 150 | + c.borrow_mut().pop(), |
| 151 | + Some(self.context), |
| 152 | + "Popped pool that was not the innermost pool" |
| 153 | + ) |
| 154 | + }); |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +/// We use a macro here so that the documentation is included whether the |
| 159 | +/// feature is enabled or not. |
| 160 | +#[cfg(not(feature = "unstable_autoreleasesafe"))] |
| 161 | +macro_rules! auto_trait { |
| 162 | + {$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => { |
| 163 | + $(#[$fn_meta])* |
| 164 | + $v unsafe trait AutoreleaseSafe {} |
| 165 | + } |
| 166 | +} |
| 167 | + |
| 168 | +#[cfg(feature = "unstable_autoreleasesafe")] |
| 169 | +macro_rules! auto_trait { |
| 170 | + {$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => { |
| 171 | + $(#[$fn_meta])* |
| 172 | + $v unsafe auto trait AutoreleaseSafe {} |
20 | 173 | }
|
21 | 174 | }
|
22 | 175 |
|
23 |
| -/** |
24 |
| -Execute `f` in the context of a new autorelease pool. The pool is drained |
25 |
| -after the execution of `f` completes. |
| 176 | +auto_trait! { |
| 177 | + /// Marks types that are safe to pass across the closure in an |
| 178 | + /// [`autoreleasepool`]. |
| 179 | + /// |
| 180 | + /// With the `unstable_autoreleasesafe` feature enabled, this is an auto |
| 181 | + /// trait that is implemented for all types except [`AutoreleasePool`]. |
| 182 | + /// |
| 183 | + /// Otherwise it is just a dummy trait that is implemented for all types; |
| 184 | + /// the safety invariants are checked with debug assertions instead. |
| 185 | + /// |
| 186 | + /// You should not normally need to implement this trait yourself. |
| 187 | + /// |
| 188 | + /// # Safety |
| 189 | + /// |
| 190 | + /// Must not be implemented for types that interract with the autorelease |
| 191 | + /// pool. So if you reimplement the [`AutoreleasePool`] struct or |
| 192 | + /// likewise, this should be negatively implemented for that. |
| 193 | + /// |
| 194 | + /// This can easily be accomplished with an `PhantomData<AutoreleasePool>` |
| 195 | + /// if the `unstable_autoreleasesafe` feature is enabled. |
| 196 | + pub unsafe trait AutoreleaseSafe {} |
| 197 | +} |
| 198 | + |
| 199 | +#[cfg(not(feature = "unstable_autoreleasesafe"))] |
| 200 | +unsafe impl<T> AutoreleaseSafe for T {} |
26 | 201 |
|
27 |
| -This corresponds to `@autoreleasepool` blocks in Objective-C and Swift. |
28 |
| -*/ |
29 |
| -pub fn autoreleasepool<T, F: FnOnce() -> T>(f: F) -> T { |
30 |
| - let _context = unsafe { AutoReleaseHelper::new() }; |
31 |
| - f() |
| 202 | +#[cfg(feature = "unstable_autoreleasesafe")] |
| 203 | +impl !AutoreleaseSafe for AutoreleasePool {} |
| 204 | + |
| 205 | +/// Execute `f` in the context of a new autorelease pool. The pool is |
| 206 | +/// drained after the execution of `f` completes. |
| 207 | +/// |
| 208 | +/// This corresponds to `@autoreleasepool` blocks in Objective-C and |
| 209 | +/// Swift. |
| 210 | +/// |
| 211 | +/// The pool is passed as a reference to the enclosing function to give it |
| 212 | +/// a lifetime parameter that autoreleased objects can refer to. |
| 213 | +/// |
| 214 | +/// The given reference must not be used in an inner `autoreleasepool`, |
| 215 | +/// doing so will panic with debug assertions enabled, and be a compile |
| 216 | +/// error in a future release. You can test the compile error with the |
| 217 | +/// `unstable_autoreleasesafe` crate feature on nightly Rust. |
| 218 | +/// |
| 219 | +/// # Examples |
| 220 | +/// |
| 221 | +/// Basic usage: |
| 222 | +/// |
| 223 | +/// ```rust |
| 224 | +/// use objc::{class, msg_send}; |
| 225 | +/// use objc::rc::{autoreleasepool, AutoreleasePool}; |
| 226 | +/// use objc::runtime::Object; |
| 227 | +/// |
| 228 | +/// fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object { |
| 229 | +/// let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; |
| 230 | +/// let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; |
| 231 | +/// // Lifetime of the returned reference is bounded by the pool |
| 232 | +/// unsafe { pool.ptr_as_mut(obj) } |
| 233 | +/// } |
| 234 | +/// |
| 235 | +/// autoreleasepool(|pool| { |
| 236 | +/// // Create `obj` and autorelease it to the pool |
| 237 | +/// let obj = needs_lifetime_from_pool(pool); |
| 238 | +/// // ... use `obj` here |
| 239 | +/// // `obj` is deallocated when the pool ends |
| 240 | +/// }); |
| 241 | +/// ``` |
| 242 | +/// |
| 243 | +/// Fails to compile because `obj` does not live long enough for us to |
| 244 | +/// safely take it out of the pool: |
| 245 | +/// |
| 246 | +/// ```rust,compile_fail |
| 247 | +/// # use objc::{class, msg_send}; |
| 248 | +/// # use objc::rc::{autoreleasepool, AutoreleasePool}; |
| 249 | +/// # use objc::runtime::Object; |
| 250 | +/// # |
| 251 | +/// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object { |
| 252 | +/// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; |
| 253 | +/// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; |
| 254 | +/// # unsafe { pool.ptr_as_mut(obj) } |
| 255 | +/// # } |
| 256 | +/// # |
| 257 | +/// let obj = autoreleasepool(|pool| { |
| 258 | +/// let obj = needs_lifetime_from_pool(pool); |
| 259 | +/// // Use `obj` |
| 260 | +/// obj |
| 261 | +/// }); |
| 262 | +/// ``` |
| 263 | +/// |
| 264 | +/// Incorrect usage which panics because we tried to pass an outer pool to an |
| 265 | +/// inner pool: |
| 266 | +/// |
| 267 | +#[cfg_attr(feature = "unstable_autoreleasesafe", doc = "```rust,compile_fail")] |
| 268 | +#[cfg_attr(not(feature = "unstable_autoreleasesafe"), doc = "```rust,should_panic")] |
| 269 | +/// # use objc::{class, msg_send}; |
| 270 | +/// # use objc::rc::{autoreleasepool, AutoreleasePool}; |
| 271 | +/// # use objc::runtime::Object; |
| 272 | +/// # |
| 273 | +/// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object { |
| 274 | +/// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; |
| 275 | +/// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; |
| 276 | +/// # unsafe { pool.ptr_as_mut(obj) } |
| 277 | +/// # } |
| 278 | +/// # |
| 279 | +/// autoreleasepool(|outer_pool| { |
| 280 | +/// let obj = autoreleasepool(|inner_pool| { |
| 281 | +/// let obj = needs_lifetime_from_pool(outer_pool); |
| 282 | +/// obj |
| 283 | +/// }); |
| 284 | +/// // `obj` could wrongly be used here because it's lifetime was |
| 285 | +/// // assigned to the outer pool, even though it was released by the |
| 286 | +/// // inner pool already. |
| 287 | +/// }); |
| 288 | +/// ``` |
| 289 | +#[doc(alias = "@autoreleasepool")] |
| 290 | +pub fn autoreleasepool<T, F>(f: F) -> T |
| 291 | +where |
| 292 | + for<'p> F: FnOnce(&'p AutoreleasePool) -> T + AutoreleaseSafe, |
| 293 | +{ |
| 294 | + let pool = unsafe { AutoreleasePool::new() }; |
| 295 | + f(&pool) |
| 296 | +} |
| 297 | + |
| 298 | +#[cfg(all(test, feature = "unstable_autoreleasesafe"))] |
| 299 | +mod tests { |
| 300 | + use super::AutoreleaseSafe; |
| 301 | + use crate::runtime::Object; |
| 302 | + |
| 303 | + fn requires_autoreleasesafe<T: AutoreleaseSafe>() {} |
| 304 | + |
| 305 | + #[test] |
| 306 | + fn test_autoreleasesafe() { |
| 307 | + requires_autoreleasesafe::<usize>(); |
| 308 | + requires_autoreleasesafe::<*mut Object>(); |
| 309 | + requires_autoreleasesafe::<&mut Object>(); |
| 310 | + } |
32 | 311 | }
|
0 commit comments