1use alloc::borrow::Cow;
49use alloc::ffi::CString;
50use alloc::format;
51use core::ffi::CStr;
52use core::mem;
53use core::ptr::{self, NonNull};
54
55use crate::encode::{Encode, Encoding};
56use crate::runtime::{AnyClass, AnyObject, ClassBuilder, MessageReceiver, Sel};
57use crate::{sel, ClassType, DefinedClass};
58
59#[repr(u8)]
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub(crate) enum DropFlag {
63 #[allow(dead_code)]
73 Allocated = 0x00,
74 InitializedIvars = 0x0f,
76 Finalized = 0xff,
78}
79
80unsafe impl Encode for DropFlag {
82 const ENCODING: Encoding = u8::ENCODING;
83}
84
85pub trait DefinedIvarsHelper {
86 const HAS_IVARS: bool;
87 const HAS_DROP_FLAG: bool;
88}
89
90impl<T: DefinedClass> DefinedIvarsHelper for T {
91 const HAS_IVARS: bool = {
97 mem::size_of::<T::Ivars>() > 0
98 || mem::align_of::<T::Ivars>() > mem::align_of::<*mut AnyObject>()
99 };
100 const HAS_DROP_FLAG: bool = mem::needs_drop::<T>() || mem::needs_drop::<T::Ivars>();
106}
107
108#[inline]
115unsafe fn ptr_to_ivar<T: ?Sized + DefinedClass>(ptr: NonNull<T>) -> NonNull<T::Ivars> {
116 unsafe { AnyObject::ivar_at_offset::<T::Ivars>(ptr.cast(), T::__ivars_offset()) }
125}
126
127#[inline]
133unsafe fn ptr_to_drop_flag<T: DefinedClass>(ptr: NonNull<T>) -> *mut DropFlag {
134 debug_assert!(T::HAS_DROP_FLAG, "type did not have drop flag");
135 unsafe { AnyObject::ivar_at_offset::<DropFlag>(ptr.cast(), T::__drop_flag_offset()).as_ptr() }
138}
139
140pub(crate) fn setup_dealloc<T: DefinedClass>(builder: &mut ClassBuilder)
141where
142 T::Super: ClassType,
143{
144 if mem::needs_drop::<T>() || mem::needs_drop::<T::Ivars>() {
146 let func: unsafe extern "C-unwind" fn(_, _) = dealloc::<T>;
147 unsafe { builder.add_method(sel!(dealloc), func) };
150 } else {
151 }
153}
154
155unsafe extern "C-unwind" fn dealloc<T: DefinedClass>(this: NonNull<T>, cmd: Sel)
162where
163 T::Super: ClassType,
164{
165 #[inline]
167 #[cold]
168 fn cold_path() {}
169
170 let drop_flag = unsafe { *ptr_to_drop_flag(this) };
173
174 if mem::needs_drop::<T>() {
175 match drop_flag {
176 DropFlag::Allocated | DropFlag::InitializedIvars => cold_path(),
181 DropFlag::Finalized => unsafe { ptr::drop_in_place(this.as_ptr()) },
190 }
191 }
192
193 if mem::needs_drop::<T::Ivars>() {
205 match drop_flag {
206 DropFlag::Allocated => cold_path(),
208 DropFlag::InitializedIvars | DropFlag::Finalized => {
209 unsafe { ptr::drop_in_place(ptr_to_ivar(this).as_ptr()) };
215 }
216 }
217 }
218
219 unsafe {
228 MessageReceiver::send_super_message(
229 this,
230 <T as ClassType>::Super::class(),
231 cmd, (), )
234 }
235}
236
237#[inline]
239pub(crate) fn register_with_ivars<T: DefinedClass>(
240 mut builder: ClassBuilder,
241) -> (&'static AnyClass, isize, isize) {
242 let (ivar_name, drop_flag_name): (Cow<'static, CStr>, Cow<'static, CStr>) = {
243 if cfg!(feature = "gnustep-1-7") {
244 (
248 CString::new(format!("{}_ivars", T::NAME)).unwrap().into(),
249 CString::new(format!("{}_drop_flag", T::NAME))
250 .unwrap()
251 .into(),
252 )
253 } else {
254 unsafe {
258 (
259 CStr::from_bytes_with_nul_unchecked(b"ivars\0").into(),
260 CStr::from_bytes_with_nul_unchecked(b"drop_flag\0").into(),
261 )
262 }
263 }
264 };
265
266 if T::HAS_IVARS {
267 let ivar_encoding = Encoding::Array(
269 mem::size_of::<T::Ivars>() as u64,
270 match mem::align_of::<T::Ivars>() {
271 1 => &u8::ENCODING,
272 2 => &u16::ENCODING,
273 4 => &u32::ENCODING,
274 8 if mem::align_of::<u64>() == 8 => &u64::ENCODING,
276 alignment => panic!("unsupported alignment {alignment} for `{}::Ivars`", T::NAME),
277 },
278 );
279 unsafe { builder.add_ivar_inner::<T::Ivars>(&ivar_name, &ivar_encoding) };
280 }
281
282 if T::HAS_DROP_FLAG {
283 builder.add_ivar::<DropFlag>(&drop_flag_name);
286 }
287
288 let cls = builder.register();
289
290 let ivars_offset = if T::HAS_IVARS {
291 fn get_ivar_failed() -> ! {
294 unreachable!("failed retrieving instance variable on newly defined class")
295 }
296
297 cls.instance_variable(&ivar_name)
298 .unwrap_or_else(|| get_ivar_failed())
299 .offset()
300 } else {
301 0
306 };
307
308 let drop_flag_offset = if T::HAS_DROP_FLAG {
309 fn get_drop_flag_failed() -> ! {
312 unreachable!("failed retrieving drop flag instance variable on newly defined class")
313 }
314
315 cls.instance_variable(&drop_flag_name)
316 .unwrap_or_else(|| get_drop_flag_failed())
317 .offset()
318 } else {
319 0
324 };
325
326 (cls, ivars_offset, drop_flag_offset)
327}
328
329#[inline]
333#[track_caller]
334pub(crate) unsafe fn initialize_ivars<T: DefinedClass>(ptr: NonNull<T>, val: T::Ivars) {
335 if T::HAS_DROP_FLAG && cfg!(debug_assertions) {
337 match unsafe { *ptr_to_drop_flag(ptr) } {
339 DropFlag::Allocated => {
340 }
342 DropFlag::InitializedIvars => {
343 panic!("tried to initialize ivars after they were already initialized")
344 }
345 DropFlag::Finalized => {
346 panic!("tried to initialize ivars on an already initialized object")
347 }
348 }
349 }
350
351 unsafe { ptr_to_ivar(ptr).as_ptr().write(val) };
357
358 if T::HAS_DROP_FLAG && (mem::needs_drop::<T::Ivars>() || cfg!(debug_assertions)) {
363 unsafe { ptr_to_drop_flag(ptr).write(DropFlag::InitializedIvars) }
365 }
366}
367
368#[inline]
373#[track_caller]
374pub(crate) unsafe fn set_finalized<T: DefinedClass>(ptr: NonNull<T>) {
375 if T::HAS_DROP_FLAG && cfg!(debug_assertions) {
377 match unsafe { *ptr_to_drop_flag(ptr) } {
379 DropFlag::Allocated => {
380 panic!("tried to finalize an object that was not yet fully initialized")
381 }
382 DropFlag::InitializedIvars => {
383 }
385 DropFlag::Finalized => {
386 panic!("tried to finalize an already finalized object")
387 }
388 }
389 }
390
391 if T::HAS_DROP_FLAG && (mem::needs_drop::<T>() || cfg!(debug_assertions)) {
393 unsafe { ptr_to_drop_flag(ptr).write(DropFlag::Finalized) }
395 }
396}
397
398#[inline]
402#[track_caller]
403pub(crate) unsafe fn get_initialized_ivar_ptr<T: DefinedClass>(
404 ptr: NonNull<T>,
405) -> NonNull<T::Ivars> {
406 if T::HAS_DROP_FLAG && cfg!(debug_assertions) {
408 match unsafe { *ptr_to_drop_flag(ptr) } {
410 DropFlag::Allocated => {
411 panic!("tried to access uninitialized instance variable")
412 }
413 DropFlag::InitializedIvars => {
414 }
419 DropFlag::Finalized => {
420 }
422 }
423 }
424
425 unsafe { ptr_to_ivar(ptr) }
427}
428
429#[cfg(test)]
430mod tests {
431 use alloc::vec::Vec;
432 use core::cell::Cell;
433 use std::sync::OnceLock;
434
435 use super::*;
436 use crate::rc::{Allocated, PartialInit, RcTestObject, Retained, ThreadTestData};
437 use crate::runtime::NSObject;
438 use crate::{define_class, msg_send, AnyThread, Message};
439
440 unsafe fn init_only_superclasses<T: DefinedClass>(obj: Allocated<T>) -> Retained<T>
442 where
443 T::Super: ClassType,
444 {
445 unsafe { Retained::from_raw(msg_send![super(Allocated::into_ptr(obj)), init]) }.unwrap()
446 }
447
448 unsafe fn init_no_finalize<T: DefinedClass>(obj: Allocated<T>) -> Retained<T>
451 where
452 T::Super: ClassType,
453 T::Ivars: Default,
454 {
455 let obj = obj.set_ivars(Default::default());
456 unsafe { Retained::from_raw(msg_send![super(PartialInit::into_ptr(obj)), init]) }.unwrap()
457 }
458
459 unsafe fn init<T: DefinedClass>(obj: Allocated<T>) -> Retained<T> {
461 unsafe { msg_send![obj, init] }
462 }
463
464 #[test]
465 fn assert_size() {
466 assert_eq!(mem::size_of::<DropFlag>(), 1);
467 }
468
469 #[test]
470 #[cfg(feature = "std")]
471 fn test_dealloc_and_dealloc_subclasses() {
472 use std::sync::Mutex;
473
474 #[derive(Debug, PartialEq)]
475 enum Operation {
476 DropIvar,
477 DropClass,
478 }
479
480 static OPERATIONS: Mutex<Vec<Operation>> = Mutex::new(Vec::new());
481
482 #[derive(Default)]
483 struct IvarThatImplsDrop;
484
485 impl Drop for IvarThatImplsDrop {
486 fn drop(&mut self) {
487 OPERATIONS.lock().unwrap().push(Operation::DropIvar);
488 }
489 }
490
491 #[track_caller]
492 fn check<const N: usize>(expected: [Operation; N]) {
493 let mut operations = OPERATIONS.lock().unwrap();
494 assert_eq!(&**operations, expected);
495 operations.clear();
496 }
497
498 define_class!(
501 #[unsafe(super(NSObject))]
502 #[ivars = ()]
503 struct ImplsDrop;
504
505 impl ImplsDrop {
506 #[unsafe(method_id(init))]
507 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
508 unsafe { msg_send![super(this.set_ivars(())), init] }
509 }
510 }
511 );
512
513 impl Drop for ImplsDrop {
514 fn drop(&mut self) {
515 OPERATIONS.lock().unwrap().push(Operation::DropClass);
516 }
517 }
518
519 let _ = ImplsDrop::alloc();
520 check([]);
521
522 let _ = unsafe { init_only_superclasses(ImplsDrop::alloc()) };
523 check([]);
524
525 let _ = unsafe { init_no_finalize(ImplsDrop::alloc()) };
526 check([]);
527
528 let _ = unsafe { init(ImplsDrop::alloc()) };
529 check([Operation::DropClass]);
530
531 define_class!(
534 #[unsafe(super(ImplsDrop))]
535 #[ivars = IvarThatImplsDrop]
536 struct IvarsImplDrop;
537
538 impl IvarsImplDrop {
539 #[unsafe(method_id(init))]
540 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
541 unsafe { msg_send![super(this.set_ivars(IvarThatImplsDrop)), init] }
542 }
543 }
544 );
545
546 let _ = IvarsImplDrop::alloc();
547 check([]);
548
549 let _ = unsafe { init_only_superclasses(IvarsImplDrop::alloc()) };
550 check([Operation::DropClass]);
551
552 let _ = unsafe { init_no_finalize(IvarsImplDrop::alloc()) };
553 check([Operation::DropIvar, Operation::DropClass]);
554
555 let _ = unsafe { init(IvarsImplDrop::alloc()) };
556 check([Operation::DropIvar, Operation::DropClass]);
557
558 define_class!(
561 #[unsafe(super(IvarsImplDrop))]
562 #[ivars = IvarThatImplsDrop]
563 struct BothIvarsAndTypeImplsDrop;
564
565 impl BothIvarsAndTypeImplsDrop {
566 #[unsafe(method_id(init))]
567 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
568 unsafe { msg_send![super(this.set_ivars(IvarThatImplsDrop)), init] }
569 }
570 }
571 );
572
573 impl Drop for BothIvarsAndTypeImplsDrop {
574 fn drop(&mut self) {
575 OPERATIONS.lock().unwrap().push(Operation::DropClass);
576 }
577 }
578
579 let _ = BothIvarsAndTypeImplsDrop::alloc();
580 check([]);
581
582 let _ = unsafe { init_only_superclasses(BothIvarsAndTypeImplsDrop::alloc()) };
583 check([Operation::DropIvar, Operation::DropClass]);
584
585 let _ = unsafe { init_no_finalize(BothIvarsAndTypeImplsDrop::alloc()) };
586 check([
587 Operation::DropIvar,
588 Operation::DropIvar,
589 Operation::DropClass,
590 ]);
591
592 let _ = unsafe { init(BothIvarsAndTypeImplsDrop::alloc()) };
593 check([
594 Operation::DropClass,
595 Operation::DropIvar,
596 Operation::DropIvar,
597 Operation::DropClass,
598 ]);
599 }
600
601 #[test]
602 fn test_no_generated_dealloc_if_not_needed() {
603 #[allow(unused)]
604 struct Ivar {
605 field1: u8,
606 field2: bool,
607 }
608
609 define_class!(
610 #[unsafe(super(NSObject))]
611 #[ivars = Ivar]
612 struct IvarsNoDrop;
613 );
614
615 assert!(!mem::needs_drop::<IvarsNoDrop>());
616 assert!(!mem::needs_drop::<Ivar>());
617 assert_eq!(
618 IvarsNoDrop::class().instance_method(sel!(dealloc)),
619 NSObject::class().instance_method(sel!(dealloc)),
620 );
621 }
622
623 #[test]
624 fn zst_ivar() {
625 #[derive(Default, Debug, Clone, Copy)]
626 struct Ivar;
627
628 define_class!(
629 #[unsafe(super(NSObject))]
630 #[ivars = Cell<Ivar>]
631 struct IvarZst;
632
633 impl IvarZst {
634 #[unsafe(method_id(init))]
635 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
636 unsafe { msg_send![super(this.set_ivars(Cell::new(Ivar))), init] }
637 }
638 }
639 );
640
641 assert_eq!(
642 IvarZst::class().instance_size(),
643 NSObject::class().instance_size(),
644 );
645 let ivar_name = if cfg!(feature = "gnustep-1-7") {
646 "IvarZst_ivars"
647 } else {
648 "ivars"
649 };
650 let ivar_name = CString::new(ivar_name).unwrap();
651 assert!(IvarZst::class().instance_variable(&ivar_name).is_none());
652
653 let obj = unsafe { init(IvarZst::alloc()) };
654 #[cfg(feature = "std")]
655 std::println!("{:?}", obj.ivars().get());
656 obj.ivars().set(Ivar);
657 }
658
659 #[test]
660 #[should_panic = "unsupported alignment 16 for `HasIvarWithHighAlignment::Ivars`"]
661 fn test_generate_ivar_high_alignment() {
662 #[repr(align(16))]
663 struct HighAlignment;
664
665 define_class!(
666 #[unsafe(super(NSObject))]
667 #[name = "HasIvarWithHighAlignment"]
668 #[ivars = HighAlignment]
669 struct HasIvarWithHighAlignment;
670 );
671
672 assert_eq!(HasIvarWithHighAlignment::class().instance_size(), 16);
675
676 let ivar_name = if cfg!(feature = "gnustep-1-7") {
677 "IvarZst_ivars"
678 } else {
679 "ivars"
680 };
681 let ivar_name = CString::new(ivar_name).unwrap();
682 let ivar = HasIvarWithHighAlignment::class()
683 .instance_variable(&ivar_name)
684 .unwrap();
685 assert_eq!(ivar.offset(), 16);
686 }
687
688 #[test]
689 fn test_ivar_access() {
690 define_class!(
691 #[unsafe(super(NSObject))]
692 #[ivars = Cell<Option<Retained<RcTestObject>>>]
693 struct RcIvar;
694
695 impl RcIvar {
696 #[unsafe(method_id(init))]
697 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
698 let this = this.set_ivars(Cell::new(Some(RcTestObject::new())));
699 unsafe { msg_send![super(this), init] }
700 }
701 }
702 );
703
704 let mut expected = ThreadTestData::current();
705
706 let _ = RcIvar::alloc();
707 expected.assert_current();
708
709 let _ = unsafe { init_only_superclasses(RcIvar::alloc()) };
710 expected.assert_current();
711
712 let obj = unsafe { init_no_finalize(RcIvar::alloc()) };
714 expected.assert_current();
715
716 obj.ivars().set(Some(RcTestObject::new()));
717 expected.alloc += 1;
718 expected.init += 1;
719 expected.assert_current();
720
721 drop(obj);
722 expected.release += 1;
723 expected.drop += 1;
724 expected.assert_current();
725
726 let obj = unsafe { init(RcIvar::alloc()) };
727 expected.alloc += 1;
728 expected.init += 1;
729 expected.assert_current();
730
731 let ivar = unsafe { &*obj.ivars().as_ptr() }.clone();
733 obj.ivars().set(ivar);
734 expected.retain += 1;
735 expected.release += 1;
736 expected.assert_current();
737
738 drop(obj);
739 expected.release += 1;
740 expected.drop += 1;
741 expected.assert_current();
742
743 #[derive(Default)]
744 struct RcIvarSubclassIvars {
745 int: Cell<i32>,
746 obj: Cell<Retained<RcTestObject>>,
747 }
748
749 define_class!(
750 #[unsafe(super(RcIvar))]
751 #[ivars = RcIvarSubclassIvars]
752 struct RcIvarSubclass;
753
754 impl RcIvarSubclass {
755 #[unsafe(method_id(init))]
756 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
757 let this = this.set_ivars(RcIvarSubclassIvars {
758 int: Cell::new(42),
759 obj: Cell::new(RcTestObject::new()),
760 });
761 unsafe { msg_send![super(this), init] }
762 }
763 }
764 );
765
766 let obj = unsafe { init(RcIvarSubclass::alloc()) };
767 expected.alloc += 2;
768 expected.init += 2;
769 expected.assert_current();
770 assert_eq!(obj.ivars().int.get(), 42);
771
772 obj.ivars().int.set(obj.ivars().int.get() + 1);
773 assert_eq!(obj.ivars().int.get(), 43);
774
775 let ivar = unsafe { &*(**obj).ivars().as_ptr() }.clone().unwrap();
777 obj.ivars().obj.set(ivar);
778 expected.retain += 1;
779 expected.release += 1;
780 expected.drop += 1;
781 expected.assert_current();
782
783 (**obj).ivars().set(None);
785 expected.release += 1;
786 expected.assert_current();
787
788 drop(obj);
789 expected.release += 1;
790 expected.drop += 1;
791 expected.assert_current();
792
793 let obj = unsafe { init_only_superclasses(RcIvarSubclass::alloc()) };
794 expected.alloc += 1;
795 expected.init += 1;
796 expected.assert_current();
797
798 #[cfg(feature = "std")]
801 std::println!("{:?}", unsafe { &*(**obj).ivars().as_ptr() });
802
803 drop(obj);
804 expected.release += 1;
805 expected.drop += 1;
806 expected.assert_current();
807 }
808
809 #[test]
810 #[cfg_attr(not(debug_assertions), ignore = "only panics with debug assertions")]
811 #[should_panic = "tried to access uninitialized instance variable"]
812 fn access_invalid() {
813 define_class!(
814 #[unsafe(super(NSObject))]
815 #[ivars = Retained<NSObject>]
817 struct InvalidAccess;
818 );
819
820 let obj = unsafe { init_only_superclasses(InvalidAccess::alloc()) };
821 #[cfg(feature = "std")]
822 std::println!("{:?}", obj.ivars());
823 }
824
825 #[test]
826 #[should_panic = "panic in drop"]
827 #[ignore = "panicking in Drop requires that we actually implement `dealloc` as `C-unwind`"]
828 fn test_panic_in_drop() {
829 define_class!(
830 #[unsafe(super(NSObject))]
831 struct DropPanics;
832 );
833
834 impl Drop for DropPanics {
835 fn drop(&mut self) {
836 panic!("panic in drop");
837 }
838 }
839
840 let obj = DropPanics::alloc().set_ivars(());
841 let obj: Retained<DropPanics> = unsafe { msg_send![super(obj), init] };
842 drop(obj);
843 }
844
845 #[test]
846 #[should_panic = "panic in ivar drop"]
847 #[ignore = "panicking in Drop requires that we actually implement `dealloc` as `C-unwind`"]
848 fn test_panic_in_ivar_drop() {
849 struct DropPanics;
850
851 impl Drop for DropPanics {
852 fn drop(&mut self) {
853 panic!("panic in ivar drop");
854 }
855 }
856
857 define_class!(
858 #[unsafe(super(NSObject))]
859 #[ivars = DropPanics]
860 struct IvarDropPanics;
861 );
862
863 let obj = IvarDropPanics::alloc().set_ivars(DropPanics);
864 let obj: Retained<IvarDropPanics> = unsafe { msg_send![super(obj), init] };
865 drop(obj);
866 }
867
868 #[test]
875 fn test_retain_leak_in_drop() {
876 define_class!(
877 #[unsafe(super(NSObject))]
879 #[derive(Debug)]
880 struct DropRetainsAndLeaksSelf;
881 );
882
883 unsafe impl Send for DropRetainsAndLeaksSelf {}
884 unsafe impl Sync for DropRetainsAndLeaksSelf {}
885
886 static OBJ: OnceLock<Retained<DropRetainsAndLeaksSelf>> = OnceLock::new();
887
888 impl Drop for DropRetainsAndLeaksSelf {
889 fn drop(&mut self) {
890 fn inner(this: &DropRetainsAndLeaksSelf) {
891 OBJ.set(this.retain()).unwrap();
893 }
894
895 inner(self)
896 }
897 }
898
899 let obj = DropRetainsAndLeaksSelf::alloc().set_ivars(());
900 let obj: Retained<DropRetainsAndLeaksSelf> = unsafe { msg_send![super(obj), init] };
901 drop(obj);
902
903 let _ = OBJ.get().unwrap();
905 }
906}