Skip to content

Commit 1f82d5b

Browse files
committed
Merge pull request #17 from madsmtm/autoreleasepool-lifetime
Make `autoreleasepool` take the pool as a parameter
2 parents de78ed4 + 478f7dc commit 1f82d5b

File tree

6 files changed

+309
-20
lines changed

6 files changed

+309
-20
lines changed

objc/CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## Unreleased
2+
3+
### Changed
4+
5+
* The closure in `autoreleasepool` now takes an argument, a reference to the
6+
pool.
7+
18
## 0.2.7
29

310
### Fixed

objc/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ exclude = [
2424
[features]
2525
exception = ["objc_exception"]
2626
verify_message = []
27+
unstable_autoreleasesafe = []
2728

2829
[dependencies]
2930
malloc_buf = "1.0"

objc/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ let obj = unsafe {
4444

4545
// Cloning retains the object an additional time
4646
let cloned = obj.clone();
47-
autoreleasepool(|| {
47+
autoreleasepool(|_| {
4848
// Autorelease consumes the StrongPtr, but won't
4949
// actually release until the end of an autoreleasepool
5050
cloned.autorelease();

objc/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ The bindings can be used on Linux or *BSD utilizing the
5959
*/
6060

6161
#![no_std]
62+
#![cfg_attr(feature = "unstable_autoreleasesafe", feature(negative_impls, auto_traits))]
63+
6264
#![warn(missing_docs)]
6365
#![allow(clippy::missing_safety_doc)]
6466
// Update in Cargo.toml as well.

objc/src/rc/autorelease.rs

+295-16
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,311 @@
1-
use crate::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush};
21
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};
36

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 {
624
context: *mut c_void,
725
}
826

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")]
1060
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
14124
}
15125
}
16126

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")]
18145
fn drop(&mut self) {
19146
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 {}
20173
}
21174
}
22175

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 {}
26201

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+
}
32311
}

objc/src/rc/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ let obj = unsafe {
2525
2626
// Cloning retains the object an additional time
2727
let cloned = obj.clone();
28-
autoreleasepool(|| {
28+
autoreleasepool(|_| {
2929
// Autorelease consumes the StrongPtr, but won't
3030
// actually release until the end of an autoreleasepool
3131
cloned.autorelease();
@@ -42,7 +42,7 @@ mod autorelease;
4242
mod strong;
4343
mod weak;
4444

45-
pub use self::autorelease::autoreleasepool;
45+
pub use self::autorelease::{autoreleasepool, AutoreleasePool, AutoreleaseSafe};
4646
pub use self::strong::StrongPtr;
4747
pub use self::weak::WeakPtr;
4848

@@ -105,7 +105,7 @@ mod tests {
105105
}
106106
let cloned = obj.clone();
107107

108-
autoreleasepool(|| {
108+
autoreleasepool(|_| {
109109
obj.autorelease();
110110
assert!(retain_count(*cloned) == 2);
111111
});

0 commit comments

Comments
 (0)