Skip to content

Commit a1b5455

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 a1b5455

File tree

3 files changed

+142
-68
lines changed

3 files changed

+142
-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

+139-68
Original file line numberDiff line numberDiff line change
@@ -81,73 +81,144 @@ 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+
// TODO: Unsure how negative impls work exactly
89+
#[cfg(feature = "unstable_autoreleasesafe")]
90+
impl !AutoreleaseSafe for AutoreleasePool {}
9491

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

0 commit comments

Comments
 (0)