Skip to content

Commit

Permalink
Make autoreleasepool take the pool as a parameter
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
madsmtm committed May 31, 2021
1 parent 69b5047 commit c5a7667
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 35 deletions.
119 changes: 105 additions & 14 deletions src/rc/autorelease.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,121 @@
use crate::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush};
use std::os::raw::c_void;
use crate::runtime::{objc_autoreleasePoolPush, objc_autoreleasePoolPop};

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

impl AutoReleaseHelper {
impl AutoreleasePool {
/// Construct a new autoreleasepool.
///
/// Use the [`autoreleasepool`] block for a safe alternative.
///
/// # Safety
///
/// The caller must ensure that when handing out `&'p AutoreleasePool` to
/// functions that this is the innermost pool.
///
/// Additionally, the pools must be dropped in the same order they were
/// created.
#[doc(alias = "objc_autoreleasePoolPush")]
unsafe fn new() -> Self {
AutoReleaseHelper { context: objc_autoreleasePoolPush() }
AutoreleasePool {
context: objc_autoreleasePoolPush(),
}
}

// TODO: Add helper functions to ensure (with debug_assertions) that the
// pool is innermost when its lifetime is tied to a reference.
}

impl Drop for AutoReleaseHelper {
impl Drop for AutoreleasePool {
/// Drains the autoreleasepool.
#[doc(alias = "objc_autoreleasePoolPop")]
fn drop(&mut self) {
unsafe { objc_autoreleasePoolPop(self.context) }
}
}

/**
Execute `f` in the context of a new autorelease pool. The pool is drained
after the execution of `f` completes.
// 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 {}

This corresponds to `@autoreleasepool` blocks in Objective-C and Swift.
*/
pub fn autoreleasepool<T, F: FnOnce() -> T>(f: F) -> T {
let _context = unsafe { AutoReleaseHelper::new() };
f()
/// 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, Owned};
/// use objc::runtime::Object;
///
/// fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
/// let obj: Owned<Object> = unsafe { Owned::new(msg_send![class!(NSObject), new]) };
/// obj.autorelease(pool)
/// }
///
/// 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, Owned};
/// # use objc::runtime::Object;
/// #
/// # fn needs_lifetime_from_pool<'p>(pool: &'p AutoreleasePool) -> &'p mut Object {
/// # let obj: Owned<Object> = unsafe { Owned::new(msg_send![class!(NSObject), new]) };
/// # obj.autorelease(pool)
/// # }
/// #
/// // 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<T, F>(f: F) -> T
where
for<'p> F: FnOnce(&'p AutoreleasePool) -> T, // + AutoreleaseSafe,
{
let pool = unsafe { AutoreleasePool::new() };
f(&pool)
}
12 changes: 6 additions & 6 deletions src/rc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ assert!(weak.load().is_null());
```
*/

mod autorelease;
mod owned;
mod retained;
mod strong;
mod weak;
mod autorelease;

pub use self::retained::Retained;
pub use self::autorelease::{autoreleasepool, AutoreleasePool};
pub use self::owned::Owned;
pub use self::retained::Retained;
pub use self::strong::StrongPtr;
pub use self::weak::WeakPtr;
pub use self::autorelease::autoreleasepool;

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

autoreleasepool(|| {
obj.autorelease();
assert!(retain_count(*cloned) == 2);
autoreleasepool(|_| {
obj.autorelease();
assert!(retain_count(*cloned) == 2);
});

// make sure that the autoreleased value has been released
Expand Down
15 changes: 14 additions & 1 deletion src/rc/owned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use core::mem;
use core::ops::{Deref, DerefMut};
use core::ptr::{drop_in_place, NonNull};

use super::AutoreleasePool;
use super::Retained;
use crate::runtime::{self, Object};

/// A smart pointer that strongly references and uniquely owns an Objective-C
/// object.
Expand Down Expand Up @@ -106,6 +106,19 @@ impl<T> Owned<T> {
phantom: PhantomData,
}
}

/// Autoreleases the retained pointer, meaning that the object is not
/// immediately released, but will be when the innermost / current
/// autorelease pool is drained.
#[doc(alias = "objc_autorelease")]
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
#[inline]
pub fn autorelease<'p>(self, pool: &'p AutoreleasePool) -> &'p mut T {
let retained: Retained<T> = self.into();
let ptr = retained.autorelease(pool) as *const T as *mut T;
// SAFETY: The pointer was previously `Owned`, so is safe to be mutable
unsafe { &mut *ptr }
}
}

/// `#[may_dangle]` (see [this][dropck_eyepatch]) would not be safe here,
Expand Down
28 changes: 14 additions & 14 deletions src/rc/retained.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use core::mem;
use core::ops::Deref;
use core::ptr::NonNull;

use super::AutoreleasePool;
use super::Owned;
use crate::runtime::{self, Object};

Expand Down Expand Up @@ -153,59 +154,58 @@ impl<T> Retained<T> {

/// TODO
#[doc(alias = "objc_retainAutoreleasedReturnValue")]
pub unsafe fn retain_autoreleased_return(obj: &T) -> Self {
pub unsafe fn retain_autoreleased_return(_obj: *const T) -> Self {
todo!()
}

/// Autoreleases the retained pointer, meaning that the object is not
/// immediately released, but will be when the innermost / current
/// autorelease pool is drained.
///
/// A pointer to the object is returned, but it's validity is only until
/// guaranteed until the innermost pool is drained.
#[doc(alias = "objc_autorelease")]
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
#[inline]
// TODO: Get a lifetime relating to the pool, so that we can return a
// reference instead of a pointer.
pub fn autorelease(self) -> NonNull<T> {
pub fn autorelease<'p>(self, _pool: &'p AutoreleasePool) -> &'p T {
let ptr = mem::ManuallyDrop::new(self).ptr;
// SAFETY: The `ptr` is guaranteed to be valid and have at least one
// retain count.
// And because of the ManuallyDrop, we don't call the Drop
// implementation, so the object won't also be released there.
unsafe { runtime::objc_autorelease(ptr.as_ptr() as *mut Object) };
ptr
// SAFETY: The lifetime is bounded by the type function signature
unsafe { &*ptr.as_ptr() }
}

/// TODO
#[doc(alias = "objc_autoreleaseReturnValue")]
pub fn autorelease_return(self) -> *const T {
pub fn autorelease_return<'p>(self, _pool: &'p AutoreleasePool) -> &'p T {
todo!()
}

/// TODO
///
/// Equivalent to `Retained::retain(&obj).autorelease()`, but slightly
/// Equivalent to `Retained::retain(&obj).autorelease(&pool)`, but slightly
/// more efficient.
#[doc(alias = "objc_retainAutorelease")]
pub unsafe fn retain_and_autorelease(obj: &T) -> *const T {
pub unsafe fn retain_and_autorelease<'p>(_obj: *const T, _pool: &'p AutoreleasePool) -> &'p T {
todo!()
}

/// TODO
///
/// Equivalent to `Retained::retain(&obj).autorelease_return()`, but
/// Equivalent to `Retained::retain(&obj).autorelease_return(&pool)`, but
/// slightly more efficient.
#[doc(alias = "objc_retainAutoreleaseReturnValue")]
pub unsafe fn retain_and_autorelease_return(obj: &T) -> *const T {
pub unsafe fn retain_and_autorelease_return<'p>(
_obj: *const T,
_pool: &'p AutoreleasePool,
) -> &'p T {
todo!()
}

#[cfg(test)] // TODO
#[doc(alias = "retainCount")]
pub fn retain_count(&self) -> usize {
unsafe { msg_send![self.ptr.as_ptr() as *mut Object, retainCount] }
unsafe { msg_send![self.as_ptr() as *mut Object, retainCount] }
}
}

Expand Down

0 comments on commit c5a7667

Please sign in to comment.