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}