Source code
Revision control
Copy as Markdown
Other Tools
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<T: ?Sized> {
/// 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<T>,
/// 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<AnyObject>,
}
// Explicitly don't implement `Deref`, `Message` nor `RefEncode`.
//
// We do want to implement `Receiver` though, to allow the user to type
// `self: Allocated<Self>`.
#[cfg(feature = "unstable-arbitrary-self-types")]
impl<T: ?Sized> core::ops::Receiver for Allocated<T> {
type Target = T;
}
impl<T: ?Sized + Message> Allocated<T> {
/// # 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<T>
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::<T>(ptr, ivars) };
// SAFETY:
// - The pointer came from a `ManuallyDrop<Allocated<T>>`, 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<T: ?Sized> Drop for Allocated<T> {
#[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<T: ?Sized> fmt::Pointer for Allocated<T> {
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<T: ?Sized> {
/// The partially initialized object.
///
/// Variance is same as `Retained`.
ptr: *const T, // Intentionally not NonNull<T>
/// Necessary for dropck, as with `Retained`.
p: PhantomData<T>,
/// Restrict auto traits, same as `Allocated<T>`.
p_auto_traits: PhantomData<AnyObject>,
}
impl<T: ?Sized + Message> PartialInit<T> {
/// # 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<T: ?Sized> Drop for PartialInit<T> {
#[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<T: ?Sized> fmt::Pointer for PartialInit<T> {
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<T>` is covariant over `T`.
#[allow(unused)]
fn assert_allocated_variance<'b>(obj: Allocated<MyObject<'static>>) -> Allocated<MyObject<'b>> {
obj
}
/// Test that `PartialInit<T>` is covariant over `T`.
#[allow(unused)]
fn assert_partialinit_variance<'b>(
obj: PartialInit<MyObject<'static>>,
) -> PartialInit<MyObject<'b>> {
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<RcTestObject> = 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<Self>) -> Retained<Self>;
);
}
let _ = RcTestObject::alloc().init_with_self();
}
}