#[cfg(doc)]
use core::cell::UnsafeCell;
use core::fmt;
use core::hash;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::{self, NonNull};
use core::str;
#[cfg(feature = "malloc")]
use malloc_buf::Malloc;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[cfg(feature = "malloc")]
use std::os::raw::c_uint;
use crate::encode::{Encode, Encoding, RefEncode};
use crate::ffi;
#[cfg(feature = "malloc")]
use crate::{
encode::{EncodeArguments, EncodeConvert},
verify::verify_message_signature,
VerificationError,
};
#[doc(inline)]
pub use crate::encode::__bool::Bool;
#[deprecated = "Use `Bool` or `ffi::BOOL` instead"]
#[allow(non_upper_case_globals)]
pub type BOOL = ffi::BOOL;
#[deprecated = "Use `Bool::YES` or `ffi::YES` instead"]
pub const YES: ffi::BOOL = ffi::YES;
#[deprecated = "Use `Bool::NO` or `ffi::NO` instead"]
pub const NO: ffi::BOOL = ffi::NO;
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[doc(alias = "SEL")]
pub struct Sel {
ptr: NonNull<ffi::objc_selector>,
}
#[repr(C)]
pub struct Ivar(ffi::objc_ivar);
#[repr(C)]
pub struct Method(ffi::objc_method);
#[repr(C)]
pub struct Class(ffi::objc_class);
#[repr(C)]
pub struct Protocol(ffi::objc_protocol);
macro_rules! standard_pointer_impls {
($($name:ident),*) => {
$(
impl PartialEq for $name {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_ptr() == other.as_ptr()
}
}
impl Eq for $name {}
impl hash::Hash for $name {
#[inline]
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.as_ptr().hash(state)
}
}
)*
}
}
standard_pointer_impls!(Ivar, Method, Class);
#[cfg(not(feature = "unstable-c-unwind"))]
type InnerImp = unsafe extern "C" fn();
#[cfg(feature = "unstable-c-unwind")]
type InnerImp = unsafe extern "C-unwind" fn();
pub type Imp = InnerImp;
impl Sel {
#[inline]
#[doc(hidden)]
pub const unsafe fn __internal_from_ptr(ptr: *const ffi::objc_selector) -> Self {
let ptr = unsafe { NonNull::new_unchecked(ptr as *mut ffi::objc_selector) };
Self { ptr }
}
#[inline]
pub(crate) unsafe fn from_ptr(ptr: *const ffi::objc_selector) -> Option<Self> {
NonNull::new(ptr as *mut ffi::objc_selector).map(|ptr| Self { ptr })
}
#[inline]
pub const fn as_ptr(&self) -> *const ffi::objc_selector {
self.ptr.as_ptr()
}
pub(crate) unsafe fn register_unchecked(name: *const c_char) -> Self {
let ptr = unsafe { ffi::sel_registerName(name) };
unsafe { Self::from_ptr(ptr).unwrap() }
}
#[doc(alias = "sel_registerName")]
pub fn register(name: &str) -> Self {
let name = CString::new(name).unwrap();
unsafe { Self::register_unchecked(name.as_ptr()) }
}
#[doc(alias = "sel_getName")]
pub fn name(&self) -> &str {
let ptr = unsafe { ffi::sel_getName(self.as_ptr()) };
let name = unsafe { CStr::from_ptr(ptr) };
str::from_utf8(name.to_bytes()).unwrap()
}
}
unsafe impl Encode for Sel {
const ENCODING: Encoding = Encoding::Sel;
}
unsafe impl Sync for Sel {}
unsafe impl Send for Sel {}
impl UnwindSafe for Sel {}
impl RefUnwindSafe for Sel {}
impl fmt::Debug for Sel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl fmt::Pointer for Sel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.ptr, f)
}
}
impl Ivar {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_ivar {
let ptr: *const Self = self;
ptr.cast()
}
pub fn name(&self) -> &str {
let name = unsafe { CStr::from_ptr(ffi::ivar_getName(self.as_ptr())) };
str::from_utf8(name.to_bytes()).unwrap()
}
pub fn offset(&self) -> isize {
unsafe { ffi::ivar_getOffset(self.as_ptr()) }
}
pub fn type_encoding(&self) -> &str {
let encoding = unsafe { CStr::from_ptr(ffi::ivar_getTypeEncoding(self.as_ptr())) };
str::from_utf8(encoding.to_bytes()).unwrap()
}
}
unsafe impl Sync for Ivar {}
unsafe impl Send for Ivar {}
impl UnwindSafe for Ivar {}
impl RefUnwindSafe for Ivar {}
impl Method {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_method {
let ptr: *const Self = self;
ptr.cast()
}
pub fn name(&self) -> Sel {
unsafe { Sel::from_ptr(ffi::method_getName(self.as_ptr())).unwrap() }
}
#[cfg(feature = "malloc")]
pub fn return_type(&self) -> Malloc<str> {
unsafe {
let encoding = ffi::method_copyReturnType(self.as_ptr());
Malloc::from_c_str(encoding).unwrap()
}
}
#[cfg(feature = "malloc")]
pub fn argument_type(&self, index: usize) -> Option<Malloc<str>> {
unsafe {
let encoding = ffi::method_copyArgumentType(self.as_ptr(), index as c_uint);
NonNull::new(encoding).map(|encoding| Malloc::from_c_str(encoding.as_ptr()).unwrap())
}
}
pub fn arguments_count(&self) -> usize {
unsafe { ffi::method_getNumberOfArguments(self.as_ptr()) as usize }
}
pub fn implementation(&self) -> Imp {
unsafe { ffi::method_getImplementation(self.as_ptr()).expect("Null IMP") }
}
}
unsafe impl Sync for Method {}
unsafe impl Send for Method {}
impl UnwindSafe for Method {}
impl RefUnwindSafe for Method {}
impl Class {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_class {
let ptr: *const Self = self;
ptr.cast()
}
pub fn get(name: &str) -> Option<&'static Self> {
let name = CString::new(name).unwrap();
let cls = unsafe { ffi::objc_getClass(name.as_ptr()) };
unsafe { cls.cast::<Self>().as_ref() }
}
#[cfg(feature = "malloc")]
pub fn classes() -> Malloc<[&'static Self]> {
unsafe {
let mut count: c_uint = 0;
let classes: *mut &Self = ffi::objc_copyClassList(&mut count).cast();
Malloc::from_array(classes, count as usize)
}
}
pub fn classes_count() -> usize {
unsafe { ffi::objc_getClassList(ptr::null_mut(), 0) as usize }
}
pub fn name(&self) -> &str {
let name = unsafe { CStr::from_ptr(ffi::class_getName(self.as_ptr())) };
str::from_utf8(name.to_bytes()).unwrap()
}
pub fn superclass(&self) -> Option<&Class> {
unsafe {
let superclass = ffi::class_getSuperclass(self.as_ptr());
superclass.cast::<Class>().as_ref()
}
}
pub fn metaclass(&self) -> &Self {
let ptr: *const Self = unsafe { ffi::object_getClass(self.as_ptr().cast()) }.cast();
unsafe { ptr.as_ref().unwrap_unchecked() }
}
#[allow(unused)]
pub(crate) fn is_metaclass(&self) -> bool {
unsafe { Bool::from_raw(ffi::class_isMetaClass(self.as_ptr())).as_bool() }
}
pub fn instance_size(&self) -> usize {
unsafe { ffi::class_getInstanceSize(self.as_ptr()) as usize }
}
pub fn instance_method(&self, sel: Sel) -> Option<&Method> {
unsafe {
let method = ffi::class_getInstanceMethod(self.as_ptr(), sel.as_ptr());
method.cast::<Method>().as_ref()
}
}
pub fn instance_variable(&self, name: &str) -> Option<&Ivar> {
let name = CString::new(name).unwrap();
unsafe {
let ivar = ffi::class_getInstanceVariable(self.as_ptr(), name.as_ptr());
ivar.cast::<Ivar>().as_ref()
}
}
#[allow(unused)]
fn instance_variable_layout(&self) -> Option<&[u8]> {
let layout: *const c_char = unsafe { ffi::class_getIvarLayout(self.as_ptr()).cast() };
if layout.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(layout) }.to_bytes())
}
}
#[allow(unused)]
fn class_variable(&self, name: &str) -> Option<&Ivar> {
let name = CString::new(name).unwrap();
let ivar = unsafe { ffi::class_getClassVariable(self.as_ptr(), name.as_ptr()) };
unsafe { ivar.cast::<Ivar>().as_ref() }
}
#[cfg(feature = "malloc")]
pub fn instance_methods(&self) -> Malloc<[&Method]> {
unsafe {
let mut count: c_uint = 0;
let methods: *mut &Method = ffi::class_copyMethodList(self.as_ptr(), &mut count).cast();
Malloc::from_array(methods, count as usize)
}
}
pub fn conforms_to(&self, proto: &Protocol) -> bool {
unsafe {
Bool::from_raw(ffi::class_conformsToProtocol(self.as_ptr(), proto.as_ptr())).as_bool()
}
}
#[cfg(feature = "malloc")]
pub fn adopted_protocols(&self) -> Malloc<[&Protocol]> {
unsafe {
let mut count: c_uint = 0;
let protos: *mut &Protocol =
ffi::class_copyProtocolList(self.as_ptr(), &mut count).cast();
Malloc::from_array(protos, count as usize)
}
}
#[cfg(feature = "malloc")]
pub fn instance_variables(&self) -> Malloc<[&Ivar]> {
unsafe {
let mut count: c_uint = 0;
let ivars: *mut &Ivar = ffi::class_copyIvarList(self.as_ptr(), &mut count).cast();
Malloc::from_array(ivars, count as usize)
}
}
#[doc(alias = "class_respondsToSelector")]
pub fn responds_to(&self, sel: Sel) -> bool {
let res = unsafe { ffi::class_respondsToSelector(self.as_ptr(), sel.as_ptr()) };
Bool::from_raw(res).as_bool()
}
#[cfg(feature = "malloc")]
pub fn verify_sel<A, R>(&self, sel: Sel) -> Result<(), VerificationError>
where
A: EncodeArguments,
R: EncodeConvert,
{
verify_message_signature::<A, R>(self, sel)
}
}
unsafe impl Sync for Class {}
unsafe impl Send for Class {}
impl UnwindSafe for Class {}
impl RefUnwindSafe for Class {}
unsafe impl RefEncode for Class {
const ENCODING_REF: Encoding = Encoding::Class;
}
impl fmt::Debug for Class {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl Protocol {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_protocol {
let ptr: *const Self = self;
ptr.cast()
}
pub fn get(name: &str) -> Option<&'static Protocol> {
let name = CString::new(name).unwrap();
unsafe {
let proto = ffi::objc_getProtocol(name.as_ptr());
proto.cast::<Self>().as_ref()
}
}
#[cfg(feature = "malloc")]
pub fn protocols() -> Malloc<[&'static Protocol]> {
unsafe {
let mut count: c_uint = 0;
let protocols: *mut &Protocol = ffi::objc_copyProtocolList(&mut count).cast();
Malloc::from_array(protocols, count as usize)
}
}
#[cfg(feature = "malloc")]
pub fn adopted_protocols(&self) -> Malloc<[&Protocol]> {
unsafe {
let mut count: c_uint = 0;
let protocols: *mut &Protocol =
ffi::protocol_copyProtocolList(self.as_ptr(), &mut count).cast();
Malloc::from_array(protocols, count as usize)
}
}
pub fn conforms_to(&self, proto: &Protocol) -> bool {
unsafe {
Bool::from_raw(ffi::protocol_conformsToProtocol(
self.as_ptr(),
proto.as_ptr(),
))
.as_bool()
}
}
pub fn name(&self) -> &str {
let name = unsafe { CStr::from_ptr(ffi::protocol_getName(self.as_ptr())) };
str::from_utf8(name.to_bytes()).unwrap()
}
}
impl PartialEq for Protocol {
#[inline]
fn eq(&self, other: &Protocol) -> bool {
unsafe { Bool::from_raw(ffi::protocol_isEqual(self.as_ptr(), other.as_ptr())).as_bool() }
}
}
unsafe impl RefEncode for Protocol {
const ENCODING_REF: Encoding = Encoding::Object;
}
impl Eq for Protocol {}
impl fmt::Debug for Protocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
unsafe impl Sync for Protocol {}
unsafe impl Send for Protocol {}
impl UnwindSafe for Protocol {}
impl RefUnwindSafe for Protocol {}
pub(crate) fn ivar_offset(cls: &Class, name: &str, expected: &Encoding) -> isize {
match cls.instance_variable(name) {
Some(ivar) => {
let encoding = ivar.type_encoding();
assert!(
expected.equivalent_to_str(encoding),
"wrong encoding. Tried to retrieve ivar with encoding {}, but the encoding of the given type was {}",
encoding,
expected,
);
ivar.offset()
}
None => panic!("Ivar {} not found on class {:?}", name, cls),
}
}
#[doc(alias = "id")]
#[repr(C)]
pub struct Object(ffi::objc_object);
unsafe impl RefEncode for Object {
const ENCODING_REF: Encoding = Encoding::Object;
}
impl Object {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_object {
let ptr: *const Self = self;
ptr.cast()
}
pub fn class(&self) -> &Class {
let ptr: *const Class = unsafe { ffi::object_getClass(self.as_ptr()) }.cast();
unsafe { ptr.as_ref().unwrap_unchecked() }
}
#[inline]
pub(crate) unsafe fn ivar_at_offset<T>(ptr: NonNull<Self>, offset: isize) -> NonNull<T> {
let ptr: NonNull<u8> = ptr.cast();
let ptr: *mut u8 = ptr.as_ptr();
let ptr: *mut u8 = unsafe { ptr.offset(offset) };
let ptr: NonNull<u8> = unsafe { NonNull::new_unchecked(ptr) };
let ptr: NonNull<T> = ptr.cast();
ptr
}
pub unsafe fn ivar_ptr<T: Encode>(&self, name: &str) -> *mut T {
let offset = ivar_offset(self.class(), name, &T::ENCODING);
let ptr = NonNull::from(self);
let ptr = unsafe { Self::ivar_at_offset::<T>(ptr, offset) };
ptr.as_ptr()
}
pub unsafe fn ivar<T: Encode>(&self, name: &str) -> &T {
unsafe { self.ivar_ptr::<T>(name).as_ref().unwrap_unchecked() }
}
#[deprecated = "Use `Object::ivar` instead."]
pub unsafe fn get_ivar<T: Encode>(&self, name: &str) -> &T {
unsafe { self.ivar::<T>(name) }
}
pub unsafe fn ivar_mut<T: Encode>(&mut self, name: &str) -> &mut T {
let offset = ivar_offset(self.class(), name, &T::ENCODING);
let ptr = NonNull::from(self);
let mut ptr = unsafe { Self::ivar_at_offset::<T>(ptr, offset) };
unsafe { ptr.as_mut() }
}
#[deprecated = "Use `Object::ivar_mut` instead."]
pub unsafe fn get_mut_ivar<T: Encode>(&mut self, name: &str) -> &mut T {
unsafe { self.ivar_mut::<T>(name) }
}
pub unsafe fn set_ivar<T: Encode>(&mut self, name: &str, value: T) {
unsafe { *self.ivar_mut::<T>(name) = value };
}
}
impl fmt::Debug for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<{:?}: {:p}>", self.class(), self.as_ptr())
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::string::ToString;
use super::{Bool, Class, Imp, Ivar, Method, Object, Protocol, Sel};
use crate::test_utils;
use crate::Encode;
use crate::{msg_send, sel};
#[test]
fn test_selector() {
macro_rules! test_sel {
($s:literal, $($tt:tt)+) => {{
let sel = sel!($($tt)*);
let expected = Sel::register($s);
assert_eq!(sel, expected);
assert_eq!(sel.name(), $s);
}}
}
test_sel!("abc", abc);
test_sel!("abc:", abc:);
test_sel!("abc:def:", abc:def:);
test_sel!("abc:def:ghi:", abc:def:ghi:);
}
#[test]
fn test_empty_selector() {
let sel = Sel::register("");
assert_eq!(sel.name(), "");
let sel = Sel::register(":");
assert_eq!(sel.name(), ":");
}
#[test]
#[should_panic = "NulError"]
fn test_sel_register_null() {
let _ = Sel::register("\0");
}
#[test]
fn test_ivar() {
let cls = test_utils::custom_class();
let ivar = cls.instance_variable("_foo").unwrap();
assert_eq!(ivar.name(), "_foo");
assert!(<u32>::ENCODING.equivalent_to_str(ivar.type_encoding()));
assert!(ivar.offset() > 0);
#[cfg(feature = "malloc")]
assert!(cls.instance_variables().len() > 0);
}
#[test]
fn test_method() {
let cls = test_utils::custom_class();
let sel = Sel::register("foo");
let method = cls.instance_method(sel).unwrap();
assert_eq!(method.name().name(), "foo");
assert_eq!(method.arguments_count(), 2);
#[cfg(feature = "malloc")]
{
assert!(<u32>::ENCODING.equivalent_to_str(&method.return_type()));
assert!(Sel::ENCODING.equivalent_to_str(&method.argument_type(1).unwrap()));
let methods = cls.instance_methods();
assert!(methods.len() > 0);
}
}
#[test]
fn test_class() {
let cls = test_utils::custom_class();
assert_eq!(cls.name(), "CustomObject");
assert!(cls.instance_size() > 0);
assert!(cls.superclass().is_none());
assert!(cls.responds_to(sel!(foo)));
assert!(cls.responds_to(sel!(setBar:)));
assert!(!cls.responds_to(sel!(abc)));
assert!(!cls.responds_to(sel!(addNumber:toNumber:)));
assert_eq!(Class::get(cls.name()), Some(cls));
let metaclass = cls.metaclass();
assert_eq!(metaclass.superclass().unwrap(), cls);
assert!(metaclass.responds_to(sel!(addNumber:toNumber:)));
assert!(metaclass.responds_to(sel!(foo)));
let subclass = test_utils::custom_subclass();
assert_eq!(subclass.superclass().unwrap(), cls);
}
#[test]
fn test_classes_count() {
assert!(Class::classes_count() > 0);
}
#[test]
#[cfg(feature = "malloc")]
fn test_classes() {
let classes = Class::classes();
assert!(classes.len() > 0);
}
#[test]
fn test_protocol() {
let proto = test_utils::custom_protocol();
assert_eq!(proto.name(), "CustomProtocol");
let class = test_utils::custom_class();
assert!(class.conforms_to(proto));
#[cfg(feature = "malloc")]
assert!(class.adopted_protocols().len() > 0);
}
#[test]
fn test_protocol_method() {
let class = test_utils::custom_class();
let result: i32 = unsafe { msg_send![class, addNumber: 1, toNumber: 2] };
assert_eq!(result, 3);
}
#[test]
fn test_subprotocols() {
let sub_proto = test_utils::custom_subprotocol();
let super_proto = test_utils::custom_protocol();
assert!(sub_proto.conforms_to(super_proto));
#[cfg(feature = "malloc")]
assert_eq!(sub_proto.adopted_protocols()[0], super_proto);
}
#[test]
fn test_protocols() {
let _ = test_utils::custom_protocol();
#[cfg(feature = "malloc")]
assert!(Protocol::protocols().len() > 0);
}
#[test]
fn test_object() {
let mut obj = test_utils::custom_object();
assert_eq!(obj.class(), test_utils::custom_class());
let result = unsafe {
obj.set_ivar::<u32>("_foo", 4);
*obj.ivar::<u32>("_foo")
};
assert_eq!(result, 4);
}
#[test]
#[should_panic = "Ivar unknown not found on class CustomObject"]
fn test_object_ivar_unknown() {
let obj = test_utils::custom_object();
let _ = unsafe { *obj.ivar::<u32>("unknown") };
}
#[test]
#[should_panic = "wrong encoding. Tried to retrieve ivar with encoding I, but the encoding of the given type was C"]
fn test_object_ivar_wrong_type() {
let obj = test_utils::custom_object();
let _ = unsafe { *obj.ivar::<u8>("_foo") };
}
#[test]
fn test_encode() {
fn assert_enc<T: Encode>(expected: &str) {
assert_eq!(&T::ENCODING.to_string(), expected);
}
assert_enc::<&Object>("@");
assert_enc::<*mut Object>("@");
assert_enc::<&Class>("#");
assert_enc::<Sel>(":");
assert_enc::<Imp>("^?");
assert_enc::<Option<Imp>>("^?");
assert_enc::<&Protocol>("@");
}
#[test]
fn test_send_sync() {
fn assert_send_sync<T: Send + Sync + ?Sized>() {}
assert_send_sync::<Bool>();
assert_send_sync::<Class>();
assert_send_sync::<Ivar>();
assert_send_sync::<Method>();
assert_send_sync::<Protocol>();
assert_send_sync::<Sel>();
}
#[test]
fn test_debug() {
assert_eq!(format!("{:?}", sel!(abc:)), "abc:");
let cls = test_utils::custom_class();
assert_eq!(format!("{:?}", cls), "CustomObject");
let protocol = test_utils::custom_protocol();
assert_eq!(format!("{:?}", protocol), "CustomProtocol");
let object = test_utils::custom_object();
assert_eq!(
format!("{:?}", &*object),
format!("<CustomObject: {:p}>", &*object)
);
}
}