use core::ffi::c_void;
#[cfg(not(all(debug_assertions, not(feature = "unstable-autoreleasesafe"))))]
use core::marker::PhantomData;
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
use std::{cell::RefCell, thread_local, vec::Vec};
use crate::ffi;
#[derive(Debug)]
struct Pool {
context: *mut c_void,
}
impl Pool {
#[inline]
unsafe fn new() -> Self {
let context = unsafe { ffi::objc_autoreleasePoolPush() };
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
POOLS.with(|c| c.borrow_mut().push(context));
Self { context }
}
#[inline]
unsafe fn drain(self) {
#[cfg(all(target_os = "macos", target_arch = "x86"))]
unsafe {
ffi::objc_autoreleasePoolPop(self.context);
}
}
}
impl Drop for Pool {
#[inline]
fn drop(&mut self) {
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
POOLS.with(|c| {
assert_eq!(
c.borrow_mut().pop(),
Some(self.context),
"popped pool that was not the innermost pool"
);
});
#[cfg(not(all(target_os = "macos", target_arch = "x86")))]
unsafe {
ffi::objc_autoreleasePoolPop(self.context);
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct AutoreleasePool<'pool> {
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
inner: Option<&'pool Pool>,
#[cfg(not(all(debug_assertions, not(feature = "unstable-autoreleasesafe"))))]
inner: PhantomData<&'pool Pool>,
}
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
thread_local! {
static POOLS: RefCell<Vec<*mut c_void>> = const { RefCell::new(Vec::new()) };
}
impl<'pool> AutoreleasePool<'pool> {
fn new(_inner: Option<&'pool Pool>) -> Self {
Self {
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
inner: _inner,
#[cfg(not(all(debug_assertions, not(feature = "unstable-autoreleasesafe"))))]
inner: PhantomData,
}
}
#[inline]
pub(crate) fn __verify_is_inner(self) {
#[cfg(all(debug_assertions, not(feature = "unstable-autoreleasesafe")))]
if let Some(pool) = &self.inner {
POOLS.with(|c| {
assert_eq!(
c.borrow().last(),
Some(&pool.context),
"tried to use lifetime from pool that was not innermost"
);
});
}
}
#[inline]
pub unsafe fn ptr_as_ref<T: ?Sized>(self, ptr: *const T) -> &'pool T {
self.__verify_is_inner();
unsafe { ptr.as_ref().unwrap_unchecked() }
}
}
#[cfg(not(feature = "unstable-autoreleasesafe"))]
macro_rules! auto_trait {
{$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => {
$(#[$fn_meta])*
$v unsafe trait AutoreleaseSafe {}
}
}
#[cfg(feature = "unstable-autoreleasesafe")]
macro_rules! auto_trait {
{$(#[$fn_meta:meta])* $v:vis unsafe trait AutoreleaseSafe {}} => {
$(#[$fn_meta])*
$v unsafe auto trait AutoreleaseSafe {}
}
}
auto_trait! {
#[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail,E0277")]
#[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```")]
#[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail,E0277")]
#[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```")]
pub unsafe trait AutoreleaseSafe {}
}
#[cfg(not(feature = "unstable-autoreleasesafe"))]
unsafe impl<T: ?Sized> AutoreleaseSafe for T {}
#[cfg(feature = "unstable-autoreleasesafe")]
impl !AutoreleaseSafe for Pool {}
#[cfg(feature = "unstable-autoreleasesafe")]
impl !AutoreleaseSafe for AutoreleasePool<'_> {}
#[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail,E0277")]
#[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```should_panic")]
#[doc(alias = "@autoreleasepool")]
#[doc(alias = "objc_autoreleasePoolPush")]
#[doc(alias = "objc_autoreleasePoolPop")]
#[inline]
pub fn autoreleasepool<T, F>(f: F) -> T
where
for<'pool> F: AutoreleaseSafe + FnOnce(AutoreleasePool<'pool>) -> T,
{
let pool = unsafe { Pool::new() };
let res = f(AutoreleasePool::new(Some(&pool)));
unsafe { pool.drain() };
res
}
#[cfg_attr(feature = "unstable-autoreleasesafe", doc = "```compile_fail,E0277")]
#[cfg_attr(not(feature = "unstable-autoreleasesafe"), doc = "```should_panic")]
#[inline]
pub fn autoreleasepool_leaking<T, F>(f: F) -> T
where
for<'pool> F: FnOnce(AutoreleasePool<'pool>) -> T,
{
f(AutoreleasePool::new(None))
}
#[cfg(test)]
mod tests {
use core::mem;
use core::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe};
use std::panic::catch_unwind;
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::{autoreleasepool, AutoreleasePool, AutoreleaseSafe};
use crate::rc::{RcTestObject, Retained, ThreadTestData};
use crate::runtime::AnyObject;
#[test]
fn auto_traits() {
assert_impl_all!(AutoreleasePool<'static>: Unpin, UnwindSafe, RefUnwindSafe);
assert_not_impl_any!(AutoreleasePool<'static>: Send, Sync);
assert_impl_all!(usize: AutoreleaseSafe);
assert_impl_all!(*mut AnyObject: AutoreleaseSafe);
assert_impl_all!(&mut AnyObject: AutoreleaseSafe);
#[cfg(feature = "unstable-autoreleasesafe")]
assert_not_impl_any!(AutoreleasePool<'static>: AutoreleaseSafe);
}
#[allow(unused)]
fn assert_covariant1<'a>(pool: AutoreleasePool<'static>) -> AutoreleasePool<'a> {
pool
}
#[allow(unused)]
fn assert_covariant2<'long: 'short, 'short>(
pool: AutoreleasePool<'long>,
) -> AutoreleasePool<'short> {
pool
}
#[allow(unused)]
fn assert_object_safe(_: &dyn AutoreleaseSafe) {}
#[cfg_attr(
not(feature = "unstable-autoreleasesafe"),
ignore = "only stably ZST when `unstable-autoreleasesafe` is enabled"
)]
#[test]
fn assert_zst() {
assert_eq!(mem::size_of::<AutoreleasePool<'static>>(), 0);
}
#[test]
#[cfg_attr(panic = "abort", ignore = "requires `catch_unwind`")]
#[cfg_attr(
all(target_os = "macos", target_arch = "x86", not(panic = "abort")),
ignore = "unwinding through an auto release pool on macOS 32 bit won't pop the pool"
)]
fn test_unwind_still_autoreleases() {
let obj = RcTestObject::new();
let mut expected = ThreadTestData::current();
catch_unwind({
let obj = AssertUnwindSafe(obj);
let expected = AssertUnwindSafe(&mut expected);
|| {
let obj = obj;
let mut expected = expected;
autoreleasepool(|pool| {
let _autoreleased = unsafe { Retained::autorelease(obj.0, pool) };
expected.autorelease += 1;
expected.assert_current();
panic!("unwind");
});
}
})
.unwrap_err();
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
}