From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- third_party/rust/objc/src/declare.rs | 340 ++++++++++++ third_party/rust/objc/src/encode.rs | 279 ++++++++++ third_party/rust/objc/src/exception.rs | 11 + third_party/rust/objc/src/lib.rs | 90 +++ third_party/rust/objc/src/macros.rs | 148 +++++ third_party/rust/objc/src/message/apple/arm.rs | 40 ++ third_party/rust/objc/src/message/apple/arm64.rs | 18 + third_party/rust/objc/src/message/apple/mod.rs | 40 ++ third_party/rust/objc/src/message/apple/x86.rs | 40 ++ third_party/rust/objc/src/message/apple/x86_64.rs | 32 ++ third_party/rust/objc/src/message/gnustep.rs | 35 ++ third_party/rust/objc/src/message/mod.rs | 296 ++++++++++ third_party/rust/objc/src/message/verify.rs | 49 ++ third_party/rust/objc/src/rc/autorelease.rs | 30 + third_party/rust/objc/src/rc/mod.rs | 123 +++++ third_party/rust/objc/src/rc/strong.rs | 73 +++ third_party/rust/objc/src/rc/weak.rs | 50 ++ third_party/rust/objc/src/runtime.rs | 632 ++++++++++++++++++++++ third_party/rust/objc/src/test_utils.rs | 187 +++++++ 19 files changed, 2513 insertions(+) create mode 100644 third_party/rust/objc/src/declare.rs create mode 100644 third_party/rust/objc/src/encode.rs create mode 100644 third_party/rust/objc/src/exception.rs create mode 100644 third_party/rust/objc/src/lib.rs create mode 100644 third_party/rust/objc/src/macros.rs create mode 100644 third_party/rust/objc/src/message/apple/arm.rs create mode 100644 third_party/rust/objc/src/message/apple/arm64.rs create mode 100644 third_party/rust/objc/src/message/apple/mod.rs create mode 100644 third_party/rust/objc/src/message/apple/x86.rs create mode 100644 third_party/rust/objc/src/message/apple/x86_64.rs create mode 100644 third_party/rust/objc/src/message/gnustep.rs create mode 100644 third_party/rust/objc/src/message/mod.rs create mode 100644 third_party/rust/objc/src/message/verify.rs create mode 100644 third_party/rust/objc/src/rc/autorelease.rs create mode 100644 third_party/rust/objc/src/rc/mod.rs create mode 100644 third_party/rust/objc/src/rc/strong.rs create mode 100644 third_party/rust/objc/src/rc/weak.rs create mode 100644 third_party/rust/objc/src/runtime.rs create mode 100644 third_party/rust/objc/src/test_utils.rs (limited to 'third_party/rust/objc/src') diff --git a/third_party/rust/objc/src/declare.rs b/third_party/rust/objc/src/declare.rs new file mode 100644 index 0000000000..ed09015e1a --- /dev/null +++ b/third_party/rust/objc/src/declare.rs @@ -0,0 +1,340 @@ +/*! +Functionality for declaring Objective-C classes. + +Classes can be declared using the `ClassDecl` struct. Instance variables and +methods can then be added before the class is ultimately registered. + +# Example + +The following example demonstrates declaring a class named `MyNumber` that has +one ivar, a `u32` named `_number` and a `number` method that returns it: + +``` no_run +# #[macro_use] extern crate objc; +# use objc::declare::ClassDecl; +# use objc::runtime::{Class, Object, Sel}; +# fn main() { +let superclass = class!(NSObject); +let mut decl = ClassDecl::new("MyNumber", superclass).unwrap(); + +// Add an instance variable +decl.add_ivar::("_number"); + +// Add an ObjC method for getting the number +extern fn my_number_get(this: &Object, _cmd: Sel) -> u32 { + unsafe { *this.get_ivar("_number") } +} +unsafe { + decl.add_method(sel!(number), + my_number_get as extern fn(&Object, Sel) -> u32); +} + +decl.register(); +# } +``` +*/ + +use std::ffi::CString; +use std::mem; +use std::ptr; + +use runtime::{BOOL, Class, Imp, NO, Object, Protocol, Sel, self}; +use {Encode, EncodeArguments, Encoding, Message}; + +/// Types that can be used as the implementation of an Objective-C method. +pub trait MethodImplementation { + /// The callee type of the method. + type Callee: Message; + /// The return type of the method. + type Ret: Encode; + /// The argument types of the method. + type Args: EncodeArguments; + + /// Returns self as an `Imp` of a method. + fn imp(self) -> Imp; +} + +macro_rules! method_decl_impl { + (-$s:ident, $r:ident, $f:ty, $($t:ident),*) => ( + impl<$s, $r $(, $t)*> MethodImplementation for $f + where $s: Message, $r: Encode $(, $t: Encode)* { + type Callee = $s; + type Ret = $r; + type Args = ($($t,)*); + + fn imp(self) -> Imp { + unsafe { mem::transmute(self) } + } + } + ); + ($($t:ident),*) => ( + method_decl_impl!(-T, R, extern fn(&T, Sel $(, $t)*) -> R, $($t),*); + method_decl_impl!(-T, R, extern fn(&mut T, Sel $(, $t)*) -> R, $($t),*); + ); +} + +method_decl_impl!(); +method_decl_impl!(A); +method_decl_impl!(A, B); +method_decl_impl!(A, B, C); +method_decl_impl!(A, B, C, D); +method_decl_impl!(A, B, C, D, E); +method_decl_impl!(A, B, C, D, E, F); +method_decl_impl!(A, B, C, D, E, F, G); +method_decl_impl!(A, B, C, D, E, F, G, H); +method_decl_impl!(A, B, C, D, E, F, G, H, I); +method_decl_impl!(A, B, C, D, E, F, G, H, I, J); +method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K); +method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K, L); + +fn count_args(sel: Sel) -> usize { + sel.name().chars().filter(|&c| c == ':').count() +} + +fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString { + let mut types = ret.as_str().to_owned(); + // First two arguments are always self and the selector + types.push_str(<*mut Object>::encode().as_str()); + types.push_str(Sel::encode().as_str()); + types.extend(args.iter().map(|e| e.as_str())); + CString::new(types).unwrap() +} + +fn log2_align_of() -> u8 { + let align = mem::align_of::(); + // Alignments are required to be powers of 2 + debug_assert!(align.count_ones() == 1); + // log2 of a power of 2 is the number of trailing zeros + align.trailing_zeros() as u8 +} + +/// A type for declaring a new class and adding new methods and ivars to it +/// before registering it. +pub struct ClassDecl { + cls: *mut Class, +} + +impl ClassDecl { + fn with_superclass(name: &str, superclass: Option<&Class>) + -> Option { + let name = CString::new(name).unwrap(); + let super_ptr = superclass.map_or(ptr::null(), |c| c); + let cls = unsafe { + runtime::objc_allocateClassPair(super_ptr, name.as_ptr(), 0) + }; + if cls.is_null() { + None + } else { + Some(ClassDecl { cls: cls }) + } + } + + /// Constructs a `ClassDecl` with the given name and superclass. + /// Returns `None` if the class couldn't be allocated. + pub fn new(name: &str, superclass: &Class) -> Option { + ClassDecl::with_superclass(name, Some(superclass)) + } + + /** + Constructs a `ClassDecl` declaring a new root class with the given name. + Returns `None` if the class couldn't be allocated. + + An implementation for `+initialize` must also be given; the runtime calls + this method for all classes, so it must be defined on root classes. + + Note that implementing a root class is not a simple endeavor. + For example, your class probably cannot be passed to Cocoa code unless + the entire `NSObject` protocol is implemented. + Functionality it expects, like implementations of `-retain` and `-release` + used by ARC, will not be present otherwise. + */ + pub fn root(name: &str, intitialize_fn: extern fn(&Class, Sel)) + -> Option { + let mut decl = ClassDecl::with_superclass(name, None); + if let Some(ref mut decl) = decl { + unsafe { + decl.add_class_method(sel!(initialize), intitialize_fn); + } + } + decl + } + + /// Adds a method with the given name and implementation to self. + /// Panics if the method wasn't sucessfully added + /// or if the selector and function take different numbers of arguments. + /// Unsafe because the caller must ensure that the types match those that + /// are expected when the method is invoked from Objective-C. + pub unsafe fn add_method(&mut self, sel: Sel, func: F) + where F: MethodImplementation { + let encs = F::Args::encodings(); + let encs = encs.as_ref(); + let sel_args = count_args(sel); + assert!(sel_args == encs.len(), + "Selector accepts {} arguments, but function accepts {}", + sel_args, encs.len(), + ); + + let types = method_type_encoding(&F::Ret::encode(), encs); + let success = runtime::class_addMethod(self.cls, sel, func.imp(), + types.as_ptr()); + assert!(success != NO, "Failed to add method {:?}", sel); + } + + /// Adds a class method with the given name and implementation to self. + /// Panics if the method wasn't sucessfully added + /// or if the selector and function take different numbers of arguments. + /// Unsafe because the caller must ensure that the types match those that + /// are expected when the method is invoked from Objective-C. + pub unsafe fn add_class_method(&mut self, sel: Sel, func: F) + where F: MethodImplementation { + let encs = F::Args::encodings(); + let encs = encs.as_ref(); + let sel_args = count_args(sel); + assert!(sel_args == encs.len(), + "Selector accepts {} arguments, but function accepts {}", + sel_args, encs.len(), + ); + + let types = method_type_encoding(&F::Ret::encode(), encs); + let metaclass = (*self.cls).metaclass() as *const _ as *mut _; + let success = runtime::class_addMethod(metaclass, sel, func.imp(), + types.as_ptr()); + assert!(success != NO, "Failed to add class method {:?}", sel); + } + + /// Adds an ivar with type `T` and the provided name to self. + /// Panics if the ivar wasn't successfully added. + pub fn add_ivar(&mut self, name: &str) where T: Encode { + let c_name = CString::new(name).unwrap(); + let encoding = CString::new(T::encode().as_str()).unwrap(); + let size = mem::size_of::(); + let align = log2_align_of::(); + let success = unsafe { + runtime::class_addIvar(self.cls, c_name.as_ptr(), size, align, + encoding.as_ptr()) + }; + assert!(success != NO, "Failed to add ivar {}", name); + } + + /// Adds a protocol to self. Panics if the protocol wasn't successfully + /// added + pub fn add_protocol(&mut self, proto: &Protocol) { + let success = unsafe { runtime::class_addProtocol(self.cls, proto) }; + assert!(success != NO, "Failed to add protocol {:?}", proto); + } + + /// Registers self, consuming it and returning a reference to the + /// newly registered `Class`. + pub fn register(self) -> &'static Class { + unsafe { + let cls = self.cls; + runtime::objc_registerClassPair(cls); + // Forget self otherwise the class will be disposed in drop + mem::forget(self); + &*cls + } + } +} + +impl Drop for ClassDecl { + fn drop(&mut self) { + unsafe { + runtime::objc_disposeClassPair(self.cls); + } + } +} + +/// A type for declaring a new protocol and adding new methods to it +/// before registering it. +pub struct ProtocolDecl { + proto: *mut Protocol +} + +impl ProtocolDecl { + /// Constructs a `ProtocolDecl` with the given name. Returns `None` if the + /// protocol couldn't be allocated. + pub fn new(name: &str) -> Option { + let c_name = CString::new(name).unwrap(); + let proto = unsafe { + runtime::objc_allocateProtocol(c_name.as_ptr()) + }; + if proto.is_null() { + None + } else { + Some(ProtocolDecl { proto: proto }) + } + } + + fn add_method_description_common(&mut self, sel: Sel, is_required: bool, + is_instance_method: bool) + where Args: EncodeArguments, + Ret: Encode { + let encs = Args::encodings(); + let encs = encs.as_ref(); + let sel_args = count_args(sel); + assert!(sel_args == encs.len(), + "Selector accepts {} arguments, but function accepts {}", + sel_args, encs.len(), + ); + let types = method_type_encoding(&Ret::encode(), encs); + unsafe { + runtime::protocol_addMethodDescription( + self.proto, sel, types.as_ptr(), is_required as BOOL, is_instance_method as BOOL); + } + } + + /// Adds an instance method declaration with a given description to self. + pub fn add_method_description(&mut self, sel: Sel, is_required: bool) + where Args: EncodeArguments, + Ret: Encode { + self.add_method_description_common::(sel, is_required, true) + } + + /// Adds a class method declaration with a given description to self. + pub fn add_class_method_description(&mut self, sel: Sel, is_required: bool) + where Args: EncodeArguments, + Ret: Encode { + self.add_method_description_common::(sel, is_required, false) + } + + /// Adds a requirement on another protocol. + pub fn add_protocol(&mut self, proto: &Protocol) { + unsafe { + runtime::protocol_addProtocol(self.proto, proto); + } + } + + /// Registers self, consuming it and returning a reference to the + /// newly registered `Protocol`. + pub fn register(self) -> &'static Protocol { + unsafe { + runtime::objc_registerProtocol(self.proto); + &*self.proto + } + } +} + +#[cfg(test)] +mod tests { + use test_utils; + + #[test] + fn test_custom_class() { + // Registering the custom class is in test_utils + let obj = test_utils::custom_object(); + unsafe { + let _: () = msg_send![obj, setFoo:13u32]; + let result: u32 = msg_send![obj, foo]; + assert!(result == 13); + } + } + + #[test] + fn test_class_method() { + let cls = test_utils::custom_class(); + unsafe { + let result: u32 = msg_send![cls, classFoo]; + assert!(result == 7); + } + } +} diff --git a/third_party/rust/objc/src/encode.rs b/third_party/rust/objc/src/encode.rs new file mode 100644 index 0000000000..921479da98 --- /dev/null +++ b/third_party/rust/objc/src/encode.rs @@ -0,0 +1,279 @@ +use std::ffi::CStr; +use std::fmt; +use std::os::raw::{c_char, c_void}; +use std::str; +use malloc_buf::MallocBuffer; + +use runtime::{Class, Object, Sel}; + +const QUALIFIERS: &'static [char] = &[ + 'r', // const + 'n', // in + 'N', // inout + 'o', // out + 'O', // bycopy + 'R', // byref + 'V', // oneway +]; + +#[cfg(target_pointer_width = "64")] +const CODE_INLINE_CAP: usize = 30; + +#[cfg(target_pointer_width = "32")] +const CODE_INLINE_CAP: usize = 14; + +enum Code { + Slice(&'static str), + Owned(String), + Inline(u8, [u8; CODE_INLINE_CAP]), + Malloc(MallocBuffer) +} + +/// An Objective-C type encoding. +/// +/// For more information, see Apple's documentation: +/// +pub struct Encoding { + code: Code, +} + +impl Encoding { + /// Constructs an `Encoding` from its string representation. + /// Unsafe because the caller must ensure the string is a valid encoding. + pub unsafe fn from_str(code: &str) -> Encoding { + from_str(code) + } + + /// Returns self as a `str`. + pub fn as_str(&self) -> &str { + match self.code { + Code::Slice(code) => code, + Code::Owned(ref code) => code, + Code::Inline(len, ref bytes) => unsafe { + str::from_utf8_unchecked(&bytes[..len as usize]) + }, + Code::Malloc(ref buf) => unsafe { + str::from_utf8_unchecked(&buf[..buf.len() - 1]) + }, + } + } +} + +impl Clone for Encoding { + fn clone(&self) -> Encoding { + if let Code::Slice(code) = self.code { + from_static_str(code) + } else { + from_str(self.as_str()) + } + } +} + +impl PartialEq for Encoding { + fn eq(&self, other: &Encoding) -> bool { + // strip qualifiers when comparing + let s = self.as_str().trim_left_matches(QUALIFIERS); + let o = other.as_str().trim_left_matches(QUALIFIERS); + s == o + } +} + +impl fmt::Debug for Encoding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +pub fn from_static_str(code: &'static str) -> Encoding { + Encoding { code: Code::Slice(code) } +} + +pub fn from_str(code: &str) -> Encoding { + if code.len() > CODE_INLINE_CAP { + Encoding { code: Code::Owned(code.to_owned()) } + } else { + let mut bytes = [0; CODE_INLINE_CAP]; + for (dst, byte) in bytes.iter_mut().zip(code.bytes()) { + *dst = byte; + } + Encoding { code: Code::Inline(code.len() as u8, bytes) } + } +} + +pub unsafe fn from_malloc_str(ptr: *mut c_char) -> Encoding { + let s = CStr::from_ptr(ptr); + let bytes = s.to_bytes_with_nul(); + assert!(str::from_utf8(bytes).is_ok()); + let buf = MallocBuffer::new(ptr as *mut u8, bytes.len()).unwrap(); + Encoding { code: Code::Malloc(buf) } +} + +/// Types that have an Objective-C type encoding. +/// +/// Unsafe because Objective-C will make assumptions about the type (like its +/// size and alignment) from its encoding, so the implementer must verify that +/// the encoding is accurate. +pub unsafe trait Encode { + /// Returns the Objective-C type encoding for Self. + fn encode() -> Encoding; +} + +macro_rules! encode_impls { + ($($t:ty : $s:expr,)*) => ($( + unsafe impl Encode for $t { + fn encode() -> Encoding { from_static_str($s) } + } + )*); +} + +encode_impls!( + i8: "c", + i16: "s", + i32: "i", + i64: "q", + u8: "C", + u16: "S", + u32: "I", + u64: "Q", + f32: "f", + f64: "d", + bool: "B", + (): "v", + *mut c_char: "*", + *const c_char: "r*", + *mut c_void: "^v", + *const c_void: "r^v", + Sel: ":", +); + +unsafe impl Encode for isize { + #[cfg(target_pointer_width = "32")] + fn encode() -> Encoding { i32::encode() } + + #[cfg(target_pointer_width = "64")] + fn encode() -> Encoding { i64::encode() } +} + +unsafe impl Encode for usize { + #[cfg(target_pointer_width = "32")] + fn encode() -> Encoding { u32::encode() } + + #[cfg(target_pointer_width = "64")] + fn encode() -> Encoding { u64::encode() } +} + +macro_rules! encode_message_impl { + ($code:expr, $name:ident) => ( + encode_message_impl!($code, $name,); + ); + ($code:expr, $name:ident, $($t:ident),*) => ( + unsafe impl<'a $(, $t)*> $crate::Encode for &'a $name<$($t),*> { + fn encode() -> Encoding { from_static_str($code) } + } + + unsafe impl<'a $(, $t)*> $crate::Encode for &'a mut $name<$($t),*> { + fn encode() -> Encoding { from_static_str($code) } + } + + unsafe impl<'a $(, $t)*> $crate::Encode for Option<&'a $name<$($t),*>> { + fn encode() -> Encoding { from_static_str($code) } + } + + unsafe impl<'a $(, $t)*> $crate::Encode for Option<&'a mut $name<$($t),*>> { + fn encode() -> Encoding { from_static_str($code) } + } + + unsafe impl<$($t),*> $crate::Encode for *const $name<$($t),*> { + fn encode() -> Encoding { from_static_str($code) } + } + + unsafe impl<$($t),*> $crate::Encode for *mut $name<$($t),*> { + fn encode() -> Encoding { from_static_str($code) } + } + ); +} + +encode_message_impl!("@", Object); + +encode_message_impl!("#", Class); + +/// Types that represent a group of arguments, where each has an Objective-C +/// type encoding. +pub trait EncodeArguments { + /// The type as which the encodings for Self will be returned. + type Encs: AsRef<[Encoding]>; + + /// Returns the Objective-C type encodings for Self. + fn encodings() -> Self::Encs; +} + +macro_rules! count_idents { + () => (0); + ($a:ident) => (1); + ($a:ident, $($b:ident),+) => (1 + count_idents!($($b),*)); +} + +macro_rules! encode_args_impl { + ($($t:ident),*) => ( + impl<$($t: Encode),*> EncodeArguments for ($($t,)*) { + type Encs = [Encoding; count_idents!($($t),*)]; + + fn encodings() -> Self::Encs { + [ + $($t::encode()),* + ] + } + } + ); +} + +encode_args_impl!(); +encode_args_impl!(A); +encode_args_impl!(A, B); +encode_args_impl!(A, B, C); +encode_args_impl!(A, B, C, D); +encode_args_impl!(A, B, C, D, E); +encode_args_impl!(A, B, C, D, E, F); +encode_args_impl!(A, B, C, D, E, F, G); +encode_args_impl!(A, B, C, D, E, F, G, H); +encode_args_impl!(A, B, C, D, E, F, G, H, I); +encode_args_impl!(A, B, C, D, E, F, G, H, I, J); +encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K); +encode_args_impl!(A, B, C, D, E, F, G, H, I, J, K, L); + +#[cfg(test)] +mod tests { + use runtime::{Class, Object, Sel}; + use super::{Encode, Encoding}; + + #[test] + fn test_encode() { + assert!(u32::encode().as_str() == "I"); + assert!(<()>::encode().as_str() == "v"); + assert!(<&Object>::encode().as_str() == "@"); + assert!(<*mut Object>::encode().as_str() == "@"); + assert!(<&Class>::encode().as_str() == "#"); + assert!(Sel::encode().as_str() == ":"); + } + + #[test] + fn test_inline_encoding() { + let enc = unsafe { Encoding::from_str("C") }; + assert!(enc.as_str() == "C"); + + let enc2 = enc.clone(); + assert!(enc2 == enc); + assert!(enc2.as_str() == "C"); + } + + #[test] + fn test_owned_encoding() { + let s = "{Test=CCCCCCCCCCCCCCCCCCCCCCCCC}"; + let enc = unsafe { Encoding::from_str(s) }; + assert!(enc.as_str() == s); + + let enc2 = enc.clone(); + assert!(enc2 == enc); + assert!(enc2.as_str() == s); + } +} diff --git a/third_party/rust/objc/src/exception.rs b/third_party/rust/objc/src/exception.rs new file mode 100644 index 0000000000..6fcfa7e87a --- /dev/null +++ b/third_party/rust/objc/src/exception.rs @@ -0,0 +1,11 @@ +use objc_exception; + +use rc::StrongPtr; +use runtime::Object; + +pub unsafe fn try(closure: F) -> Result + where F: FnOnce() -> R { + objc_exception::try(closure).map_err(|exception| { + StrongPtr::new(exception as *mut Object) + }) +} diff --git a/third_party/rust/objc/src/lib.rs b/third_party/rust/objc/src/lib.rs new file mode 100644 index 0000000000..cdd30f5fd5 --- /dev/null +++ b/third_party/rust/objc/src/lib.rs @@ -0,0 +1,90 @@ +/*! +Objective-C Runtime bindings and wrapper for Rust. + +# Messaging objects + +Objective-C objects can be messaged using the [`msg_send!`](macro.msg_send!.html) macro: + +``` no_run +# #[macro_use] extern crate objc; +# use objc::runtime::{BOOL, Class, Object}; +# fn main() { +# unsafe { +let cls = class!(NSObject); +let obj: *mut Object = msg_send![cls, new]; +let hash: usize = msg_send![obj, hash]; +let is_kind: BOOL = msg_send![obj, isKindOfClass:cls]; +// Even void methods must have their return type annotated +let _: () = msg_send![obj, release]; +# } +# } +``` + +# Reference counting + +Utilities for reference counting Objective-C objects are provided in the +[`rc`](rc/index.html) module. + +# Declaring classes + +Objective-C classes can even be declared from Rust using the functionality of +the [`declare`](declare/index.html) module. + +# Exceptions + +By default, if the `msg_send!` macro causes an exception to be thrown, this +will unwind into Rust resulting in unsafe, undefined behavior. +However, this crate has an `"exception"` feature which, when enabled, wraps +each `msg_send!` in a `@try`/`@catch` and panics if an exception is caught, +preventing Objective-C from unwinding into Rust. + +# Message type verification + +The Objective-C runtime includes encodings for each method that describe the +argument and return types. This crate can take advantage of these encodings to +verify that the types used in Rust match the types encoded for the method. + +To use this functionality, enable the `"verify_message"` feature. +With this feature enabled, type checking is performed for every message send, +which also requires that all arguments and return values for all messages +implement `Encode`. + +If this requirement is burdensome or you'd rather +just verify specific messages, you can call the +[`Message::verify_message`](trait.Message.html#method.verify_message) method +for specific selectors. + +# Support for other Operating Systems + +The bindings can be used on Linux or *BSD utilizing the +[GNUstep Objective-C runtime](https://www.github.com/gnustep/libobjc2). +*/ + +#![crate_name = "objc"] +#![crate_type = "lib"] + +#![warn(missing_docs)] + +extern crate malloc_buf; +#[cfg(feature = "exception")] +extern crate objc_exception; + +pub use encode::{Encode, EncodeArguments, Encoding}; +pub use message::{Message, MessageArguments, MessageError}; + +pub use message::send_message as __send_message; +pub use message::send_super_message as __send_super_message; + +#[macro_use] +mod macros; + +pub mod runtime; +pub mod declare; +pub mod rc; +mod encode; +#[cfg(feature = "exception")] +mod exception; +mod message; + +#[cfg(test)] +mod test_utils; diff --git a/third_party/rust/objc/src/macros.rs b/third_party/rust/objc/src/macros.rs new file mode 100644 index 0000000000..c791f20ff9 --- /dev/null +++ b/third_party/rust/objc/src/macros.rs @@ -0,0 +1,148 @@ +/** +Gets a reference to a `Class`. + +Panics if no class with the given name can be found. +To check for a class that may not exist, use `Class::get`. + +# Example +``` no_run +# #[macro_use] extern crate objc; +# fn main() { +let cls = class!(NSObject); +# } +``` +*/ +#[macro_export] +macro_rules! class { + ($name:ident) => ({ + #[allow(deprecated)] + #[inline(always)] + fn get_class(name: &str) -> Option<&'static $crate::runtime::Class> { + unsafe { + #[cfg_attr(feature = "cargo-clippy", allow(replace_consts))] + static CLASS: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT; + // `Relaxed` should be fine since `objc_getClass` is thread-safe. + let ptr = CLASS.load(::std::sync::atomic::Ordering::Relaxed) as *const $crate::runtime::Class; + if ptr.is_null() { + let cls = $crate::runtime::objc_getClass(name.as_ptr() as *const _); + CLASS.store(cls as usize, ::std::sync::atomic::Ordering::Relaxed); + if cls.is_null() { None } else { Some(&*cls) } + } else { + Some(&*ptr) + } + } + } + match get_class(concat!(stringify!($name), '\0')) { + Some(cls) => cls, + None => panic!("Class with name {} could not be found", stringify!($name)), + } + }) +} + +#[doc(hidden)] +#[macro_export] +macro_rules! sel_impl { + // Declare a function to hide unsafety, otherwise we can trigger the + // unused_unsafe lint; see rust-lang/rust#8472 + ($name:expr) => ({ + #[allow(deprecated)] + #[inline(always)] + fn register_sel(name: &str) -> $crate::runtime::Sel { + unsafe { + #[cfg_attr(feature = "cargo-clippy", allow(replace_consts))] + static SEL: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT; + let ptr = SEL.load(::std::sync::atomic::Ordering::Relaxed) as *const ::std::os::raw::c_void; + // It should be fine to use `Relaxed` ordering here because `sel_registerName` is + // thread-safe. + if ptr.is_null() { + let sel = $crate::runtime::sel_registerName(name.as_ptr() as *const _); + SEL.store(sel.as_ptr() as usize, ::std::sync::atomic::Ordering::Relaxed); + sel + } else { + $crate::runtime::Sel::from_ptr(ptr) + } + } + } + register_sel($name) + }) +} + +/** +Registers a selector, returning a `Sel`. + +# Example +``` +# #[macro_use] extern crate objc; +# fn main() { +let sel = sel!(description); +let sel = sel!(setObject:forKey:); +# } +``` +*/ +#[macro_export] +macro_rules! sel { + ($name:ident) => ({sel_impl!(concat!(stringify!($name), '\0'))}); + ($($name:ident :)+) => ({sel_impl!(concat!($(stringify!($name), ':'),+, '\0'))}); +} + +/** +Sends a message to an object. + +The first argument can be any type that dereferences to a type that implements +`Message`, like a reference, pointer, or an `Id`. +The syntax is similar to the message syntax in Objective-C. +Variadic arguments are not currently supported. + +# Example +``` no_run +# #[macro_use] extern crate objc; +# use objc::runtime::Object; +# fn main() { +# unsafe { +let obj: *mut Object; +# let obj: *mut Object = 0 as *mut Object; +let description: *const Object = msg_send![obj, description]; +let _: () = msg_send![obj, setArg1:1 arg2:2]; +# } +# } +``` +*/ +#[macro_export] +macro_rules! msg_send { + (super($obj:expr, $superclass:expr), $name:ident) => ({ + let sel = sel!($name); + let result; + match $crate::__send_super_message(&*$obj, $superclass, sel, ()) { + Err(s) => panic!("{}", s), + Ok(r) => result = r, + } + result + }); + (super($obj:expr, $superclass:expr), $($name:ident : $arg:expr)+) => ({ + let sel = sel!($($name:)+); + let result; + match $crate::__send_super_message(&*$obj, $superclass, sel, ($($arg,)*)) { + Err(s) => panic!("{}", s), + Ok(r) => result = r, + } + result + }); + ($obj:expr, $name:ident) => ({ + let sel = sel!($name); + let result; + match $crate::__send_message(&*$obj, sel, ()) { + Err(s) => panic!("{}", s), + Ok(r) => result = r, + } + result + }); + ($obj:expr, $($name:ident : $arg:expr)+) => ({ + let sel = sel!($($name:)+); + let result; + match $crate::__send_message(&*$obj, sel, ($($arg,)*)) { + Err(s) => panic!("{}", s), + Ok(r) => result = r, + } + result + }); +} diff --git a/third_party/rust/objc/src/message/apple/arm.rs b/third_party/rust/objc/src/message/apple/arm.rs new file mode 100644 index 0000000000..d26defc0de --- /dev/null +++ b/third_party/rust/objc/src/message/apple/arm.rs @@ -0,0 +1,40 @@ +use std::any::{Any, TypeId}; +use std::mem; + +use runtime::Imp; + +extern { + fn objc_msgSend(); + fn objc_msgSend_stret(); + + fn objc_msgSendSuper(); + fn objc_msgSendSuper_stret(); +} + +pub fn msg_send_fn() -> Imp { + // Double-word sized fundamental data types don't use stret, + // but any composite type larger than 4 bytes does. + // + + let type_id = TypeId::of::(); + if mem::size_of::() <= 4 || + type_id == TypeId::of::() || + type_id == TypeId::of::() || + type_id == TypeId::of::() { + objc_msgSend + } else { + objc_msgSend_stret + } +} + +pub fn msg_send_super_fn() -> Imp { + let type_id = TypeId::of::(); + if mem::size_of::() <= 4 || + type_id == TypeId::of::() || + type_id == TypeId::of::() || + type_id == TypeId::of::() { + objc_msgSendSuper + } else { + objc_msgSendSuper_stret + } +} diff --git a/third_party/rust/objc/src/message/apple/arm64.rs b/third_party/rust/objc/src/message/apple/arm64.rs new file mode 100644 index 0000000000..54cfc897c5 --- /dev/null +++ b/third_party/rust/objc/src/message/apple/arm64.rs @@ -0,0 +1,18 @@ +use runtime::Imp; + +extern { + fn objc_msgSend(); + + fn objc_msgSendSuper(); +} + +pub fn msg_send_fn() -> Imp { + // stret is not even available in arm64. + // + + objc_msgSend +} + +pub fn msg_send_super_fn() -> Imp { + objc_msgSendSuper +} diff --git a/third_party/rust/objc/src/message/apple/mod.rs b/third_party/rust/objc/src/message/apple/mod.rs new file mode 100644 index 0000000000..30f59ca870 --- /dev/null +++ b/third_party/rust/objc/src/message/apple/mod.rs @@ -0,0 +1,40 @@ +use std::any::Any; + +use runtime::{Class, Object, Sel}; +use super::{Message, MessageArguments, MessageError, Super}; + +#[cfg(target_arch = "x86")] +#[path = "x86.rs"] +mod arch; +#[cfg(target_arch = "x86_64")] +#[path = "x86_64.rs"] +mod arch; +#[cfg(target_arch = "arm")] +#[path = "arm.rs"] +mod arch; +#[cfg(target_arch = "aarch64")] +#[path = "arm64.rs"] +mod arch; + +use self::arch::{msg_send_fn, msg_send_super_fn}; + +pub unsafe fn send_unverified(obj: *const T, sel: Sel, args: A) + -> Result + where T: Message, A: MessageArguments, R: Any { + let receiver = obj as *mut T as *mut Object; + let msg_send_fn = msg_send_fn::(); + objc_try!({ + A::invoke(msg_send_fn, receiver, sel, args) + }) +} + +pub unsafe fn send_super_unverified(obj: *const T, superclass: &Class, + sel: Sel, args: A) -> Result + where T: Message, A: MessageArguments, R: Any { + let sup = Super { receiver: obj as *mut T as *mut Object, superclass: superclass }; + let receiver = &sup as *const Super as *mut Object; + let msg_send_fn = msg_send_super_fn::(); + objc_try!({ + A::invoke(msg_send_fn, receiver, sel, args) + }) +} diff --git a/third_party/rust/objc/src/message/apple/x86.rs b/third_party/rust/objc/src/message/apple/x86.rs new file mode 100644 index 0000000000..93422de24b --- /dev/null +++ b/third_party/rust/objc/src/message/apple/x86.rs @@ -0,0 +1,40 @@ +use std::any::{Any, TypeId}; +use std::mem; + +use runtime::Imp; + +extern { + fn objc_msgSend(); + fn objc_msgSend_fpret(); + fn objc_msgSend_stret(); + + fn objc_msgSendSuper(); + fn objc_msgSendSuper_stret(); +} + +pub fn msg_send_fn() -> Imp { + // Structures 1 or 2 bytes in size are placed in EAX. + // Structures 4 or 8 bytes in size are placed in: EAX and EDX. + // Structures of other sizes are placed at the address supplied by the caller. + // + + let type_id = TypeId::of::(); + let size = mem::size_of::(); + if type_id == TypeId::of::() || + type_id == TypeId::of::() { + objc_msgSend_fpret + } else if size == 0 || size == 1 || size == 2 || size == 4 || size == 8 { + objc_msgSend + } else { + objc_msgSend_stret + } +} + +pub fn msg_send_super_fn() -> Imp { + let size = mem::size_of::(); + if size == 0 || size == 1 || size == 2 || size == 4 || size == 8 { + objc_msgSendSuper + } else { + objc_msgSendSuper_stret + } +} diff --git a/third_party/rust/objc/src/message/apple/x86_64.rs b/third_party/rust/objc/src/message/apple/x86_64.rs new file mode 100644 index 0000000000..49c90ce53e --- /dev/null +++ b/third_party/rust/objc/src/message/apple/x86_64.rs @@ -0,0 +1,32 @@ +use std::mem; + +use runtime::Imp; + +extern { + fn objc_msgSend(); + fn objc_msgSend_stret(); + + fn objc_msgSendSuper(); + fn objc_msgSendSuper_stret(); +} + +pub fn msg_send_fn() -> Imp { + // If the size of an object is larger than two eightbytes, it has class MEMORY. + // If the type has class MEMORY, then the caller provides space for the return + // value and passes the address of this storage. + // + + if mem::size_of::() <= 16 { + objc_msgSend + } else { + objc_msgSend_stret + } +} + +pub fn msg_send_super_fn() -> Imp { + if mem::size_of::() <= 16 { + objc_msgSendSuper + } else { + objc_msgSendSuper_stret + } +} diff --git a/third_party/rust/objc/src/message/gnustep.rs b/third_party/rust/objc/src/message/gnustep.rs new file mode 100644 index 0000000000..2e28689cef --- /dev/null +++ b/third_party/rust/objc/src/message/gnustep.rs @@ -0,0 +1,35 @@ +use std::any::Any; +use std::mem; + +use runtime::{Class, Object, Imp, Sel}; +use super::{Message, MessageArguments, MessageError, Super}; + +extern { + fn objc_msg_lookup(receiver: *mut Object, op: Sel) -> Imp; + fn objc_msg_lookup_super(sup: *const Super, sel: Sel) -> Imp; +} + +pub unsafe fn send_unverified(obj: *const T, sel: Sel, args: A) + -> Result + where T: Message, A: MessageArguments, R: Any { + if obj.is_null() { + return mem::zeroed(); + } + + let receiver = obj as *mut T as *mut Object; + let msg_send_fn = objc_msg_lookup(receiver, sel); + objc_try!({ + A::invoke(msg_send_fn, receiver, sel, args) + }) +} + +pub unsafe fn send_super_unverified(obj: *const T, superclass: &Class, + sel: Sel, args: A) -> Result + where T: Message, A: MessageArguments, R: Any { + let receiver = obj as *mut T as *mut Object; + let sup = Super { receiver: receiver, superclass: superclass }; + let msg_send_fn = objc_msg_lookup_super(&sup, sel); + objc_try!({ + A::invoke(msg_send_fn, receiver, sel, args) + }) +} diff --git a/third_party/rust/objc/src/message/mod.rs b/third_party/rust/objc/src/message/mod.rs new file mode 100644 index 0000000000..cb11e993a8 --- /dev/null +++ b/third_party/rust/objc/src/message/mod.rs @@ -0,0 +1,296 @@ +use std::any::Any; +use std::error::Error; +use std::fmt; +use std::mem; + +use runtime::{Class, Imp, Object, Sel}; +use {Encode, EncodeArguments}; + +#[cfg(feature = "exception")] +macro_rules! objc_try { + ($b:block) => ( + $crate::exception::try(|| $b).map_err(|exception| + if exception.is_null() { + MessageError("Uncaught exception nil".to_owned()) + } else { + MessageError(format!("Uncaught exception {:?}", &**exception)) + } + ) + ) +} + +#[cfg(not(feature = "exception"))] +macro_rules! objc_try { + ($b:block) => (Ok($b)) +} + +mod verify; + +#[cfg(any(target_os = "macos", target_os = "ios"))] +#[path = "apple/mod.rs"] +mod platform; +#[cfg(not(any(target_os = "macos", target_os = "ios")))] +#[path = "gnustep.rs"] +mod platform; + +use self::platform::{send_unverified, send_super_unverified}; +use self::verify::verify_message_signature; + +/// Specifies the superclass of an instance. +#[repr(C)] +pub struct Super { + /// Specifies an instance of a class. + pub receiver: *mut Object, + /// Specifies the particular superclass of the instance to message. + pub superclass: *const Class, +} + +/// Types that may be sent Objective-C messages. +/// For example: objects, classes, and blocks. +pub unsafe trait Message { + /** + Sends a message to self with the given selector and arguments. + + The correct version of `objc_msgSend` will be chosen based on the + return type. For more information, see Apple's documentation: + + + If the selector is known at compile-time, it is recommended to use the + `msg_send!` macro rather than this method. + */ + #[cfg(not(feature = "verify_message"))] + unsafe fn send_message(&self, sel: Sel, args: A) + -> Result + where Self: Sized, A: MessageArguments, R: Any { + send_message(self, sel, args) + } + + #[cfg(feature = "verify_message")] + unsafe fn send_message(&self, sel: Sel, args: A) + -> Result + where Self: Sized, A: MessageArguments + EncodeArguments, + R: Any + Encode { + send_message(self, sel, args) + } + + /** + Verifies that the argument and return types match the encoding of the + method for the given selector. + + This will look up the encoding of the method for the given selector, `sel`, + and return a `MessageError` if any encodings differ for the arguments `A` + and return type `R`. + + # Example + ``` no_run + # #[macro_use] extern crate objc; + # use objc::runtime::{BOOL, Class, Object}; + # use objc::Message; + # fn main() { + let obj: &Object; + # obj = unsafe { msg_send![class!(NSObject), new] }; + let sel = sel!(isKindOfClass:); + // Verify isKindOfClass: takes one Class and returns a BOOL + let result = obj.verify_message::<(&Class,), BOOL>(sel); + assert!(result.is_ok()); + # } + ``` + */ + fn verify_message(&self, sel: Sel) -> Result<(), MessageError> + where Self: Sized, A: EncodeArguments, R: Encode { + let obj = unsafe { &*(self as *const _ as *const Object) }; + verify_message_signature::(obj.class(), sel) + } +} + +unsafe impl Message for Object { } + +unsafe impl Message for Class { } + +/// Types that may be used as the arguments of an Objective-C message. +pub trait MessageArguments: Sized { + /// Invoke an `Imp` with the given object, selector, and arguments. + /// + /// This method is the primitive used when sending messages and should not + /// be called directly; instead, use the `msg_send!` macro or, in cases + /// with a dynamic selector, the `Message::send_message` method. + unsafe fn invoke(imp: Imp, obj: *mut Object, sel: Sel, args: Self) -> R + where R: Any; +} + +macro_rules! message_args_impl { + ($($a:ident : $t:ident),*) => ( + impl<$($t),*> MessageArguments for ($($t,)*) { + unsafe fn invoke(imp: Imp, obj: *mut Object, sel: Sel, ($($a,)*): Self) -> R + where R: Any { + let imp: unsafe extern fn(*mut Object, Sel $(, $t)*) -> R = + mem::transmute(imp); + imp(obj, sel $(, $a)*) + } + } + ); +} + +message_args_impl!(); +message_args_impl!(a: A); +message_args_impl!(a: A, b: B); +message_args_impl!(a: A, b: B, c: C); +message_args_impl!(a: A, b: B, c: C, d: D); +message_args_impl!(a: A, b: B, c: C, d: D, e: E); +message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F); +message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G); +message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); +message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); +message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J); +message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K); +message_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L); + +/** +An error encountered while attempting to send a message. + +Currently, an error may be returned in two cases: + +* an Objective-C exception is thrown and the `exception` feature is enabled +* the encodings of the arguments do not match the encoding of the method + and the `verify_message` feature is enabled +*/ +#[derive(Debug)] +pub struct MessageError(String); + +impl fmt::Display for MessageError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl Error for MessageError { + fn description(&self) -> &str { + &self.0 + } +} + +#[doc(hidden)] +#[inline(always)] +#[cfg(not(feature = "verify_message"))] +pub unsafe fn send_message(obj: *const T, sel: Sel, args: A) + -> Result + where T: Message, A: MessageArguments, R: Any { + send_unverified(obj, sel, args) +} + +#[doc(hidden)] +#[inline(always)] +#[cfg(feature = "verify_message")] +pub unsafe fn send_message(obj: *const T, sel: Sel, args: A) + -> Result + where T: Message, A: MessageArguments + EncodeArguments, + R: Any + Encode { + let cls = if obj.is_null() { + return Err(MessageError(format!("Messaging {:?} to nil", sel))); + } else { + (*(obj as *const Object)).class() + }; + + verify_message_signature::(cls, sel).and_then(|_| { + send_unverified(obj, sel, args) + }) +} + +#[doc(hidden)] +#[inline(always)] +#[cfg(not(feature = "verify_message"))] +pub unsafe fn send_super_message(obj: *const T, superclass: &Class, + sel: Sel, args: A) -> Result + where T: Message, A: MessageArguments, R: Any { + send_super_unverified(obj, superclass, sel, args) +} + +#[doc(hidden)] +#[inline(always)] +#[cfg(feature = "verify_message")] +pub unsafe fn send_super_message(obj: *const T, superclass: &Class, + sel: Sel, args: A) -> Result + where T: Message, A: MessageArguments + EncodeArguments, + R: Any + Encode { + if obj.is_null() { + return Err(MessageError(format!("Messaging {:?} to nil", sel))); + } + + verify_message_signature::(superclass, sel).and_then(|_| { + send_super_unverified(obj, superclass, sel, args) + }) +} + +#[cfg(test)] +mod tests { + use test_utils; + use runtime::Object; + use super::Message; + + #[test] + fn test_send_message() { + let obj = test_utils::custom_object(); + let result: u32 = unsafe { + let _: () = msg_send![obj, setFoo:4u32]; + msg_send![obj, foo] + }; + assert!(result == 4); + } + + #[test] + fn test_send_message_stret() { + let obj = test_utils::custom_object(); + let result: test_utils::CustomStruct = unsafe { + msg_send![obj, customStruct] + }; + let expected = test_utils::CustomStruct { a: 1, b:2, c: 3, d: 4 }; + assert!(result == expected); + } + + #[cfg(not(feature = "verify_message"))] + #[test] + fn test_send_message_nil() { + let nil: *mut Object = ::std::ptr::null_mut(); + let result: usize = unsafe { + msg_send![nil, hash] + }; + assert!(result == 0); + + let result: *mut Object = unsafe { + msg_send![nil, description] + }; + assert!(result.is_null()); + + let result: f64 = unsafe { + msg_send![nil, doubleValue] + }; + assert!(result == 0.0); + } + + #[test] + fn test_send_message_super() { + let obj = test_utils::custom_subclass_object(); + let superclass = test_utils::custom_class(); + unsafe { + let _: () = msg_send![obj, setFoo:4u32]; + let foo: u32 = msg_send![super(obj, superclass), foo]; + assert!(foo == 4); + + // The subclass is overriden to return foo + 2 + let foo: u32 = msg_send![obj, foo]; + assert!(foo == 6); + } + } + + #[test] + fn test_verify_message() { + let obj = test_utils::custom_object(); + assert!(obj.verify_message::<(), u32>(sel!(foo)).is_ok()); + assert!(obj.verify_message::<(u32,), ()>(sel!(setFoo:)).is_ok()); + + // Incorrect types + assert!(obj.verify_message::<(), u64>(sel!(setFoo:)).is_err()); + // Unimplemented selector + assert!(obj.verify_message::<(u32,), ()>(sel!(setFoo)).is_err()); + } +} diff --git a/third_party/rust/objc/src/message/verify.rs b/third_party/rust/objc/src/message/verify.rs new file mode 100644 index 0000000000..61bd4ebb39 --- /dev/null +++ b/third_party/rust/objc/src/message/verify.rs @@ -0,0 +1,49 @@ +use runtime::{Class, Object, Sel}; +use {Encode, EncodeArguments}; +use super::MessageError; + +pub fn verify_message_signature(cls: &Class, sel: Sel) + -> Result<(), MessageError> + where A: EncodeArguments, R: Encode { + let method = match cls.instance_method(sel) { + Some(method) => method, + None => return Err(MessageError( + format!("Method {:?} not found on class {:?}", + sel, cls) + )), + }; + + let ret = R::encode(); + let expected_ret = method.return_type(); + if ret != expected_ret { + return Err(MessageError( + format!("Return type code {:?} does not match expected {:?} for method {:?}", + ret, expected_ret, method.name()) + )); + } + + let self_and_cmd = [<*mut Object>::encode(), Sel::encode()]; + let args = A::encodings(); + let args = args.as_ref(); + + let count = self_and_cmd.len() + args.len(); + let expected_count = method.arguments_count(); + if count != expected_count { + return Err(MessageError( + format!("Method {:?} accepts {} arguments, but {} were given", + method.name(), expected_count, count) + )); + } + + for (i, arg) in self_and_cmd.iter().chain(args).enumerate() { + let expected = method.argument_type(i).unwrap(); + if *arg != expected { + return Err(MessageError( + format!("Method {:?} expected argument at index {} with type code {:?} but was given {:?}", + method.name(), i, expected, arg) + )); + } + } + + Ok(()) +} diff --git a/third_party/rust/objc/src/rc/autorelease.rs b/third_party/rust/objc/src/rc/autorelease.rs new file mode 100644 index 0000000000..ad7a5e2847 --- /dev/null +++ b/third_party/rust/objc/src/rc/autorelease.rs @@ -0,0 +1,30 @@ +use std::os::raw::c_void; +use runtime::{objc_autoreleasePoolPush, objc_autoreleasePoolPop}; + +// we use a struct to ensure that objc_autoreleasePoolPop during unwinding. +struct AutoReleaseHelper { + context: *mut c_void, +} + +impl AutoReleaseHelper { + unsafe fn new() -> Self { + AutoReleaseHelper { context: objc_autoreleasePoolPush() } + } +} + +impl Drop for AutoReleaseHelper { + fn drop(&mut self) { + unsafe { objc_autoreleasePoolPop(self.context) } + } +} + +/** +Execute `f` in the context of a new autorelease pool. The pool is drained +after the execution of `f` completes. + +This corresponds to `@autoreleasepool` blocks in Objective-C and Swift. +*/ +pub fn autoreleasepool T>(f: F) -> T { + let _context = unsafe { AutoReleaseHelper::new() }; + f() +} diff --git a/third_party/rust/objc/src/rc/mod.rs b/third_party/rust/objc/src/rc/mod.rs new file mode 100644 index 0000000000..f6d26f7a1b --- /dev/null +++ b/third_party/rust/objc/src/rc/mod.rs @@ -0,0 +1,123 @@ +/*! +Utilities for reference counting Objective-C objects. + +The utilities of the `rc` module provide ARC-like semantics for working with +Objective-C's reference counted objects in Rust. +A `StrongPtr` retains an object and releases the object when dropped. +A `WeakPtr` will not retain the object, but can be upgraded to a `StrongPtr` +and safely fails if the object has been deallocated. + +These utilities are not intended to provide a fully safe interface, but can be +useful when writing higher-level Rust wrappers for Objective-C code. + +For more information on Objective-C's reference counting, see Apple's documentation: + + +# Example + +``` no_run +# #[macro_use] extern crate objc; +# use objc::rc::{autoreleasepool, StrongPtr}; +# fn main() { +// StrongPtr will release the object when dropped +let obj = unsafe { + StrongPtr::new(msg_send![class!(NSObject), new]) +}; + +// Cloning retains the object an additional time +let cloned = obj.clone(); +autoreleasepool(|| { + // Autorelease consumes the StrongPtr, but won't + // actually release until the end of an autoreleasepool + cloned.autorelease(); +}); + +// Weak references won't retain the object +let weak = obj.weak(); +drop(obj); +assert!(weak.load().is_null()); +# } +``` +*/ + +mod strong; +mod weak; +mod autorelease; + +pub use self::strong::StrongPtr; +pub use self::weak::WeakPtr; +pub use self::autorelease::autoreleasepool; + +// These tests use NSObject, which isn't present for GNUstep +#[cfg(all(test, any(target_os = "macos", target_os = "ios")))] +mod tests { + use runtime::Object; + use super::StrongPtr; + use super::autoreleasepool; + + #[test] + fn test_strong_clone() { + fn retain_count(obj: *mut Object) -> usize { + unsafe { msg_send![obj, retainCount] } + } + + let obj = unsafe { + StrongPtr::new(msg_send![class!(NSObject), new]) + }; + assert!(retain_count(*obj) == 1); + + let cloned = obj.clone(); + assert!(retain_count(*cloned) == 2); + assert!(retain_count(*obj) == 2); + + drop(obj); + assert!(retain_count(*cloned) == 1); + } + + #[test] + fn test_weak() { + let obj = unsafe { + StrongPtr::new(msg_send![class!(NSObject), new]) + }; + let weak = obj.weak(); + + let strong = weak.load(); + assert!(*strong == *obj); + drop(strong); + + drop(obj); + assert!(weak.load().is_null()); + } + + #[test] + fn test_weak_copy() { + let obj = unsafe { + StrongPtr::new(msg_send![class!(NSObject), new]) + }; + let weak = obj.weak(); + + let weak2 = weak.clone(); + let strong = weak2.load(); + assert!(*strong == *obj); + } + + #[test] + fn test_autorelease() { + let obj = unsafe { + StrongPtr::new(msg_send![class!(NSObject), new]) + }; + + fn retain_count(obj: *mut Object) -> usize { + unsafe { msg_send![obj, retainCount] } + } + let cloned = obj.clone(); + + autoreleasepool(|| { + obj.autorelease(); + assert!(retain_count(*cloned) == 2); + }); + + // make sure that the autoreleased value has been released + assert!(retain_count(*cloned) == 1); + } +} diff --git a/third_party/rust/objc/src/rc/strong.rs b/third_party/rust/objc/src/rc/strong.rs new file mode 100644 index 0000000000..a61881b6ff --- /dev/null +++ b/third_party/rust/objc/src/rc/strong.rs @@ -0,0 +1,73 @@ +use std::fmt; +use std::mem; +use std::ops::Deref; + +use runtime::{Object, self}; +use super::WeakPtr; + +/// A pointer that strongly references an object, ensuring it won't be deallocated. +pub struct StrongPtr(*mut Object); + +impl StrongPtr { + /// Constructs a `StrongPtr` to a newly created object that already has a + /// +1 retain count. This will not retain the object. + /// When dropped, the object will be released. + /// Unsafe because the caller must ensure the given object pointer is valid. + pub unsafe fn new(ptr: *mut Object) -> Self { + StrongPtr(ptr) + } + + /// Retains the given object and constructs a `StrongPtr` to it. + /// When dropped, the object will be released. + /// Unsafe because the caller must ensure the given object pointer is valid. + pub unsafe fn retain(ptr: *mut Object) -> Self { + StrongPtr(runtime::objc_retain(ptr)) + } + + /// Autoreleases self, meaning that the object is not immediately released, + /// but will be when the autorelease pool is drained. A pointer to the + /// object is returned, but its validity is no longer ensured. + pub fn autorelease(self) -> *mut Object { + let ptr = self.0; + mem::forget(self); + unsafe { + runtime::objc_autorelease(ptr); + } + ptr + } + + /// Returns a `WeakPtr` to self. + pub fn weak(&self) -> WeakPtr { + unsafe { WeakPtr::new(self.0) } + } +} + +impl Drop for StrongPtr { + fn drop(&mut self) { + unsafe { + runtime::objc_release(self.0); + } + } +} + +impl Clone for StrongPtr { + fn clone(&self) -> StrongPtr { + unsafe { + StrongPtr::retain(self.0) + } + } +} + +impl Deref for StrongPtr { + type Target = *mut Object; + + fn deref(&self) -> &*mut Object { + &self.0 + } +} + +impl fmt::Pointer for StrongPtr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Pointer::fmt(&self.0, f) + } +} diff --git a/third_party/rust/objc/src/rc/weak.rs b/third_party/rust/objc/src/rc/weak.rs new file mode 100644 index 0000000000..289bd8dedb --- /dev/null +++ b/third_party/rust/objc/src/rc/weak.rs @@ -0,0 +1,50 @@ +use std::cell::UnsafeCell; +use std::ptr; + +use runtime::{Object, self}; +use super::StrongPtr; + +// Our pointer must have the same address even if we are moved, so Box it. +// Although loading the WeakPtr may modify the pointer, it is thread safe, +// so we must use an UnsafeCell to get a *mut without self being mutable. + +/// A pointer that weakly references an object, allowing to safely check +/// whether it has been deallocated. +pub struct WeakPtr(Box>); + +impl WeakPtr { + /// Constructs a `WeakPtr` to the given object. + /// Unsafe because the caller must ensure the given object pointer is valid. + pub unsafe fn new(obj: *mut Object) -> Self { + let ptr = Box::new(UnsafeCell::new(ptr::null_mut())); + runtime::objc_initWeak(ptr.get(), obj); + WeakPtr(ptr) + } + + /// Loads the object self points to, returning a `StrongPtr`. + /// If the object has been deallocated, the returned pointer will be null. + pub fn load(&self) -> StrongPtr { + unsafe { + let ptr = runtime::objc_loadWeakRetained(self.0.get()); + StrongPtr::new(ptr) + } + } +} + +impl Drop for WeakPtr { + fn drop(&mut self) { + unsafe { + runtime::objc_destroyWeak(self.0.get()); + } + } +} + +impl Clone for WeakPtr { + fn clone(&self) -> Self { + let ptr = Box::new(UnsafeCell::new(ptr::null_mut())); + unsafe { + runtime::objc_copyWeak(ptr.get(), self.0.get()); + } + WeakPtr(ptr) + } +} diff --git a/third_party/rust/objc/src/runtime.rs b/third_party/rust/objc/src/runtime.rs new file mode 100644 index 0000000000..3b533efe6f --- /dev/null +++ b/third_party/rust/objc/src/runtime.rs @@ -0,0 +1,632 @@ +//! A Rust interface for the functionality of the Objective-C runtime. +//! +//! For more information on foreign functions, see Apple's documentation: +//! + +use std::ffi::{CStr, CString}; +use std::fmt; +use std::os::raw::{c_char, c_int, c_uint, c_void}; +use std::ptr; +use std::str; +use malloc_buf::MallocBuffer; + +use encode; +use {Encode, Encoding}; + +/// The Objective-C `BOOL` type. +/// +/// To convert an Objective-C `BOOL` into a Rust `bool`, compare it with `NO`. +#[cfg(not(target_arch = "aarch64"))] +pub type BOOL = ::std::os::raw::c_schar; +/// The equivalent of true for Objective-C's `BOOL` type. +#[cfg(not(target_arch = "aarch64"))] +pub const YES: BOOL = 1; +/// The equivalent of false for Objective-C's `BOOL` type. +#[cfg(not(target_arch = "aarch64"))] +pub const NO: BOOL = 0; + +#[cfg(target_arch = "aarch64")] +pub type BOOL = bool; +#[cfg(target_arch = "aarch64")] +pub const YES: BOOL = true; +#[cfg(target_arch = "aarch64")] +pub const NO: BOOL = false; + +/// A type that represents a method selector. +#[repr(C)] +pub struct Sel { + ptr: *const c_void, +} + +/// A marker type to be embedded into other types just so that they cannot be +/// constructed externally. +type PrivateMarker = [u8; 0]; + +/// A type that represents an instance variable. +#[repr(C)] +pub struct Ivar { + _priv: PrivateMarker, +} + +/// A type that represents a method in a class definition. +#[repr(C)] +pub struct Method { + _priv: PrivateMarker, +} + +/// A type that represents an Objective-C class. +#[repr(C)] +pub struct Class { + _priv: PrivateMarker, +} + +/// A type that represents an Objective-C protocol. +#[repr(C)] +pub struct Protocol { + _priv: PrivateMarker +} + +/// A type that represents an instance of a class. +#[repr(C)] +pub struct Object { + _priv: PrivateMarker, +} + +/// A pointer to the start of a method implementation. +pub type Imp = unsafe extern fn(); + +#[link(name = "objc", kind = "dylib")] +extern { + pub fn sel_registerName(name: *const c_char) -> Sel; + pub fn sel_getName(sel: Sel) -> *const c_char; + + pub fn class_getName(cls: *const Class) -> *const c_char; + pub fn class_getSuperclass(cls: *const Class) -> *const Class; + pub fn class_getInstanceSize(cls: *const Class) -> usize; + pub fn class_getInstanceMethod(cls: *const Class, sel: Sel) -> *const Method; + pub fn class_getInstanceVariable(cls: *const Class, name: *const c_char) -> *const Ivar; + pub fn class_copyMethodList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Method; + pub fn class_copyIvarList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Ivar; + pub fn class_addMethod(cls: *mut Class, name: Sel, imp: Imp, types: *const c_char) -> BOOL; + pub fn class_addIvar(cls: *mut Class, name: *const c_char, size: usize, alignment: u8, types: *const c_char) -> BOOL; + pub fn class_addProtocol(cls: *mut Class, proto: *const Protocol) -> BOOL; + pub fn class_conformsToProtocol(cls: *const Class, proto: *const Protocol) -> BOOL; + pub fn class_copyProtocolList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Protocol; + + pub fn objc_allocateClassPair(superclass: *const Class, name: *const c_char, extraBytes: usize) -> *mut Class; + pub fn objc_disposeClassPair(cls: *mut Class); + pub fn objc_registerClassPair(cls: *mut Class); + + pub fn class_createInstance(cls: *const Class, extraBytes: usize) -> *mut Object; + pub fn object_dispose(obj: *mut Object) -> *mut Object; + pub fn object_getClass(obj: *const Object) -> *const Class; + + pub fn objc_getClassList(buffer: *mut *const Class, bufferLen: c_int) -> c_int; + pub fn objc_copyClassList(outCount: *mut c_uint) -> *mut *const Class; + pub fn objc_getClass(name: *const c_char) -> *const Class; + pub fn objc_getProtocol(name: *const c_char) -> *const Protocol; + pub fn objc_copyProtocolList(outCount: *mut c_uint) -> *mut *const Protocol; + pub fn objc_allocateProtocol(name: *const c_char) -> *mut Protocol; + pub fn objc_registerProtocol(proto: *mut Protocol); + + pub fn objc_autoreleasePoolPush() -> *mut c_void; + pub fn objc_autoreleasePoolPop(context: *mut c_void); + + pub fn protocol_addMethodDescription(proto: *mut Protocol, name: Sel, types: *const c_char, isRequiredMethod: BOOL, + isInstanceMethod: BOOL); + pub fn protocol_addProtocol(proto: *mut Protocol, addition: *const Protocol); + pub fn protocol_getName(proto: *const Protocol) -> *const c_char; + pub fn protocol_isEqual(proto: *const Protocol, other: *const Protocol) -> BOOL; + pub fn protocol_copyProtocolList(proto: *const Protocol, outCount: *mut c_uint) -> *mut *const Protocol; + pub fn protocol_conformsToProtocol(proto: *const Protocol, other: *const Protocol) -> BOOL; + + pub fn ivar_getName(ivar: *const Ivar) -> *const c_char; + pub fn ivar_getOffset(ivar: *const Ivar) -> isize; + pub fn ivar_getTypeEncoding(ivar: *const Ivar) -> *const c_char; + + pub fn method_getName(method: *const Method) -> Sel; + pub fn method_getImplementation(method: *const Method) -> Imp; + pub fn method_copyReturnType(method: *const Method) -> *mut c_char; + pub fn method_copyArgumentType(method: *const Method, index: c_uint) -> *mut c_char; + pub fn method_getNumberOfArguments(method: *const Method) -> c_uint; + pub fn method_setImplementation(method: *mut Method, imp: Imp) -> Imp; + pub fn method_exchangeImplementations(m1: *mut Method, m2: *mut Method); + + pub fn objc_retain(obj: *mut Object) -> *mut Object; + pub fn objc_release(obj: *mut Object); + pub fn objc_autorelease(obj: *mut Object); + + pub fn objc_loadWeakRetained(location: *mut *mut Object) -> *mut Object; + pub fn objc_initWeak(location: *mut *mut Object, obj: *mut Object) -> *mut Object; + pub fn objc_destroyWeak(location: *mut *mut Object); + pub fn objc_copyWeak(to: *mut *mut Object, from: *mut *mut Object); +} + +impl Sel { + /// Registers a method with the Objective-C runtime system, + /// maps the method name to a selector, and returns the selector value. + pub fn register(name: &str) -> Sel { + let name = CString::new(name).unwrap(); + unsafe { + sel_registerName(name.as_ptr()) + } + } + + /// Returns the name of the method specified by self. + pub fn name(&self) -> &str { + let name = unsafe { + CStr::from_ptr(sel_getName(*self)) + }; + str::from_utf8(name.to_bytes()).unwrap() + } + + /// Wraps a raw pointer to a selector into a `Sel` object. + /// + /// This is almost never what you want; use `Sel::register()` instead. + #[inline] + pub unsafe fn from_ptr(ptr: *const c_void) -> Sel { + Sel { + ptr: ptr, + } + } + + /// Returns a pointer to the raw selector. + #[inline] + pub fn as_ptr(&self) -> *const c_void { + self.ptr + } +} + +impl PartialEq for Sel { + fn eq(&self, other: &Sel) -> bool { + self.ptr == other.ptr + } +} + +impl Eq for Sel { } + +// Sel is safe to share across threads because it is immutable +unsafe impl Sync for Sel { } +unsafe impl Send for Sel { } + +impl Copy for Sel { } + +impl Clone for Sel { + fn clone(&self) -> Sel { *self } +} + +impl fmt::Debug for Sel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + +impl Ivar { + /// Returns the name of self. + pub fn name(&self) -> &str { + let name = unsafe { + CStr::from_ptr(ivar_getName(self)) + }; + str::from_utf8(name.to_bytes()).unwrap() + } + + /// Returns the offset of self. + pub fn offset(&self) -> isize { + let offset = unsafe { + ivar_getOffset(self) + }; + offset as isize + } + + /// Returns the `Encoding` of self. + pub fn type_encoding(&self) -> Encoding { + let encoding = unsafe { + CStr::from_ptr(ivar_getTypeEncoding(self)) + }; + let s = str::from_utf8(encoding.to_bytes()).unwrap(); + encode::from_str(s) + } +} + +impl Method { + /// Returns the name of self. + pub fn name(&self) -> Sel { + unsafe { + method_getName(self) + } + } + + /// Returns the `Encoding` of self's return type. + pub fn return_type(&self) -> Encoding { + unsafe { + let encoding = method_copyReturnType(self); + encode::from_malloc_str(encoding) + } + } + + /// Returns the `Encoding` of a single parameter type of self, or + /// `None` if self has no parameter at the given index. + pub fn argument_type(&self, index: usize) -> Option { + unsafe { + let encoding = method_copyArgumentType(self, index as c_uint); + if encoding.is_null() { + None + } else { + Some(encode::from_malloc_str(encoding)) + } + } + } + + /// Returns the number of arguments accepted by self. + pub fn arguments_count(&self) -> usize { + unsafe { + method_getNumberOfArguments(self) as usize + } + } + + /// Returns the implementation of self. + pub fn implementation(&self) -> Imp { + unsafe { + method_getImplementation(self) + } + } +} + +impl Class { + /// Returns the class definition of a specified class, or `None` if the + /// class is not registered with the Objective-C runtime. + pub fn get(name: &str) -> Option<&'static Class> { + let name = CString::new(name).unwrap(); + unsafe { + let cls = objc_getClass(name.as_ptr()); + if cls.is_null() { None } else { Some(&*cls) } + } + } + + /// Obtains the list of registered class definitions. + pub fn classes() -> MallocBuffer<&'static Class> { + unsafe { + let mut count: c_uint = 0; + let classes = objc_copyClassList(&mut count); + MallocBuffer::new(classes as *mut _, count as usize).unwrap() + } + } + + /// Returns the total number of registered classes. + pub fn classes_count() -> usize { + unsafe { + objc_getClassList(ptr::null_mut(), 0) as usize + } + } + + /// Returns the name of self. + pub fn name(&self) -> &str { + let name = unsafe { + CStr::from_ptr(class_getName(self)) + }; + str::from_utf8(name.to_bytes()).unwrap() + } + + /// Returns the superclass of self, or `None` if self is a root class. + pub fn superclass(&self) -> Option<&Class> { + unsafe { + let superclass = class_getSuperclass(self); + if superclass.is_null() { None } else { Some(&*superclass) } + } + } + + /// Returns the metaclass of self. + pub fn metaclass(&self) -> &Class { + unsafe { + let self_ptr: *const Class = self; + &*object_getClass(self_ptr as *const Object) + } + } + + /// Returns the size of instances of self. + pub fn instance_size(&self) -> usize { + unsafe { + class_getInstanceSize(self) as usize + } + } + + /// Returns a specified instance method for self, or `None` if self and + /// its superclasses do not contain an instance method with the + /// specified selector. + pub fn instance_method(&self, sel: Sel) -> Option<&Method> { + unsafe { + let method = class_getInstanceMethod(self, sel); + if method.is_null() { None } else { Some(&*method) } + } + } + + /// Returns the ivar for a specified instance variable of self, or `None` + /// if self has no ivar with the given name. + pub fn instance_variable(&self, name: &str) -> Option<&Ivar> { + let name = CString::new(name).unwrap(); + unsafe { + let ivar = class_getInstanceVariable(self, name.as_ptr()); + if ivar.is_null() { None } else { Some(&*ivar) } + } + } + + /// Describes the instance methods implemented by self. + pub fn instance_methods(&self) -> MallocBuffer<&Method> { + unsafe { + let mut count: c_uint = 0; + let methods = class_copyMethodList(self, &mut count); + MallocBuffer::new(methods as *mut _, count as usize).unwrap() + } + + } + + /// Checks whether this class conforms to the specified protocol. + pub fn conforms_to(&self, proto: &Protocol) -> bool { + unsafe { class_conformsToProtocol(self, proto) == YES } + } + + /// Get a list of the protocols to which this class conforms. + pub fn adopted_protocols(&self) -> MallocBuffer<&Protocol> { + unsafe { + let mut count: c_uint = 0; + let protos = class_copyProtocolList(self, &mut count); + MallocBuffer::new(protos as *mut _, count as usize).unwrap() + } + } + + /// Describes the instance variables declared by self. + pub fn instance_variables(&self) -> MallocBuffer<&Ivar> { + unsafe { + let mut count: c_uint = 0; + let ivars = class_copyIvarList(self, &mut count); + MallocBuffer::new(ivars as *mut _, count as usize).unwrap() + } + } +} + +impl PartialEq for Class { + fn eq(&self, other: &Class) -> bool { + let self_ptr: *const Class = self; + let other_ptr: *const Class = other; + self_ptr == other_ptr + } +} + +impl Eq for Class { } + +impl fmt::Debug for Class { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + +impl Protocol { + /// Returns the protocol definition of a specified protocol, or `None` if the + /// protocol is not registered with the Objective-C runtime. + pub fn get(name: &str) -> Option<&'static Protocol> { + let name = CString::new(name).unwrap(); + unsafe { + let proto = objc_getProtocol(name.as_ptr()); + if proto.is_null() { None } else { Some(&*proto) } + } + } + + /// Obtains the list of registered protocol definitions. + pub fn protocols() -> MallocBuffer<&'static Protocol> { + unsafe { + let mut count: c_uint = 0; + let protocols = objc_copyProtocolList(&mut count); + MallocBuffer::new(protocols as *mut _, count as usize).unwrap() + } + } + + /// Get a list of the protocols to which this protocol conforms. + pub fn adopted_protocols(&self) -> MallocBuffer<&Protocol> { + unsafe { + let mut count: c_uint = 0; + let protocols = protocol_copyProtocolList(self, &mut count); + MallocBuffer::new(protocols as *mut _, count as usize).unwrap() + } + } + + /// Checks whether this protocol conforms to the specified protocol. + pub fn conforms_to(&self, proto: &Protocol) -> bool { + unsafe { protocol_conformsToProtocol(self, proto) == YES } + } + + /// Returns the name of self. + pub fn name(&self) -> &str { + let name = unsafe { + CStr::from_ptr(protocol_getName(self)) + }; + str::from_utf8(name.to_bytes()).unwrap() + } +} + +impl PartialEq for Protocol { + fn eq(&self, other: &Protocol) -> bool { + unsafe { protocol_isEqual(self, other) == YES } + } +} + +impl Eq for Protocol { } + +impl fmt::Debug for Protocol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + +impl Object { + /// Returns the class of self. + pub fn class(&self) -> &Class { + unsafe { + &*object_getClass(self) + } + } + + /// Returns a reference to the ivar of self with the given name. + /// Panics if self has no ivar with the given name. + /// Unsafe because the caller must ensure that the ivar is actually + /// of type `T`. + pub unsafe fn get_ivar(&self, name: &str) -> &T where T: Encode { + let offset = { + let cls = self.class(); + match cls.instance_variable(name) { + Some(ivar) => { + assert!(ivar.type_encoding() == T::encode()); + ivar.offset() + } + None => panic!("Ivar {} not found on class {:?}", name, cls), + } + }; + let ptr = { + let self_ptr: *const Object = self; + (self_ptr as *const u8).offset(offset) as *const T + }; + &*ptr + } + + /// Returns a mutable reference to the ivar of self with the given name. + /// Panics if self has no ivar with the given name. + /// Unsafe because the caller must ensure that the ivar is actually + /// of type `T`. + pub unsafe fn get_mut_ivar(&mut self, name: &str) -> &mut T + where T: Encode { + let offset = { + let cls = self.class(); + match cls.instance_variable(name) { + Some(ivar) => { + assert!(ivar.type_encoding() == T::encode()); + ivar.offset() + } + None => panic!("Ivar {} not found on class {:?}", name, cls), + } + }; + let ptr = { + let self_ptr: *mut Object = self; + (self_ptr as *mut u8).offset(offset) as *mut T + }; + &mut *ptr + } + + /// Sets the value of the ivar of self with the given name. + /// Panics if self has no ivar with the given name. + /// Unsafe because the caller must ensure that the ivar is actually + /// of type `T`. + pub unsafe fn set_ivar(&mut self, name: &str, value: T) + where T: Encode { + *self.get_mut_ivar::(name) = value; + } +} + +impl fmt::Debug for Object { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "<{:?}: {:p}>", self.class(), self) + } +} + +#[cfg(test)] +mod tests { + use test_utils; + use Encode; + use super::{Class, Protocol, Sel}; + + #[test] + fn test_ivar() { + let cls = test_utils::custom_class(); + let ivar = cls.instance_variable("_foo").unwrap(); + assert!(ivar.name() == "_foo"); + assert!(ivar.type_encoding() == ::encode()); + assert!(ivar.offset() > 0); + + let ivars = cls.instance_variables(); + assert!(ivars.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!(method.name().name() == "foo"); + assert!(method.arguments_count() == 2); + assert!(method.return_type() == ::encode()); + assert!(method.argument_type(1).unwrap() == Sel::encode()); + + let methods = cls.instance_methods(); + assert!(methods.len() > 0); + } + + #[test] + fn test_class() { + let cls = test_utils::custom_class(); + assert!(cls.name() == "CustomObject"); + assert!(cls.instance_size() > 0); + assert!(cls.superclass().is_none()); + + assert!(Class::get(cls.name()) == Some(cls)); + + let metaclass = cls.metaclass(); + // The metaclass of a root class is a subclass of the root class + assert!(metaclass.superclass().unwrap() == cls); + + let subclass = test_utils::custom_subclass(); + assert!(subclass.superclass().unwrap() == cls); + } + + #[test] + fn test_classes() { + assert!(Class::classes_count() > 0); + let classes = Class::classes(); + assert!(classes.len() > 0); + } + + #[test] + fn test_protocol() { + let proto = test_utils::custom_protocol(); + assert!(proto.name() == "CustomProtocol"); + let class = test_utils::custom_class(); + assert!(class.conforms_to(proto)); + let class_protocols = class.adopted_protocols(); + assert!(class_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)); + let adopted_protocols = sub_proto.adopted_protocols(); + assert_eq!(adopted_protocols[0], super_proto); + } + + #[test] + fn test_protocols() { + // Ensure that a protocol has been registered on linux + let _ = test_utils::custom_protocol(); + + let protocols = Protocol::protocols(); + assert!(protocols.len() > 0); + } + + #[test] + fn test_object() { + let mut obj = test_utils::custom_object(); + assert!(obj.class() == test_utils::custom_class()); + let result: u32 = unsafe { + obj.set_ivar("_foo", 4u32); + *obj.get_ivar("_foo") + }; + assert!(result == 4); + } +} diff --git a/third_party/rust/objc/src/test_utils.rs b/third_party/rust/objc/src/test_utils.rs new file mode 100644 index 0000000000..873f82f6b1 --- /dev/null +++ b/third_party/rust/objc/src/test_utils.rs @@ -0,0 +1,187 @@ +use std::ops::{Deref, DerefMut}; +use std::os::raw::c_char; +use std::sync::{Once, ONCE_INIT}; + +use declare::{ClassDecl, ProtocolDecl}; +use runtime::{Class, Object, Protocol, Sel, self}; +use {Encode, Encoding}; + +pub struct CustomObject { + obj: *mut Object, +} + +impl CustomObject { + fn new(class: &Class) -> Self { + let obj = unsafe { + runtime::class_createInstance(class, 0) + }; + CustomObject { obj: obj } + } +} + +impl Deref for CustomObject { + type Target = Object; + + fn deref(&self) -> &Object { + unsafe { &*self.obj } + } +} + +impl DerefMut for CustomObject { + fn deref_mut(&mut self) -> &mut Object { + unsafe { &mut *self.obj } + } +} + +impl Drop for CustomObject { + fn drop(&mut self) { + unsafe { + runtime::object_dispose(self.obj); + } + } +} + +#[derive(Eq, PartialEq)] +pub struct CustomStruct { + pub a: u64, + pub b: u64, + pub c: u64, + pub d: u64, +} + +unsafe impl Encode for CustomStruct { + fn encode() -> Encoding { + let mut code = "{CustomStruct=".to_owned(); + for _ in 0..4 { + code.push_str(u64::encode().as_str()); + } + code.push_str("}"); + unsafe { + Encoding::from_str(&code) + } + } +} + +pub fn custom_class() -> &'static Class { + static REGISTER_CUSTOM_CLASS: Once = ONCE_INIT; + + REGISTER_CUSTOM_CLASS.call_once(|| { + // The runtime will call this method, so it has to be implemented + extern fn custom_obj_class_initialize(_this: &Class, _cmd: Sel) { } + + let mut decl = ClassDecl::root("CustomObject", custom_obj_class_initialize).unwrap(); + let proto = custom_protocol(); + + decl.add_protocol(proto); + decl.add_ivar::("_foo"); + + extern fn custom_obj_set_foo(this: &mut Object, _cmd: Sel, foo: u32) { + unsafe { this.set_ivar::("_foo", foo); } + } + + extern fn custom_obj_get_foo(this: &Object, _cmd: Sel) -> u32 { + unsafe { *this.get_ivar::("_foo") } + } + + extern fn custom_obj_get_struct(_this: &Object, _cmd: Sel) -> CustomStruct { + CustomStruct { a: 1, b: 2, c: 3, d: 4 } + } + + extern fn custom_obj_class_method(_this: &Class, _cmd: Sel) -> u32 { + 7 + } + + extern fn custom_obj_set_bar(this: &mut Object, _cmd: Sel, bar: u32) { + unsafe { this.set_ivar::("_foo", bar) ;} + } + + extern fn custom_obj_add_number_to_number(_this: &Class, _cmd: Sel, fst: i32, snd: i32) -> i32 { + fst + snd + } + + unsafe { + let set_foo: extern fn(&mut Object, Sel, u32) = custom_obj_set_foo; + decl.add_method(sel!(setFoo:), set_foo); + let get_foo: extern fn(&Object, Sel) -> u32 = custom_obj_get_foo; + decl.add_method(sel!(foo), get_foo); + let get_struct: extern fn(&Object, Sel) -> CustomStruct = custom_obj_get_struct; + decl.add_method(sel!(customStruct), get_struct); + let class_method: extern fn(&Class, Sel) -> u32 = custom_obj_class_method; + decl.add_class_method(sel!(classFoo), class_method); + + let protocol_instance_method: extern fn(&mut Object, Sel, u32) = custom_obj_set_bar; + decl.add_method(sel!(setBar:), protocol_instance_method); + let protocol_class_method: extern fn(&Class, Sel, i32, i32) -> i32 = custom_obj_add_number_to_number; + decl.add_class_method(sel!(addNumber:toNumber:), protocol_class_method); + } + + decl.register(); + }); + + class!(CustomObject) +} + +pub fn custom_protocol() -> &'static Protocol { + static REGISTER_CUSTOM_PROTOCOL: Once = ONCE_INIT; + + REGISTER_CUSTOM_PROTOCOL.call_once(|| { + let mut decl = ProtocolDecl::new("CustomProtocol").unwrap(); + + decl.add_method_description::<(i32,), ()>(sel!(setBar:), true); + decl.add_method_description::<(), *const c_char>(sel!(getName), false); + decl.add_class_method_description::<(i32, i32), i32>(sel!(addNumber:toNumber:), true); + + decl.register(); + }); + + Protocol::get("CustomProtocol").unwrap() +} + +pub fn custom_subprotocol() -> &'static Protocol { + static REGISTER_CUSTOM_SUBPROTOCOL: Once = ONCE_INIT; + + REGISTER_CUSTOM_SUBPROTOCOL.call_once(|| { + let super_proto = custom_protocol(); + let mut decl = ProtocolDecl::new("CustomSubProtocol").unwrap(); + + decl.add_protocol(super_proto); + decl.add_method_description::<(u32,), u32>(sel!(calculateFoo:), true); + + decl.register(); + }); + + Protocol::get("CustomSubProtocol").unwrap() +} + +pub fn custom_object() -> CustomObject { + CustomObject::new(custom_class()) +} + +pub fn custom_subclass() -> &'static Class { + static REGISTER_CUSTOM_SUBCLASS: Once = ONCE_INIT; + + REGISTER_CUSTOM_SUBCLASS.call_once(|| { + let superclass = custom_class(); + let mut decl = ClassDecl::new("CustomSubclassObject", superclass).unwrap(); + + extern fn custom_subclass_get_foo(this: &Object, _cmd: Sel) -> u32 { + let foo: u32 = unsafe { + msg_send![super(this, custom_class()), foo] + }; + foo + 2 + } + + unsafe { + let get_foo: extern fn(&Object, Sel) -> u32 = custom_subclass_get_foo; + decl.add_method(sel!(foo), get_foo); + } + + decl.register(); + }); + + class!(CustomSubclassObject) +} + +pub fn custom_subclass_object() -> CustomObject { + CustomObject::new(custom_subclass()) +} -- cgit v1.2.3