Skip to main content

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}