Skip to content

Commit 0d256c3

Browse files
committed
Add unstable_autoreleasesafe feature
A nightly-only feature that adds the auto trait `AutoreleaseSafe` and uses it to prevent calling `autoreleasepool` with closures that capture an outer `AutoreleasePool`. This fixes on nightly the unsoundness hole preventing `autoreleasepool` from being used to safely construct references to autoreleased objects.
1 parent 93ca34a commit 0d256c3

File tree

3 files changed

+141
-68
lines changed

3 files changed

+141
-68
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ exclude = [
2323
[features]
2424
exception = ["objc_exception"]
2525
verify_message = []
26+
unstable_autoreleasesafe = []
2627

2728
[dependencies]
2829
malloc_buf = "1.0"

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ The bindings can be used on Linux or *BSD utilizing the
6363
#![crate_name = "objc"]
6464
#![crate_type = "lib"]
6565

66+
#![cfg_attr(feature = "unstable_autoreleasesafe", feature(negative_impls, auto_traits))]
67+
6668
#![warn(missing_docs)]
6769

6870
extern crate malloc_buf;

src/rc/autorelease.rs

+138-68
Original file line numberDiff line numberDiff line change
@@ -81,73 +81,143 @@ impl Drop for AutoreleasePool {
8181
}
8282
}
8383

84-
// TODO:
85-
// #![feature(negative_impls)]
86-
// #![feature(auto_traits)]
87-
// /// A trait for the sole purpose of ensuring we can't pass an `&AutoreleasePool`
88-
// /// through to the closure inside `autoreleasepool`
89-
// pub unsafe auto trait AutoreleaseSafe {}
90-
// // TODO: Unsure how negative impls work exactly
91-
// unsafe impl !AutoreleaseSafe for AutoreleasePool {}
92-
// unsafe impl !AutoreleaseSafe for &AutoreleasePool {}
93-
// unsafe impl !AutoreleaseSafe for &mut AutoreleasePool {}
84+
/// A trait for the sole purpose of ensuring we can't pass an `&AutoreleasePool`
85+
/// through to the closure inside `autoreleasepool`
86+
#[cfg(feature = "unstable_autoreleasesafe")]
87+
pub unsafe auto trait AutoreleaseSafe {}
88+
#[cfg(feature = "unstable_autoreleasesafe")]
89+
impl !AutoreleaseSafe for AutoreleasePool {}
9490

95-
/// Execute `f` in the context of a new autorelease pool. The pool is drained
96-
/// after the execution of `f` completes.
97-
///
98-
/// This corresponds to `@autoreleasepool` blocks in Objective-C and Swift.
99-
///
100-
/// The pool is passed as a reference to the enclosing function to give it a
101-
/// lifetime parameter that autoreleased objects can refer to.
102-
///
103-
/// # Examples
104-
///
105-
/// ```rust
106-
/// use objc::{class, msg_send};
107-
/// use objc::rc::{autoreleasepool, AutoreleasePool};
108-
/// use objc::runtime::Object;
109-
///
110-
/// fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object {
111-
/// let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
112-
/// let obj: *mut Object = unsafe { msg_send![obj, autorelease] };
113-
/// // SAFETY: Lifetime bounded by the pool
114-
/// unsafe { &mut *obj }
115-
/// }
116-
///
117-
/// autoreleasepool(|pool| {
118-
/// let obj = needs_lifetime_from_pool(pool);
119-
/// // Use `obj`
120-
/// });
121-
///
122-
/// // `obj` is deallocated when the pool ends
123-
/// ```
124-
///
125-
/// ```rust,compile_fail
126-
/// # use objc::{class, msg_send};
127-
/// # use objc::rc::{autoreleasepool, AutoreleasePool};
128-
/// # use objc::runtime::Object;
129-
/// #
130-
/// # fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object {
131-
/// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
132-
/// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] };
133-
/// # unsafe { &mut *obj }
134-
/// # }
135-
/// #
136-
/// // Fails to compile because `obj` does not live long enough for us to
137-
/// // safely take it out of the pool.
138-
///
139-
/// let obj = autoreleasepool(|pool| {
140-
/// let obj = needs_lifetime_from_pool(pool);
141-
/// // Use `obj`
142-
/// obj
143-
/// });
144-
/// ```
145-
///
146-
/// TODO: More examples.
147-
pub fn autoreleasepool<T, F>(f: F) -> T
148-
where
149-
for<'p> F: FnOnce(&'p AutoreleasePool) -> T, // + AutoreleaseSafe,
150-
{
151-
let pool = unsafe { AutoreleasePool::new() };
152-
f(&pool)
91+
#[cfg(feature = "unstable_autoreleasesafe")]
92+
macro_rules! fn_autoreleasepool {
93+
{$(#[$fn_meta:meta])* $v:vis fn $fn:ident($f:ident) $b:block} => {
94+
$(#[$fn_meta])*
95+
$v fn $fn<T, F>($f: F) -> T
96+
where
97+
for<'p> F: FnOnce(&'p AutoreleasePool) -> T + AutoreleaseSafe,
98+
{
99+
$b
100+
}
101+
}
102+
}
103+
104+
#[cfg(not(feature = "unstable_autoreleasesafe"))]
105+
macro_rules! fn_autoreleasepool {
106+
{$(#[$fn_meta:meta])* $v:vis fn $fn:ident($f:ident) $b:block} => {
107+
$(#[$fn_meta])*
108+
$v fn $fn<T, F>($f: F) -> T
109+
where
110+
for<'p> F: FnOnce(&'p AutoreleasePool) -> T,
111+
{
112+
$b
113+
}
114+
}
115+
}
116+
117+
fn_autoreleasepool!(
118+
/// Execute `f` in the context of a new autorelease pool. The pool is
119+
/// drained after the execution of `f` completes.
120+
///
121+
/// This corresponds to `@autoreleasepool` blocks in Objective-C and
122+
/// Swift.
123+
///
124+
/// The pool is passed as a reference to the enclosing function to give it
125+
/// a lifetime parameter that autoreleased objects can refer to.
126+
///
127+
/// The given reference must not be used in an inner `autoreleasepool`,
128+
/// doing so will be a compile error in a future release. You can test
129+
/// this guarantee with the `unstable_autoreleasesafe` crate feature on
130+
/// nightly Rust.
131+
///
132+
/// So using `autoreleasepool` is unsound right now because of this
133+
/// specific problem.
134+
///
135+
/// # Examples
136+
///
137+
/// Basic usage:
138+
///
139+
/// ```rust
140+
/// use objc::{class, msg_send};
141+
/// use objc::rc::{autoreleasepool, AutoreleasePool};
142+
/// use objc::runtime::Object;
143+
///
144+
/// fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object {
145+
/// let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
146+
/// let obj: *mut Object = unsafe { msg_send![obj, autorelease] };
147+
/// // SAFETY: Lifetime bounded by the pool
148+
/// unsafe { &mut *obj }
149+
/// }
150+
///
151+
/// autoreleasepool(|pool| {
152+
/// // Create `obj` and autorelease it to the pool
153+
/// let obj = needs_lifetime_from_pool(pool);
154+
/// // ... use `obj` here
155+
/// // `obj` is deallocated when the pool ends
156+
/// });
157+
/// ```
158+
///
159+
/// Fails to compile because `obj` does not live long enough for us to
160+
/// safely take it out of the pool:
161+
///
162+
/// ```rust,compile_fail
163+
/// # use objc::{class, msg_send};
164+
/// # use objc::rc::{autoreleasepool, AutoreleasePool};
165+
/// # use objc::runtime::Object;
166+
/// #
167+
/// # fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object {
168+
/// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
169+
/// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] };
170+
/// # unsafe { &mut *obj }
171+
/// # }
172+
/// #
173+
/// let obj = autoreleasepool(|pool| {
174+
/// let obj = needs_lifetime_from_pool(pool);
175+
/// // Use `obj`
176+
/// obj
177+
/// });
178+
/// ```
179+
///
180+
/// Incorrect usage which causes undefined behaviour:
181+
///
182+
#[cfg_attr(feature = "unstable_autoreleasesafe", doc = "```rust,compile_fail")]
183+
#[cfg_attr(not(feature = "unstable_autoreleasesafe"), doc = "```rust")]
184+
/// # use objc::{class, msg_send};
185+
/// # use objc::rc::{autoreleasepool, AutoreleasePool};
186+
/// # use objc::runtime::Object;
187+
/// #
188+
/// # fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object {
189+
/// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
190+
/// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] };
191+
/// # unsafe { &mut *obj }
192+
/// # }
193+
/// #
194+
/// autoreleasepool(|outer_pool| {
195+
/// let obj = autoreleasepool(|inner_pool| {
196+
/// let obj = needs_lifetime_from_pool(outer_pool);
197+
/// obj
198+
/// });
199+
/// // `obj` can wrongly be used here because it's lifetime was
200+
/// // assigned to the outer pool, even though it was released by the
201+
/// // inner pool already.
202+
/// });
203+
/// ```
204+
pub fn autoreleasepool(f) {
205+
let pool = unsafe { AutoreleasePool::new() };
206+
f(&pool)
207+
}
208+
);
209+
210+
#[cfg(all(test, feature = "unstable_autoreleasesafe"))]
211+
mod tests {
212+
use super::AutoreleaseSafe;
213+
use crate::runtime::Object;
214+
215+
fn requires_autoreleasesafe<T: AutoreleaseSafe>() {}
216+
217+
#[test]
218+
fn test_autoreleasesafe() {
219+
requires_autoreleasesafe::<usize>();
220+
requires_autoreleasesafe::<*mut Object>();
221+
requires_autoreleasesafe::<&mut Object>();
222+
}
153223
}

0 commit comments

Comments
 (0)