use alloc::ffi::CString;
use alloc::format;
use alloc::string::ToString;
use core::ffi::CStr;
use core::mem;
use core::mem::ManuallyDrop;
use core::ptr;
use core::ptr::NonNull;
use crate::encode::{Encode, EncodeArguments, EncodeReturn, Encoding};
use crate::ffi;
use crate::runtime::{AnyClass, AnyObject, AnyProtocol, Bool, Imp, MethodImplementation, Sel};
use crate::sel;
use crate::Message;
fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString {
let mut types = format!("{ret}{}{}", <*mut AnyObject>::ENCODING, Sel::ENCODING);
for enc in args {
use core::fmt::Write;
write!(&mut types, "{enc}").unwrap();
}
CString::new(types).unwrap()
}
trait Log2Alignment {
const LOG2_ALIGNMENT: u8;
}
impl<T> Log2Alignment for T {
const LOG2_ALIGNMENT: u8 = {
let align = mem::align_of::<T>();
assert!(
align.count_ones() == 1,
"alignment required to be a power of 2"
);
align.trailing_zeros() as u8
};
}
#[derive(Debug)]
pub struct ClassBuilder {
cls: NonNull<AnyClass>,
}
unsafe impl Send for ClassBuilder {}
unsafe impl Sync for ClassBuilder {}
impl ClassBuilder {
fn as_mut_ptr(&mut self) -> *mut AnyClass {
self.cls.as_ptr()
}
#[allow(unused)]
pub(crate) fn superclass(&self) -> Option<&AnyClass> {
unsafe { AnyClass::superclass_raw(self.cls.as_ptr()) }
}
#[allow(unused)]
fn name(&self) -> &CStr {
unsafe { AnyClass::name_raw(self.cls.as_ptr()) }
}
#[inline]
fn with_superclass(name: &CStr, superclass: Option<&AnyClass>) -> Option<Self> {
let super_ptr = superclass.map_or(ptr::null(), |c| c).cast();
let cls = unsafe { ffi::objc_allocateClassPair(super_ptr, name.as_ptr(), 0) };
NonNull::new(cls).map(|cls| Self { cls })
}
#[inline]
pub fn new(name: &CStr, superclass: &AnyClass) -> Option<Self> {
Self::with_superclass(name, Some(superclass))
}
pub fn root<F>(name: &CStr, initialize_fn: F) -> Option<Self>
where
F: MethodImplementation<Callee = AnyClass, Arguments = (), Return = ()>,
{
Self::with_superclass(name, None).map(|mut this| {
unsafe { this.add_class_method(sel!(initialize), initialize_fn) };
this
})
}
pub unsafe fn add_method<T, F>(&mut self, sel: Sel, func: F)
where
T: Message + ?Sized,
F: MethodImplementation<Callee = T>,
{
unsafe {
self.add_method_inner(
sel,
F::Arguments::ENCODINGS,
&F::Return::ENCODING_RETURN,
func.__imp(),
);
}
}
unsafe fn add_method_inner(
&mut self,
sel: Sel,
enc_args: &[Encoding],
enc_ret: &Encoding,
func: Imp,
) {
let sel_args = sel.number_of_arguments();
assert_eq!(
sel_args,
enc_args.len(),
"selector {sel} accepts {sel_args} arguments, but function accepts {}",
enc_args.len(),
);
#[cfg(all(debug_assertions, not(feature = "disable-encoding-assertions")))]
if let Some(superclass) = self.superclass() {
if let Some(method) = superclass.instance_method(sel) {
if let Err(err) = crate::verify::verify_method_signature(method, enc_args, enc_ret)
{
panic!(
"defined invalid method -[{} {sel}]: {err}",
self.name().to_string_lossy()
)
}
}
}
let types = method_type_encoding(enc_ret, enc_args);
let success = unsafe { ffi::class_addMethod(self.as_mut_ptr(), sel, func, types.as_ptr()) };
assert!(success.as_bool(), "failed to add method {sel}");
}
fn metaclass_mut(&mut self) -> *mut AnyClass {
unsafe { ffi::object_getClass(self.as_mut_ptr().cast()) as *mut AnyClass }
}
pub unsafe fn add_class_method<F>(&mut self, sel: Sel, func: F)
where
F: MethodImplementation<Callee = AnyClass>,
{
unsafe {
self.add_class_method_inner(
sel,
F::Arguments::ENCODINGS,
&F::Return::ENCODING_RETURN,
func.__imp(),
);
}
}
unsafe fn add_class_method_inner(
&mut self,
sel: Sel,
enc_args: &[Encoding],
enc_ret: &Encoding,
func: Imp,
) {
let sel_args = sel.number_of_arguments();
assert_eq!(
sel_args,
enc_args.len(),
"selector {sel} accepts {sel_args} arguments, but function accepts {}",
enc_args.len(),
);
#[cfg(all(debug_assertions, not(feature = "disable-encoding-assertions")))]
if let Some(superclass) = self.superclass() {
if let Some(method) = superclass.class_method(sel) {
if let Err(err) = crate::verify::verify_method_signature(method, enc_args, enc_ret)
{
panic!(
"defined invalid method +[{} {sel}]: {err}",
self.name().to_string_lossy()
)
}
}
}
let types = method_type_encoding(enc_ret, enc_args);
let success =
unsafe { ffi::class_addMethod(self.metaclass_mut(), sel, func, types.as_ptr()) };
assert!(success.as_bool(), "failed to add class method {sel}");
}
pub fn add_ivar<T: Encode>(&mut self, name: &CStr) {
unsafe { self.add_ivar_inner::<T>(name, &T::ENCODING) }
}
pub(crate) unsafe fn add_ivar_inner<T>(&mut self, name: &CStr, encoding: &Encoding) {
unsafe { self.add_ivar_inner_mono(name, mem::size_of::<T>(), T::LOG2_ALIGNMENT, encoding) }
}
unsafe fn add_ivar_inner_mono(
&mut self,
name: &CStr,
size: usize,
align: u8,
encoding: &Encoding,
) {
let encoding = CString::new(encoding.to_string()).unwrap();
let success = unsafe {
ffi::class_addIvar(
self.as_mut_ptr(),
name.as_ptr(),
size,
align,
encoding.as_ptr(),
)
};
assert!(success.as_bool(), "failed to add ivar {name:?}");
}
#[inline]
pub fn add_protocol(&mut self, proto: &AnyProtocol) -> bool {
let success = unsafe { ffi::class_addProtocol(self.as_mut_ptr(), proto) };
success.as_bool()
}
#[inline]
pub fn register(self) -> &'static AnyClass {
let mut this = ManuallyDrop::new(self);
unsafe { ffi::objc_registerClassPair(this.as_mut_ptr()) };
unsafe { this.cls.as_ref() }
}
}
impl Drop for ClassBuilder {
#[inline]
fn drop(&mut self) {
#[cfg(feature = "gnustep-1-7")]
unsafe {
ffi::objc_registerClassPair(self.as_mut_ptr());
}
unsafe { ffi::objc_disposeClassPair(self.as_mut_ptr()) }
}
}
#[derive(Debug)]
pub struct ProtocolBuilder {
proto: NonNull<AnyProtocol>,
}
unsafe impl Send for ProtocolBuilder {}
unsafe impl Sync for ProtocolBuilder {}
impl ProtocolBuilder {
fn as_mut_ptr(&mut self) -> *mut AnyProtocol {
self.proto.as_ptr()
}
#[inline]
pub fn new(name: &CStr) -> Option<Self> {
let proto = unsafe { ffi::objc_allocateProtocol(name.as_ptr()) };
NonNull::new(proto.cast()).map(|proto| Self { proto })
}
fn add_method_description_inner(
&mut self,
sel: Sel,
enc_args: &[Encoding],
enc_ret: &Encoding,
required: bool,
instance_method: bool,
) {
let sel_args = sel.number_of_arguments();
assert_eq!(
sel_args,
enc_args.len(),
"selector {sel} accepts {sel_args} arguments, but function accepts {}",
enc_args.len(),
);
let types = method_type_encoding(enc_ret, enc_args);
unsafe {
ffi::protocol_addMethodDescription(
self.as_mut_ptr(),
sel,
types.as_ptr(),
Bool::new(required),
Bool::new(instance_method),
);
}
}
pub fn add_method_description<Args, Ret>(&mut self, sel: Sel, required: bool)
where
Args: EncodeArguments,
Ret: EncodeReturn,
{
self.add_method_description_inner(
sel,
Args::ENCODINGS,
&Ret::ENCODING_RETURN,
required,
true,
);
}
pub fn add_class_method_description<Args, Ret>(&mut self, sel: Sel, required: bool)
where
Args: EncodeArguments,
Ret: EncodeReturn,
{
self.add_method_description_inner(
sel,
Args::ENCODINGS,
&Ret::ENCODING_RETURN,
required,
false,
);
}
pub fn add_protocol(&mut self, proto: &AnyProtocol) {
unsafe { ffi::protocol_addProtocol(self.as_mut_ptr(), proto) };
}
pub fn register(mut self) -> &'static AnyProtocol {
unsafe {
ffi::objc_registerProtocol(self.as_mut_ptr());
self.proto.as_ref()
}
}
}
impl Drop for ProtocolBuilder {
#[inline]
fn drop(&mut self) {
}
}
#[cfg(test)]
mod tests {
use core::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use memoffset::offset_of;
use super::*;
use crate::encode::RefEncode;
use crate::rc::Retained;
use crate::runtime::{NSObject, NSObjectProtocol};
use crate::{define_class, extern_methods, msg_send, test_utils, ClassType, ProtocolType};
fn c(s: &str) -> CString {
CString::new(s).unwrap()
}
#[test]
fn test_alignment() {
assert_eq!(<()>::LOG2_ALIGNMENT, 0);
assert_eq!(u8::LOG2_ALIGNMENT, 0);
assert_eq!(u16::LOG2_ALIGNMENT, 1);
assert_eq!(u32::LOG2_ALIGNMENT, 2);
assert_eq!(
u64::LOG2_ALIGNMENT,
if cfg!(target_pointer_width = "64") {
3
} else {
2
}
);
#[repr(align(16))]
struct Align16;
assert_eq!(Align16::LOG2_ALIGNMENT, 4);
#[repr(align(32))]
struct Align32;
assert_eq!(Align32::LOG2_ALIGNMENT, 5);
#[repr(align(64))]
struct Align64;
assert_eq!(Align64::LOG2_ALIGNMENT, 6);
#[repr(align(128))]
struct Align128;
assert_eq!(Align128::LOG2_ALIGNMENT, 7);
#[repr(align(256))]
struct Align256;
assert_eq!(Align256::LOG2_ALIGNMENT, 8);
#[repr(align(536870912))]
struct Align536870912;
assert_eq!(Align536870912::LOG2_ALIGNMENT, 29);
}
#[test]
fn test_classbuilder_duplicate() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new(&c("TestClassBuilderDuplicate"), cls).unwrap();
let _ = builder.register();
assert!(ClassBuilder::new(&c("TestClassBuilderDuplicate"), cls).is_none());
}
#[test]
#[should_panic = "failed to add ivar \"xyz\""]
fn duplicate_ivar() {
let cls = test_utils::custom_class();
let mut builder = ClassBuilder::new(&c("TestClassBuilderDuplicateIvar"), cls).unwrap();
builder.add_ivar::<i32>(&c("xyz"));
builder.add_ivar::<i32>(&c("xyz"));
}
#[test]
#[should_panic = "failed to add method xyz"]
fn duplicate_method() {
let cls = test_utils::custom_class();
let mut builder = ClassBuilder::new(&c("TestClassBuilderDuplicateMethod"), cls).unwrap();
extern "C" fn xyz(_this: &NSObject, _cmd: Sel) {}
unsafe {
builder.add_method(sel!(xyz), xyz as extern "C" fn(_, _));
builder.add_method(sel!(xyz), xyz as extern "C" fn(_, _));
}
}
#[test]
#[should_panic = "selector xyz: accepts 1 arguments, but function accepts 0"]
fn wrong_arguments() {
let cls = test_utils::custom_class();
let mut builder = ClassBuilder::new(&c("TestClassBuilderWrongArguments"), cls).unwrap();
extern "C" fn xyz(_this: &NSObject, _cmd: Sel) {}
unsafe {
builder.add_method(sel!(xyz:), xyz as extern "C" fn(_, _));
}
}
#[test]
#[cfg_attr(
all(debug_assertions, not(feature = "disable-encoding-assertions")),
should_panic = "defined invalid method -[TestClassBuilderInvalidMethod foo]: expected return to have type code 'I', but found 's'"
)]
fn invalid_method() {
let cls = test_utils::custom_class();
let mut builder = ClassBuilder::new(&c("TestClassBuilderInvalidMethod"), cls).unwrap();
extern "C" fn foo(_this: &NSObject, _cmd: Sel) -> i16 {
0
}
unsafe {
builder.add_method(sel!(foo), foo as extern "C" fn(_, _) -> _);
}
}
#[test]
#[cfg_attr(
all(
debug_assertions,
not(feature = "disable-encoding-assertions"),
not(feature = "relax-sign-encoding")
),
should_panic = "defined invalid method +[TestClassBuilderInvalidClassMethod classFoo]: expected return to have type code 'I', but found 'i'"
)]
fn invalid_class_method() {
let cls = test_utils::custom_class();
let mut builder = ClassBuilder::new(&c("TestClassBuilderInvalidClassMethod"), cls).unwrap();
extern "C" fn class_foo(_cls: &AnyClass, _cmd: Sel) -> i32 {
0
}
unsafe {
builder.add_class_method(sel!(classFoo), class_foo as extern "C" fn(_, _) -> _);
}
}
#[test]
fn inheriting_does_not_implement_protocols() {
let builder = ClassBuilder::new(
&c("TestClassBuilderInheritingDoesNotImplementProtocols"),
NSObject::class(),
)
.unwrap();
let cls = builder.register();
let conforms = cls.conforms_to(<dyn NSObjectProtocol>::protocol().unwrap());
if cfg!(feature = "gnustep-1-7") {
assert!(conforms);
} else {
assert!(!conforms);
}
}
#[test]
fn inherit_nsobject_add_protocol() {
let mut builder = ClassBuilder::new(
&c("TestClassBuilderInheritNSObjectAddProtocol"),
NSObject::class(),
)
.unwrap();
let protocol = <dyn NSObjectProtocol>::protocol().unwrap();
if cfg!(feature = "gnustep-1-7") {
assert!(!builder.add_protocol(protocol));
} else {
assert!(builder.add_protocol(protocol));
}
let cls = builder.register();
assert!(cls.conforms_to(protocol));
}
#[test]
fn duplicate_protocol() {
let cls = test_utils::custom_class();
let mut builder = ClassBuilder::new(&c("TestClassBuilderDuplicateProtocol"), cls).unwrap();
let protocol = ProtocolBuilder::new(&c("TestClassBuilderDuplicateProtocol"))
.unwrap()
.register();
assert!(builder.add_protocol(protocol));
assert!(!builder.add_protocol(protocol));
}
#[test]
fn add_protocol_subprotocol_ordering() {
let builder = ProtocolBuilder::new(&c("Superprotocol")).unwrap();
let superprotocol = builder.register();
let mut builder = ProtocolBuilder::new(&c("Subprotocol")).unwrap();
builder.add_protocol(superprotocol);
let subprotocol = builder.register();
let mut builder =
ClassBuilder::new(&c("AddProtocolSuperThenSub"), NSObject::class()).unwrap();
assert!(builder.add_protocol(superprotocol));
assert!(builder.add_protocol(subprotocol));
let _cls = builder.register();
let mut builder =
ClassBuilder::new(&c("AddProtocolSubThenSuper"), NSObject::class()).unwrap();
assert!(builder.add_protocol(subprotocol));
assert!(!builder.add_protocol(superprotocol));
let _cls = builder.register();
}
#[test]
fn test_classbuilder_drop() {
let cls = test_utils::custom_class();
let builder = ClassBuilder::new(&c("TestClassBuilderDrop"), cls).unwrap();
drop(builder);
let _builder = ClassBuilder::new(&c("TestClassBuilderDrop"), cls).unwrap();
}
#[test]
fn test_custom_class() {
let obj = test_utils::custom_object();
let _: () = unsafe { msg_send![&obj, setFoo: 13u32] };
let result: u32 = unsafe { msg_send![&obj, foo] };
assert_eq!(result, 13);
}
#[test]
fn test_in_all_classes() {
fn is_present(cls: *const AnyClass) -> bool {
AnyClass::classes().iter().any(|item| ptr::eq(cls, *item))
}
let superclass = test_utils::custom_class();
let builder = ClassBuilder::new(&c("TestFetchWhileCreatingClass"), superclass).unwrap();
if cfg!(all(
target_vendor = "apple",
any(target_arch = "aarch64", target_arch = "x86_64")
)) {
assert!(is_present(builder.cls.as_ptr().cast()));
} else {
assert!(!is_present(builder.cls.as_ptr().cast()));
}
let cls = builder.register();
assert!(is_present(cls));
}
#[test]
fn test_class_method() {
let cls = test_utils::custom_class();
let result: u32 = unsafe { msg_send![cls, classFoo] };
assert_eq!(result, 7);
}
#[test]
fn test_generic() {
struct GenericDefineClass<T>(T);
unsafe impl<T> RefEncode for GenericDefineClass<T> {
const ENCODING_REF: Encoding = Encoding::Object;
}
unsafe impl<T> Message for GenericDefineClass<T> {}
unsafe impl<T> ClassType for GenericDefineClass<T> {
type Super = NSObject;
type ThreadKind = <Self::Super as ClassType>::ThreadKind;
const NAME: &'static str = "GenericDefineClass";
#[inline]
fn as_super(&self) -> &Self::Super {
unimplemented!()
}
fn class() -> &'static AnyClass {
let superclass = NSObject::class();
let mut builder = ClassBuilder::new(&c(Self::NAME), superclass).unwrap();
unsafe {
builder.add_method(
sel!(generic),
<GenericDefineClass<T>>::generic as unsafe extern "C" fn(_, _),
);
}
builder.register()
}
const __INNER: () = ();
type __SubclassingType = Self;
}
impl<T> GenericDefineClass<T> {
extern "C" fn generic(&self, _cmd: Sel) {}
}
let _ = GenericDefineClass::<()>::class();
}
#[test]
fn test_inherited_nsobject_methods_work() {
define_class!(
#[unsafe(super(NSObject))]
#[name = "TestInheritedNSObjectMethodsWork"]
#[derive(Debug, PartialEq, Eq, Hash)]
struct Custom;
);
impl Custom {
extern_methods!(
#[unsafe(method(new))]
fn new() -> Retained<Self>;
);
}
let obj1 = Custom::new();
let obj2 = Custom::new();
assert_eq!(obj1, obj1);
assert_ne!(obj1, obj2);
let expected =
format!("Custom {{ super: <TestInheritedNSObjectMethodsWork: {obj1:p}>, ivars: () }}");
assert_eq!(format!("{obj1:?}"), expected);
let mut hashstate1 = DefaultHasher::new();
let mut hashstate2 = DefaultHasher::new();
obj1.hash(&mut hashstate1);
obj1.hash(&mut hashstate2);
assert_eq!(hashstate1.finish(), hashstate2.finish());
let mut hashstate2 = DefaultHasher::new();
obj2.hash(&mut hashstate2);
assert_ne!(hashstate1.finish(), hashstate2.finish());
assert!(obj1.isKindOfClass(NSObject::class()));
assert!(obj1.isKindOfClass(Custom::class()));
assert!((**obj1).isKindOfClass(Custom::class()));
}
#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "ivars cannot have the same name on GNUStep"
)]
fn test_ivar_sizing() {
#[repr(align(16))]
struct U128align16 {
_inner: [u64; 2],
}
unsafe impl Encode for U128align16 {
const ENCODING: Encoding = <[u64; 2]>::ENCODING;
}
let mut superclass =
ClassBuilder::new(&c("DefineClassDuplicateIvarSuperclass"), NSObject::class()).unwrap();
superclass.add_ivar::<u8>(&c("ivar1"));
superclass.add_ivar::<U128align16>(&c("ivar2"));
superclass.add_ivar::<u8>(&c("ivar3"));
superclass.add_ivar::<[u8; 0]>(&c("ivar4"));
let superclass = superclass.register();
let mut subclass =
ClassBuilder::new(&c("DefineClassDuplicateIvarSubclass"), superclass).unwrap();
subclass.add_ivar::<i16>(&c("ivar1"));
subclass.add_ivar::<usize>(&c("ivar2"));
subclass.add_ivar::<*const AnyObject>(&c("ivar3"));
subclass.add_ivar::<usize>(&c("ivar4"));
let subclass = subclass.register();
#[repr(C)]
struct NSObjectLayout {
isa: *const AnyClass,
}
assert_eq!(
NSObject::class().instance_size(),
mem::size_of::<NSObjectLayout>(),
);
#[repr(C)]
struct SuperLayout {
isa: *const AnyClass,
ivar1: u8,
ivar2: U128align16,
ivar3: u8,
ivar4: [u8; 0],
}
assert_eq!(
superclass.instance_size(),
mem::size_of::<SuperLayout>() - 16 + mem::size_of::<*const AnyClass>(),
);
#[repr(C)]
struct SubLayout {
isa: *const AnyClass,
ivar1: u8,
ivar2: U128align16,
ivar3: u8,
ivar4: [u8; 0],
ivar1_b: i16,
ivar2_b: usize,
ivar3_b: *const AnyObject,
ivar4_b: usize,
}
assert_eq!(subclass.instance_size(), mem::size_of::<SubLayout>());
let superclass_ivar1 = superclass.instance_variable(&c("ivar1")).unwrap();
let superclass_ivar2 = superclass.instance_variable(&c("ivar2")).unwrap();
let superclass_ivar3 = superclass.instance_variable(&c("ivar3")).unwrap();
let superclass_ivar4 = superclass.instance_variable(&c("ivar4")).unwrap();
let subclass_ivar1 = subclass.instance_variable(&c("ivar1")).unwrap();
let subclass_ivar2 = subclass.instance_variable(&c("ivar2")).unwrap();
let subclass_ivar3 = subclass.instance_variable(&c("ivar3")).unwrap();
let subclass_ivar4 = subclass.instance_variable(&c("ivar4")).unwrap();
assert_ne!(superclass_ivar1, subclass_ivar1);
assert_ne!(superclass_ivar2, subclass_ivar2);
assert_ne!(superclass_ivar3, subclass_ivar3);
assert_ne!(superclass_ivar4, subclass_ivar4);
assert_eq!(
superclass_ivar1.offset(),
offset_of!(SuperLayout, ivar1) as isize
);
assert_eq!(
superclass_ivar2.offset(),
offset_of!(SuperLayout, ivar2) as isize
);
assert_eq!(
superclass_ivar3.offset(),
offset_of!(SuperLayout, ivar3) as isize
);
assert_eq!(
superclass_ivar4.offset(),
offset_of!(SuperLayout, ivar4) as isize
);
assert_eq!(
subclass_ivar1.offset(),
offset_of!(SubLayout, ivar1_b) as isize
);
assert_eq!(
subclass_ivar2.offset(),
offset_of!(SubLayout, ivar2_b) as isize
);
assert_eq!(
subclass_ivar3.offset(),
offset_of!(SubLayout, ivar3_b) as isize
);
assert_eq!(
subclass_ivar4.offset(),
offset_of!(SubLayout, ivar4_b) as isize
);
let obj: Retained<NSObject> = unsafe { msg_send![subclass, new] };
let ptr = unsafe { *subclass_ivar3.load::<*const AnyObject>(&obj) };
assert!(ptr.is_null());
}
#[test]
fn auto_name() {
define_class!(
#[unsafe(super(NSObject))]
#[ivars = ()]
struct AutoName;
);
let expected = format!(
"objc2::runtime::define::tests::AutoName{}",
env!("CARGO_PKG_VERSION")
);
let cls = AutoName::class();
assert_eq!(cls.name().to_str().unwrap(), expected);
assert_eq!(AutoName::NAME, expected);
}
}