use core::fmt;
use core::marker::PhantomData;
use core::mem::{self, ManuallyDrop};
use core::ops::{Deref, DerefMut};
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::NonNull;
use super::Allocated;
use super::AutoreleasePool;
use super::{Owned, Ownership, Shared};
use crate::ffi;
use crate::{ClassType, Message};
#[repr(transparent)]
pub struct Id<T: ?Sized, O: Ownership> {
ptr: NonNull<T>,
item: PhantomData<T>,
own: PhantomData<O>,
notunwindsafe: PhantomData<&'static mut ()>,
}
impl<T: ?Sized, O: Ownership> Id<T, O> {
#[inline]
unsafe fn new_nonnull(ptr: NonNull<T>) -> Self {
Self {
ptr,
item: PhantomData,
own: PhantomData,
notunwindsafe: PhantomData,
}
}
}
impl<T: Message + ?Sized, O: Ownership> Id<Allocated<T>, O> {
#[inline]
pub(crate) unsafe fn new_allocated(ptr: *mut T) -> Option<Self> {
NonNull::new(ptr as *mut Allocated<T>).map(|ptr| unsafe { Self::new_nonnull(ptr) })
}
#[inline]
pub(crate) unsafe fn assume_init(this: Self) -> Id<T, O> {
let ptr = ManuallyDrop::new(this).ptr;
let ptr = ptr.as_ptr() as *mut T;
let ptr = unsafe { NonNull::new_unchecked(ptr) };
unsafe { Id::new_nonnull(ptr) }
}
}
impl<T: Message + ?Sized, O: Ownership> Id<T, O> {
#[inline]
pub unsafe fn new(ptr: *mut T) -> Option<Id<T, O>> {
NonNull::new(ptr).map(|ptr| unsafe { Id::new_nonnull(ptr) })
}
#[inline]
pub fn as_ptr(this: &Id<T, O>) -> *const T {
this.ptr.as_ptr()
}
#[inline]
pub(crate) fn consume_as_ptr(this: ManuallyDrop<Self>) -> *mut T {
this.ptr.as_ptr()
}
#[inline]
pub(crate) fn option_into_ptr(obj: Option<Self>) -> *mut T {
unsafe { mem::transmute::<ManuallyDrop<Option<Self>>, *mut T>(ManuallyDrop::new(obj)) }
}
}
impl<T: Message + ?Sized> Id<T, Owned> {
#[inline]
pub fn as_mut_ptr(this: &mut Id<T, Owned>) -> *mut T {
this.ptr.as_ptr()
}
}
impl<T: Message, O: Ownership> Id<T, O> {
#[inline]
pub unsafe fn cast<U: Message>(this: Self) -> Id<U, O> {
let ptr = ManuallyDrop::new(this).ptr.cast();
unsafe { Id::new_nonnull(ptr) }
}
#[doc(alias = "objc_retain")]
#[inline]
pub unsafe fn retain(ptr: *mut T) -> Option<Id<T, O>> {
let res: *mut T = unsafe { ffi::objc_retain(ptr.cast()) }.cast();
debug_assert_eq!(res, ptr, "objc_retain did not return the same pointer");
unsafe { Self::new(res) }
}
#[doc(alias = "objc_retainAutoreleasedReturnValue")]
#[inline]
pub unsafe fn retain_autoreleased(ptr: *mut T) -> Option<Id<T, O>> {
#[cfg(all(feature = "apple", not(target_os = "windows")))]
{
#[cfg(target_arch = "x86_64")]
{
}
#[cfg(target_arch = "arm")]
unsafe {
core::arch::asm!("mov r7, r7", options(nomem, preserves_flags, nostack))
};
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!("mov fp, fp", options(nomem, preserves_flags, nostack))
};
#[cfg(target_arch = "x86")]
unsafe {
core::arch::asm!("mov ebp, ebp", options(nomem, preserves_flags, nostack))
};
}
let res: *mut T = unsafe { ffi::objc_retainAutoreleasedReturnValue(ptr.cast()) }.cast();
#[cfg(all(feature = "apple", not(target_os = "windows"), target_arch = "x86_64"))]
{
unsafe { core::arch::asm!("nop", options(nomem, preserves_flags, nostack)) };
}
debug_assert_eq!(
res, ptr,
"objc_retainAutoreleasedReturnValue did not return the same pointer"
);
unsafe { Self::new(res) }
}
#[inline]
fn autorelease_inner(self) -> *mut T {
let ptr = ManuallyDrop::new(self).ptr.as_ptr();
let res: *mut T = unsafe { ffi::objc_autorelease(ptr.cast()) }.cast();
debug_assert_eq!(res, ptr, "objc_autorelease did not return the same pointer");
res
}
#[doc(alias = "objc_autoreleaseReturnValue")]
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
#[inline]
pub fn autorelease_return(self) -> *mut T {
let ptr = ManuallyDrop::new(self).ptr.as_ptr();
let res: *mut T = unsafe { ffi::objc_autoreleaseReturnValue(ptr.cast()) }.cast();
debug_assert_eq!(
res, ptr,
"objc_autoreleaseReturnValue did not return the same pointer"
);
res
}
}
impl<T: Message> Id<T, Owned> {
#[doc(alias = "objc_autorelease")]
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
#[inline]
#[allow(clippy::needless_lifetimes)]
#[allow(clippy::mut_from_ref)]
pub fn autorelease<'p>(self, pool: &'p AutoreleasePool) -> &'p mut T {
let ptr = self.autorelease_inner();
unsafe { pool.ptr_as_mut(ptr) }
}
#[inline]
pub unsafe fn from_shared(obj: Id<T, Shared>) -> Self {
let ptr = ManuallyDrop::new(obj).ptr;
unsafe { <Id<T, Owned>>::new_nonnull(ptr) }
}
#[inline]
pub fn into_shared(obj: Self) -> Id<T, Shared> {
let ptr = ManuallyDrop::new(obj).ptr;
unsafe { <Id<T, Shared>>::new_nonnull(ptr) }
}
}
impl<T: Message> Id<T, Shared> {
#[doc(alias = "objc_autorelease")]
#[must_use = "If you don't intend to use the object any more, just drop it as usual"]
#[inline]
#[allow(clippy::needless_lifetimes)]
pub fn autorelease<'p>(self, pool: &'p AutoreleasePool) -> &'p T {
let ptr = self.autorelease_inner();
unsafe { pool.ptr_as_ref(ptr) }
}
}
impl<T: ClassType + 'static, O: Ownership> Id<T, O>
where
T::Super: 'static,
{
#[inline]
pub fn into_super(this: Self) -> Id<T::Super, O> {
unsafe { Self::cast::<T::Super>(this) }
}
}
impl<T: Message> From<Id<T, Owned>> for Id<T, Shared> {
#[inline]
fn from(obj: Id<T, Owned>) -> Self {
Id::into_shared(obj)
}
}
impl<T: Message> Clone for Id<T, Shared> {
#[doc(alias = "objc_retain")]
#[doc(alias = "retain")]
#[inline]
fn clone(&self) -> Self {
let obj = unsafe { Id::retain(self.ptr.as_ptr()) };
unsafe { obj.unwrap_unchecked() }
}
}
impl<T: ?Sized, O: Ownership> Drop for Id<T, O> {
#[doc(alias = "objc_release")]
#[doc(alias = "release")]
#[inline]
fn drop(&mut self) {
unsafe { ffi::objc_release(self.ptr.as_ptr().cast()) };
}
}
unsafe impl<T: Sync + Send + ?Sized> Send for Id<T, Shared> {}
unsafe impl<T: Sync + Send + ?Sized> Sync for Id<T, Shared> {}
unsafe impl<T: Send + ?Sized> Send for Id<T, Owned> {}
unsafe impl<T: Sync + ?Sized> Sync for Id<T, Owned> {}
impl<T: ?Sized, O: Ownership> Deref for Id<T, O> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
}
impl<T: ?Sized> DerefMut for Id<T, Owned> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { self.ptr.as_mut() }
}
}
impl<T: ?Sized, O: Ownership> fmt::Pointer for Id<T, O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.ptr.as_ptr(), f)
}
}
impl<T: ?Sized, O: Ownership> Unpin for Id<T, O> {}
impl<T: RefUnwindSafe + ?Sized, O: Ownership> RefUnwindSafe for Id<T, O> {}
impl<T: RefUnwindSafe + ?Sized> UnwindSafe for Id<T, Shared> {}
impl<T: UnwindSafe + ?Sized> UnwindSafe for Id<T, Owned> {}
#[cfg(test)]
mod tests {
use super::*;
use crate::msg_send;
use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData};
use crate::runtime::Object;
#[track_caller]
fn assert_retain_count(obj: &Object, expected: usize) {
let retain_count: usize = unsafe { msg_send![obj, retainCount] };
assert_eq!(retain_count, expected);
}
#[test]
fn test_drop() {
let mut expected = ThreadTestData::current();
let obj = RcTestObject::new();
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
drop(obj);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_autorelease() {
let obj: Id<_, Shared> = RcTestObject::new().into();
let cloned = obj.clone();
let mut expected = ThreadTestData::current();
autoreleasepool(|pool| {
let _ref = obj.autorelease(pool);
expected.autorelease += 1;
expected.assert_current();
assert_retain_count(&cloned, 2);
});
expected.release += 1;
expected.assert_current();
assert_retain_count(&cloned, 1);
autoreleasepool(|pool| {
let _ref = cloned.autorelease(pool);
expected.autorelease += 1;
expected.assert_current();
});
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_clone() {
let obj: Id<_, Owned> = RcTestObject::new();
assert_retain_count(&obj, 1);
let mut expected = ThreadTestData::current();
let obj: Id<_, Shared> = obj.into();
expected.assert_current();
assert_retain_count(&obj, 1);
let cloned = obj.clone();
expected.retain += 1;
expected.assert_current();
assert_retain_count(&cloned, 2);
assert_retain_count(&obj, 2);
drop(obj);
expected.release += 1;
expected.assert_current();
assert_retain_count(&cloned, 1);
drop(cloned);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_retain_autoreleased_works_as_retain() {
let obj: Id<_, Shared> = RcTestObject::new().into();
let mut expected = ThreadTestData::current();
let ptr = Id::as_ptr(&obj) as *mut RcTestObject;
let _obj2: Id<_, Shared> = unsafe { Id::retain_autoreleased(ptr) }.unwrap();
expected.retain += 1;
expected.assert_current();
}
#[test]
fn test_cast() {
let obj: Id<RcTestObject, _> = RcTestObject::new();
let expected = ThreadTestData::current();
let obj: Id<Object, _> = unsafe { Id::cast(obj) };
expected.assert_current();
let _obj: Id<RcTestObject, _> = unsafe { Id::cast(obj) };
expected.assert_current();
}
}