Skip to content

Commit ff261e6

Browse files
committed
Make autoreleasepool take the pool as a parameter
The lifetime of the parameter is the lifetime of references in the pool. This allows us to bound lifetimes on autoreleased objects, thereby making them safe to return.
1 parent c8696b0 commit ff261e6

File tree

4 files changed

+137
-20
lines changed

4 files changed

+137
-20
lines changed

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

README.md

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

3333
// Cloning retains the object an additional time
3434
let cloned = obj.clone();
35-
autoreleasepool(|| {
35+
autoreleasepool(|_| {
3636
// Autorelease consumes the StrongPtr, but won't
3737
// actually release until the end of an autoreleasepool
3838
cloned.autorelease();

src/rc/autorelease.rs

+124-14
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,140 @@
1+
use crate::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush};
12
use std::os::raw::c_void;
2-
use crate::runtime::{objc_autoreleasePoolPush, objc_autoreleasePoolPop};
33

4-
// we use a struct to ensure that objc_autoreleasePoolPop during unwinding.
5-
struct AutoReleaseHelper {
4+
/// An Objective-C autorelease pool.
5+
///
6+
/// The pool is drained when dropped.
7+
///
8+
/// This is not `Send`, since `objc_autoreleasePoolPop` must be called on the
9+
/// same thread.
10+
///
11+
/// And this is not `Sync`, since you can only autorelease a reference to a
12+
/// pool on the current thread.
13+
///
14+
/// See [the clang documentation][clang-arc] and
15+
/// [this apple article][memory-mgmt] for more information on automatic
16+
/// reference counting.
17+
///
18+
/// [clang-arc]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html
19+
/// [memory-mgmt]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html
20+
pub struct AutoreleasePool {
621
context: *mut c_void,
722
}
823

9-
impl AutoReleaseHelper {
24+
impl AutoreleasePool {
25+
/// Construct a new autoreleasepool.
26+
///
27+
/// Use the [`autoreleasepool`] block for a safe alternative.
28+
///
29+
/// # Safety
30+
///
31+
/// The caller must ensure that when handing out `&'p AutoreleasePool` to
32+
/// functions that this is the innermost pool.
33+
///
34+
/// Additionally, the pools must be dropped in the same order they were
35+
/// created.
36+
#[doc(alias = "objc_autoreleasePoolPush")]
1037
unsafe fn new() -> Self {
11-
AutoReleaseHelper { context: objc_autoreleasePoolPush() }
38+
// TODO: Make this function pub when we're more certain of the API
39+
AutoreleasePool {
40+
context: objc_autoreleasePoolPush(),
41+
}
1242
}
43+
44+
// TODO: Add helper functions to ensure (with debug_assertions) that the
45+
// pool is innermost when its lifetime is tied to a reference.
1346
}
1447

15-
impl Drop for AutoReleaseHelper {
48+
impl Drop for AutoreleasePool {
49+
/// Drains the autoreleasepool.
50+
///
51+
/// The [clang documentation] says that `@autoreleasepool` blocks are not
52+
/// drained when exceptions occur because:
53+
///
54+
/// > Not draining the pool during an unwind is apparently required by the
55+
/// > Objective-C exceptions implementation.
56+
///
57+
/// This was true in the past, but since [revision `371`] of
58+
/// `objc-exception.m` (ships with MacOS 10.5) the exception is now
59+
/// retained when `@throw` is encountered.
60+
///
61+
/// Hence it is safe to drain the pool when unwinding.
62+
///
63+
/// [clang documentation]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#autoreleasepool
64+
/// [revision `371`]: https://opensource.apple.com/source/objc4/objc4-371/runtime/objc-exception.m.auto.html
65+
#[doc(alias = "objc_autoreleasePoolPop")]
1666
fn drop(&mut self) {
1767
unsafe { objc_autoreleasePoolPop(self.context) }
1868
}
1969
}
2070

21-
/**
22-
Execute `f` in the context of a new autorelease pool. The pool is drained
23-
after the execution of `f` completes.
71+
// TODO:
72+
// #![feature(negative_impls)]
73+
// #![feature(auto_traits)]
74+
// /// A trait for the sole purpose of ensuring we can't pass an `&AutoreleasePool`
75+
// /// through to the closure inside `autoreleasepool`
76+
// pub unsafe auto trait AutoreleaseSafe {}
77+
// // TODO: Unsure how negative impls work exactly
78+
// unsafe impl !AutoreleaseSafe for AutoreleasePool {}
79+
// unsafe impl !AutoreleaseSafe for &AutoreleasePool {}
80+
// unsafe impl !AutoreleaseSafe for &mut AutoreleasePool {}
2481

25-
This corresponds to `@autoreleasepool` blocks in Objective-C and Swift.
26-
*/
27-
pub fn autoreleasepool<T, F: FnOnce() -> T>(f: F) -> T {
28-
let _context = unsafe { AutoReleaseHelper::new() };
29-
f()
82+
/// Execute `f` in the context of a new autorelease pool. The pool is drained
83+
/// after the execution of `f` completes.
84+
///
85+
/// This corresponds to `@autoreleasepool` blocks in Objective-C and Swift.
86+
///
87+
/// The pool is passed as a reference to the enclosing function to give it a
88+
/// lifetime parameter that autoreleased objects can refer to.
89+
///
90+
/// # Examples
91+
///
92+
/// ```rust
93+
/// use objc::{class, msg_send};
94+
/// use objc::rc::{autoreleasepool, AutoreleasePool};
95+
/// use objc::runtime::Object;
96+
///
97+
/// fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object {
98+
/// let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
99+
/// let obj: *mut Object = unsafe { msg_send![obj, autorelease] };
100+
/// // SAFETY: Lifetime bounded by the pool
101+
/// unsafe { &mut *obj }
102+
/// }
103+
///
104+
/// autoreleasepool(|pool| {
105+
/// let obj = needs_lifetime_from_pool(pool);
106+
/// // Use `obj`
107+
/// });
108+
///
109+
/// // `obj` is deallocated when the pool ends
110+
/// ```
111+
///
112+
/// ```rust,compile_fail
113+
/// # use objc::{class, msg_send};
114+
/// # use objc::rc::{autoreleasepool, AutoreleasePool};
115+
/// # use objc::runtime::Object;
116+
/// #
117+
/// # fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object {
118+
/// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
119+
/// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] };
120+
/// # unsafe { &mut *obj }
121+
/// # }
122+
/// #
123+
/// // Fails to compile because `obj` does not live long enough for us to
124+
/// // safely take it out of the pool.
125+
///
126+
/// let obj = autoreleasepool(|pool| {
127+
/// let obj = needs_lifetime_from_pool(pool);
128+
/// // Use `obj`
129+
/// obj
130+
/// });
131+
/// ```
132+
///
133+
/// TODO: More examples.
134+
pub fn autoreleasepool<T, F>(f: F) -> T
135+
where
136+
for<'p> F: FnOnce(&'p AutoreleasePool) -> T, // + AutoreleaseSafe,
137+
{
138+
let pool = unsafe { AutoreleasePool::new() };
139+
f(&pool)
30140
}

src/rc/mod.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ let obj = unsafe {
2626
2727
// Cloning retains the object an additional time
2828
let cloned = obj.clone();
29-
autoreleasepool(|| {
29+
autoreleasepool(|_| {
3030
// Autorelease consumes the StrongPtr, but won't
3131
// actually release until the end of an autoreleasepool
3232
cloned.autorelease();
@@ -44,9 +44,9 @@ mod strong;
4444
mod weak;
4545
mod autorelease;
4646

47+
pub use self::autorelease::{autoreleasepool, AutoreleasePool};
4748
pub use self::strong::StrongPtr;
4849
pub use self::weak::WeakPtr;
49-
pub use self::autorelease::autoreleasepool;
5050

5151
// These tests use NSObject, which isn't present for GNUstep
5252
#[cfg(all(test, any(target_os = "macos", target_os = "ios")))]
@@ -112,9 +112,9 @@ mod tests {
112112
}
113113
let cloned = obj.clone();
114114

115-
autoreleasepool(|| {
116-
obj.autorelease();
117-
assert!(retain_count(*cloned) == 2);
115+
autoreleasepool(|_| {
116+
obj.autorelease();
117+
assert!(retain_count(*cloned) == 2);
118118
});
119119

120120
// make sure that the autoreleased value has been released

0 commit comments

Comments
 (0)