objc2/rc/autorelease.rs
1use core::ffi::c_void;
2#[cfg(not(all(debug_assertions, not(feature = "unstable-autoreleasesafe"))))]
3use core::marker::PhantomData;
4#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
5use std::{cell::RefCell, thread_local, vec::Vec};
6
7use crate::ffi;
8
9/// The actual pool object.
10///
11/// It is drained when dropped.
12///
13/// This is not [`Send`], since `objc_autoreleasePoolPop` must be called on
14/// the same thread as `objc_autoreleasePoolPush`.
15///
16/// And this is not [`Sync`], since that would make `AutoreleasePool` `Send`.
17#[derive(Debug)]
18struct Pool {
19 /// This is an opaque handle, and is not guaranteed to be neither a valid
20 /// nor an aligned pointer.
21 context: *mut c_void,
22}
23
24impl Pool {
25 /// Construct a new autorelease pool.
26 ///
27 ///
28 /// # Safety
29 ///
30 /// The caller must ensure that when handing out `AutoreleasePool<'p>` to
31 /// functions that this is the innermost pool.
32 ///
33 /// Additionally, the pools must be dropped in the same order they were
34 /// created.
35 #[inline]
36 unsafe fn new() -> Self {
37 let context = unsafe { ffi::objc_autoreleasePoolPush() };
38 #[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
39 POOLS.with(|c| c.borrow_mut().push(context));
40 Self { context }
41 }
42
43 /// Drains the autoreleasepool.
44 ///
45 /// The [clang documentation] says that `@autoreleasepool` blocks are not
46 /// drained when exceptions occur because:
47 ///
48 /// > Not draining the pool during an unwind is apparently required by the
49 /// > Objective-C exceptions implementation.
50 ///
51 /// The above statement was true in the past, but since [revision `551.1`]
52 /// of objc4 (ships with MacOS 10.9) the exception is now retained when
53 /// `@throw` is encountered (on __OBJC2__, so e.g. not on macOS 32bit).
54 ///
55 /// Since an unwind here is probably caused by Rust, and forgetting to pop
56 /// the pool will likely leak memory, we would really like to do drain in
57 /// `drop` when possible, so that is what we are going to do.
58 ///
59 /// [clang documentation]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#autoreleasepool
60 /// [revision `551.1`]: https://github.com/apple-oss-distributions/objc4/blob/objc4-551.1/runtime/objc-exception.mm#L540
61 #[inline]
62 unsafe fn drain(self) {
63 #[cfg(all(target_os = "macos", target_arch = "x86"))]
64 unsafe {
65 ffi::objc_autoreleasePoolPop(self.context);
66 }
67 }
68}
69
70impl Drop for Pool {
71 #[inline]
72 fn drop(&mut self) {
73 #[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
74 POOLS.with(|c| {
75 assert_eq!(
76 c.borrow_mut().pop(),
77 Some(self.context),
78 "popped pool that was not the innermost pool"
79 );
80 });
81
82 // See `drain`.
83 #[cfg(not(all(target_os = "macos", target_arch = "x86")))]
84 unsafe {
85 ffi::objc_autoreleasePoolPop(self.context);
86 }
87 }
88}
89
90/// An Objective-C autorelease pool.
91///
92/// Autorelease pools are a way to store objects in a certain thread-local
93/// scope, such that they are only released at the end of said scope.
94///
95/// See [`autoreleasepool`] and [`autoreleasepool_leaking`] for how to create
96/// this.
97///
98/// This is not [`Send`] nor [`Sync`], since you can only autorelease a
99/// reference to a pool on the current thread.
100///
101///
102/// # Example
103///
104/// Use the pool as a bound on a function, and release an object to that pool.
105///
106/// ```
107/// use objc2::rc::{autoreleasepool, AutoreleasePool};
108/// use objc2::runtime::NSObject;
109/// use objc2::msg_send;
110///
111/// unsafe fn needs_lifetime_from_pool<'p>(pool: AutoreleasePool<'p>) -> &'p NSObject {
112/// let obj = NSObject::new();
113/// // Do action that returns an autoreleased object
114/// let description: *mut NSObject = unsafe { msg_send![&obj, description] };
115/// // Bound the lifetime of the reference to that of the pool.
116/// //
117/// // Note that this only helps ensuring soundness, our function is still
118/// // unsafe because the pool cannot be guaranteed to be the innermost
119/// // pool.
120/// unsafe { pool.ptr_as_ref(description) }
121/// }
122///
123/// autoreleasepool(|pool| {
124/// // SAFETY: The given pool is the innermost pool.
125/// let obj = unsafe { needs_lifetime_from_pool(pool) };
126/// println!("{obj:?}");
127/// });
128/// ```
129#[derive(Debug, Copy, Clone)]
130pub struct AutoreleasePool<'pool> {
131 /// A reference to the pool.
132 ///
133 /// The lifetime is covariant, since shortening the lifetime is not a
134 /// problem (the lifetime talks about the pool, and not any data inside
135 /// the pool).
136 ///
137 /// To somewhat prove this, consider the following example using
138 /// `typed-arena` to partially implement the autorelease pool:
139 ///
140 /// ```ignore
141 /// struct Pool(typed_arena::Arena<String>);
142 ///
143 /// pub struct AutoreleasePool<'pool>(&'pool Pool);
144 ///
145 /// impl<'pool> AutoreleasePool<'pool> {
146 /// pub fn autorelease(self, s: String) -> &'pool str {
147 /// &*self.0.0.alloc(s)
148 /// }
149 /// }
150 ///
151 /// pub fn autoreleasepool<F, R>(f: F) -> R
152 /// where
153 /// F: for<'pool> FnOnce(AutoreleasePool<'pool>) -> R
154 /// {
155 /// let pool = Pool(Default::default());
156 /// f(AutoreleasePool(&pool))
157 /// }
158 /// ```
159 ///
160 /// Hence assuming `typed-arena` is sound, having covariance here should
161 /// also be sound.
162 #[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
163 inner: Option<&'pool Pool>,
164 /// We use `PhantomData` here to make `AutoreleasePool` a ZST.
165 #[cfg(not(all(debug_assertions, not(feature = "unstable-autoreleasesafe"))))]
166 inner: PhantomData<&'pool Pool>,
167}
168
169#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
170thread_local! {
171 /// We track the thread's pools to verify that object lifetimes are only
172 /// taken from the innermost pool.
173 static POOLS: RefCell<Vec<*mut c_void>> = const { RefCell::new(Vec::new()) };
174}
175
176impl<'pool> AutoreleasePool<'pool> {
177 fn new(_inner: Option<&'pool Pool>) -> Self {
178 Self {
179 #[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
180 inner: _inner,
181 #[cfg(not(all(debug_assertions, not(feature = "unstable-autoreleasesafe"))))]
182 inner: PhantomData,
183 }
184 }
185
186 #[inline]
187 pub(crate) fn __verify_is_inner(self) {
188 #[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
189 if let Some(pool) = &self.inner {
190 POOLS.with(|c| {
191 assert_eq!(
192 c.borrow().last(),
193 Some(&pool.context),
194 "tried to use lifetime from pool that was not innermost"
195 );
196 });
197 }
198 }
199
200 /// Returns a shared reference to the given autoreleased pointer object.
201 ///
202 /// This is the preferred way to make references from autoreleased
203 /// objects, since it binds the lifetime of the reference to the pool, and
204 /// does some extra checks when debug assertions are enabled.
205 ///
206 /// Note that this is helpful, but not sufficient, for ensuring that the
207 /// lifetime of the reference does not exceed the lifetime of the
208 /// autorelease pool. When calling this, you must also ensure that the
209 /// pool is actually the current innermost pool.
210 ///
211 ///
212 /// # Panics
213 ///
214 /// If the pool is not the innermost pool, this function may panic when
215 /// the `"std"` Cargo feature and debug assertions are enabled.
216 ///
217 ///
218 /// # Safety
219 ///
220 /// This is equivalent to `&*ptr`, and shares the unsafety of that, except
221 /// the lifetime is bound to the pool instead of being unbounded.
222 ///
223 /// The pool must be the innermost pool for the lifetime to be correct.
224 #[inline]
225 pub unsafe fn ptr_as_ref<T: ?Sized>(self, ptr: *const T) -> &'pool T {
226 self.__verify_is_inner();
227 // SAFETY: Checked by the caller
228 unsafe { ptr.as_ref().unwrap_unchecked() }
229 }
230}
231
232/// We use a macro here so that the documentation is included whether the
233/// feature is enabled or not.
234#[cfg(not(feature = "unstable-autoreleasesafe"))]
235macro_rules! auto_trait {
236 {$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => {
237 $(#[$fn_meta])*
238 $v unsafe trait AutoreleaseSafe {}
239 }
240}
241
242#[cfg(feature = "unstable-autoreleasesafe")]
243macro_rules! auto_trait {
244 {$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => {
245 $(#[$fn_meta])*
246 $v unsafe auto trait AutoreleaseSafe {}
247 }
248}
249
250auto_trait! {
251 /// Marks types that are safe to pass across the closure in an
252 /// [`autoreleasepool`].
253 ///
254 /// With the `"unstable-autoreleasesafe"` feature enabled, this is an auto
255 /// trait that is implemented for all types except [`AutoreleasePool`].
256 ///
257 /// Otherwise it is a dummy trait that is implemented for all types; the
258 /// safety invariants are checked with debug assertions instead.
259 ///
260 /// You should not normally need to implement this trait yourself.
261 ///
262 ///
263 /// # Safety
264 ///
265 /// Must not be implemented for types that interact with the autorelease
266 /// pool. So if you reimplement the [`AutoreleasePool`] struct or
267 /// likewise, this should be negatively implemented for that.
268 ///
269 /// This can be accomplished with an `PhantomData<AutoreleasePool<'_>>` if
270 /// the `"unstable-autoreleasesafe"` feature is enabled.
271 ///
272 ///
273 /// # Examples
274 ///
275 /// Most types are [`AutoreleaseSafe`].
276 ///
277 /// ```
278 /// use objc2::rc::{AutoreleasePool, AutoreleaseSafe};
279 /// fn requires_autoreleasesafe<T: AutoreleaseSafe>() {}
280 /// requires_autoreleasesafe::<()>();
281 /// requires_autoreleasesafe::<Box<Vec<i32>>>();
282 /// requires_autoreleasesafe::<fn(AutoreleasePool<'_>)>();
283 /// ```
284 ///
285 /// But [`AutoreleasePool`] isn't (if the `"unstable-autoreleasesafe"`
286 /// feature is enabled).
287 ///
288 #[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail,E0277")]
289 #[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```")]
290 /// use objc2::rc::AutoreleasePool;
291 /// # use objc2::rc::AutoreleaseSafe;
292 /// # fn requires_autoreleasesafe<T: AutoreleaseSafe>() {}
293 /// requires_autoreleasesafe::<AutoreleasePool<'static>>();
294 /// ```
295 ///
296 /// This also means that trait objects aren't (since they may contain an
297 /// [`AutoreleasePool`] internally):
298 ///
299 #[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail,E0277")]
300 #[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```")]
301 /// # use objc2::rc::AutoreleaseSafe;
302 /// # fn requires_autoreleasesafe<T: AutoreleaseSafe>() {}
303 /// requires_autoreleasesafe::<&dyn std::io::Write>();
304 /// ```
305 pub unsafe trait AutoreleaseSafe {}
306}
307
308#[cfg(not(feature = "unstable-autoreleasesafe"))]
309unsafe impl<T: ?Sized> AutoreleaseSafe for T {}
310
311#[cfg(feature = "unstable-autoreleasesafe")]
312impl !AutoreleaseSafe for Pool {}
313#[cfg(feature = "unstable-autoreleasesafe")]
314impl !AutoreleaseSafe for AutoreleasePool<'_> {}
315
316/// Execute `f` in the context of a new autorelease pool. The pool is drained
317/// after the execution of `f` completes.
318///
319/// This corresponds to `@autoreleasepool` blocks in Objective-C and Swift,
320/// see [Apple's documentation][apple-autorelease] for general information on
321/// when to use those.
322///
323/// [The pool] is passed as a parameter to the closure to give you a lifetime
324/// parameter that autoreleased objects can refer to.
325///
326/// Note that this is mostly useful for preventing leaks (as any Objective-C
327/// method may autorelease internally - see also [`autoreleasepool_leaking`]).
328/// If implementing an interface to an object, you should try to return
329/// retained pointers with [`msg_send!`] wherever you can instead, since
330/// it is usually more efficient, safer, and having to use this function can
331/// be quite cumbersome for users.
332///
333/// [apple-autorelease]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
334/// [The pool]: AutoreleasePool
335/// [`msg_send!`]: crate::msg_send
336///
337///
338/// # Restrictions
339///
340/// The given parameter must not be used inside a nested `autoreleasepool`,
341/// since doing so will give the objects that it is used with an incorrect
342/// lifetime bound.
343///
344/// You can try to enable the `"unstable-autoreleasesafe"` Cargo feature using
345/// nightly Rust - if your use of this function compiles with that, it is more
346/// likely to be correct, though note that Rust does not have a way to express
347/// the lifetimes involved, so we cannot detect all invalid usage. See issue
348/// [#540] for details.
349///
350/// [#540]: https://github.com/madsmtm/objc2/issues/540
351///
352///
353/// # Examples
354///
355/// Use an external API, and ensure that the memory that it used is cleaned
356/// up afterwards.
357///
358/// ```
359/// use objc2::rc::autoreleasepool;
360/// # fn example_function() {}
361/// # #[cfg(for_illustrative_purposes)]
362/// use example_crate::example_function;
363///
364/// autoreleasepool(|_| {
365/// // Call `example_function` in the context of a pool
366/// example_function();
367/// }); // Memory released into the pool is cleaned up when the scope ends
368/// ```
369///
370/// Autorelease an object into an autorelease pool:
371///
372/// ```
373/// use objc2::rc::{autoreleasepool, Retained};
374/// use objc2::runtime::NSObject;
375///
376/// autoreleasepool(|pool| {
377/// // Create `obj` and autorelease it to the pool
378/// // SAFETY: The pool is the current innermost pool
379/// let obj = unsafe { Retained::autorelease(NSObject::new(), pool) };
380/// // We now have a reference that we can freely use
381/// println!("{obj:?}");
382/// }); // `obj` is deallocated when the pool ends
383/// // And is no longer usable outside the closure
384/// ```
385///
386/// Fails to compile because `obj` does not live long enough for us to take it
387/// out of the pool:
388///
389/// ```compile_fail
390/// use objc2::rc::{autoreleasepool, Retained};
391/// use objc2::runtime::NSObject;
392///
393/// let obj = autoreleasepool(|pool| unsafe {
394/// Retained::autorelease(NSObject::new(), pool)
395/// });
396/// ```
397///
398/// Fails to compile with the `"unstable-autoreleasesafe"` feature enabled, or
399/// panics with debug assertions enabled, because we tried to pass an outer
400/// pool to an inner pool:
401///
402#[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail,E0277")]
403#[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```should_panic")]
404/// use objc2::rc::{autoreleasepool, Retained};
405/// use objc2::runtime::NSObject;
406///
407/// autoreleasepool(|outer_pool| {
408/// let obj = autoreleasepool(|inner_pool| {
409/// // SAFETY: NOT safe, the pool is _not_ the innermost pool!
410/// unsafe { Retained::autorelease(NSObject::new(), outer_pool) }
411/// });
412/// // `obj` could wrongly be used here because its lifetime was
413/// // assigned to the outer pool, even though it was released by the
414/// // inner pool already.
415/// });
416/// #
417/// # panic!("Does not panic in release mode, so for testing we make it!");
418/// ```
419///
420/// It is impossible to extend the lifetime of the pool.
421///
422/// ```compile_fail,E0521
423/// use std::cell::RefCell;
424/// use objc2::rc::{autoreleasepool, AutoreleasePool};
425///
426/// thread_local! {
427/// static POOL: RefCell<Option<&'static AutoreleasePool<'static>>> = RefCell::new(None);
428/// }
429///
430/// autoreleasepool(|pool| {
431/// POOL.with(|p| {
432/// *p.borrow_mut() = Some(Box::leak(Box::new(pool)))
433/// });
434/// });
435/// ```
436#[doc(alias = "@autoreleasepool")]
437#[doc(alias = "objc_autoreleasePoolPush")]
438#[doc(alias = "objc_autoreleasePoolPop")]
439#[inline]
440pub fn autoreleasepool<T, F>(f: F) -> T
441where
442 for<'pool> F: AutoreleaseSafe + FnOnce(AutoreleasePool<'pool>) -> T,
443{
444 // SAFETY:
445 // - The `AutoreleaseSafe` bound on the closure ensures that no pool from
446 // a different "level" can be passed down through and used in this one.
447 // - The pools are guaranteed to be dropped in the reverse order they were
448 // created (since you can't possibly "interleave" closures).
449 //
450 // This would not work if we e.g. allowed users to create pools on the
451 // stack, since they could then safely control the drop order.
452 let pool = unsafe { Pool::new() };
453 let res = f(AutoreleasePool::new(Some(&pool)));
454 unsafe { pool.drain() };
455 res
456}
457
458/// Execute `f` in the context of a "fake" autorelease pool.
459///
460/// This is useful to create a context in which to use autoreleased objects,
461/// without the overhead of actually creating and draining the pool.
462///
463/// Any function boundary in Objective-C is an implicit autorelease pool, so
464/// there you'd do `id obj2 = [obj autorelease]` and be done with it - but we
465/// do this using a closure instead because we need some way to bind the
466/// lifetime of any objects released to the pool.
467///
468///
469/// # Examples
470///
471/// Autorelease an object to an outer pool, from inside an inner, "fake" pool.
472///
473/// ```
474/// use objc2::rc::{autoreleasepool, autoreleasepool_leaking, Retained};
475/// use objc2::runtime::NSObject;
476///
477/// autoreleasepool(|outer_pool| {
478/// let obj = autoreleasepool_leaking(|inner_pool| {
479/// // SAFETY: The given `outer_pool` is the actual innermost pool.
480/// unsafe { Retained::autorelease(NSObject::new(), outer_pool) }
481/// });
482/// // `obj` is still usable here, since the leaking pool doesn't actually
483/// // do anything.
484/// println!("{obj:?}");
485/// });
486///
487/// // But it is not usable here, since the outer pool has been closed
488/// ```
489///
490/// Like [`autoreleasepool`], you can't extend the lifetime of an object to
491/// outside the closure.
492///
493/// ```compile_fail
494/// use objc2::rc::{autoreleasepool_leaking, Retained};
495/// use objc2::runtime::NSObject;
496///
497/// let obj = autoreleasepool_leaking(|pool| unsafe {
498/// unsafe { Retained::autorelease(NSObject::new(), pool) }
499/// });
500/// ```
501///
502/// While you can pass an outer pool into this, you still can't pass the pool
503/// from this into [`autoreleasepool`]:
504///
505#[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail,E0277")]
506#[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```should_panic")]
507/// use objc2::rc::{autoreleasepool, autoreleasepool_leaking, Retained};
508/// use objc2::runtime::NSObject;
509///
510/// autoreleasepool_leaking(|outer_pool| {
511/// let obj = autoreleasepool(|inner_pool| {
512/// // SAFETY: NOT safe, the pool is _not_ the innermost pool!
513/// unsafe { Retained::autorelease(NSObject::new(), outer_pool) }
514/// });
515/// });
516/// #
517/// # panic!("Does not panic in release mode, so for testing we make it!");
518/// ```
519#[inline]
520pub fn autoreleasepool_leaking<T, F>(f: F) -> T
521where
522 for<'pool> F: FnOnce(AutoreleasePool<'pool>) -> T,
523{
524 // SAFETY: This is effectively what most Objective-C code does; they
525 // assume that there's an autorelease pool _somewhere_ in the call stack
526 // above it, and then use their autoreleased objects for a duration that
527 // is guaranteed to be shorter than that.
528 //
529 // The `AutoreleaseSafe` bound is not required, since we don't actually do
530 // anything inside this; hence if the user know they have the _actual_
531 // innermost pool, they may still safely use it to extend the lifetime
532 // beyond this closure.
533 f(AutoreleasePool::new(None))
534}
535
536#[cfg(test)]
537mod tests {
538 use core::mem;
539 use core::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe};
540 use std::panic::catch_unwind;
541
542 use static_assertions::{assert_impl_all, assert_not_impl_any};
543
544 use super::{autoreleasepool, AutoreleasePool, AutoreleaseSafe};
545 use crate::rc::{RcTestObject, Retained, ThreadTestData};
546 use crate::runtime::AnyObject;
547
548 #[test]
549 fn auto_traits() {
550 assert_impl_all!(AutoreleasePool<'static>: Unpin, UnwindSafe, RefUnwindSafe);
551 assert_not_impl_any!(AutoreleasePool<'static>: Send, Sync);
552
553 assert_impl_all!(usize: AutoreleaseSafe);
554 assert_impl_all!(*mut AnyObject: AutoreleaseSafe);
555 assert_impl_all!(&mut AnyObject: AutoreleaseSafe);
556 #[cfg(feature = "unstable-autoreleasesafe")]
557 assert_not_impl_any!(AutoreleasePool<'static>: AutoreleaseSafe);
558 }
559
560 #[allow(unused)]
561 fn assert_covariant1<'a>(pool: AutoreleasePool<'static>) -> AutoreleasePool<'a> {
562 pool
563 }
564
565 #[allow(unused)]
566 fn assert_covariant2<'long: 'short, 'short>(
567 pool: AutoreleasePool<'long>,
568 ) -> AutoreleasePool<'short> {
569 pool
570 }
571
572 #[allow(unused)]
573 fn assert_object_safe(_: &dyn AutoreleaseSafe) {}
574
575 #[cfg_attr(
576 not(feature = "unstable-autoreleasesafe"),
577 ignore = "only stably ZST when `unstable-autoreleasesafe` is enabled"
578 )]
579 #[test]
580 fn assert_zst() {
581 assert_eq!(mem::size_of::<AutoreleasePool<'static>>(), 0);
582 }
583
584 #[test]
585 #[cfg_attr(panic = "abort", ignore = "requires `catch_unwind`")]
586 #[cfg_attr(
587 all(target_os = "macos", target_arch = "x86", not(panic = "abort")),
588 ignore = "unwinding through an auto release pool on macOS 32 bit won't pop the pool"
589 )]
590 fn test_unwind_still_autoreleases() {
591 let obj = RcTestObject::new();
592 let mut expected = ThreadTestData::current();
593
594 catch_unwind({
595 let obj = AssertUnwindSafe(obj);
596 let expected = AssertUnwindSafe(&mut expected);
597 || {
598 let obj = obj;
599 let mut expected = expected;
600
601 autoreleasepool(|pool| {
602 let _autoreleased = unsafe { Retained::autorelease(obj.0, pool) };
603 expected.autorelease += 1;
604 expected.assert_current();
605 panic!("unwind");
606 });
607 }
608 })
609 .unwrap_err();
610
611 expected.release += 1;
612 expected.drop += 1;
613 expected.assert_current();
614 }
615}