diff --git a/crates/objc2/examples/class_with_lifetime.rs b/crates/objc2/examples/class_with_lifetime.rs index 54410530d..95e4984bc 100644 --- a/crates/objc2/examples/class_with_lifetime.rs +++ b/crates/objc2/examples/class_with_lifetime.rs @@ -4,36 +4,19 @@ use std::marker::PhantomData; use std::sync::Once; -use objc2::declare::{ClassBuilder, Ivar, IvarEncode, IvarType}; +use objc2::declare::ClassBuilder; use objc2::mutability::Mutable; use objc2::rc::Id; use objc2::runtime::{AnyClass, NSObject, Sel}; use objc2::{msg_send, msg_send_id, sel}; use objc2::{ClassType, Encoding, Message, RefEncode}; -/// Helper type for the instance variable -struct NumberIvar<'a> { - // Doesn't actually matter what we put here, but we have to use the - // lifetime parameter somehow - p: PhantomData<&'a mut u8>, -} - -unsafe impl<'a> IvarType for NumberIvar<'a> { - type Type = IvarEncode<&'a mut u8>; - const NAME: &'static str = "_number_ptr"; -} - /// Struct that represents our custom object. #[repr(C)] pub struct MyObject<'a> { // Required to give MyObject the proper layout superclass: NSObject, - // SAFETY: The ivar is declared below, and is properly initialized in the - // designated initializer. - // - // Note! Attempting to acess the ivar before it has been initialized is - // undefined behaviour! - number: Ivar>, + p: PhantomData<&'a mut u8>, } unsafe impl RefEncode for MyObject<'_> { @@ -50,8 +33,12 @@ impl<'a> MyObject<'a> { ) -> Option<&'s mut Self> { let this: Option<&mut Self> = unsafe { msg_send![super(self), init] }; this.map(|this| { - // Properly initialize the number reference - Ivar::write(&mut this.number, ptr.expect("got NULL number ptr")); + let ivar = Self::class().instance_variable("number").unwrap(); + // SAFETY: The ivar is added with the same type below + unsafe { + ivar.load_ptr::<&mut u8>(&this.superclass) + .write(ptr.expect("got NULL number ptr")) + }; this }) } @@ -63,11 +50,15 @@ impl<'a> MyObject<'a> { } pub fn get(&self) -> &u8 { - &self.number + let ivar = Self::class().instance_variable("number").unwrap(); + // SAFETY: The ivar is added with the same type below, and is initialized in `init_with_ptr` + unsafe { *ivar.load::<&mut u8>(&self.superclass) } } pub fn set(&mut self, number: u8) { - **self.number = number; + let ivar = Self::class().instance_variable("number").unwrap(); + // SAFETY: The ivar is added with the same type below, and is initialized in `init_with_ptr` + unsafe { **ivar.load_mut::<&mut u8>(&mut self.superclass) = number }; } } @@ -84,7 +75,7 @@ unsafe impl<'a> ClassType for MyObject<'a> { let superclass = NSObject::class(); let mut builder = ClassBuilder::new(Self::NAME, superclass).unwrap(); - builder.add_static_ivar::>(); + builder.add_ivar::<&mut u8>("number"); unsafe { builder.add_method( diff --git a/crates/objc2/src/__macro_helpers/common_selectors.rs b/crates/objc2/src/__macro_helpers/common_selectors.rs index 508a2f3c1..c68809d42 100644 --- a/crates/objc2/src/__macro_helpers/common_selectors.rs +++ b/crates/objc2/src/__macro_helpers/common_selectors.rs @@ -28,3 +28,18 @@ pub fn new_sel() -> Sel { pub fn dealloc_sel() -> Sel { __sel_inner!(__sel_data!(dealloc), "dealloc") } + +/// An undocumented selector called by the Objective-C runtime when +/// initalizing instance variables. +#[inline] +#[allow(dead_code)] // May be useful in the future +fn cxx_construct_sel() -> Sel { + __sel_inner!(".cxx_construct\0", ".cxx_construct") +} + +/// An undocumented selector called by the Objective-C runtime when +/// deinitalizing instance variables. +#[inline] +pub(crate) fn cxx_destruct_sel() -> Sel { + __sel_inner!(".cxx_destruct\0", ".cxx_destruct") +} diff --git a/crates/objc2/src/__macro_helpers/declare_class.rs b/crates/objc2/src/__macro_helpers/declare_class.rs index c98e8a99b..43bbeff1b 100644 --- a/crates/objc2/src/__macro_helpers/declare_class.rs +++ b/crates/objc2/src/__macro_helpers/declare_class.rs @@ -4,18 +4,15 @@ use core::marker::PhantomData; #[cfg(all(debug_assertions, feature = "verify"))] use std::collections::HashSet; +use crate::declare::ClassBuilder; +use crate::encode::{Encode, Encoding}; +use crate::rc::{Allocated, Id}; +use crate::runtime::{AnyClass, AnyObject, MessageReceiver, MethodImplementation, Sel}; #[cfg(all(debug_assertions, feature = "verify"))] use crate::runtime::{AnyProtocol, MethodDescription}; +use crate::{ClassType, DeclaredClass, Message, ProtocolType}; -use objc2_encode::Encoding; - -use crate::declare::{ClassBuilder, IvarType}; -use crate::encode::Encode; -use crate::rc::{Allocated, Id}; -use crate::runtime::{AnyClass, MethodImplementation, Sel}; -use crate::runtime::{AnyObject, MessageReceiver}; -use crate::{ClassType, Message, ProtocolType}; - +use super::declared_ivars::{register_with_ivars, setup_ivars}; use super::{CopyOrMutCopy, Init, MaybeUnwrap, New, Other}; use crate::mutability; @@ -213,18 +210,20 @@ fn failed_declaring_class(name: &str) -> ! { panic!("could not create new class {name}. Perhaps a class with that name already exists?") } -impl ClassBuilderHelper { +impl ClassBuilderHelper { #[inline] #[track_caller] pub fn new() -> Self where T::Super: ClassType, { - let builder = match ClassBuilder::new(T::NAME, ::class()) { + let mut builder = match ClassBuilder::new(T::NAME, ::class()) { Some(builder) => builder, None => failed_declaring_class(T::NAME), }; + setup_ivars::(&mut builder); + Self { builder, p: PhantomData, @@ -288,13 +287,8 @@ impl ClassBuilderHelper { } #[inline] - pub fn add_static_ivar(&mut self) { - self.builder.add_static_ivar::() - } - - #[inline] - pub fn register(self) -> &'static AnyClass { - self.builder.register() + pub fn register(mut self) -> (&'static AnyClass, isize, isize) { + register_with_ivars::(self.builder) } } @@ -321,7 +315,7 @@ pub struct ClassProtocolMethodsBuilder<'a, T: ?Sized> { registered_class_methods: HashSet, } -impl ClassProtocolMethodsBuilder<'_, T> { +impl ClassProtocolMethodsBuilder<'_, T> { #[inline] pub unsafe fn add_method(&mut self, sel: Sel, func: F) where diff --git a/crates/objc2/src/__macro_helpers/declared_ivars.rs b/crates/objc2/src/__macro_helpers/declared_ivars.rs new file mode 100644 index 000000000..77e5a645b --- /dev/null +++ b/crates/objc2/src/__macro_helpers/declared_ivars.rs @@ -0,0 +1,564 @@ +//! # Supporting code for instance variables on declared classes. +//! +//! Adding instance variables to Objective-C classes is fairly simple, it can +//! be done using `ClassBuilder::add_ivar`. +//! +//! However, things become more complicated once we have to handle `Drop`, +//! deallocation and unwind safety. +//! +//! TODO about Swift also + +// TODO SAFETY: +// - The ivars are in a type used as an Objective-C object. +// - The ivar is added to the class in `__objc2_declare_ivars`. +// - Caller upholds that the ivars are properly initialized. + +use alloc::borrow::Cow; +use alloc::format; +use core::mem; +use core::ptr::{self, NonNull}; + +use crate::declare::ClassBuilder; +use crate::encode::{Encode, Encoding}; +use crate::runtime::{AnyClass, AnyObject, MessageReceiver, Sel}; +use crate::{ClassType, DeclaredClass}; + +use super::common_selectors::{cxx_destruct_sel, dealloc_sel}; + +/// Helper function for marking the cold path when branching. +#[inline] +#[cold] +fn cold_path() {} + +/// Helper function for getting a pointer to the instance variable. +#[inline] +unsafe fn ptr_to_ivar(ptr: NonNull) -> NonNull { + // SAFETY: That an instance variable with the given type exists at the + // specified offset is ensured by `DeclaredClass` trait implementor. + // TODO + unsafe { AnyObject::ivar_at_offset::(ptr.cast(), T::__ivars_offset()) } +} + +/// Helper function for getting a pointer to the drop flag. +#[inline] +unsafe fn ptr_to_drop_flag(ptr: NonNull) -> NonNull { + // SAFETY: That a drop flag exists at the specified offset is ensured + // by `DeclaredClass` trait implementor. + // TODO + unsafe { AnyObject::ivar_at_offset::(ptr.cast(), T::__drop_flag_offset()) } +} + +// I think the most efficient way to do the storage is as follows: +// +// match (mem::needs_drop::(), mem::needs_drop::()) { +// (false, false) => { +// struct { +// ivars: Self::Ivars, +// } +// } +// (false, true) if None::.is_all_zeroes_bitpattern() => { +// struct { +// ivars: Option, +// } +// } +// _ => { +// struct { +// ivars: Self::Ivars, +// drop_flag: u8, +// } +// } +// } +// +// TODO: Maybe we can reuse the drop flag when subclassing an already +// declared class? +// +// if None::.is_all_zeroes_bitpattern( +// +// Note that it'd be wonderful if we could do this optimization for +// ivars that have a zero niche, e.g. somehow know if the type is safe to drop when zero-initialized, +// but detecting if the `None` is all +// zeroes requires reading the bytes, which is not possible for +// types that may have padding. +// +// FUture: bytemuck::Zeroable +// +// See: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ea068e8d9e55801aa9520ea914eb2822 +#[repr(u8)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) enum DropFlag { + /// Set to zero to ensure that this is the default when created from + /// Objective-C as well. + /// + /// Ivars are [documented][obj-init-zeroed] to be zero-initialized after + /// allocation, and that has been true since at least [the Objective-C + /// version shipped with Mac OS X 10.0][objc4-208-init]. + /// + /// [obj-init-zeroed]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithObjects/WorkingwithObjects.html#//apple_ref/doc/uid/TP40011210-CH4-SW7 + /// [objc4-208-init]: https://github.com/apple-oss-distributions/objc4/blob/objc4-208/runtime/objc-class.m#L367 + #[default] + Allocated = 0, + InitializedIvars = 1, + Finalized = 2, +} + +// SAFETY: The DropFlag is #[repr(u8)] +unsafe impl Encode for DropFlag { + const ENCODING: Encoding = u8::ENCODING; +} + +/// The `dealloc` Objective-C method. +/// +/// See the following links for more details about `dealloc`: +/// - +/// - +/// - +/// +/// TODO: Change this to `extern "C-unwind"`, unwinding in dealloc is allowed. +unsafe extern "C" fn dealloc(this: NonNull, cmd: Sel) +where + T::Super: ClassType, +{ + match unsafe { *ptr_to_drop_flag(this).as_ptr() } { + // Don't deallocate the current instance if it has not been fully + // initialized. + // + // Note that we still run the superclass deinitializer below. + DropFlag::Allocated | DropFlag::InitializedIvars => cold_path(), + // SAFETY: This is the `dealloc` method, so we know that the type + // never needs to be deallocated again. + // + // Additionally, we know that the type was fully initialized, since + // that's what the drop flag says. + DropFlag::Finalized => unsafe { ptr::drop_in_place(this.as_ptr()) }, + } + + // TODO: Debug assertions that the retain count is still 1 here. + + // The superclass' "marker" that this stores is wrapped in `ManuallyDrop`, + // we drop it by calling the superclass' `dealloc` method instead. + // + // Note: ARC does this automatically, which means most Objective-C code in + // the wild don't contain this call; but we _are_ ARC, so we must do this. + unsafe { + MessageReceiver::send_super_message( + this, + ::Super::class(), + cmd, // Reuse the selector + (), // No arguments + ) + } +} + +/// Objective-C runtimes call `.cxx_destruct` as part of the final `dealloc` +/// call inside `NSObject` (and has done so since macOS 10.4). +/// +/// This is severely undocumented in clang, although [GCC does document it +/// somewhat][gcc-docs], but since this selector is emitted into the final +/// binary by clang, it is _probably_ fine to rely on this. +/// +/// Note that this _is_ the only sound way to destruct instance variables! The +/// natural alternative, doing it inside `dealloc`, would be problematic, +/// since if a superclass calls an overwritten method in its `dealloc`, it'd +/// access deinitialized instance variables. +/// +/// TODO: Unsure whether "C-unwind" is allowed here as this has a different +/// ABI than normal message sends. +/// +/// [gcc-docs]: https://gcc.gnu.org/onlinedocs/gcc/Objective-C-and-Objective-C_002b_002b-Dialect-Options.html#index-fobjc-call-cxx-cdtors +unsafe extern "C" fn cxx_destruct(this: NonNull) { + match unsafe { *ptr_to_drop_flag(this).as_ptr() } { + // Do nothing if the ivars have not been initialized. + DropFlag::Allocated => cold_path(), + DropFlag::InitializedIvars | DropFlag::Finalized => { + // SAFETY: The instance TODO + unsafe { ptr::drop_in_place(ptr_to_ivar(this).as_ptr()) }; + } + } +} + +unsafe extern "C" fn cxx_destruct_with_sel(this: NonNull, _cmd: Sel) { + unsafe { cxx_destruct::(this) } +} + +pub(crate) fn setup_ivars(builder: &mut ClassBuilder) +where + T::Super: ClassType, +{ + // Only add dealloc if the class needs dropping. + // + // `needs_drop::` can reliably detect a direct implementation of + // `Drop`, since the type only includes `ManuallyDrop` or `PhantomData` + // fields. + if mem::needs_drop::() { + let func: unsafe extern "C" fn(_, _) = dealloc::; + unsafe { builder.add_method(dealloc_sel(), func) }; + } + + // Only add destructor if the ivars need dropping. + if mem::needs_drop::() { + let func = if cfg!(feature = "apple") { + let func: unsafe extern "C" fn(NonNull) = cxx_destruct::; + // SAFETY: The ABI of `.cxx_destruct` in Apple's runtime is + // actually that it does NOT take a selector, unlike every other + // Objective-C method, see: + // + // + // This is likely because it's not a real Objective-C method that + // can be called from userspace / objc_msgSend, and it's more + // efficient to not pass the selector. + // + // Note that even if Apple decides to suddenly add the selector + // one day, this will still be sound, since the function uses the + // C calling convention, where such an extra parameter would be + // allowed on all relevant architectures. + let func: unsafe extern "C" fn(NonNull, Sel) = unsafe { mem::transmute(func) }; + func + } else { + cxx_destruct_with_sel:: + }; + + // SAFETY: The ABI of `.cxx_destruct` is upheld. + unsafe { builder.add_method(cxx_destruct_sel(), func) }; + } +} + +#[inline] +pub(crate) fn register_with_ivars( + mut builder: ClassBuilder, +) -> (&'static AnyClass, isize, isize) { + let (ivar_name, drop_flag_name): (Cow<'static, str>, Cow<'static, str>) = { + if cfg!(feature = "gnustep-1-7") { + // GNUStep does not support a subclass having an ivar with the + // same name as a superclass, so let's just use the class name as + // the ivar name to ensure uniqueness. + ( + format!("{}_ivars", T::NAME).into(), + format!("{}_drop_flag", T::NAME).into(), + ) + } else { + ("ivars".into(), "drop_flag".into()) + } + }; + + // Only add ivar if we need the runtime to allocate memory for it. + let has_ivars = + mem::size_of::() > 0 || mem::align_of::() > mem::align_of::<*mut T>(); + if has_ivars { + // TODO about why we add the encoding. Swift doesn't do that! + let ivar_encoding = Encoding::Array( + mem::size_of::() as u64, + match mem::align_of::() { + 1 => &u8::ENCODING, + 2 => &u16::ENCODING, + 4 => &u32::ENCODING, + // Unsure if this is true on all archs? + 8 => &u64::ENCODING, + alignment => panic!("unsupported alignment {alignment}"), + }, + ); + unsafe { builder.add_ivar_inner::(&ivar_name, &ivar_encoding) }; + } + + // Only add drop flag if the type or the ivars need it. + // + // TODO: Maybe we can get around the need for the "is finalized" drop + // flag when the ivars are zero-sized? + let has_drop_flag = mem::needs_drop::() || mem::needs_drop::(); + if has_drop_flag { + builder.add_ivar::(&drop_flag_name); + } + + let cls = builder.register(); + + let ivars_offset = if has_ivars { + cls.instance_variable(&ivar_name) + .expect("failed retrieving instance variable on newly declared class") + .offset() + } else { + // Fallback to an offset of zero. + // + // This is fine, since any reads here will only be via. zero-sized + // ivars, where the actual pointer doesn't matter. + 0 + }; + + let drop_flag_offset = if has_drop_flag { + cls.instance_variable(&drop_flag_name) + .expect("failed retrieving instance variable on newly declared class") + .offset() + } else { + // Fall back to an offset of zero. + // + // This is fine, since the drop flag is never actually used in the + // cases where it was not added. + 0 + }; + + (cls, ivars_offset, drop_flag_offset) +} + +/// # Safety +/// +/// The pointer must be a valid allocated instance. +#[inline] +pub(crate) unsafe fn initialize_ivars(ptr: NonNull, val: T::Ivars) { + // TODO: Debug assert drop flag? + + // SAFETY: + // - Caller ensures the pointer is valid. + // - The location is properly aligned by `ClassBuilder::add_ivar`. + // + // TODO regarding concurrency + unsafe { ptr_to_ivar(ptr).as_ptr().write(val) }; + + // Write to drop flag that we've initialized the instance variables. + // + // Note: We intentionally only do this _after_ writing to the ivars, + // for better unwind safety. + // + // TODO regarding concurrency + unsafe { + ptr_to_drop_flag(ptr) + .as_ptr() + .write(DropFlag::InitializedIvars) + } +} + +/// # Safety +/// +/// The pointer must be valid and finalized (i.e. all super initializers must +/// have been run). +#[inline] +pub(crate) unsafe fn set_finalized(ptr: NonNull) { + // Write to drop flag that we've fully initialized the class. + // + // TODO regarding concurrency + unsafe { ptr_to_drop_flag(ptr).as_ptr().write(DropFlag::Finalized) } +} + +/// # Safety +/// +/// The pointer must be valid and the instance variables must be initialized. +#[inline] +pub(crate) unsafe fn get_initialized_ivar_ptr( + ptr: NonNull, +) -> NonNull { + #[cfg(debug_assertions)] + if mem::needs_drop::() { + // SAFETY: That the pointer is valid is ensured by caller + match unsafe { *ptr_to_drop_flag(ptr).as_ptr() } { + DropFlag::Allocated => { + unreachable!("tried to access uninitialized instance variable") + } + // TODO: Note: The class itself is allowed to not be _set_ as initialized yet, since that only happens after being initialized, but it may be accessed in subclasses before this. + DropFlag::InitializedIvars => {} + DropFlag::Finalized => {} + } + } + + // SAFETY: That the pointer is valid is ensured by caller. + unsafe { ptr_to_ivar(ptr) } +} + +#[cfg(test)] +mod tests { + use core::sync::atomic::{AtomicBool, Ordering}; + + use alloc::boxed::Box; + + use super::*; + use crate::mutability::Mutable; + use crate::rc::{Allocated, Id, __RcTestObject, __ThreadTestData}; + use crate::runtime::NSObject; + use crate::{declare_class, msg_send, msg_send_id, ClassType, DeclaredClass}; + + #[test] + fn assert_size() { + assert_eq!(mem::size_of::(), 1); + } + + #[test] + fn ensure_custom_drop_is_possible() { + static HAS_RUN_DEALLOC: AtomicBool = AtomicBool::new(false); + + #[derive(Debug, PartialEq, Eq)] + struct Ivars { + ivar: u8, + ivar_bool: bool, + } + + declare_class!( + struct CustomDrop; + + unsafe impl ClassType for CustomDrop { + type Super = NSObject; + type Mutability = Mutable; + const NAME: &'static str = "CustomDrop"; + } + + impl DeclaredClass for CustomDrop { + type Ivars = Ivars; + } + ); + + impl Drop for CustomDrop { + fn drop(&mut self) { + HAS_RUN_DEALLOC.store(true, Ordering::Relaxed); + } + } + + let _: Id = unsafe { msg_send_id![CustomDrop::class(), new] }; + + assert!(HAS_RUN_DEALLOC.load(Ordering::Relaxed)); + } + + #[derive(Debug, PartialEq, Eq)] + struct IvarTesterIvars { + ivar1: Id<__RcTestObject>, + ivar2: Option>, + ivar3: Box>, + ivar4: Option>>, + } + + declare_class!( + #[derive(Debug, PartialEq, Eq)] + struct IvarTester; + + unsafe impl ClassType for IvarTester { + type Super = NSObject; + type Mutability = Mutable; + const NAME: &'static str = "IvarTester"; + } + + impl DeclaredClass for IvarTester { + type Ivars = IvarTesterIvars; + } + + unsafe impl IvarTester { + #[method_id(init)] + fn init(this: Allocated) -> Option> { + let this = this.set_ivars(IvarTesterIvars { + ivar1: __RcTestObject::new(), + ivar2: Some(__RcTestObject::new()), + ivar3: Box::new(__RcTestObject::new()), + ivar4: Some(Box::new(__RcTestObject::new())), + }); + unsafe { msg_send_id![super(this), init] } + } + + #[method(initInvalid)] + fn init_invalid(this: *mut Self) -> *mut Self { + // Don't actually initialize anything here; this creates an + // invalid instance, where accessing the two ivars `ivar1` + // and `ivar3` is UB + unsafe { msg_send![super(this), init] } + } + } + ); + + #[derive(Debug, PartialEq, Eq)] + struct IvarTesterSubclassIvars { + ivar5: Id<__RcTestObject>, + } + + declare_class!( + #[derive(Debug, PartialEq, Eq)] + struct IvarTesterSubclass; + + unsafe impl ClassType for IvarTesterSubclass { + type Super = IvarTester; + type Mutability = Mutable; + const NAME: &'static str = "IvarTesterSubclass"; + } + + impl DeclaredClass for IvarTesterSubclass { + type Ivars = IvarTesterSubclassIvars; + } + + unsafe impl IvarTesterSubclass { + #[method_id(init)] + fn init(this: Allocated) -> Option> { + let this = this.set_ivars(IvarTesterSubclassIvars { + ivar5: __RcTestObject::new(), + }); + unsafe { msg_send_id![super(this), init] } + } + } + ); + + #[test] + fn test_alloc_dealloc() { + let expected = __ThreadTestData::current(); + + let obj: Allocated = unsafe { msg_send_id![IvarTester::class(), alloc] }; + expected.assert_current(); + + drop(obj); + expected.assert_current(); + } + + #[test] + fn test_init_drop() { + let mut expected = __ThreadTestData::current(); + + let mut obj: Id = unsafe { msg_send_id![IvarTester::class(), new] }; + expected.alloc += 4; + expected.init += 4; + expected.assert_current(); + + obj.ivars_mut().ivar1 = obj.ivars().ivar1.clone(); + expected.retain += 1; + expected.release += 1; + expected.assert_current(); + + obj.ivars_mut().ivar2 = None; + expected.release += 1; + expected.dealloc += 1; + expected.assert_current(); + + drop(obj); + expected.release += 3; + expected.dealloc += 3; + expected.assert_current(); + } + + #[test] + fn test_subclass() { + let mut expected = __ThreadTestData::current(); + + let mut obj: Id = + unsafe { msg_send_id![IvarTesterSubclass::class(), new] }; + expected.alloc += 5; + expected.init += 5; + expected.assert_current(); + + obj.ivars_mut().ivar5 = (&**obj).ivars().ivar1.clone(); + expected.retain += 1; + expected.release += 1; + expected.dealloc += 1; + expected.assert_current(); + + drop(obj); + expected.release += 5; + expected.dealloc += 4; + expected.assert_current(); + } + + #[test] + #[cfg_attr(not(debug_assertions), ignore = "only panics with debug assertions")] + #[should_panic = "an Id in instance variables must always be initialized before use"] + fn test_init_invalid_ref() { + let obj: Id = unsafe { msg_send_id![IvarTester::alloc(), initInvalid] }; + + std::println!("{:?}", obj.ivars().ivar1); + } + + #[test] + #[cfg_attr(not(debug_assertions), ignore = "only panics with debug assertions")] + #[should_panic = "an Id in instance variables must always be initialized before use"] + fn test_init_invalid_mut() { + let mut obj: Id = unsafe { msg_send_id![IvarTester::alloc(), initInvalid] }; + + obj.ivars_mut().ivar1 = __RcTestObject::new(); + } +} diff --git a/crates/objc2/src/__macro_helpers/mod.rs b/crates/objc2/src/__macro_helpers/mod.rs index b7cffdbf7..3bf2a6e7a 100644 --- a/crates/objc2/src/__macro_helpers/mod.rs +++ b/crates/objc2/src/__macro_helpers/mod.rs @@ -2,11 +2,10 @@ pub use core::borrow::{Borrow, BorrowMut}; pub use core::cell::UnsafeCell; pub use core::convert::{AsMut, AsRef}; pub use core::marker::{PhantomData, Sized}; -pub use core::mem::{needs_drop, size_of, ManuallyDrop}; +pub use core::mem::{size_of, ManuallyDrop, MaybeUninit}; pub use core::ops::{Deref, DerefMut}; pub use core::option::Option::{self, None, Some}; -pub use core::primitive::{bool, str, u8}; -pub use core::ptr::drop_in_place; +pub use core::primitive::{bool, isize, str, u8}; pub use core::{compile_error, concat, panic, stringify}; // TODO: Use `core::cell::LazyCell` pub use std::sync::Once; @@ -15,6 +14,7 @@ mod cache; mod common_selectors; mod convert; mod declare_class; +pub(crate) mod declared_ivars; mod method_family; mod msg_send; mod msg_send_id; diff --git a/crates/objc2/src/declare/ivar.rs b/crates/objc2/src/declare/ivar.rs deleted file mode 100644 index d89b32a6e..000000000 --- a/crates/objc2/src/declare/ivar.rs +++ /dev/null @@ -1,388 +0,0 @@ -use core::fmt; -use core::marker::PhantomData; -use core::mem::{self, MaybeUninit}; -use core::ops::{Deref, DerefMut}; -use core::ptr::{self, NonNull}; - -use crate::encode::Encode; -use crate::runtime::AnyObject; - -pub(crate) mod private { - pub trait Sealed {} -} - -/// Types that may be used in ivars. -/// -/// This may be either: -/// - [`IvarBool`][super::IvarBool]. -/// - [`IvarDrop`][super::IvarDrop]. -/// - [`IvarEncode`][super::IvarEncode]. -/// -/// This is a sealed trait, and should not need to be implemented. Open an -/// issue if you know a use-case where this restrition should be lifted! -/// -/// -/// # Safety -/// -/// You cannot rely on any safety guarantees from this. -// -// The type must have the same memory layout as the output type. -// -// Additionally, the type must be safe to drop even if zero-initialized. -// -// Ivars are documented to be zero-initialized in [this section of the -// Objective-C manual][obj-dynamically-created], and that has been true since -// at least [the Objective-C version shipped with Mac OS X 10.0][objc4-208]. -// -// [obj-dynamically-created]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithObjects/WorkingwithObjects.html#//apple_ref/doc/uid/TP40011210-CH4-SW7 -// [objc4-208]: https://github.com/apple-oss-distributions/objc4/blob/objc4-208/runtime/objc-class.m#L367 -pub unsafe trait InnerIvarType: private::Sealed + Encode { - /// The type that an `Ivar` containing this will dereference to. - /// - /// E.g. `Ivar>>` will deref to `Box`. - type Output; - - /// # Safety - /// - /// The instance variable must have been initialized. - #[doc(hidden)] - unsafe fn __deref(&self) -> &Self::Output; - - /// # Safety - /// - /// The instance variable must have been initialized. - #[doc(hidden)] - unsafe fn __deref_mut(&mut self) -> &mut Self::Output; -} - -/// Helper trait for defining instance variables. -/// -/// This should be implemented for an empty marker type, which can then be -/// used within [`Ivar`] to refer to the instance variable. -/// -/// -/// # Safety -/// -/// Really, [`Ivar`] should be marked as `unsafe`, but since we can't do that -/// we'll mark this trait as `unsafe` instead. See [`Ivar`] for safety -/// requirements. -/// -/// -/// # Examples -/// -/// Create an instance variable `myCustomIvar` with type `i32`. -/// -/// ``` -/// use objc2::declare::{IvarEncode, IvarType}; -/// -/// // Helper type -/// struct MyCustomIvar; -/// -/// unsafe impl IvarType for MyCustomIvar { -/// type Type = IvarEncode; -/// const NAME: &'static str = "myCustomIvar"; -/// } -/// -/// // `Ivar` can now be used -/// ``` -pub unsafe trait IvarType { - /// The type of the instance variable. - type Type: InnerIvarType; - /// The name of the instance variable. - const NAME: &'static str; - - #[doc(hidden)] - unsafe fn __ivar_ptr(ptr: NonNull) -> NonNull { - // FIXME: This is currently unsound! Looking up the instance variable - // dynamically will return the wrong variable if two variables with - // the same name exist. - let ivar = unsafe { ptr.as_ref() }.lookup_instance_variable_dynamically(Self::NAME); - ivar.debug_assert_encoding(&Self::Type::ENCODING); - unsafe { AnyObject::ivar_at_offset(ptr, ivar.offset()) } - } -} - -/// A wrapper type over a custom instance variable. -/// -/// This type is not meant to be constructed by itself, it must reside within -/// another struct meant to represent an Objective-C object. -/// -/// On [`Deref`] it then uses the [`IvarType::NAME`] string to access the ivar -/// of the containing object. -/// -/// Note that this is not ([currently][zst-hack]) allowed by [stacked -/// borrows][sb], but due to objects being zero-sized types, we don't have -/// provenance over the ivars anyhow, this should be just as sound as normal -/// instance variable access. -/// -/// [sb]: https://github.com/rust-lang/unsafe-code-guidelines/blob/e21202c60c7be03dd2ab016ada92fb5305d40438/wip/stacked-borrows.md -/// [zst-hack]: https://github.com/rust-lang/unsafe-code-guidelines/issues/305 -/// -/// -/// # `bool` handling -/// -/// This does _not_ perform a conversion step between [`bool`] and the -/// Objective-C `BOOL`; use [`runtime::Bool`][crate::runtime::Bool] when you -/// want your instance variable to be accessible from other Objective-C code. -/// -/// -/// # Safety -/// -/// This must be used within a type that act as an Objective-C object. In -/// particular, this is never safe to have on the stack by itself. -/// -/// Additionally, the instance variable described by `T` must be available on -/// the specific instance, and be of the exact same type. When declaring the -/// object yourself, you can ensure this using -/// [`ClassBuilder::add_static_ivar`]. -/// -/// Finally, two ivars with the same name must not be used on the same object. -/// -/// [`ClassBuilder::add_static_ivar`]: crate::declare::ClassBuilder::add_static_ivar -/// -/// -/// # Examples -/// -/// ``` -/// use objc2::declare::{Ivar, IvarEncode, IvarType}; -/// use objc2::runtime::NSObject; -/// -/// // Declare ivar with given type and name -/// struct MyCustomIvar; -/// unsafe impl IvarType for MyCustomIvar { -/// type Type = IvarEncode; -/// const NAME: &'static str = "myCustomIvar"; -/// } -/// -/// // Custom object -/// #[repr(C)] -/// pub struct MyObject { -/// inner: NSObject, -/// // SAFETY: The instance variable is used within an object, and it is -/// // properly declared below. -/// my_ivar: Ivar, -/// } -/// -/// # use objc2::ClassType; -/// # use objc2::declare::ClassBuilder; -/// # let mut builder = ClassBuilder::new("MyObject", NSObject::class()).unwrap(); -/// // Declare the class and add the instance variable to it -/// builder.add_static_ivar::(); -/// # let _cls = builder.register(); -/// -/// let obj: MyObject; -/// // You can now access `obj.my_ivar` -/// ``` -/// -/// See also the `declare_ivar.rs` example. -#[repr(C)] -// Must not be `Copy` nor `Clone`! -pub struct Ivar { - /// Make this type allowed in `repr(C)` - inner: [u8; 0], - /// For proper variance and auto traits - item: PhantomData, -} - -impl Drop for Ivar { - #[inline] - fn drop(&mut self) { - if mem::needs_drop::() { - unsafe { ptr::drop_in_place(self.as_inner_mut_ptr().as_ptr()) } - } - } -} - -impl Ivar { - /// Get a pointer to the instance variable. - /// - /// Note that if the ivar has already been initialized, you can use the - /// `Deref` implementation to get a reference. - /// - /// This is similar to [`MaybeUninit::as_ptr`], see that for usage - /// instructions. - pub fn as_ptr(this: &Self) -> *const ::Target { - this.as_inner_ptr().as_ptr().cast() - } - - fn as_inner_ptr(&self) -> NonNull { - let ptr: NonNull = NonNull::from(self).cast(); - - // SAFETY: The user ensures that this is placed in a struct that can - // be reinterpreted as an `AnyObject`. Since `Ivar` can never be - // constructed by itself (and is neither Copy nor Clone), we know that - // it is guaranteed to _stay_ in said struct. - // - // Even if the user were to do `mem::swap`, the `Ivar` has a unique - // type (and does not hold any data), so that wouldn't break anything. - // - // Note: We technically don't have provenance over the object, nor the - // ivar, but the object doesn't have provenance over the ivar either, - // so that is fine. - unsafe { T::__ivar_ptr(ptr) } - } - - /// Get a mutable pointer to the instance variable. - /// - /// This is useful when you want to initialize the ivar inside an `init` - /// method (where it may otherwise not have been safely initialized yet). - /// - /// Note that if the ivar has already been initialized, you can use the - /// `DerefMut` implementation to get a mutable reference. - /// - /// This is similar to [`MaybeUninit::as_mut_ptr`], see that for usage - /// instructions. - pub fn as_mut_ptr(this: &mut Self) -> *mut ::Target { - this.as_inner_mut_ptr().as_ptr().cast() - } - - fn as_inner_mut_ptr(&mut self) -> NonNull { - let ptr: NonNull = NonNull::from(self).cast(); - - // SAFETY: Same as `as_inner_ptr` - unsafe { T::__ivar_ptr(ptr) } - } - - /// Sets the value of the instance variable. - /// - /// This is useful when you want to initialize the ivar inside an `init` - /// method (where it may otherwise not have been safely initialized yet). - /// - /// This is similar to [`MaybeUninit::write`], see that for usage - /// instructions. - pub fn write(this: &mut Self, val: ::Target) -> &mut ::Target { - let ptr: *mut ::Output = Self::as_mut_ptr(this); - let ptr: *mut MaybeUninit<::Output> = ptr.cast(); - let ivar = unsafe { ptr.as_mut().unwrap_unchecked() }; - ivar.write(val) - } -} - -impl Deref for Ivar { - type Target = ::Output; - - #[inline] - fn deref(&self) -> &Self::Target { - // SAFETY: User ensures that the `Ivar` is only used when the ivar - // exists, has the correct type, and has been properly initialized. - // - // Since all accesses to a particular ivar only goes through one - // `Ivar`, if we have `&Ivar` we know that `&T` is safe. - unsafe { self.as_inner_ptr().as_ref().__deref() } - } -} - -impl DerefMut for Ivar { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - // SAFETY: User ensures that the `Ivar` is only used when the ivar - // exists, has the correct type, and has been properly initialized. - // - // Safe as mutable because there is only one access to a - // particular ivar at a time (since we have `&mut self`). - - // Note: We're careful not to create `&mut AnyObject` because the user - // might have two mutable references to different ivars, as such: - // - // ``` - // #[repr(C)] - // struct X { - // inner: AnyObject, - // ivar1: Ivar, - // ivar2: Ivar, - // } - // - // let mut x: X; - // let ivar1: &mut Ivar = &mut x.ivar1; - // let ivar2: &mut Ivar = &mut x.ivar2; - // ``` - // - // And using `mut` would create aliasing mutable reference to the - // object. - unsafe { self.as_inner_mut_ptr().as_mut().__deref_mut() } - } -} - -/// Format as a pointer to the instance variable. -impl fmt::Pointer for Ivar { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Pointer::fmt(&Self::as_ptr(self), f) - } -} - -#[cfg(test)] -mod tests { - use core::mem; - use core::panic::{RefUnwindSafe, UnwindSafe}; - use std::sync::atomic::{AtomicBool, Ordering}; - - use super::*; - use crate::declare::{IvarBool, IvarEncode}; - use crate::mutability::Mutable; - use crate::rc::Id; - use crate::runtime::NSObject; - use crate::{declare_class, msg_send, msg_send_id, test_utils, ClassType}; - - struct TestIvar; - - unsafe impl IvarType for TestIvar { - type Type = IvarEncode; - const NAME: &'static str = "_foo"; - } - - #[repr(C)] - struct IvarTestObject { - inner: NSObject, - foo: Ivar, - } - - #[test] - fn auto_traits() { - fn assert_auto_traits() {} - assert_auto_traits::>(); - - // Ensure that `Ivar` is zero-sized - assert_eq!(mem::size_of::>(), 0); - assert_eq!(mem::align_of::>(), 1); - } - - #[test] - fn access_ivar() { - let mut obj = test_utils::custom_object(); - let _: () = unsafe { msg_send![&mut obj, setFoo: 42u32] }; - - let obj = unsafe { Id::as_ptr(&obj).cast::().as_ref().unwrap() }; - assert_eq!(*obj.foo, 42); - } - - #[test] - fn ensure_custom_drop_is_possible() { - static HAS_RUN_DEALLOC: AtomicBool = AtomicBool::new(false); - - declare_class!( - #[derive(Debug, PartialEq, Eq)] - struct CustomDrop { - ivar: IvarEncode, - ivar_bool: IvarBool<"_ivar_bool">, - } - - mod customdrop; - - unsafe impl ClassType for CustomDrop { - type Super = NSObject; - type Mutability = Mutable; - const NAME: &'static str = "CustomDrop"; - } - ); - - impl Drop for CustomDrop { - fn drop(&mut self) { - HAS_RUN_DEALLOC.store(true, Ordering::Relaxed); - } - } - - let _: Id = unsafe { msg_send_id![CustomDrop::class(), new] }; - - assert!(HAS_RUN_DEALLOC.load(Ordering::Relaxed)); - } -} diff --git a/crates/objc2/src/declare/ivar_bool.rs b/crates/objc2/src/declare/ivar_bool.rs deleted file mode 100644 index f7e1930a5..000000000 --- a/crates/objc2/src/declare/ivar_bool.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::encode::{Encode, Encoding}; - -use super::InnerIvarType; - -/// Ivar of [`bool`]. -/// -/// This is used to work around the fact that `bool` is not [`Encode`]. -/// -/// If you want to access this instance variable to Objective-C, you must do -/// so using C99 `_Bool`; if you want to use `BOOL` in Objective-C, you should -/// use `IvarEncode`. -#[repr(transparent)] -#[allow(missing_copy_implementations)] -#[allow(missing_debug_implementations)] -pub struct IvarBool(bool); - -unsafe impl Encode for IvarBool { - const ENCODING: Encoding = Encoding::Bool; -} - -impl super::ivar::private::Sealed for IvarBool {} - -// SAFETY: IvarBool is `#[repr(transparent)]`, and `bool` is safe to -// zero-initialize -unsafe impl InnerIvarType for IvarBool { - type Output = bool; - - #[inline] - unsafe fn __deref(&self) -> &Self::Output { - &self.0 - } - - #[inline] - unsafe fn __deref_mut(&mut self) -> &mut Self::Output { - &mut self.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use core::mem; - - #[test] - fn needs_drop() { - assert!(!mem::needs_drop::()); - assert_eq!(mem::size_of::(), mem::size_of::()); - } -} diff --git a/crates/objc2/src/declare/ivar_drop.rs b/crates/objc2/src/declare/ivar_drop.rs deleted file mode 100644 index 0fa488608..000000000 --- a/crates/objc2/src/declare/ivar_drop.rs +++ /dev/null @@ -1,378 +0,0 @@ -use alloc::boxed::Box; -use core::ffi::c_void; - -use crate::encode::{Encode, Encoding}; -use crate::rc::Id; -use crate::Message; - -use super::InnerIvarType; - -mod private { - /// # Safety - /// - /// The inner type must be safe to zero-initialize. - pub unsafe trait IvarDropHelper { - type Inner; - } -} - -/// Ivar types that may drop. -/// -/// This currently works with the following types: -/// - `Box` -/// - `Option>` -/// - `Id` -/// - `Option>` -/// -/// Further may be added when the standard library guarantee their layout. -#[repr(transparent)] -#[allow(missing_debug_implementations)] -pub struct IvarDrop(::Inner); - -impl super::ivar::private::Sealed for IvarDrop {} - -// Note that we use `*const c_void` and not `*const T` to allow _any_ type, -// not just types that can be encoded by Objective-C -unsafe impl Encode for IvarDrop> { - const ENCODING: Encoding = <*const c_void>::ENCODING; -} - -// SAFETY: `Option>` is safe to zero-initialize -unsafe impl private::IvarDropHelper for Box { - type Inner = Option>; -} - -// SAFETY: The memory layout of `Box` is guaranteed to be a pointer: -// -// -// The user ensures that the Box has been initialized in an `init` method -// before being used. -unsafe impl InnerIvarType for IvarDrop> { - type Output = Box; - - #[inline] - unsafe fn __deref(&self) -> &Self::Output { - match &self.0 { - Some(inner) => inner, - None => unsafe { box_unreachable() }, - } - } - - #[inline] - unsafe fn __deref_mut(&mut self) -> &mut Self::Output { - match &mut self.0 { - Some(inner) => inner, - None => unsafe { box_unreachable() }, - } - } -} - -unsafe impl Encode for IvarDrop>> { - const ENCODING: Encoding = <*const c_void>::ENCODING; -} - -// SAFETY: `Option>` is safe to zero-initialize -unsafe impl private::IvarDropHelper for Option> { - type Inner = Option>; -} - -// SAFETY: `Option>` guarantees the null-pointer optimization, so for -// `T: Sized` the layout is just a pointer: -// -// -// This is valid to initialize as all-zeroes, so the user doesn't have to do -// anything to initialize it. -unsafe impl InnerIvarType for IvarDrop>> { - type Output = Option>; - - #[inline] - unsafe fn __deref(&self) -> &Self::Output { - &self.0 - } - - #[inline] - unsafe fn __deref_mut(&mut self) -> &mut Self::Output { - &mut self.0 - } -} - -unsafe impl Encode for IvarDrop> { - const ENCODING: Encoding = <*const T>::ENCODING; -} - -// SAFETY: `Option>` is safe to zero-initialize -unsafe impl private::IvarDropHelper for Id { - type Inner = Option>; -} - -// SAFETY: `Id` is `NonNull`, and hence safe to store as a pointer. -// -// The user ensures that the Id has been initialized in an `init` method -// before being used. -// -// Note: We could technically do `impl InnerIvarType for Ivar>` -// directly today, but since we can't do so for `Box` (because that is -// `#[fundamental]`), I think it makes sense to handle them similarly. -unsafe impl InnerIvarType for IvarDrop> { - type Output = Id; - - #[inline] - unsafe fn __deref(&self) -> &Self::Output { - match &self.0 { - Some(inner) => inner, - None => unsafe { id_unreachable() }, - } - } - - #[inline] - unsafe fn __deref_mut(&mut self) -> &mut Self::Output { - match &mut self.0 { - Some(inner) => inner, - None => unsafe { id_unreachable() }, - } - } -} - -unsafe impl Encode for IvarDrop>> { - const ENCODING: Encoding = <*const T>::ENCODING; -} - -// SAFETY: `Option>` is safe to zero-initialize -unsafe impl private::IvarDropHelper for Option> { - type Inner = Option>; -} - -// SAFETY: `Id` guarantees the null-pointer optimization. -// -// This is valid to initialize as all-zeroes, so the user doesn't have to do -// anything to initialize it. -unsafe impl InnerIvarType for IvarDrop>> { - type Output = Option>; - - #[inline] - unsafe fn __deref(&self) -> &Self::Output { - &self.0 - } - - #[inline] - unsafe fn __deref_mut(&mut self) -> &mut Self::Output { - &mut self.0 - } -} - -// TODO: Allow the following once their layout is guaranteed by `std`: -// - Arc -// - Option> -// - sync::Weak -// - Rc -// - Option> -// - rc::Weak -// - Vec -// - String - -// TODO: Allow `WeakId` once we figure out how to allow it being initialized -// by default. - -#[inline] -#[track_caller] -unsafe fn id_unreachable() -> ! { - #[cfg(debug_assertions)] - { - unreachable!("an Id in instance variables must always be initialized before use!") - } - // SAFETY: Checked by caller - #[cfg(not(debug_assertions))] - unsafe { - core::hint::unreachable_unchecked() - } -} - -#[inline] -#[track_caller] -unsafe fn box_unreachable() -> ! { - #[cfg(debug_assertions)] - { - unreachable!("a Box in instance variables must always be initialized before use!") - } - // SAFETY: Checked by caller - #[cfg(not(debug_assertions))] - unsafe { - core::hint::unreachable_unchecked() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::declare::{Ivar, IvarType}; - use crate::mutability::Mutable; - use crate::rc::{Allocated, __RcTestObject, __ThreadTestData}; - use crate::runtime::NSObject; - use crate::{declare_class, msg_send, msg_send_id, ClassType}; - - struct TestIvar1; - unsafe impl IvarType for TestIvar1 { - type Type = IvarDrop>; - const NAME: &'static str = "_abc"; - } - - struct TestIvar2; - unsafe impl IvarType for TestIvar2 { - type Type = IvarDrop>>; - const NAME: &'static str = "_abc"; - } - - struct TestIvar3; - unsafe impl IvarType for TestIvar3 { - type Type = IvarDrop>; - const NAME: &'static str = "_abc"; - } - - struct TestIvar4; - unsafe impl IvarType for TestIvar4 { - type Type = IvarDrop>>; - const NAME: &'static str = "_abc"; - } - - declare_class!( - #[derive(Debug, PartialEq, Eq)] - struct IvarTester { - ivar1: IvarDrop, "_ivar1">, - ivar2: IvarDrop>, "_ivar2">, - ivar3: IvarDrop>, "_ivar3">, - ivar4: IvarDrop>>, "_ivar4">, - } - - mod ivartester; - - unsafe impl ClassType for IvarTester { - type Super = NSObject; - type Mutability = Mutable; - const NAME: &'static str = "IvarTester"; - } - - unsafe impl IvarTester { - #[method(init)] - fn init(&mut self) -> Option<&mut Self> { - let this: Option<&mut Self> = unsafe { msg_send![super(self), init] }; - this.map(|this| { - Ivar::write(&mut this.ivar1, __RcTestObject::new()); - *this.ivar2 = Some(__RcTestObject::new()); - Ivar::write(&mut this.ivar3, Box::new(__RcTestObject::new())); - *this.ivar4 = Some(Box::new(__RcTestObject::new())); - this - }) - } - - #[method(initInvalid)] - fn init_invalid(&mut self) -> Option<&mut Self> { - // Don't actually initialize anything here; this creates an - // invalid instance, where accessing the two ivars `ivar1` - // and `ivar3` is UB - unsafe { msg_send![super(self), init] } - } - } - ); - - declare_class!( - #[derive(Debug, PartialEq, Eq)] - struct IvarTesterSubclass { - ivar5: IvarDrop, "_ivar5">, - } - - mod ivartestersubclass; - - unsafe impl ClassType for IvarTesterSubclass { - type Super = IvarTester; - type Mutability = Mutable; - const NAME: &'static str = "IvarTesterSubclass"; - } - - unsafe impl IvarTesterSubclass { - #[method(init)] - fn init(&mut self) -> Option<&mut Self> { - let this: Option<&mut Self> = unsafe { msg_send![super(self), init] }; - this.map(|this| { - Ivar::write(&mut this.ivar5, __RcTestObject::new()); - this - }) - } - } - ); - - #[test] - fn test_alloc_dealloc() { - let expected = __ThreadTestData::current(); - - let obj: Allocated = unsafe { msg_send_id![IvarTester::class(), alloc] }; - expected.assert_current(); - - drop(obj); - expected.assert_current(); - } - - #[test] - fn test_init_drop() { - let mut expected = __ThreadTestData::current(); - - let mut obj: Id = unsafe { msg_send_id![IvarTester::class(), new] }; - expected.alloc += 4; - expected.init += 4; - expected.assert_current(); - - *obj.ivar1 = (*obj.ivar1).clone(); - expected.retain += 1; - expected.release += 1; - expected.assert_current(); - - *obj.ivar2 = None; - expected.release += 1; - expected.dealloc += 1; - expected.assert_current(); - - drop(obj); - expected.release += 3; - expected.dealloc += 3; - expected.assert_current(); - } - - #[test] - fn test_subclass() { - let mut expected = __ThreadTestData::current(); - - let mut obj: Id = - unsafe { msg_send_id![IvarTesterSubclass::class(), new] }; - expected.alloc += 5; - expected.init += 5; - expected.assert_current(); - - *obj.ivar5 = (*obj.ivar1).clone(); - expected.retain += 1; - expected.release += 1; - expected.dealloc += 1; - expected.assert_current(); - - drop(obj); - expected.release += 5; - expected.dealloc += 4; - expected.assert_current(); - } - - #[test] - #[cfg_attr(not(debug_assertions), ignore = "only panics in debug mode")] - #[should_panic = "an Id in instance variables must always be initialized before use"] - fn test_init_invalid_ref() { - let obj: Id = unsafe { msg_send_id![IvarTester::alloc(), initInvalid] }; - - std::println!("{:?}", obj.ivar1); - } - - #[test] - #[cfg_attr(not(debug_assertions), ignore = "only panics in debug mode")] - #[should_panic = "an Id in instance variables must always be initialized before use"] - fn test_init_invalid_mut() { - let mut obj: Id = unsafe { msg_send_id![IvarTester::alloc(), initInvalid] }; - - *obj.ivar1 = __RcTestObject::new(); - } -} diff --git a/crates/objc2/src/declare/ivar_encode.rs b/crates/objc2/src/declare/ivar_encode.rs deleted file mode 100644 index 3a4300059..000000000 --- a/crates/objc2/src/declare/ivar_encode.rs +++ /dev/null @@ -1,75 +0,0 @@ -use core::mem::MaybeUninit; - -use crate::encode::{Encode, Encoding}; - -use super::InnerIvarType; - -/// Ivar types that are [`Encode`]. -// -// Note: We put the inner type in a `MaybeUninit`, since we may need to access -// this type before the inner type has been properly initialized. -#[repr(transparent)] -#[allow(missing_debug_implementations)] -pub struct IvarEncode(MaybeUninit); - -// We intentionally don't implement `Drop`, since that may happen before the -// ivar has been initialized. -// -// For example in the case of `NonNull`, it would be zero-initialized, -// which is an invalid state for that to have. - -// SAFETY: `IvarEncode` is `#[repr(transparent)]`, and the layout of -// `MaybeUninit` is the same as `T`. -unsafe impl Encode for IvarEncode { - const ENCODING: Encoding = T::ENCODING; -} - -impl super::ivar::private::Sealed for IvarEncode {} - -// SAFETY: `IvarEncode` has the same memory layout as T, and -// `MaybeUninit` is safe to zero-initialize. -unsafe impl InnerIvarType for IvarEncode { - type Output = T; - - #[inline] - unsafe fn __deref(&self) -> &Self::Output { - // SAFETY: Checked by caller - unsafe { self.0.assume_init_ref() } - } - - #[inline] - unsafe fn __deref_mut(&mut self) -> &mut Self::Output { - // SAFETY: Checked by caller - unsafe { self.0.assume_init_mut() } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use core::mem; - - #[test] - fn needs_drop() { - assert!(!mem::needs_drop::>()); - assert!(!mem::needs_drop::>()); - - // You wouldn't do this, but let's make sure it works as expected - #[repr(transparent)] - struct DropAndEncode(i32); - - unsafe impl Encode for DropAndEncode { - const ENCODING: Encoding = i32::ENCODING; - } - - impl Drop for DropAndEncode { - fn drop(&mut self) {} - } - - assert!(mem::needs_drop::()); - assert!(!mem::needs_drop::>()); - - assert_eq!(mem::size_of::>(), mem::size_of::()); - } -} diff --git a/crates/objc2/src/declare/ivar_forwarding_impls.rs b/crates/objc2/src/declare/ivar_forwarding_impls.rs deleted file mode 100644 index 3b80f25c7..000000000 --- a/crates/objc2/src/declare/ivar_forwarding_impls.rs +++ /dev/null @@ -1,340 +0,0 @@ -//! Trivial forwarding impls on `Ivar`. -//! -//! Kept here to keep `ivar.rs` free from this boilerplate. -//! -//! `#[inline]` is used where the standard library `Box` uses it. - -#![forbid(unsafe_code)] - -// use alloc::borrow; -use alloc::string::String; -use alloc::vec::Vec; -use core::cmp::Ordering; -use core::fmt; -use core::future::Future; -use core::hash; -use core::iter::FusedIterator; -use core::ops::Deref; -use core::pin::Pin; -use core::task::{Context, Poll}; -use std::error::Error; -use std::io; - -use super::{Ivar, IvarType}; - -impl PartialEq for Ivar -where - ::Target: PartialEq, -{ - #[inline] - fn eq(&self, other: &Self) -> bool { - (**self).eq(&**other) - } - - #[inline] - #[allow(clippy::partialeq_ne_impl)] - fn ne(&self, other: &Self) -> bool { - (**self).ne(&**other) - } -} - -impl Eq for Ivar where ::Target: Eq {} - -impl PartialOrd for Ivar -where - ::Target: PartialOrd, -{ - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - (**self).partial_cmp(&**other) - } - #[inline] - fn lt(&self, other: &Self) -> bool { - (**self).lt(&**other) - } - #[inline] - fn le(&self, other: &Self) -> bool { - (**self).le(&**other) - } - #[inline] - fn ge(&self, other: &Self) -> bool { - (**self).ge(&**other) - } - #[inline] - fn gt(&self, other: &Self) -> bool { - (**self).gt(&**other) - } -} - -impl Ord for Ivar -where - ::Target: Ord, -{ - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - (**self).cmp(&**other) - } -} - -impl hash::Hash for Ivar -where - ::Target: hash::Hash, -{ - fn hash(&self, state: &mut H) { - (**self).hash(state) - } -} - -impl hash::Hasher for Ivar -where - ::Target: hash::Hasher, -{ - fn finish(&self) -> u64 { - (**self).finish() - } - fn write(&mut self, bytes: &[u8]) { - (**self).write(bytes) - } - fn write_u8(&mut self, i: u8) { - (**self).write_u8(i) - } - fn write_u16(&mut self, i: u16) { - (**self).write_u16(i) - } - fn write_u32(&mut self, i: u32) { - (**self).write_u32(i) - } - fn write_u64(&mut self, i: u64) { - (**self).write_u64(i) - } - fn write_u128(&mut self, i: u128) { - (**self).write_u128(i) - } - fn write_usize(&mut self, i: usize) { - (**self).write_usize(i) - } - fn write_i8(&mut self, i: i8) { - (**self).write_i8(i) - } - fn write_i16(&mut self, i: i16) { - (**self).write_i16(i) - } - fn write_i32(&mut self, i: i32) { - (**self).write_i32(i) - } - fn write_i64(&mut self, i: i64) { - (**self).write_i64(i) - } - fn write_i128(&mut self, i: i128) { - (**self).write_i128(i) - } - fn write_isize(&mut self, i: isize) { - (**self).write_isize(i) - } -} - -impl fmt::Display for Ivar -where - ::Target: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl fmt::Debug for Ivar -where - ::Target: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl Iterator for Ivar -where - ::Target: Iterator, -{ - type Item = <::Target as Iterator>::Item; - fn next(&mut self) -> Option<<::Target as Iterator>::Item> { - (**self).next() - } - fn size_hint(&self) -> (usize, Option) { - (**self).size_hint() - } - fn nth(&mut self, n: usize) -> Option<<::Target as Iterator>::Item> { - (**self).nth(n) - } -} - -impl DoubleEndedIterator for Ivar -where - ::Target: DoubleEndedIterator, -{ - fn next_back(&mut self) -> Option<<::Target as Iterator>::Item> { - (**self).next_back() - } - fn nth_back(&mut self, n: usize) -> Option<<::Target as Iterator>::Item> { - (**self).nth_back(n) - } -} - -impl ExactSizeIterator for Ivar -where - ::Target: ExactSizeIterator, -{ - fn len(&self) -> usize { - (**self).len() - } -} - -impl FusedIterator for Ivar where ::Target: FusedIterator {} - -// impl borrow::Borrow<::Target> for Ivar { -// fn borrow(&self) -> &::Target { -// self -// } -// } -// -// impl borrow::BorrowMut<::Target> for Ivar { -// fn borrow_mut(&mut self) -> &mut ::Target { -// self -// } -// } - -impl AsRef<::Target> for Ivar { - fn as_ref(&self) -> &::Target { - // Auto-derefs - self - } -} - -impl AsMut<::Target> for Ivar { - fn as_mut(&mut self) -> &mut ::Target { - // Auto-derefs - self - } -} - -impl Error for Ivar -where - ::Target: Error, -{ - fn source(&self) -> Option<&(dyn Error + 'static)> { - (**self).source() - } -} - -impl io::Read for Ivar -where - ::Target: io::Read, -{ - #[inline] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - (**self).read(buf) - } - - #[inline] - fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { - (**self).read_vectored(bufs) - } - - #[inline] - fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - (**self).read_to_end(buf) - } - - #[inline] - fn read_to_string(&mut self, buf: &mut String) -> io::Result { - (**self).read_to_string(buf) - } - - #[inline] - fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - (**self).read_exact(buf) - } -} - -impl io::Write for Ivar -where - ::Target: io::Write, -{ - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - (**self).write(buf) - } - - #[inline] - fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { - (**self).write_vectored(bufs) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - (**self).flush() - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - (**self).write_all(buf) - } - - #[inline] - fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { - (**self).write_fmt(fmt) - } -} - -impl io::Seek for Ivar -where - ::Target: io::Seek, -{ - #[inline] - fn seek(&mut self, pos: io::SeekFrom) -> io::Result { - (**self).seek(pos) - } - - #[inline] - fn stream_position(&mut self) -> io::Result { - (**self).stream_position() - } -} - -impl io::BufRead for Ivar -where - ::Target: io::BufRead, -{ - #[inline] - fn fill_buf(&mut self) -> io::Result<&[u8]> { - (**self).fill_buf() - } - - #[inline] - fn consume(&mut self, amt: usize) { - (**self).consume(amt) - } - - #[inline] - fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { - (**self).read_until(byte, buf) - } - - #[inline] - fn read_line(&mut self, buf: &mut String) -> io::Result { - (**self).read_line(buf) - } -} - -impl Future for Ivar -where - Self: Unpin, - ::Target: Future + Unpin, -{ - type Output = <::Target as Future>::Output; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - <::Target as Future>::poll(Pin::new(&mut *self), cx) - } -} - -// TODO: impl Fn traits, CoerceUnsized, Stream and so on when stabilized diff --git a/crates/objc2/src/declare/mod.rs b/crates/objc2/src/declare/mod.rs index 6f61fc867..fd533e81e 100644 --- a/crates/objc2/src/declare/mod.rs +++ b/crates/objc2/src/declare/mod.rs @@ -4,12 +4,6 @@ //! variables and methods can then be added before the class is ultimately //! registered. -mod ivar; -mod ivar_bool; -mod ivar_drop; -mod ivar_encode; -mod ivar_forwarding_impls; - use alloc::format; use alloc::string::ToString; use core::mem; @@ -24,11 +18,6 @@ use crate::runtime::{AnyClass, AnyObject, AnyProtocol, Bool, Imp, MethodImplemen use crate::sel; use crate::Message; -pub use ivar::{InnerIvarType, Ivar, IvarType}; -pub use ivar_bool::IvarBool; -pub use ivar_drop::IvarDrop; -pub use ivar_encode::IvarEncode; - fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString { // First two arguments are always self and the selector let mut types = format!("{ret}{}{}", <*mut AnyObject>::ENCODING, Sel::ENCODING); @@ -399,6 +388,10 @@ impl ClassBuilder { unsafe { self.add_ivar_inner::(name, &T::ENCODING) } } + pub(crate) unsafe fn add_ivar_inner(&mut self, name: &str, encoding: &Encoding) { + unsafe { self.add_ivar_inner_mono(name, mem::size_of::(), T::LOG2_ALIGNMENT, encoding) } + } + // Monomorphized version unsafe fn add_ivar_inner_mono( &mut self, @@ -430,21 +423,6 @@ impl ClassBuilder { assert!(success.as_bool(), "failed to add ivar {name}"); } - unsafe fn add_ivar_inner(&mut self, name: &str, encoding: &Encoding) { - unsafe { self.add_ivar_inner_mono(name, mem::size_of::(), T::LOG2_ALIGNMENT, encoding) } - } - - /// Adds an instance variable from an [`IvarType`]. - /// - /// - /// # Panics - /// - /// Same as [`ClassBuilder::add_ivar`]. - pub fn add_static_ivar(&mut self) { - // SAFETY: The encoding is correct - unsafe { self.add_ivar_inner::(T::NAME, &T::Type::ENCODING) } - } - /// Adds the given protocol to self. /// /// # Panics @@ -617,7 +595,8 @@ mod tests { use crate::rc::Id; use crate::runtime::{NSObject, NSObjectProtocol}; use crate::{ - declare_class, extern_methods, msg_send, msg_send_id, test_utils, ClassType, ProtocolType, + declare_class, extern_methods, msg_send, msg_send_id, test_utils, ClassType, DeclaredClass, + ProtocolType, }; #[test] @@ -905,6 +884,8 @@ mod tests { type Mutability = Immutable; const NAME: &'static str = "TestInheritedNSObjectMethodsWork"; } + + impl DeclaredClass for Custom {} ); extern_methods!( diff --git a/crates/objc2/src/lib.rs b/crates/objc2/src/lib.rs index 1dbaeb38e..c6cec5447 100644 --- a/crates/objc2/src/lib.rs +++ b/crates/objc2/src/lib.rs @@ -190,7 +190,7 @@ pub use objc_sys as ffi; #[doc(no_inline)] pub use self::encode::{Encode, Encoding, RefEncode}; -pub use self::top_level_traits::{ClassType, Message, ProtocolType}; +pub use self::top_level_traits::{ClassType, DeclaredClass, Message, ProtocolType}; #[cfg(feature = "objc2-proc-macros")] #[doc(hidden)] diff --git a/crates/objc2/src/macros/__field_helpers.rs b/crates/objc2/src/macros/__field_helpers.rs deleted file mode 100644 index b545f17c6..000000000 --- a/crates/objc2/src/macros/__field_helpers.rs +++ /dev/null @@ -1,355 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! __emit_struct_and_ivars { - ( - ($(#[$m:meta])*) - ($v:vis) - ($($struct:tt)*) - ($($ivar_helper_module_v:vis mod $ivar_helper_module:ident)?) - ($($fields:tt)*) - ($($parsed_fields:tt)*) - ) => { - $crate::__parse_fields! { - ($($fields)*) - ($($ivar_helper_module_v mod $ivar_helper_module)?) - () () // No parsed ivars - ($($parsed_fields)*) - - ($crate::__emit_struct) - ($(#[$m])*) - ($v) - ($($struct)*) - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __emit_struct { - ( - ($(#[$m:meta])*) - ($v:vis) - ($($struct:tt)*) - - ($($fields:tt)*) - ) => { - $(#[$m])* - #[repr(C)] - $v struct $($struct)* { - // These are at this point all zero-sized. - $($fields)* - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __parse_fields { - // Base-case, no ivars, no module - ( - () // No more fields left - () // No module - () () // No ivars - ($($parsed_fields:tt)*) - - ($out_macro:path) - $($macro_args:tt)* - ) => { - $out_macro! { - $($macro_args)* - - ($($parsed_fields)*) - } - }; - - // Base-case, has ivars, no module - ( - () // No more fields left - () // No module - ($($ivar_output:tt)+) ($($ivar_type_name:tt)+) - ($($parsed_fields:tt)*) - - ($out_macro:path) - $($macro_args:tt)* - ) => { - $crate::__macro_helpers::compile_error!( - "must specify an ivar module when the type has ivars" - ); - - $($ivar_output)+ - - $out_macro! { - $($macro_args)* - - ($($parsed_fields)*) - } - }; - - // Base-case, no ivars, has module - ( - () // No more fields left - ($ivar_helper_module_v:vis mod $ivar_helper_module:ident) - () () // No ivars - ($($parsed_fields:tt)*) - - ($out_macro:path) - $($macro_args:tt)* - ) => { - $ivar_helper_module_v mod $ivar_helper_module { - $crate::__macro_helpers::compile_error!( - "no need to specify an ivar module when the type has no ivars" - ); - - pub(super) fn __objc2_declare_ivars( - __objc2_builder: &mut $crate::__macro_helpers::ClassBuilderHelper, - ) {} - } - - $out_macro! { - $($macro_args)* - - ($($parsed_fields)*) - } - }; - - // Base-case, has ivars, has module - ( - () // No more fields left - ($ivar_helper_module_v:vis mod $ivar_helper_module:ident) - ($($ivar_output:tt)+) ($($ivar_type_name:ident)+) - ($($parsed_fields:tt)*) - - ($out_macro:path) - $($macro_args:tt)* - ) => { - $ivar_helper_module_v mod $ivar_helper_module { - use super::*; - - $($ivar_output)+ - - pub(super) fn __objc2_declare_ivars( - __objc2_builder: &mut $crate::__macro_helpers::ClassBuilderHelper, - ) { - // Ivars - $( - __objc2_builder.add_static_ivar::<$ivar_type_name>(); - )+ - } - } - - $out_macro! { - $($macro_args)* - - ($($parsed_fields)*) - } - }; - - // PhantomData - ( - ( - $(#[$m:meta])* - $vis:vis $field_name:ident: PhantomData<$ty:ty> - $(, $($rest_fields:tt)*)? - ) - ($($ivar_helper_module_v:vis mod $ivar_helper_module:ident)?) - ($($ivar_output:tt)*) ($($ivar_type_name:ident)*) - ($($parsed_fields:tt)*) - - ($out_macro:path) - $($macro_args:tt)* - ) => { - $crate::__parse_fields! { - ($($($rest_fields)*)?) - ($($ivar_helper_module_v mod $ivar_helper_module)?) - ($($ivar_output)*) ($($ivar_type_name)*) - ( - $($parsed_fields)* - - // A user could have defined their own PhantomData-like, type, - // and then tried to use it here, which we would accept, but - // which wouldn't necessarily be zero-sized! - // - // Hence we wrap it in an extra PhantomData, to ensure it is - // (while still not generating "unused imports" for the user). - $(#[$m])* - $vis $field_name: $crate::__macro_helpers::PhantomData>, - ) - - ($out_macro) - $($macro_args)* - } - }; - - // IvarDrop - ( - ( - $(#[$m:meta])* - $vis:vis $field_name:ident: IvarDrop<$ty:ty, $ivar_name:literal> - $(, $($rest_fields:tt)*)? - ) - ($($ivar_helper_module_v:vis mod $ivar_helper_module:ident)?) - ($($ivar_output:tt)*) ($($ivar_type_name:ident)*) - ($($parsed_fields:tt)*) - - ($out_macro:path) - $($macro_args:tt)* - ) => { - $crate::__parse_fields! { - ($($($rest_fields)*)?) - ($($ivar_helper_module_v mod $ivar_helper_module)?) - ( - $($ivar_output)* - - #[allow(non_camel_case_types)] - #[allow(unreachable_pub)] - pub struct $field_name { - __priv: (), - } - - // SAFETY: - // - The ivars are in a type used as an Objective-C object. - // - The ivar is added to the class in `__objc2_declare_ivars`. - // - Caller upholds that the ivars are properly initialized. - unsafe impl $crate::declare::IvarType for $field_name { - type Type = IvarDrop<$ty>; - const NAME: &'static $crate::__macro_helpers::str = $ivar_name; - } - ) ($($ivar_type_name)* $field_name) - ( - $($parsed_fields)* - - $(#[$m])* - $vis $field_name: $crate::declare::Ivar<$($ivar_helper_module ::)? $field_name>, - ) - - ($out_macro) - $($macro_args)* - } - }; - - // IvarEncode - ( - ( - $(#[$m:meta])* - $vis:vis $field_name:ident: IvarEncode<$ty:ty, $ivar_name:literal> - $(, $($rest_fields:tt)*)? - ) - ($($ivar_helper_module_v:vis mod $ivar_helper_module:ident)?) - ($($ivar_output:tt)*) ($($ivar_type_name:ident)*) - ($($parsed_fields:tt)*) - - ($out_macro:path) - $($macro_args:tt)* - ) => { - $crate::__parse_fields! { - ($($($rest_fields)*)?) - ($($ivar_helper_module_v mod $ivar_helper_module)?) - ( - $($ivar_output)* - - #[allow(non_camel_case_types)] - #[allow(unreachable_pub)] - pub struct $field_name { - __priv: (), - } - - // SAFETY: See above - unsafe impl $crate::declare::IvarType for $field_name { - type Type = IvarEncode<$ty>; - const NAME: &'static $crate::__macro_helpers::str = $ivar_name; - } - ) ($($ivar_type_name)* $field_name) - ( - $($parsed_fields)* - - $(#[$m])* - $vis $field_name: $crate::declare::Ivar<$($ivar_helper_module ::)? $field_name>, - ) - - ($out_macro) - $($macro_args)* - } - }; - - // IvarBool - ( - ( - $(#[$m:meta])* - $vis:vis $field_name:ident: IvarBool<$ivar_name:literal> - $(, $($rest_fields:tt)*)? - ) - ($($ivar_helper_module_v:vis mod $ivar_helper_module:ident)?) - ($($ivar_output:tt)*) ($($ivar_type_name:ident)*) - ($($parsed_fields:tt)*) - - ($out_macro:path) - $($macro_args:tt)* - ) => { - $crate::__parse_fields! { - ($($($rest_fields)*)?) - ($($ivar_helper_module_v mod $ivar_helper_module)?) - ( - $($ivar_output)* - - #[allow(non_camel_case_types)] - #[allow(unreachable_pub)] - pub struct $field_name { - __priv: (), - } - - // SAFETY: See above - unsafe impl $crate::declare::IvarType for $field_name { - type Type = IvarBool; - const NAME: &'static $crate::__macro_helpers::str = $ivar_name; - } - ) ($($ivar_type_name)* $field_name) - ( - $($parsed_fields)* - - $(#[$m])* - $vis $field_name: $crate::declare::Ivar<$($ivar_helper_module ::)? $field_name>, - ) - - ($out_macro) - $($macro_args)* - } - }; - - // Invalid type - ( - ( - $(#[$m:meta])* - $vis:vis $field_name:ident: $ty:ty - $(, $($rest_fields:tt)*)? - ) - ($($ivar_helper_module_v:vis mod $ivar_helper_module:ident)?) - ($($ivar_output:tt)*) ($($ivar_type_name:ident)*) - ($($parsed_fields:tt)*) - - ($out_macro:path) - $($macro_args:tt)* - ) => { - $crate::__macro_helpers::compile_error!($crate::__macro_helpers::concat!( - "invalid type ", - $crate::__macro_helpers::stringify!($ty), - " in field ", - $crate::__macro_helpers::stringify!($field_name), - ". Type must be either `PhantomData`, `IvarDrop`, `IvarBool` or `IvarEncode`." - )); - - $crate::__parse_fields! { - ($($($rest_fields)*)?) - ($($ivar_helper_module_v mod $ivar_helper_module)?) - ($($ivar_output)*) ($($ivar_type_name)*) - ( - $($parsed_fields)* - - $(#[$m])* - $vis $field_name: $ty, - ) - - ($out_macro) - $($macro_args)* - } - } -} diff --git a/crates/objc2/src/macros/declare_class.rs b/crates/objc2/src/macros/declare_class.rs index 2b710477c..74741af71 100644 --- a/crates/objc2/src/macros/declare_class.rs +++ b/crates/objc2/src/macros/declare_class.rs @@ -17,8 +17,9 @@ /// # Specification /// /// This macro consists of roughly four parts: -/// - The type and ivar definition. +/// - The type declaration. /// - The [`ClassType`] implementation. +/// - The [`DeclaredClass`] definition. /// - Any number of method definitions. /// - Any number of protocol implementations. /// @@ -30,35 +31,23 @@ /// [`extern_methods!`]: crate::extern_methods /// /// -/// ## Ivar definition +/// ## Type declaration /// -/// The type definition works a lot like [`extern_class!`] (including the +/// The type declaration works a lot like [`extern_class!`] (including the /// allowed attributes), with the added capability that struct fields are /// automatically defined as custom instance variables, which are then /// accessible on instances of the class. (E.g. you can use `self.my_ivar` as /// if the class was a normal Rust struct). /// -/// The instance variables are specified as such: -/// - [`IvarEncode`](crate::declare::IvarEncode) -/// - [`IvarBool<"my_crate_ivar">`](crate::declare::IvarBool) -/// - [`IvarDrop`](crate::declare::IvarDrop) -/// -/// This is special syntax that will be used to generate helper types that -/// implement [`declare::IvarType`], which is then used inside the new struct. -/// -/// Instance variable names must be unique, and must not conflict with any -/// superclass' instance variables - this means is is good practice to name -/// them with a prefix of your crate name, or similar. -/// -/// [`declare::IvarType`]: crate::declare::IvarType +/// Additionally TODO /// /// /// ## `ClassType` implementation /// -/// This also resembles that in [`extern_class!`], except that -/// [`ClassType::NAME`] must be specified, and it must be unique across the -/// entire application. Good practice here is to include your crate name in -/// the prefix. +/// This resembles that in [`extern_class!`], except that [`ClassType::NAME`] +/// must be specified, and it must be unique across the entire application. +/// +/// Good practice here is to include your crate name in the prefix. /// /// The class is guaranteed to have been created and registered with the /// Objective-C runtime after the [`ClassType::class`] function has been @@ -71,6 +60,25 @@ /// [`ClassType::class`]: crate::ClassType::class /// /// +/// ## `DeclaredClass` definition +/// +/// TODO +/// +/// The instance variables are specified as such: +/// - [`IvarEncode`](crate::declare::IvarEncode) +/// - [`IvarBool<"my_crate_ivar">`](crate::declare::IvarBool) +/// - [`IvarDrop`](crate::declare::IvarDrop) +/// +/// This is special syntax that will be used to generate helper types that +/// implement [`declare::IvarType`], which is then used inside the new struct. +/// +/// Instance variable names must be unique, and must not conflict with any +/// superclass' instance variables - this means is is good practice to name +/// them with a prefix of your crate name, or similar. +/// +/// [`declare::IvarType`]: crate::declare::IvarType +/// +/// /// ## Method definitions /// /// Within the `impl` block you can define two types of functions; @@ -190,19 +198,21 @@ /// # #[cfg(available_in_icrate)] /// use icrate::Foundation::{NSCopying, NSObject, NSObjectProtocol, NSZone}; /// use objc2::declare::{Ivar, IvarDrop, IvarEncode}; -/// use objc2::rc::Id; +/// use objc2::rc::{Allocated, Id}; /// use objc2::{ -/// declare_class, extern_protocol, msg_send, msg_send_id, mutability, ClassType, ProtocolType, +/// declare_class, extern_protocol, msg_send, msg_send_id, mutability, ClassType, +/// DeclaredClass, ProtocolType, /// }; /// -/// declare_class!( -/// struct MyCustomObject { -/// foo: IvarEncode, -/// pub bar: IvarEncode, -/// object: IvarDrop, "_object">, -/// } +/// #[derive(Clone)] +/// struct Ivars { +/// foo: u8, +/// bar: c_int, +/// object: Id, +/// } /// -/// mod ivars; +/// declare_class!( +/// struct MyCustomObject; /// /// unsafe impl ClassType for MyCustomObject { /// type Super = NSObject; @@ -210,40 +220,29 @@ /// const NAME: &'static str = "MyCustomObject"; /// } /// +/// impl DeclaredClass for MyCustomObject { +/// type Ivars = Ivars; +/// } +/// /// unsafe impl MyCustomObject { /// #[method(initWithFoo:)] -/// fn init_with(this: &mut Self, foo: u8) -> Option<&mut Self> { -/// let this: Option<&mut Self> = unsafe { -/// msg_send![super(this), init] -/// }; -/// -/// this.map(|this| { -/// // Initialize instance variables -/// -/// // Some types like `u8`, `bool`, `Option>` and -/// // `Option>` are safe to zero-initialize, and we can -/// // write to the variable as normal: -/// *this.foo = foo; -/// *this.bar = 42; -/// -/// // For others like `&u8`, `Box` or `Id`, we have to -/// // initialize them with `Ivar::write`: -/// Ivar::write(&mut this.object, NSObject::new()); -/// -/// // All the instance variables have been initialized; our -/// // initializer is sound -/// this -/// }) +/// fn init_with(this: Allocated, foo: u8) -> Option> { +/// let this = this.set_ivars(Ivars { +/// foo, +/// bar: 42, +/// object: NSObject::new(), +/// }); +/// unsafe { msg_send![super(this), init] } /// } /// /// #[method(foo)] /// fn __get_foo(&self) -> u8 { -/// *self.foo +/// *self.ivars().foo /// } /// /// #[method_id(object)] /// fn __get_object(&self) -> Id { -/// self.object.clone() +/// self.ivars().object.clone() /// } /// /// #[method(myClassMethod)] @@ -253,9 +252,8 @@ /// # /// # #[method_id(copyWithZone:)] /// # fn copyWithZone(&self, _zone: *const NSZone) -> Id { -/// # let mut obj = Self::new(*self.foo); -/// # *obj.bar = *self.bar; -/// # obj +/// # let new = Self::alloc().set_ivars(self.ivars().clone()); +/// # unsafe { msg_send![super(new), init] } /// # } /// } /// @@ -265,9 +263,8 @@ /// unsafe impl NSCopying for MyCustomObject { /// #[method_id(copyWithZone:)] /// fn copyWithZone(&self, _zone: *const NSZone) -> Id { -/// let mut obj = Self::new(*self.foo); -/// *obj.bar = *self.bar; -/// obj +/// let new = Self::alloc().set_ivars(self.ivars().clone()); +/// unsafe { msg_send![super(new), init] } /// } /// /// // If we have tried to add other methods here, or had forgotten @@ -316,22 +313,18 @@ /// ```text /// #import /// -/// @interface MyCustomObject: NSObject { -/// // Public ivar -/// int bar; -/// } -/// +/// @interface MyCustomObject: NSObject /// - (instancetype)initWithFoo:(uint8_t)foo; /// - (uint8_t)foo; /// - (NSObject*)object; /// + (BOOL)myClassMethod; -/// /// @end /// /// /// @implementation MyCustomObject { -/// // Private ivar +/// // Instance variables /// uint8_t foo; +/// int bar; /// NSObject* _Nonnull object; /// } /// @@ -360,9 +353,10 @@ /// // NSCopying /// /// - (id)copyWithZone:(NSZone *)_zone { -/// MyCustomObject* obj = [[MyCustomObject alloc] initWithFoo: self->foo]; -/// obj->bar = self->bar; -/// return obj; +/// MyCustomObject* new = [[MyCustomObject alloc] initWithFoo: self->foo]; +/// new->bar = self->bar; +/// new->obj = self->obj; +/// return new; /// } /// /// @end @@ -371,16 +365,11 @@ #[doc(alias = "@implementation")] #[macro_export] macro_rules! declare_class { - // With ivar helper { $(#[$m:meta])* - $v:vis struct $name:ident { - $($fields:tt)* - } - - $ivar_helper_module_v:vis mod $ivar_helper_module:ident; + $v:vis struct $name:ident; - unsafe impl ClassType for $for:ty { + unsafe impl ClassType for $for_class:ty { $(#[inherits($($inheritance_rest:ty),+)])? type Super = $superclass:ty; @@ -389,238 +378,108 @@ macro_rules! declare_class { const NAME: &'static str = $name_const:expr; } - $($impls:tt)* - } => { - $crate::__emit_struct_and_ivars! { - ($(#[$m])*) - ($v) - ($name) - ($ivar_helper_module_v mod $ivar_helper_module) - ($($fields)*) - ( - // Superclasses are deallocated by calling `[super dealloc]`. - __superclass: $crate::__macro_helpers::ManuallyDrop<$superclass>, - ) - } - - $crate::__declare_class_inner! { - ($ivar_helper_module) - - unsafe impl ClassType for $for { - $(#[inherits($($inheritance_rest),+)])? - type Super = $superclass; - - type Mutability = $mutability; - - const NAME: &'static str = $name_const; - } - - $($impls)* - } - }; - - // No ivar helper - { - $(#[$m:meta])* - $v:vis struct $name:ident { - $($fields:tt)* - } - - unsafe impl ClassType for $for:ty { - $(#[inherits($($inheritance_rest:ty),+)])? - type Super = $superclass:ty; - - type Mutability = $mutability:ty; - - const NAME: &'static str = $name_const:expr; + impl DeclaredClass for $for_declared:ty { + $(type Ivars = $ivars:ty;)? } $($impls:tt)* } => { - $crate::__emit_struct_and_ivars! { - ($(#[$m])*) - ($v) - ($name) - () - ($($fields)*) - ( - // Superclasses are deallocated by calling `[super dealloc]`. - __superclass: $crate::__macro_helpers::ManuallyDrop<$superclass>, - ) + $(#[$m])* + #[repr(C)] + $v struct $name { + // Superclasses are deallocated by calling `[super dealloc]`. + __superclass: $crate::__macro_helpers::ManuallyDrop<$superclass>, + // Include ivars for proper auto traits. + __ivars: $crate::__macro_helpers::PhantomData<::Ivars>, } - $crate::__declare_class_inner! { - () - - unsafe impl ClassType for $for { - $(#[inherits($($inheritance_rest),+)])? - type Super = $superclass; + $crate::__extern_class_impl_traits! { + // SAFETY: Upheld by caller + unsafe impl () for $for_class { + INHERITS = [$superclass, $($($inheritance_rest,)+)? $crate::runtime::AnyObject]; - type Mutability = $mutability; + fn as_super(&self) { + &*self.__superclass + } - const NAME: &'static str = $name_const; + fn as_super_mut(&mut self) { + &mut *self.__superclass + } } - - $($impls)* } - }; - - // Allow declaring class with no instance variables - { - $(#[$m:meta])* - $v:vis struct $name:ident; - unsafe impl ClassType for $for:ty { - $(#[inherits($($inheritance_rest:ty),+)])? - type Super = $superclass:ty; + // Anonymous block to hide the shared statics + const _: () = { + static mut __OBJC2_CLASS: $crate::__macro_helpers::MaybeUninit<&'static $crate::runtime::AnyClass> = $crate::__macro_helpers::MaybeUninit::uninit(); + static mut __OBJC2_IVAR_OFFSET: $crate::__macro_helpers::MaybeUninit<$crate::__macro_helpers::isize> = $crate::__macro_helpers::MaybeUninit::uninit(); + static mut __OBJC2_DROP_FLAG_OFFSET: $crate::__macro_helpers::MaybeUninit<$crate::__macro_helpers::isize> = $crate::__macro_helpers::MaybeUninit::uninit(); - type Mutability = $mutability:ty; - - const NAME: &'static str = $name_const:expr; - } - - $($impls:tt)* - } => { - $crate::__emit_struct_and_ivars! { - ($(#[$m])*) - ($v) - ($name) - () - () - ( - // Superclasses are deallocated by calling `[super dealloc]`. - __superclass: $crate::__macro_helpers::ManuallyDrop<$superclass>, - ) - } - - $crate::__declare_class_inner! { - () - - unsafe impl ClassType for $for { - $(#[inherits($($inheritance_rest),+)])? + // Creation + unsafe impl ClassType for $for_class { type Super = $superclass; - type Mutability = $mutability; + const NAME: &'static $crate::__macro_helpers::str = $name_const; - const NAME: &'static str = $name_const; - } + fn class() -> &'static $crate::runtime::AnyClass { + $crate::__macro_helpers::assert_mutability_matches_superclass_mutability::(); - $($impls)* - } - }; -} + // TODO: Use `core::cell::LazyCell` + static REGISTER_CLASS: $crate::__macro_helpers::Once = $crate::__macro_helpers::Once::new(); -#[doc(hidden)] -#[macro_export] -macro_rules! __declare_class_inner { - { - ($($ivar_helper_module:ident)?) + REGISTER_CLASS.call_once(|| { + let mut __objc2_builder = $crate::__macro_helpers::ClassBuilderHelper::::new(); - unsafe impl ClassType for $for:ty { - $(#[inherits($($inheritance_rest:ty),+)])? - type Super = $superclass:ty; + // Implement protocols and methods + $crate::__declare_class_register_impls! { + (__objc2_builder) + $($impls)* + } - type Mutability = $mutability:ty; + let (__objc2_cls, __objc2_ivar_offset, __objc2_drop_flag_offset) = __objc2_builder.register(); - const NAME: &'static str = $name_const:expr; - } + // SAFETY: Modification is ensured by `Once` to happen + // before any access to the variables. + unsafe { + __OBJC2_CLASS.write(__objc2_cls); + __OBJC2_IVAR_OFFSET.write(__objc2_ivar_offset); + __OBJC2_DROP_FLAG_OFFSET.write(__objc2_drop_flag_offset); + } + }); - $($impls:tt)* - } => { - $crate::__extern_class_impl_traits! { - // SAFETY: Upheld by caller - unsafe impl () for $for { - INHERITS = [$superclass, $($($inheritance_rest,)+)? $crate::runtime::AnyObject]; + // SAFETY: We just registered the class, so is now available + unsafe { __OBJC2_CLASS.assume_init() } + } - fn as_super(&self) { + #[inline] + fn as_super(&self) -> &Self::Super { &*self.__superclass } - fn as_super_mut(&mut self) { + #[inline] + fn as_super_mut(&mut self) -> &mut Self::Super { &mut *self.__superclass } } - } - // Creation - unsafe impl ClassType for $for { - type Super = $superclass; - type Mutability = $mutability; - const NAME: &'static $crate::__macro_helpers::str = $name_const; - - fn class() -> &'static $crate::runtime::AnyClass { - $crate::__macro_helpers::assert_mutability_matches_superclass_mutability::(); - - // TODO: Use `core::cell::LazyCell` - static REGISTER_CLASS: $crate::__macro_helpers::Once = $crate::__macro_helpers::Once::new(); - - REGISTER_CLASS.call_once(|| { - let mut __objc2_builder = $crate::__macro_helpers::ClassBuilderHelper::::new(); - - $($ivar_helper_module::__objc2_declare_ivars(&mut __objc2_builder);)? - - // See the following links for more details: - // - - // - - // - - unsafe extern "C" fn __objc2_dealloc(__objc2_self: *mut $for, __objc2_cmd: $crate::runtime::Sel) { - // SAFETY: Ivars are explicitly designed to always - // be valid to drop, and since this is the - // `dealloc` method, we know the ivars are never - // going to be touched again. - // - // This also runs any `Drop` impl that the type may - // have. - unsafe { $crate::__macro_helpers::drop_in_place(__objc2_self) }; - - // The superclass' "marker" that this stores is - // wrapped in `ManuallyDrop`, instead we drop it by - // calling the superclass' `dealloc` method. - // - // Note: ARC does this automatically, which means - // most Objective-C code in the wild don't contain - // this; but we _are_ ARC, so we must do this. - unsafe { - $crate::__macro_helpers::MsgSend::send_super_message_static( - __objc2_self, - __objc2_cmd, // Reuse the selector - (), // No arguments - ) - } - } - - if $crate::__macro_helpers::needs_drop::() { - unsafe { - __objc2_builder.add_method( - $crate::sel!(dealloc), - __objc2_dealloc as unsafe extern "C" fn(_, _), - ); - } - } + unsafe impl DeclaredClass for $for_declared { + type Ivars = $crate::__select_ivars!($($ivars)?); - // Implement protocols and methods - $crate::__declare_class_register_impls! { - (__objc2_builder) - $($impls)* - } - - let _cls = __objc2_builder.register(); - }); - - // We just registered the class, so it should be available - $crate::runtime::AnyClass::get(::NAME).unwrap() - } + #[inline] + fn __ivars_offset() -> $crate::__macro_helpers::isize { + // SAFETY: Accessing the offset is guaranteed to only be + // done after the class has been initialized. + unsafe { __OBJC2_IVAR_OFFSET.assume_init() } + } - #[inline] - fn as_super(&self) -> &Self::Super { - &*self.__superclass - } + #[inline] + fn __drop_flag_offset() -> $crate::__macro_helpers::isize { + // SAFETY: Same as above. + unsafe { __OBJC2_DROP_FLAG_OFFSET.assume_init() } + } - #[inline] - fn as_super_mut(&mut self) -> &mut Self::Super { - &mut *self.__superclass + const __INNER: () = (); } - } + }; // Methods $crate::__declare_class_output_impls! { @@ -640,6 +499,18 @@ macro_rules! __select_name { }; } +#[doc(hidden)] +#[macro_export] +macro_rules! __select_ivars { + ($ivars:ty) => { + $ivars + }; + () => { + // Default ivars to empty tuple + () + }; +} + #[doc(hidden)] #[macro_export] macro_rules! __declare_class_output_impls { diff --git a/crates/objc2/src/macros/mod.rs b/crates/objc2/src/macros/mod.rs index a5adb07a9..473156ac5 100644 --- a/crates/objc2/src/macros/mod.rs +++ b/crates/objc2/src/macros/mod.rs @@ -1,5 +1,4 @@ mod __attribute_helpers; -mod __field_helpers; mod __method_msg_send; mod __msg_send_parse; mod __rewrite_self_param; diff --git a/crates/objc2/src/rc/allocated.rs b/crates/objc2/src/rc/allocated.rs index fee507913..ec704f9b4 100644 --- a/crates/objc2/src/rc/allocated.rs +++ b/crates/objc2/src/rc/allocated.rs @@ -1,10 +1,14 @@ use core::fmt; use core::marker::PhantomData; use core::mem::ManuallyDrop; +use core::ptr::NonNull; +use crate::__macro_helpers::declared_ivars::initialize_ivars; use crate::mutability::IsMutable; use crate::runtime::{objc_release_fast, AnyObject}; -use crate::Message; +use crate::{DeclaredClass, Message}; + +use super::PartialInit; /// An Objective-C object that has been allocated, but not initialized. /// @@ -123,6 +127,34 @@ impl Allocated { let this = ManuallyDrop::new(this); this.ptr as *mut T } + + /// Initialize the instance variables for this object. + /// + /// TODO + // + // 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: DeclaredClass, + { + let ptr: NonNull = match NonNull::new(ManuallyDrop::new(self).ptr as *mut _) { + Some(ptr) => ptr, + // TODO: Should we just return a NULL PartialInit here??? + None => panic!("tried to initialize instance variables on NULL allocated object"), + }; + + // 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 transfered ownership over +1 retain count. + // - The instance variables for this class have been intialized above. + unsafe { PartialInit::new(ptr) } + } } impl Drop for Allocated { diff --git a/crates/objc2/src/rc/id.rs b/crates/objc2/src/rc/id.rs index 7398c9c2d..82e360c71 100644 --- a/crates/objc2/src/rc/id.rs +++ b/crates/objc2/src/rc/id.rs @@ -837,7 +837,7 @@ mod tests { use crate::mutability::{Immutable, Mutable}; use crate::rc::{__RcTestObject, __ThreadTestData, autoreleasepool}; use crate::runtime::{AnyObject, NSObject}; - use crate::{declare_class, msg_send}; + use crate::{declare_class, msg_send, DeclaredClass}; #[test] fn auto_traits() { @@ -851,6 +851,8 @@ mod tests { type Mutability = $mutability; const NAME: &'static str = concat!(stringify!($name), "Test"); } + + impl DeclaredClass for $name {} ); }; } diff --git a/crates/objc2/src/rc/id_traits.rs b/crates/objc2/src/rc/id_traits.rs index 4ab267273..2bef09a79 100644 --- a/crates/objc2/src/rc/id_traits.rs +++ b/crates/objc2/src/rc/id_traits.rs @@ -130,7 +130,7 @@ mod tests { use super::*; use crate::mutability::Mutable; use crate::runtime::NSObject; - use crate::{declare_class, msg_send_id, ClassType}; + use crate::{declare_class, msg_send_id, ClassType, DeclaredClass}; declare_class!( #[derive(PartialEq, Eq, Hash, Debug)] @@ -141,6 +141,8 @@ mod tests { type Mutability = Mutable; const NAME: &'static str = "MyCustomCollection"; } + + impl DeclaredClass for Collection {} ); impl DefaultId for Collection { diff --git a/crates/objc2/src/rc/mod.rs b/crates/objc2/src/rc/mod.rs index 4c6dc0953..39c89eb5b 100644 --- a/crates/objc2/src/rc/mod.rs +++ b/crates/objc2/src/rc/mod.rs @@ -53,6 +53,7 @@ mod autorelease; mod id; mod id_forwarding_impls; mod id_traits; +mod partial_init; mod test_object; mod weak_id; @@ -62,6 +63,7 @@ pub use self::autorelease::{ }; pub use self::id::Id; pub use self::id_traits::{DefaultId, IdFromIterator, IdIntoIterator}; +pub use self::partial_init::PartialInit; #[doc(hidden)] pub use self::test_object::{__RcTestObject, __ThreadTestData}; pub use self::weak_id::WeakId; diff --git a/crates/objc2/src/rc/partial_init.rs b/crates/objc2/src/rc/partial_init.rs new file mode 100644 index 000000000..acf7af668 --- /dev/null +++ b/crates/objc2/src/rc/partial_init.rs @@ -0,0 +1,60 @@ +use core::fmt; +use core::marker::PhantomData; +use core::ptr::NonNull; + +use crate::runtime::objc_release_fast; +use crate::DeclaredClass; + +/// A marker type that can be used to indicate that the object has been +/// initialized in the superclass, but not the current class. +/// +/// This is returned by `msg_send_id!` `super` calls. +// +// Internally, this is very similar to `rc::Allocated`, except that we have +// different guarantees on the validity of the object. +// +// TODO: We don't want to guarantee the layout of this just yet, as we may be +// able to move a drop flag to the stack! +#[repr(transparent)] +#[derive(Debug)] +pub struct PartialInit { + /// The partially initialized object. + /// + /// Variance is same as `Id`. + ptr: NonNull, + /// Necessary for dropck, as with `Id`. + p: PhantomData, +} + +impl PartialInit { + /// # Safety + /// + /// The caller must ensure the given object has +1 retain count, and that + /// the superclass initializer for the object behind the pointer has been + /// run, while the object itself hasn't yet been initialized. + #[inline] + pub(crate) unsafe fn new(ptr: NonNull) -> Self { + Self { + ptr, + p: PhantomData, + } + } +} + +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. + // + // Rest is same as `Id`. + unsafe { objc_release_fast(self.ptr.as_ptr().cast()) }; + } +} + +impl fmt::Pointer for PartialInit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Pointer::fmt(&self.ptr.as_ptr(), f) + } +} diff --git a/crates/objc2/src/rc/test_object.rs b/crates/objc2/src/rc/test_object.rs index 202de9997..5cc4bf555 100644 --- a/crates/objc2/src/rc/test_object.rs +++ b/crates/objc2/src/rc/test_object.rs @@ -4,7 +4,7 @@ use core::ptr; use super::{Allocated, Id}; use crate::mutability::Immutable; use crate::runtime::{NSObject, NSZone}; -use crate::{declare_class, msg_send, msg_send_id, ClassType}; +use crate::{declare_class, msg_send, msg_send_id, ClassType, DeclaredClass}; // TODO: Put tests that use this in another crate #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -70,6 +70,8 @@ declare_class!( const NAME: &'static str = "__RcTestObject"; } + impl DeclaredClass for __RcTestObject {} + unsafe impl __RcTestObject { #[method_id(newReturningNull)] fn new_returning_null() -> Option> { @@ -311,6 +313,8 @@ declare_class!( type Mutability = Immutable; const NAME: &'static str = "RcTestObjectSubclass"; } + + impl DeclaredClass for RcTestObjectSubclass {} ); #[cfg_attr(not(test), allow(unused))] diff --git a/crates/objc2/src/runtime/bool.rs b/crates/objc2/src/runtime/bool.rs index 74b4bf9c0..e97a3b355 100644 --- a/crates/objc2/src/runtime/bool.rs +++ b/crates/objc2/src/runtime/bool.rs @@ -10,7 +10,9 @@ use crate::ffi; /// soon as possible. /// /// This is FFI-safe and can be used directly with `msg_send!` and `extern` -/// functions. +/// functions as a substitute for `BOOL` in Objective-C. If your Objective-C +/// code uses C99 `_Bool`, you should use a `#[repr(transparent)]` wrapper +/// around `bool` instead. /// /// Note that this is able to contain more states than `bool` on some /// platforms, but these cases should not be relied on! diff --git a/crates/objc2/src/runtime/message_receiver.rs b/crates/objc2/src/runtime/message_receiver.rs index 7f5497394..2cb2de549 100644 --- a/crates/objc2/src/runtime/message_receiver.rs +++ b/crates/objc2/src/runtime/message_receiver.rs @@ -489,7 +489,7 @@ mod tests { use crate::rc::{Allocated, Id}; use crate::runtime::NSObject; use crate::test_utils; - use crate::{declare_class, msg_send, msg_send_id, ClassType}; + use crate::{declare_class, msg_send, msg_send_id, ClassType, DeclaredClass}; declare_class!( struct MutableObject; @@ -499,6 +499,8 @@ mod tests { type Mutability = mutability::Mutable; const NAME: &'static str = "TestMutableObject"; } + + impl DeclaredClass for MutableObject {} ); #[allow(unused)] diff --git a/crates/objc2/src/runtime/protocol_object.rs b/crates/objc2/src/runtime/protocol_object.rs index ae8c8d085..cab98a469 100644 --- a/crates/objc2/src/runtime/protocol_object.rs +++ b/crates/objc2/src/runtime/protocol_object.rs @@ -194,7 +194,7 @@ mod tests { use super::*; use crate::mutability::Mutable; use crate::runtime::{NSObject, NSObjectProtocol}; - use crate::{declare_class, extern_methods, extern_protocol, ClassType}; + use crate::{declare_class, extern_methods, extern_protocol, ClassType, DeclaredClass}; extern_protocol!( unsafe trait Foo { @@ -254,6 +254,8 @@ mod tests { const NAME: &'static str = "ProtocolTestsDummyClass"; } + impl DeclaredClass for DummyClass {} + unsafe impl NSObjectProtocol for DummyClass {} ); diff --git a/crates/objc2/src/top_level_traits.rs b/crates/objc2/src/top_level_traits.rs index 8f00b8b22..9e0cdbe70 100644 --- a/crates/objc2/src/top_level_traits.rs +++ b/crates/objc2/src/top_level_traits.rs @@ -1,3 +1,6 @@ +use core::ptr::NonNull; + +use crate::__macro_helpers::declared_ivars::get_initialized_ivar_ptr; use crate::encode::RefEncode; use crate::msg_send_id; use crate::mutability::{IsAllocableAnyThread, IsRetainable, Mutability}; @@ -307,6 +310,48 @@ pub unsafe trait ClassType: Message { // TODO: `fn mtm(&self) -> MainThreadMarker where T::Mutability: MainThreadOnly` } +/// TODO +/// +/// # Safety +/// +/// The ivar offset must be implemented correctly. +pub unsafe trait DeclaredClass: ClassType { + type Ivars: Sized; + + // TODO: Add `ivars_ptr(this: NonNull) -> NonNull`? + + #[inline] + fn ivars(&self) -> &Self::Ivars { + let ptr: NonNull = NonNull::from(self); + // SAFETY: The pointer is valid and initialized. + let ivars = unsafe { get_initialized_ivar_ptr(ptr) }; + // SAFETY: The lifetime of the instance variable is tied to the object. + unsafe { ivars.as_ref() } + } + + #[inline] + fn ivars_mut(&mut self) -> &mut Self::Ivars { + let ptr: NonNull = NonNull::from(self); + // SAFETY: The pointer is valid and initialized. + let mut ivars = unsafe { get_initialized_ivar_ptr(ptr) }; + // SAFETY: The lifetime of the instance variable is tied to the object. + // + // Mutability is safe since the object itself is mutable. See + // `ClassType::as_super_mut` for why this is safe without + // `Self: IsMutable`. + unsafe { ivars.as_mut() } + } + + #[doc(hidden)] + fn __ivars_offset() -> isize; + + #[doc(hidden)] + fn __drop_flag_offset() -> isize; + + #[doc(hidden)] + const __INNER: (); +} + /// Marks types that represent specific protocols. /// /// This is the protocol equivalent of [`ClassType`]. diff --git a/crates/objc2/tests/declare_class.rs b/crates/objc2/tests/declare_class.rs index 08a9d0622..12d5a0962 100644 --- a/crates/objc2/tests/declare_class.rs +++ b/crates/objc2/tests/declare_class.rs @@ -5,13 +5,12 @@ use objc2::declare::IvarEncode; use objc2::mutability::Immutable; use objc2::rc::Id; use objc2::runtime::NSObject; -use objc2::{declare_class, extern_methods, sel, ClassType}; +use objc2::{declare_class, extern_methods, sel, ClassType, DeclaredClass}; // Test that adding the `deprecated` attribute does not mean that warnings // when using the method internally are output. declare_class!( - // Also ensure that empty fields still work - struct DeclareClassDepreactedMethod {} + struct DeclareClassDepreactedMethod; unsafe impl ClassType for DeclareClassDepreactedMethod { type Super = NSObject; @@ -19,6 +18,8 @@ declare_class!( const NAME: &'static str = "DeclareClassDepreactedMethod"; } + impl DeclaredClass for DeclareClassDepreactedMethod {} + #[deprecated] unsafe impl DeclareClassDepreactedMethod { #[method(deprecatedOnImpl)] @@ -50,6 +51,8 @@ declare_class!( const NAME: &'static str = "DeclareClassCfg"; } + impl DeclaredClass for DeclareClassCfg {} + unsafe impl DeclareClassCfg { #[cfg(debug_assertions)] #[method(changesOnCfg1)] @@ -192,6 +195,8 @@ declare_class!( const NAME: &'static str = "TestMultipleColonSelector"; } + impl DeclaredClass for TestMultipleColonSelector {} + unsafe impl TestMultipleColonSelector { #[method(test::arg3:)] fn _test_class(arg1: i32, arg2: i32, arg3: i32) -> i32 { @@ -265,6 +270,8 @@ declare_class!( const NAME: &'static str = "DeclareClassAllTheBool"; } + impl DeclaredClass for DeclareClassAllTheBool {} + unsafe impl DeclareClassAllTheBool { #[method(returnsBool)] fn returns_bool() -> bool { @@ -328,6 +335,8 @@ declare_class!( const NAME: &'static str = "DeclareClassUnreachable"; } + impl DeclaredClass for DeclareClassUnreachable {} + // Ensure none of these warn unsafe impl DeclareClassUnreachable { #[method(unreachable)] @@ -367,27 +376,6 @@ fn test_unreachable() { let _ = DeclareClassUnreachable::class(); } -#[test] -#[should_panic = "failed to add ivar _ivar"] -fn test_duplicate_ivar() { - declare_class!( - struct DeclareClassDuplicateIvar { - ivar1: IvarEncode, - ivar2: IvarEncode, - } - - mod ivars; - - unsafe impl ClassType for DeclareClassDuplicateIvar { - type Super = NSObject; - type Mutability = Immutable; - const NAME: &'static str = "DeclareClassDuplicateIvar"; - } - ); - - let _ = DeclareClassDuplicateIvar::class(); -} - declare_class!( #[derive(Debug)] struct OutParam; @@ -398,6 +386,8 @@ declare_class!( const NAME: &'static str = "OutParam"; } + impl DeclaredClass for OutParam {} + unsafe impl OutParam { #[method(unsupported1:)] fn _unsupported1(_param: &mut Id) {} @@ -486,6 +476,8 @@ fn test_pointer_receiver_allowed() { const NAME: &'static str = "PointerReceiver"; } + impl DeclaredClass for PointerReceiver {} + unsafe impl PointerReceiver { #[method(constPtr)] fn const_ptr(_this: *const Self) {} diff --git a/crates/objc2/tests/declare_class_self.rs b/crates/objc2/tests/declare_class_self.rs index 6915e5f0f..6355e8429 100644 --- a/crates/objc2/tests/declare_class_self.rs +++ b/crates/objc2/tests/declare_class_self.rs @@ -3,7 +3,7 @@ //! do it in a context where `Self` works. use objc2::rc::{Allocated, Id}; use objc2::runtime::NSObject; -use objc2::{declare_class, mutability, ClassType}; +use objc2::{declare_class, mutability, ClassType, DeclaredClass}; trait GetSameType { type SameType: ?Sized; @@ -37,6 +37,8 @@ declare_class!( const NAME: &'static str = "MyTestObject"; } + impl DeclaredClass for MyTestObject {} + unsafe impl MyTestObject { #[method_id(initWith:)] fn init( diff --git a/crates/objc2/tests/macros_mainthreadmarker.rs b/crates/objc2/tests/macros_mainthreadmarker.rs index dff31e4c4..bf67b8614 100644 --- a/crates/objc2/tests/macros_mainthreadmarker.rs +++ b/crates/objc2/tests/macros_mainthreadmarker.rs @@ -1,6 +1,9 @@ use objc2::rc::Id; use objc2::runtime::{NSObject, NSObjectProtocol}; -use objc2::{declare_class, extern_methods, extern_protocol, mutability, ClassType, ProtocolType}; +use objc2::{ + declare_class, extern_methods, extern_protocol, mutability, ClassType, DeclaredClass, + ProtocolType, +}; extern_protocol!( #[allow(clippy::missing_safety_doc)] @@ -27,6 +30,8 @@ declare_class!( const NAME: &'static str = "MainThreadMarkerTest"; } + impl DeclaredClass for Cls {} + unsafe impl Proto for Cls { #[method(myMethod:)] fn _my_mainthreadonly_method(arg: i32) -> i32 { diff --git a/crates/objc2/tests/no_prelude.rs b/crates/objc2/tests/no_prelude.rs index cf79c10f5..c09ab224d 100644 --- a/crates/objc2/tests/no_prelude.rs +++ b/crates/objc2/tests/no_prelude.rs @@ -9,7 +9,7 @@ extern crate objc2 as new_objc2; -use new_objc2::{ClassType, ProtocolType}; +use new_objc2::{ClassType, DeclaredClass, ProtocolType}; mod core {} mod std {} @@ -82,14 +82,16 @@ type ExactSizeIterator = BogusType; type SliceConcatExt = BogusType; type ToString = BogusType; +type PhantomData = BogusType; + // Test begin below this line -type PhantomData = T; +pub struct MyCustomIvars { + ivars: i32, +} new_objc2::declare_class!( - pub struct CustomObject { - field1: PhantomData, - } + pub struct CustomObject; unsafe impl ClassType for CustomObject { type Super = new_objc2::runtime::NSObject; @@ -97,6 +99,10 @@ new_objc2::declare_class!( const NAME: &'static str = "CustomObject"; } + impl DeclaredClass for CustomObject { + type Ivars = MyCustomIvars; + } + unsafe impl CustomObject { #[method(a)] fn _a() {} diff --git a/crates/objc2/tests/track_caller.rs b/crates/objc2/tests/track_caller.rs index f50b32984..29bc8a47c 100644 --- a/crates/objc2/tests/track_caller.rs +++ b/crates/objc2/tests/track_caller.rs @@ -9,7 +9,7 @@ use std::sync::Mutex; use objc2::encode::Encode; use objc2::rc::{Allocated, Id, __RcTestObject}; use objc2::runtime::NSObject; -use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType}; +use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass}; static EXPECTED_MESSAGE: Mutex = Mutex::new(String::new()); static EXPECTED_LINE: Mutex = Mutex::new(0); @@ -202,6 +202,8 @@ declare_class!( const NAME: &'static str = "PanickingClass"; } + impl DeclaredClass for PanickingClass {} + unsafe impl PanickingClass { #[method(panic)] fn _panic() -> *mut Self { diff --git a/crates/objc2/tests/use_macros.rs b/crates/objc2/tests/use_macros.rs index 081b1e9f7..ce2ffe197 100644 --- a/crates/objc2/tests/use_macros.rs +++ b/crates/objc2/tests/use_macros.rs @@ -1,6 +1,6 @@ use objc2::mutability::Immutable; use objc2::runtime::{AnyClass, NSObject}; -use objc2::{class, declare_class, msg_send, sel, ClassType}; +use objc2::{class, declare_class, msg_send, sel, ClassType, DeclaredClass}; declare_class!( pub struct MyObject; @@ -10,6 +10,8 @@ declare_class!( type Mutability = Immutable; const NAME: &'static str = "MyObject"; } + + impl DeclaredClass for MyObject {} ); #[test] diff --git a/crates/test-assembly/crates/test_declare_class/lib.rs b/crates/test-assembly/crates/test_declare_class/lib.rs index ea181ca05..a638fe97b 100644 --- a/crates/test-assembly/crates/test_declare_class/lib.rs +++ b/crates/test-assembly/crates/test_declare_class/lib.rs @@ -1,22 +1,22 @@ //! Test assembly output of `declare_class!`. #![deny(unsafe_op_in_unsafe_fn)] #![cfg(feature = "apple")] -use core::ptr::{self}; +use core::ptr; use icrate::Foundation::{NSCopying, NSObject}; -use objc2::declare::{Ivar, IvarDrop, IvarEncode}; -use objc2::rc::Id; +use objc2::rc::{Allocated, Id}; use objc2::runtime::{AnyClass, NSZone}; -use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType}; +use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass}; + +#[derive(Clone)] +pub struct Ivars { + foo: u8, + obj: Option>, +} declare_class!( #[no_mangle] - pub struct Custom { - foo: IvarEncode, - obj: IvarDrop>, "_obj">, - } - - mod ivars; + pub struct Custom; unsafe impl ClassType for Custom { type Super = NSObject; @@ -24,19 +24,16 @@ declare_class!( const NAME: &'static str = "CustomClassName"; } + impl DeclaredClass for Custom { + type Ivars = Ivars; + } + unsafe impl Custom { #[no_mangle] - #[method(init)] - unsafe fn init(this: *mut Self) -> *mut Self { - let this: Option<&mut Self> = unsafe { msg_send![super(this), init] }; - - this.map(|this| { - Ivar::write(&mut this.foo, 42); - Ivar::write(&mut this.obj, None); - let this: *mut Self = this; - this - }) - .unwrap_or_else(ptr::null_mut) + #[method_id(init)] + fn init(this: Allocated) -> Option> { + let this = this.set_ivars(Ivars { foo: 42, obj: None }); + unsafe { msg_send_id![super(this), init] } } #[no_mangle] @@ -56,7 +53,7 @@ declare_class!( #[no_mangle] #[method_id(methodId)] fn method_id(&self) -> Option> { - self.obj.clone() + self.ivars().obj.clone() } // Test that `objc_autoreleaseReturnValue` is tail-called @@ -66,7 +63,7 @@ declare_class!( // Explicitly create outside condition let obj = NSObject::new(); if param { - self.obj.clone() + self.ivars().obj.clone() } else { Some(obj) } @@ -77,14 +74,9 @@ declare_class!( #[no_mangle] #[method_id(copyWithZone:)] fn copyWithZone(&self, _zone: *const NSZone) -> Option> { - get_obj().map(|new| { - let hack = Id::as_ptr(&new) as *mut Self; - let hack = unsafe { &mut *hack }; - - Ivar::write(&mut hack.foo, *self.foo); - Ivar::write(&mut hack.obj, self.obj.clone()); - new - }) + let new = Self::alloc(); + let new = new.set_ivars(self.ivars().clone()); + unsafe { msg_send![super(new), init] } } } ); @@ -105,9 +97,11 @@ pub fn get_obj() -> Option> { #[inline(never)] pub fn access_ivars() -> (u8, *const NSObject) { let obj = unsafe { get_obj().unwrap_unchecked() }; + let ivars = obj.ivars(); ( - *obj.foo, - (*obj.obj) + ivars.foo, + ivars + .obj .as_ref() .map(|obj| Id::as_ptr(&obj)) .unwrap_or_else(ptr::null), diff --git a/crates/tests/src/test_declare_class_protocol.rs b/crates/tests/src/test_declare_class_protocol.rs index e408e296a..1a1c9cd67 100644 --- a/crates/tests/src/test_declare_class_protocol.rs +++ b/crates/tests/src/test_declare_class_protocol.rs @@ -3,7 +3,7 @@ use icrate::Foundation::NSCopying; use objc2::mutability::Immutable; use objc2::rc::Id; use objc2::runtime::{NSObject, NSZone}; -use objc2::{declare_class, ClassType, ProtocolType}; +use objc2::{declare_class, ClassType, DeclaredClass, ProtocolType}; #[test] #[should_panic = "could not create new class TestDeclareClassDuplicate. Perhaps a class with that name already exists?"] @@ -16,6 +16,8 @@ fn test_declare_class_duplicate() { type Mutability = Immutable; const NAME: &'static str = "TestDeclareClassDuplicate"; } + + impl DeclaredClass for Custom1 {} ); declare_class!( @@ -26,6 +28,8 @@ fn test_declare_class_duplicate() { type Mutability = Immutable; const NAME: &'static str = "TestDeclareClassDuplicate"; } + + impl DeclaredClass for Custom2 {} ); let _cls = Custom1::class(); @@ -44,6 +48,8 @@ fn test_declare_class_protocol() { const NAME: &'static str = "TestDeclareClassProtocolNotFound"; } + impl DeclaredClass for Custom {} + unsafe impl NSCopying for Custom { #[method_id(copyWithZone:)] fn copy_with_zone(&self, _zone: *const NSZone) -> Id { @@ -71,6 +77,8 @@ fn test_declare_class_invalid_method() { const NAME: &'static str = "TestDeclareClassInvalidMethod"; } + impl DeclaredClass for Custom {} + unsafe impl Custom { // Override `description` with a bad return type #[method(description)] @@ -96,6 +104,8 @@ fn test_declare_class_missing_protocol_method() { const NAME: &'static str = "TestDeclareClassMissingProtocolMethod"; } + impl DeclaredClass for Custom {} + unsafe impl NSCopying for Custom { // Missing required method } @@ -116,6 +126,8 @@ fn test_declare_class_invalid_protocol_method() { const NAME: &'static str = "TestDeclareClassInvalidProtocolMethod"; } + impl DeclaredClass for Custom {} + unsafe impl NSCopying for Custom { // Override with a bad return type #[method(copyWithZone:)] @@ -143,6 +155,8 @@ fn test_declare_class_extra_protocol_method() { const NAME: &'static str = "TestDeclareClassExtraProtocolMethod"; } + impl DeclaredClass for Custom {} + unsafe impl NSCopying for Custom { #[method_id(copyWithZone:)] fn copy_with_zone(&self, _zone: *const NSZone) -> Id {