use core::marker::PhantomData; use core::mem::ManuallyDrop; use core::ptr::NonNull; use core::{fmt, ptr}; use crate::__macro_helpers::defined_ivars::initialize_ivars; use crate::runtime::{objc_release_fast, AnyClass, AnyObject}; use crate::{DefinedClass, Message}; /// An Objective-C object that has been allocated, but not initialized. /// /// Objective-C splits the allocation and initialization steps up into two, so /// we need to track it in the type-system whether something has been /// initialized or not. /// /// Note that allocation in Objective-C can fail, e.g. in Out Of Memory /// situations! This is handled by `objc2` automatically, but if you really /// need to, you can check for this explicitly by inspecting the pointer /// returned from [`as_ptr`]. /// /// Note also that this represents that the _current_ class's instance /// variables are not yet initialized; but subclass instance variables may /// have been so. /// /// See [Apple's documentation on Object Allocation][object-allocation] for a /// few more details. /// /// [`as_ptr`]: Self::as_ptr /// [object-allocation]: https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/ObjectAllocation/ObjectAllocation.html /// /// /// # Memory layout /// /// This is guaranteed to have the same size and alignment as a pointer to the /// object, `*const T`. The pointer may be NULL. #[repr(transparent)] #[derive(Debug)] #[cfg_attr( feature = "unstable-coerce-pointee", derive(std::marker::CoercePointee) )] pub struct Allocated { /// The yet-to-be initialized object. /// /// We don't use `Retained` here, since that has different auto-trait /// impls, and requires in its safety contract that the object is /// initialized (which makes it difficult to ensure correctness if such /// things are split across different files). Additionally, we want to /// have fine control over NULL-ness. /// /// Covariance is correct, same as `Retained`. ptr: *const T, // Intentionally not `NonNull`! /// Necessary for dropck, as with `Retained`. p: PhantomData, /// Necessary for restricting auto traits. /// /// We _could_ probably implement auto traits `Send` and `Sync` here, but to be /// safe, we won't for now. p_auto_traits: PhantomData, } // Explicitly don't implement `Deref`, `Message` nor `RefEncode`. // // We do want to implement `Receiver` though, to allow the user to type // `self: Allocated`. #[cfg(feature = "unstable-arbitrary-self-types")] impl core::ops::Receiver for Allocated { type Target = T; } impl Allocated { /// # Safety /// /// The caller must ensure the pointer is NULL, or that the given object /// has +1 retain count, and that the object behind the pointer has been /// allocated (but not yet initialized). #[inline] pub(crate) unsafe fn new(ptr: *mut T) -> Self { Self { ptr, p: PhantomData, p_auto_traits: PhantomData, } } /// Allocate the object with a fast path using `objc_alloc`. /// /// /// # Safety /// /// The object must be safe to allocate on the current thread, and the /// object `T` must be an instance of the class. #[doc(alias = "objc_alloc")] #[inline] pub(crate) unsafe fn alloc(cls: &AnyClass) -> Self where T: Sized, { // Available on non-fragile Apple runtimes. #[cfg(all( target_vendor = "apple", not(all(target_os = "macos", target_arch = "x86")) ))] { // SAFETY: Thread safety checked by the caller. let obj: *mut T = unsafe { crate::ffi::objc_alloc(cls).cast() }; // SAFETY: The object is newly allocated, so this has +1 retain count unsafe { Self::new(obj) } } #[cfg(not(all( target_vendor = "apple", not(all(target_os = "macos", target_arch = "x86")) )))] { // SAFETY: Thread safety checked by the caller. unsafe { crate::msg_send![cls, alloc] } } } /// Returns a raw pointer to the object. /// /// The pointer is valid for at least as long as the `Allocated` is held. /// /// See [`Allocated::as_mut_ptr`] for the mutable equivalent. /// /// This is an associated method, and must be called as /// `Allocated::as_ptr(obj)`. #[inline] pub fn as_ptr(this: &Self) -> *const T { this.ptr } /// Returns a raw mutable pointer to the object. /// /// The pointer is valid for at least as long as the `Allocated` is held. /// /// See [`Allocated::as_ptr`] for the immutable equivalent. /// /// This is an associated method, and must be called as /// `Allocated::as_mut_ptr(obj)`. /// /// /// # Note about mutable references /// /// In general, you're not allowed to create a mutable reference from /// `Allocated`, unless you're defining the object and know that to be /// safe. /// /// For example, `+[NSMutableString alloc]` is allowed to return a /// non-unique object as an optimization, and then only figure out /// afterwards whether it needs to allocate, or if it can store an /// `NSString` internally. /// /// Similarly, while e.g. `+[NSData alloc]` may return a unique object, /// calling `-[NSData init]` afterwards could return a shared empty /// `NSData` instance. #[inline] #[allow(unknown_lints)] // New lint below #[allow(clippy::needless_pass_by_ref_mut)] pub fn as_mut_ptr(this: &mut Self) -> *mut T { // Note: Mutable pointers _can_ be safe for non-mutable classes, // especially right when they're being allocated / initialized. this.ptr as *mut T } #[inline] pub(crate) fn into_ptr(this: Self) -> *mut T { let this = ManuallyDrop::new(this); this.ptr as *mut T } /// Initialize the instance variables for this object. /// /// This consumes the allocated instance, and returns the now partially /// initialized instance instead, which can be further used in /// [`msg_send!`] `super` calls. /// /// This works very similarly to [Swift's two-phase initialization /// scheme][two-phase-init], see that for details. /// /// [`msg_send!`]: crate::msg_send /// [two-phase-init]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/#Two-Phase-Initialization /// /// /// # Panics /// /// If debug assertions are enabled, this function will panic if the /// allocated instance is `NULL`, which usually only happens in Out of /// Memory situations. /// /// If debug assertions are disabled, this will return a `NULL` instance /// and the ivars will be dropped. The NULL instance cannot cause /// unsoundness and will likely lead to an initialization failure later on /// instead, but not panicking here is done as a code-size optimization. // // Note: This is intentionally _not_ an associated method, even though // `Allocated` will become `MethodReceiver` in the future. #[inline] #[track_caller] pub fn set_ivars(self, ivars: T::Ivars) -> PartialInit where T: DefinedClass + Sized, { if let Some(ptr) = NonNull::new(ManuallyDrop::new(self).ptr as *mut T) { // SAFETY: The pointer came from `self`, so it is valid. unsafe { initialize_ivars::(ptr, ivars) }; // SAFETY: // - The pointer came from a `ManuallyDrop>`, which means // that we've now transferred ownership over +1 retain count. // - The instance variables for this class have been initialized above. unsafe { PartialInit::new(ptr.as_ptr()) } } else if cfg!(debug_assertions) { panic!("tried to initialize instance variables on a NULL allocated object") } else { // Explicitly drop the ivars in this branch drop(ivars); // Create a new NULL PartialInit, which will likely be checked for // NULL-ness later on, after initialization of it has failed. // // SAFETY: The pointer is NULL. unsafe { PartialInit::new(ptr::null_mut()) } } } } impl Drop for Allocated { #[inline] fn drop(&mut self) { // SAFETY: Allocated objects can always safely be released, since // destructors are written to take into account that the object may // not have been initialized. // // This is also safe in the case where the object is NULL, // since `objc_release` allows NULL pointers. // // Rest is same as `Retained`'s `Drop`. unsafe { objc_release_fast(self.ptr as *mut _) }; } } impl fmt::Pointer for Allocated { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Pointer::fmt(&self.ptr, f) } } /// An Objective-C object that has been allocated and initialized in the /// current class, but not yet initialized in the superclass. /// /// This is returned by [`Allocated::set_ivars`], and is intended to be used /// further in [`msg_send!`] `super` calls. /// /// [`msg_send!`]: crate::msg_send /// /// /// # Memory layout /// /// The memory layout of this struct is NOT currently guaranteed, as we may /// want to be able to move a drop flag to the stack in the future. // // Internally, this is very similar to `Allocated`, except that we have // different guarantees on the validity of the object. #[repr(transparent)] #[derive(Debug)] pub struct PartialInit { /// The partially initialized object. /// /// Variance is same as `Retained`. ptr: *const T, // Intentionally not NonNull /// Necessary for dropck, as with `Retained`. p: PhantomData, /// Restrict auto traits, same as `Allocated`. p_auto_traits: PhantomData, } impl PartialInit { /// # Safety /// /// The caller must ensure the pointer is NULL, or that the given object /// is allocated, has +1 retain count, and that the class' instance /// variables have been initialized. #[inline] pub(crate) unsafe fn new(ptr: *mut T) -> Self { Self { ptr, p: PhantomData, p_auto_traits: PhantomData, } } /// Returns a raw pointer to the object. /// /// The pointer is valid for at least as long as the `PartialInit` is /// held. /// /// See [`PartialInit::as_mut_ptr`] for the mutable equivalent. /// /// This is an associated method, and must be called as /// `PartialInit::as_ptr(obj)`. #[inline] pub fn as_ptr(this: &Self) -> *const T { this.ptr } /// Returns a raw mutable pointer to the object. /// /// The pointer is valid for at least as long as the `PartialInit` is /// held. /// /// See [`PartialInit::as_ptr`] for the immutable equivalent. /// /// This is an associated method, and must be called as /// `PartialInit::as_mut_ptr(obj)`. #[inline] #[allow(unknown_lints)] // New lint below #[allow(clippy::needless_pass_by_ref_mut)] pub fn as_mut_ptr(this: &mut Self) -> *mut T { this.ptr as *mut T } #[inline] pub(crate) fn into_ptr(this: Self) -> *mut T { let this = ManuallyDrop::new(this); this.ptr as *mut T } } impl Drop for PartialInit { #[inline] fn drop(&mut self) { // SAFETY: Partially initialized objects can always safely be // released, since destructors are written to take into account that // the object may not have been fully initialized. // // This is also safe in the case where the object is NULL, // since `objc_release` allows NULL pointers. // // Rest is same as `Retained`. unsafe { objc_release_fast(self.ptr as *mut _) }; } } impl fmt::Pointer for PartialInit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Pointer::fmt(&self.ptr, f) } } #[cfg(test)] mod tests { use core::panic::{RefUnwindSafe, UnwindSafe}; use static_assertions::assert_not_impl_any; use super::*; use crate::rc::RcTestObject; use crate::runtime::NSObject; #[test] fn auto_traits() { assert_not_impl_any!(Allocated<()>: Send, Sync, UnwindSafe, RefUnwindSafe, Unpin); assert_not_impl_any!(PartialInit<()>: Send, Sync, UnwindSafe, RefUnwindSafe, Unpin); } #[repr(C)] struct MyObject<'a> { inner: NSObject, p: PhantomData<&'a str>, } /// Test that `Allocated` is covariant over `T`. #[allow(unused)] fn assert_allocated_variance<'b>(obj: Allocated>) -> Allocated> { obj } /// Test that `PartialInit` is covariant over `T`. #[allow(unused)] fn assert_partialinit_variance<'b>( obj: PartialInit>, ) -> PartialInit> { obj } #[test] #[cfg_attr( debug_assertions, should_panic = "tried to initialize instance variables on a NULL allocated object" )] fn test_set_ivars_null() { // SAFETY: The pointer is NULL let obj: Allocated = unsafe { Allocated::new(ptr::null_mut()) }; let _ = obj.set_ivars(()); } #[test] #[cfg(feature = "unstable-arbitrary-self-types")] fn arbitrary_self_types() { use crate::rc::Retained; use crate::{extern_methods, AnyThread}; impl RcTestObject { extern_methods!( #[unsafe(method(init))] fn init_with_self(self: Allocated) -> Retained; ); } let _ = RcTestObject::alloc().init_with_self(); } }