From 0d256c3eeb1e381358fa9f2c0d8cdb18718d8131 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 13 Jun 2021 15:30:17 +0200 Subject: [PATCH] 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. --- Cargo.toml | 1 + src/lib.rs | 2 + src/rc/autorelease.rs | 206 ++++++++++++++++++++++++++++-------------- 3 files changed, 141 insertions(+), 68 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3a7f2b8c2..1570560d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ exclude = [ [features] exception = ["objc_exception"] verify_message = [] +unstable_autoreleasesafe = [] [dependencies] malloc_buf = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 5f2d4e9f1..f71ba8cfa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,8 @@ The bindings can be used on Linux or *BSD utilizing the #![crate_name = "objc"] #![crate_type = "lib"] +#![cfg_attr(feature = "unstable_autoreleasesafe", feature(negative_impls, auto_traits))] + #![warn(missing_docs)] extern crate malloc_buf; diff --git a/src/rc/autorelease.rs b/src/rc/autorelease.rs index b887698c7..5c1ac494e 100644 --- a/src/rc/autorelease.rs +++ b/src/rc/autorelease.rs @@ -81,73 +81,143 @@ impl Drop for AutoreleasePool { } } -// TODO: -// #![feature(negative_impls)] -// #![feature(auto_traits)] -// /// A trait for the sole purpose of ensuring we can't pass an `&AutoreleasePool` -// /// through to the closure inside `autoreleasepool` -// pub unsafe auto trait AutoreleaseSafe {} -// // TODO: Unsure how negative impls work exactly -// unsafe impl !AutoreleaseSafe for AutoreleasePool {} -// unsafe impl !AutoreleaseSafe for &AutoreleasePool {} -// unsafe impl !AutoreleaseSafe for &mut AutoreleasePool {} +/// A trait for the sole purpose of ensuring we can't pass an `&AutoreleasePool` +/// through to the closure inside `autoreleasepool` +#[cfg(feature = "unstable_autoreleasesafe")] +pub unsafe auto trait AutoreleaseSafe {} +#[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. -/// -/// # Examples -/// -/// ```rust -/// use objc::{class, msg_send}; -/// use objc::rc::{autoreleasepool, AutoreleasePool}; -/// use objc::runtime::Object; -/// -/// fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object { -/// let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; -/// let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; -/// // SAFETY: Lifetime bounded by the pool -/// unsafe { &mut *obj } -/// } -/// -/// autoreleasepool(|pool| { -/// let obj = needs_lifetime_from_pool(pool); -/// // Use `obj` -/// }); -/// -/// // `obj` is deallocated when the pool ends -/// ``` -/// -/// ```rust,compile_fail -/// # use objc::{class, msg_send}; -/// # use objc::rc::{autoreleasepool, AutoreleasePool}; -/// # use objc::runtime::Object; -/// # -/// # fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object { -/// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; -/// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; -/// # unsafe { &mut *obj } -/// # } -/// # -/// // Fails to compile because `obj` does not live long enough for us to -/// // safely take it out of the pool. -/// -/// let obj = autoreleasepool(|pool| { -/// let obj = needs_lifetime_from_pool(pool); -/// // Use `obj` -/// obj -/// }); -/// ``` -/// -/// TODO: More examples. -pub fn autoreleasepool(f: F) -> T -where - for<'p> F: FnOnce(&'p AutoreleasePool) -> T, // + AutoreleaseSafe, -{ - let pool = unsafe { AutoreleasePool::new() }; - f(&pool) +#[cfg(feature = "unstable_autoreleasesafe")] +macro_rules! fn_autoreleasepool { + {$(#[$fn_meta:meta])* $v:vis fn $fn:ident($f:ident) $b:block} => { + $(#[$fn_meta])* + $v fn $fn($f: F) -> T + where + for<'p> F: FnOnce(&'p AutoreleasePool) -> T + AutoreleaseSafe, + { + $b + } + } +} + +#[cfg(not(feature = "unstable_autoreleasesafe"))] +macro_rules! fn_autoreleasepool { + {$(#[$fn_meta:meta])* $v:vis fn $fn:ident($f:ident) $b:block} => { + $(#[$fn_meta])* + $v fn $fn($f: F) -> T + where + for<'p> F: FnOnce(&'p AutoreleasePool) -> T, + { + $b + } + } +} + +fn_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 be a compile error in a future release. You can test + /// this guarantee with the `unstable_autoreleasesafe` crate feature on + /// nightly Rust. + /// + /// So using `autoreleasepool` is unsound right now because of this + /// specific problem. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```rust + /// use objc::{class, msg_send}; + /// use objc::rc::{autoreleasepool, AutoreleasePool}; + /// use objc::runtime::Object; + /// + /// fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object { + /// let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; + /// let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; + /// // SAFETY: Lifetime bounded by the pool + /// unsafe { &mut *obj } + /// } + /// + /// 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: + /// + /// ```rust,compile_fail + /// # use objc::{class, msg_send}; + /// # use objc::rc::{autoreleasepool, AutoreleasePool}; + /// # use objc::runtime::Object; + /// # + /// # fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object { + /// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; + /// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; + /// # unsafe { &mut *obj } + /// # } + /// # + /// let obj = autoreleasepool(|pool| { + /// let obj = needs_lifetime_from_pool(pool); + /// // Use `obj` + /// obj + /// }); + /// ``` + /// + /// Incorrect usage which causes undefined behaviour: + /// + #[cfg_attr(feature = "unstable_autoreleasesafe", doc = "```rust,compile_fail")] + #[cfg_attr(not(feature = "unstable_autoreleasesafe"), doc = "```rust")] + /// # use objc::{class, msg_send}; + /// # use objc::rc::{autoreleasepool, AutoreleasePool}; + /// # use objc::runtime::Object; + /// # + /// # fn needs_lifetime_from_pool<'p>(_pool: &'p AutoreleasePool) -> &'p mut Object { + /// # let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] }; + /// # let obj: *mut Object = unsafe { msg_send![obj, autorelease] }; + /// # unsafe { &mut *obj } + /// # } + /// # + /// autoreleasepool(|outer_pool| { + /// let obj = autoreleasepool(|inner_pool| { + /// let obj = needs_lifetime_from_pool(outer_pool); + /// obj + /// }); + /// // `obj` can wrongly be used here because it's lifetime was + /// // assigned to the outer pool, even though it was released by the + /// // inner pool already. + /// }); + /// ``` + pub fn autoreleasepool(f) { + 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() {} + + #[test] + fn test_autoreleasesafe() { + requires_autoreleasesafe::(); + requires_autoreleasesafe::<*mut Object>(); + requires_autoreleasesafe::<&mut Object>(); + } }