1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
use core::cell::UnsafeCell;
use core::ffi::c_void;
use core::fmt;
use core::marker::PhantomData;
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
use std::{cell::RefCell, thread_local, vec::Vec};

use crate::ffi;

/// An Objective-C autorelease pool.
///
/// The pool is drained when dropped.
///
/// This is not [`Send`], since `objc_autoreleasePoolPop` must be called on
/// the same thread.
///
/// And this is not [`Sync`], since you can only autorelease a reference to a
/// pool on the current thread.
#[derive(Debug)]
pub struct AutoreleasePool {
    /// This is an opaque handle, and is not guaranteed to be neither a valid
    /// nor aligned pointer.
    context: *mut c_void,
    /// May point to data that is mutated (even though we hold shared access).
    p: PhantomData<*mut UnsafeCell<c_void>>,
}

#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
thread_local! {
    /// We track the thread's pools to verify that object lifetimes are only
    /// taken from the innermost pool.
    static POOLS: RefCell<Vec<*mut c_void>> = RefCell::new(Vec::new());
}

impl AutoreleasePool {
    /// Construct a new autorelease pool.
    ///
    /// Use the [`autoreleasepool`] block for a safe alternative.
    ///
    /// # Safety
    ///
    /// The caller must ensure that when handing out `&'p AutoreleasePool` to
    /// functions that this is the innermost pool.
    ///
    /// Additionally, the pools must be dropped in the same order they were
    /// created.
    #[doc(alias = "objc_autoreleasePoolPush")]
    #[inline]
    unsafe fn new() -> Self {
        // TODO: Make this function pub when we're more certain of the API
        let context = unsafe { ffi::objc_autoreleasePoolPush() };
        #[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
        POOLS.with(|c| c.borrow_mut().push(context));
        Self {
            context,
            p: PhantomData,
        }
    }

    /// This will be removed in a future version.
    #[inline]
    #[doc(hidden)]
    pub fn __verify_is_inner(&self) {
        #[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
        POOLS.with(|c| {
            assert_eq!(
                c.borrow().last(),
                Some(&self.context),
                "Tried to use lifetime from pool that was not innermost"
            )
        });
    }

    /// Returns a shared reference to the given autoreleased pointer object.
    ///
    /// This is the preferred way to make references from autoreleased
    /// objects, since it binds the lifetime of the reference to the pool, and
    /// does some extra checks when debug assertions are enabled.
    ///
    /// For the mutable counterpart see [`ptr_as_mut`](#method.ptr_as_mut).
    ///
    /// # Safety
    ///
    /// This is equivalent to `&*ptr`, and shares the unsafety of that, except
    /// the lifetime is bound to the pool instead of being unbounded.
    #[inline]
    #[allow(clippy::needless_lifetimes)]
    pub unsafe fn ptr_as_ref<'p, T: ?Sized>(&'p self, ptr: *const T) -> &'p T {
        self.__verify_is_inner();
        // SAFETY: Checked by the caller
        unsafe { ptr.as_ref().unwrap_unchecked() }
    }

    /// Returns a unique reference to the given autoreleased pointer object.
    ///
    /// This is the preferred way to make mutable references from autoreleased
    /// objects, since it binds the lifetime of the reference to the pool, and
    /// does some extra checks when debug assertions are enabled.
    ///
    /// For the shared counterpart see [`ptr_as_ref`](#method.ptr_as_ref).
    ///
    /// # Safety
    ///
    /// This is equivalent to `&mut *ptr`, and shares the unsafety of that,
    /// except the lifetime is bound to the pool instead of being unbounded.
    #[inline]
    #[allow(clippy::needless_lifetimes)]
    #[allow(clippy::mut_from_ref)]
    pub unsafe fn ptr_as_mut<'p, T: ?Sized>(&'p self, ptr: *mut T) -> &'p mut T {
        self.__verify_is_inner();
        // SAFETY: Checked by the caller
        unsafe { ptr.as_mut().unwrap_unchecked() }
    }
}

impl Drop for AutoreleasePool {
    /// Drains the autoreleasepool.
    ///
    /// The [clang documentation] says that `@autoreleasepool` blocks are not
    /// drained when exceptions occur because:
    ///
    /// > Not draining the pool during an unwind is apparently required by the
    /// > Objective-C exceptions implementation.
    ///
    /// However, we would like to do this anyway whenever possible, since the
    /// unwind is probably caused by Rust, and forgetting to pop the pool will
    /// likely leak memory.
    ///
    /// Fortunately, the above statement was true in the past, but since
    /// [revision `371`] of objc4 (ships with MacOS 10.5) the exception is now
    /// retained when `@throw` is encountered.
    ///
    /// Hence it is safe to drain the pool when unwinding.
    ///
    /// TODO: Verify this claim on 32bit!
    ///
    /// [clang documentation]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#autoreleasepool
    /// [revision `371`]: https://github.com/apple-oss-distributions/objc4/blob/objc4-371/runtime/objc-exception.m#L479-L482
    #[doc(alias = "objc_autoreleasePoolPop")]
    #[inline]
    fn drop(&mut self) {
        unsafe { ffi::objc_autoreleasePoolPop(self.context) }
        #[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
        POOLS.with(|c| {
            assert_eq!(
                c.borrow_mut().pop(),
                Some(self.context),
                "Popped pool that was not the innermost pool"
            )
        });
    }
}

impl fmt::Pointer for AutoreleasePool {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Pointer::fmt(&self.context, f)
    }
}

/// We use a macro here so that the documentation is included whether the
/// feature is enabled or not.
#[cfg(not(feature = "unstable-autoreleasesafe"))]
macro_rules! auto_trait {
    {$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => {
        $(#[$fn_meta])*
        $v unsafe trait AutoreleaseSafe {}
    }
}

#[cfg(feature = "unstable-autoreleasesafe")]
macro_rules! auto_trait {
    {$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => {
        $(#[$fn_meta])*
        $v unsafe auto trait AutoreleaseSafe {}
    }
}

auto_trait! {
    /// Marks types that are safe to pass across the closure in an
    /// [`autoreleasepool`].
    ///
    /// With the `unstable-autoreleasesafe` feature enabled, this is an auto
    /// trait that is implemented for all types except [`AutoreleasePool`].
    ///
    /// Otherwise it is just a dummy trait that is implemented for all types;
    /// the safety invariants are checked with debug assertions instead.
    ///
    /// You should not normally need to implement this trait yourself.
    ///
    /// # Safety
    ///
    /// Must not be implemented for types that interract with the autorelease
    /// pool. So if you reimplement the [`AutoreleasePool`] struct or
    /// likewise, this should be negatively implemented for that.
    ///
    /// This can easily be accomplished with an `PhantomData<AutoreleasePool>`
    /// if the `unstable-autoreleasesafe` feature is enabled.
    pub unsafe trait AutoreleaseSafe {}
}

#[cfg(not(feature = "unstable-autoreleasesafe"))]
unsafe impl<T: ?Sized> AutoreleaseSafe for T {}

#[cfg(feature = "unstable-autoreleasesafe")]
impl !AutoreleaseSafe for AutoreleasePool {}

/// Execute `f` in the context of a new autorelease pool. The pool is
/// drained after the execution of `f` completes.
///
/// This corresponds to `@autoreleasepool` blocks in Objective-C and
/// Swift.
///
/// The pool is passed as a reference to the enclosing function to give it
/// a lifetime parameter that autoreleased objects can refer to.
///
/// The given reference must not be used in an inner `autoreleasepool`,
/// doing so will panic with debug assertions enabled, and be a compile
/// error in a future release. You can test the compile error with the
/// `unstable-autoreleasesafe` crate feature on nightly Rust.
///
/// Note that this is mostly useful for preventing leaks (as any Objective-C
/// method may leak internally). If implementing an interface to an object,
/// you should try to return retained pointers with [`msg_send_id!`] wherever
/// you can instead, since having to use this function can be quite cumbersome
/// for your users!
///
/// [`msg_send_id!`]: crate::msg_send_id
///
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use core::mem::ManuallyDrop;
/// use objc2::{class, msg_send, msg_send_id};
/// use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned};
/// use objc2::runtime::Object;
///
/// fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
///     let obj: Id<Object, Owned> = unsafe { msg_send_id![class!(NSObject), new] };
///     let obj = ManuallyDrop::new(obj);
///     let obj: *mut Object = unsafe { msg_send![obj, autorelease] };
///     // Lifetime of the returned reference is bounded by the pool
///     unsafe { pool.ptr_as_mut(obj) }
///
///     // Or simply
///     // let obj: Id<Object, Owned> = unsafe { msg_send_id![class!(NSObject), new] };
///     // obj.autorelease(pool)
/// }
///
/// autoreleasepool(|pool| {
///     // Create `obj` and autorelease it to the pool
///     let obj = needs_lifetime_from_pool(pool);
///     // ... use `obj` here
///     // `obj` is deallocated when the pool ends
/// });
/// ```
///
/// Fails to compile because `obj` does not live long enough for us to
/// safely take it out of the pool:
///
/// ```compile_fail
/// # use objc2::{class, msg_send_id};
/// # use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned};
/// # use objc2::runtime::Object;
/// #
/// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
/// #     let obj: Id<Object, Owned> = unsafe { msg_send_id![class!(NSObject), new] };
/// #     obj.autorelease(pool)
/// # }
/// #
/// let obj = autoreleasepool(|pool| {
///     let obj = needs_lifetime_from_pool(pool);
///     // Use `obj`
///     obj
/// });
/// ```
///
/// Incorrect usage which panics (with debug assertions enabled) because we
/// tried to pass an outer pool to an inner pool:
///
#[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail")]
#[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```should_panic")]
/// # use objc2::{class, msg_send_id};
/// # use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned};
/// # use objc2::runtime::Object;
/// #
/// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
/// #     let obj: Id<Object, Owned> = unsafe { msg_send_id![class!(NSObject), new] };
/// #     obj.autorelease(pool)
/// # }
/// #
/// autoreleasepool(|outer_pool| {
///     let obj = autoreleasepool(|inner_pool| {
///         let obj = needs_lifetime_from_pool(outer_pool);
///         obj
///     });
///     // `obj` could wrongly be used here because its lifetime was
///     // assigned to the outer pool, even though it was released by the
///     // inner pool already.
/// });
/// #
/// # panic!("Does not panic in release mode, so for testing we make it!");
/// ```
#[doc(alias = "@autoreleasepool")]
#[inline]
pub fn autoreleasepool<T, F>(f: F) -> T
where
    for<'p> F: FnOnce(&'p AutoreleasePool) -> T + AutoreleaseSafe,
{
    let pool = unsafe { AutoreleasePool::new() };
    f(&pool)
}

#[cfg(all(test, feature = "unstable-autoreleasesafe"))]
mod tests {
    use super::AutoreleaseSafe;
    use crate::runtime::Object;

    fn requires_autoreleasesafe<T: AutoreleaseSafe>() {}

    #[test]
    fn test_autoreleasesafe() {
        requires_autoreleasesafe::<usize>();
        requires_autoreleasesafe::<*mut Object>();
        requires_autoreleasesafe::<&mut Object>();
    }
}