objc2/rc/
allocated_partial_init.rs

1use core::marker::PhantomData;
2use core::mem::ManuallyDrop;
3use core::ptr::NonNull;
4use core::{fmt, ptr};
5
6use crate::__macro_helpers::defined_ivars::initialize_ivars;
7use crate::runtime::{objc_release_fast, AnyClass, AnyObject};
8use crate::{DefinedClass, Message};
9
10/// An Objective-C object that has been allocated, but not initialized.
11///
12/// Objective-C splits the allocation and initialization steps up into two, so
13/// we need to track it in the type-system whether something has been
14/// initialized or not.
15///
16/// Note that allocation in Objective-C can fail, e.g. in Out Of Memory
17/// situations! This is handled by `objc2` automatically, but if you really
18/// need to, you can check for this explicitly by inspecting the pointer
19/// returned from [`as_ptr`].
20///
21/// Note also that this represents that the _current_ class's instance
22/// variables are not yet initialized; but subclass instance variables may
23/// have been so.
24///
25/// See [Apple's documentation on Object Allocation][object-allocation] for a
26/// few more details.
27///
28/// [`as_ptr`]: Self::as_ptr
29/// [object-allocation]: https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/ObjectAllocation/ObjectAllocation.html
30///
31///
32/// # Memory layout
33///
34/// This is guaranteed to have the same size and alignment as a pointer to the
35/// object, `*const T`. The pointer may be NULL.
36#[repr(transparent)]
37#[derive(Debug)]
38#[cfg_attr(
39    feature = "unstable-coerce-pointee",
40    derive(std::marker::CoercePointee)
41)]
42pub struct Allocated<T: ?Sized> {
43    /// The yet-to-be initialized object.
44    ///
45    /// We don't use `Retained` here, since that has different auto-trait
46    /// impls, and requires in its safety contract that the object is
47    /// initialized (which makes it difficult to ensure correctness if such
48    /// things are split across different files). Additionally, we want to
49    /// have fine control over NULL-ness.
50    ///
51    /// Covariance is correct, same as `Retained`.
52    ptr: *const T, // Intentionally not `NonNull`!
53    /// Necessary for dropck, as with `Retained`.
54    p: PhantomData<T>,
55    /// Necessary for restricting auto traits.
56    ///
57    /// We _could_ probably implement auto traits `Send` and `Sync` here, but to be
58    /// safe, we won't for now.
59    p_auto_traits: PhantomData<AnyObject>,
60}
61
62// Explicitly don't implement `Deref`, `Message` nor `RefEncode`.
63//
64// We do want to implement `Receiver` though, to allow the user to type
65// `self: Allocated<Self>`.
66#[cfg(feature = "unstable-arbitrary-self-types")]
67impl<T: ?Sized> core::ops::Receiver for Allocated<T> {
68    type Target = T;
69}
70
71impl<T: ?Sized + Message> Allocated<T> {
72    /// # Safety
73    ///
74    /// The caller must ensure the pointer is NULL, or that the given object
75    /// has +1 retain count, and that the object behind the pointer has been
76    /// allocated (but not yet initialized).
77    #[inline]
78    pub(crate) unsafe fn new(ptr: *mut T) -> Self {
79        Self {
80            ptr,
81            p: PhantomData,
82            p_auto_traits: PhantomData,
83        }
84    }
85
86    /// Allocate the object with a fast path using `objc_alloc`.
87    ///
88    ///
89    /// # Safety
90    ///
91    /// The object must be safe to allocate on the current thread, and the
92    /// object `T` must be an instance of the class.
93    #[doc(alias = "objc_alloc")]
94    #[inline]
95    pub(crate) unsafe fn alloc(cls: &AnyClass) -> Self
96    where
97        T: Sized,
98    {
99        // Available on non-fragile Apple runtimes.
100        #[cfg(all(
101            target_vendor = "apple",
102            not(all(target_os = "macos", target_arch = "x86"))
103        ))]
104        {
105            // SAFETY: Thread safety checked by the caller.
106            let obj: *mut T = unsafe { crate::ffi::objc_alloc(cls).cast() };
107            // SAFETY: The object is newly allocated, so this has +1 retain count
108            unsafe { Self::new(obj) }
109        }
110        #[cfg(not(all(
111            target_vendor = "apple",
112            not(all(target_os = "macos", target_arch = "x86"))
113        )))]
114        {
115            // SAFETY: Thread safety checked by the caller.
116            unsafe { crate::msg_send![cls, alloc] }
117        }
118    }
119
120    /// Returns a raw pointer to the object.
121    ///
122    /// The pointer is valid for at least as long as the `Allocated` is held.
123    ///
124    /// See [`Allocated::as_mut_ptr`] for the mutable equivalent.
125    ///
126    /// This is an associated method, and must be called as
127    /// `Allocated::as_ptr(obj)`.
128    #[inline]
129    pub fn as_ptr(this: &Self) -> *const T {
130        this.ptr
131    }
132
133    /// Returns a raw mutable pointer to the object.
134    ///
135    /// The pointer is valid for at least as long as the `Allocated` is held.
136    ///
137    /// See [`Allocated::as_ptr`] for the immutable equivalent.
138    ///
139    /// This is an associated method, and must be called as
140    /// `Allocated::as_mut_ptr(obj)`.
141    ///
142    ///
143    /// # Note about mutable references
144    ///
145    /// In general, you're not allowed to create a mutable reference from
146    /// `Allocated`, unless you're defining the object and know that to be
147    /// safe.
148    ///
149    /// For example, `+[NSMutableString alloc]` is allowed to return a
150    /// non-unique object as an optimization, and then only figure out
151    /// afterwards whether it needs to allocate, or if it can store an
152    /// `NSString` internally.
153    ///
154    /// Similarly, while e.g. `+[NSData alloc]` may return a unique object,
155    /// calling `-[NSData init]` afterwards could return a shared empty
156    /// `NSData` instance.
157    #[inline]
158    #[allow(unknown_lints)] // New lint below
159    #[allow(clippy::needless_pass_by_ref_mut)]
160    pub fn as_mut_ptr(this: &mut Self) -> *mut T {
161        // Note: Mutable pointers _can_ be safe for non-mutable classes,
162        // especially right when they're being allocated / initialized.
163        this.ptr as *mut T
164    }
165
166    #[inline]
167    pub(crate) fn into_ptr(this: Self) -> *mut T {
168        let this = ManuallyDrop::new(this);
169        this.ptr as *mut T
170    }
171
172    /// Initialize the instance variables for this object.
173    ///
174    /// This consumes the allocated instance, and returns the now partially
175    /// initialized instance instead, which can be further used in
176    /// [`msg_send!`] `super` calls.
177    ///
178    /// This works very similarly to [Swift's two-phase initialization
179    /// scheme][two-phase-init], see that for details.
180    ///
181    /// [`msg_send!`]: crate::msg_send
182    /// [two-phase-init]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/#Two-Phase-Initialization
183    ///
184    ///
185    /// # Panics
186    ///
187    /// If debug assertions are enabled, this function will panic if the
188    /// allocated instance is `NULL`, which usually only happens in Out of
189    /// Memory situations.
190    ///
191    /// If debug assertions are disabled, this will return a `NULL` instance
192    /// and the ivars will be dropped. The NULL instance cannot cause
193    /// unsoundness and will likely lead to an initialization failure later on
194    /// instead, but not panicking here is done as a code-size optimization.
195    //
196    // Note: This is intentionally _not_ an associated method, even though
197    // `Allocated` will become `MethodReceiver` in the future.
198    #[inline]
199    #[track_caller]
200    pub fn set_ivars(self, ivars: T::Ivars) -> PartialInit<T>
201    where
202        T: DefinedClass + Sized,
203    {
204        if let Some(ptr) = NonNull::new(ManuallyDrop::new(self).ptr as *mut T) {
205            // SAFETY: The pointer came from `self`, so it is valid.
206            unsafe { initialize_ivars::<T>(ptr, ivars) };
207
208            // SAFETY:
209            // - The pointer came from a `ManuallyDrop<Allocated<T>>`, which means
210            //   that we've now transferred ownership over +1 retain count.
211            // - The instance variables for this class have been initialized above.
212            unsafe { PartialInit::new(ptr.as_ptr()) }
213        } else if cfg!(debug_assertions) {
214            panic!("tried to initialize instance variables on a NULL allocated object")
215        } else {
216            // Explicitly drop the ivars in this branch
217            drop(ivars);
218
219            // Create a new NULL PartialInit, which will likely be checked for
220            // NULL-ness later on, after initialization of it has failed.
221            //
222            // SAFETY: The pointer is NULL.
223            unsafe { PartialInit::new(ptr::null_mut()) }
224        }
225    }
226}
227
228impl<T: ?Sized> Drop for Allocated<T> {
229    #[inline]
230    fn drop(&mut self) {
231        // SAFETY: Allocated objects can always safely be released, since
232        // destructors are written to take into account that the object may
233        // not have been initialized.
234        //
235        // This is also safe in the case where the object is NULL,
236        // since `objc_release` allows NULL pointers.
237        //
238        // Rest is same as `Retained`'s `Drop`.
239        unsafe { objc_release_fast(self.ptr as *mut _) };
240    }
241}
242
243impl<T: ?Sized> fmt::Pointer for Allocated<T> {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        fmt::Pointer::fmt(&self.ptr, f)
246    }
247}
248
249/// An Objective-C object that has been allocated and initialized in the
250/// current class, but not yet initialized in the superclass.
251///
252/// This is returned by [`Allocated::set_ivars`], and is intended to be used
253/// further in [`msg_send!`] `super` calls.
254///
255/// [`msg_send!`]: crate::msg_send
256///
257///
258/// # Memory layout
259///
260/// The memory layout of this struct is NOT currently guaranteed, as we may
261/// want to be able to move a drop flag to the stack in the future.
262//
263// Internally, this is very similar to `Allocated`, except that we have
264// different guarantees on the validity of the object.
265#[repr(transparent)]
266#[derive(Debug)]
267pub struct PartialInit<T: ?Sized> {
268    /// The partially initialized object.
269    ///
270    /// Variance is same as `Retained`.
271    ptr: *const T, // Intentionally not NonNull<T>
272    /// Necessary for dropck, as with `Retained`.
273    p: PhantomData<T>,
274    /// Restrict auto traits, same as `Allocated<T>`.
275    p_auto_traits: PhantomData<AnyObject>,
276}
277
278impl<T: ?Sized + Message> PartialInit<T> {
279    /// # Safety
280    ///
281    /// The caller must ensure the pointer is NULL, or that the given object
282    /// is allocated, has +1 retain count, and that the class' instance
283    /// variables have been initialized.
284    #[inline]
285    pub(crate) unsafe fn new(ptr: *mut T) -> Self {
286        Self {
287            ptr,
288            p: PhantomData,
289            p_auto_traits: PhantomData,
290        }
291    }
292
293    /// Returns a raw pointer to the object.
294    ///
295    /// The pointer is valid for at least as long as the `PartialInit` is
296    /// held.
297    ///
298    /// See [`PartialInit::as_mut_ptr`] for the mutable equivalent.
299    ///
300    /// This is an associated method, and must be called as
301    /// `PartialInit::as_ptr(obj)`.
302    #[inline]
303    pub fn as_ptr(this: &Self) -> *const T {
304        this.ptr
305    }
306
307    /// Returns a raw mutable pointer to the object.
308    ///
309    /// The pointer is valid for at least as long as the `PartialInit` is
310    /// held.
311    ///
312    /// See [`PartialInit::as_ptr`] for the immutable equivalent.
313    ///
314    /// This is an associated method, and must be called as
315    /// `PartialInit::as_mut_ptr(obj)`.
316    #[inline]
317    #[allow(unknown_lints)] // New lint below
318    #[allow(clippy::needless_pass_by_ref_mut)]
319    pub fn as_mut_ptr(this: &mut Self) -> *mut T {
320        this.ptr as *mut T
321    }
322
323    #[inline]
324    pub(crate) fn into_ptr(this: Self) -> *mut T {
325        let this = ManuallyDrop::new(this);
326        this.ptr as *mut T
327    }
328}
329
330impl<T: ?Sized> Drop for PartialInit<T> {
331    #[inline]
332    fn drop(&mut self) {
333        // SAFETY: Partially initialized objects can always safely be
334        // released, since destructors are written to take into account that
335        // the object may not have been fully initialized.
336        //
337        // This is also safe in the case where the object is NULL,
338        // since `objc_release` allows NULL pointers.
339        //
340        // Rest is same as `Retained`.
341        unsafe { objc_release_fast(self.ptr as *mut _) };
342    }
343}
344
345impl<T: ?Sized> fmt::Pointer for PartialInit<T> {
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        fmt::Pointer::fmt(&self.ptr, f)
348    }
349}
350
351#[cfg(test)]
352mod tests {
353    use core::panic::{RefUnwindSafe, UnwindSafe};
354
355    use static_assertions::assert_not_impl_any;
356
357    use super::*;
358    use crate::rc::RcTestObject;
359    use crate::runtime::NSObject;
360
361    #[test]
362    fn auto_traits() {
363        assert_not_impl_any!(Allocated<()>: Send, Sync, UnwindSafe, RefUnwindSafe, Unpin);
364        assert_not_impl_any!(PartialInit<()>: Send, Sync, UnwindSafe, RefUnwindSafe, Unpin);
365    }
366
367    #[repr(C)]
368    struct MyObject<'a> {
369        inner: NSObject,
370        p: PhantomData<&'a str>,
371    }
372
373    /// Test that `Allocated<T>` is covariant over `T`.
374    #[allow(unused)]
375    fn assert_allocated_variance<'b>(obj: Allocated<MyObject<'static>>) -> Allocated<MyObject<'b>> {
376        obj
377    }
378
379    /// Test that `PartialInit<T>` is covariant over `T`.
380    #[allow(unused)]
381    fn assert_partialinit_variance<'b>(
382        obj: PartialInit<MyObject<'static>>,
383    ) -> PartialInit<MyObject<'b>> {
384        obj
385    }
386
387    #[test]
388    #[cfg_attr(
389        debug_assertions,
390        should_panic = "tried to initialize instance variables on a NULL allocated object"
391    )]
392    fn test_set_ivars_null() {
393        // SAFETY: The pointer is NULL
394        let obj: Allocated<RcTestObject> = unsafe { Allocated::new(ptr::null_mut()) };
395        let _ = obj.set_ivars(());
396    }
397
398    #[test]
399    #[cfg(feature = "unstable-arbitrary-self-types")]
400    fn arbitrary_self_types() {
401        use crate::rc::Retained;
402        use crate::{extern_methods, AnyThread};
403
404        impl RcTestObject {
405            extern_methods!(
406                #[unsafe(method(init))]
407                fn init_with_self(self: Allocated<Self>) -> Retained<Self>;
408            );
409        }
410
411        let _ = RcTestObject::alloc().init_with_self();
412    }
413}