//! Common selectors. //! //! These are put here to deduplicate the cached selector, and when using //! `unstable-static-sel`, the statics. //! //! Note that our assembly tests of `unstable-static-sel-inlined` output a GOT //! entry for such accesses, but that is just a limitation of our tests - the //! actual assembly is as one would expect. use crate::__sel_inner; use crate::runtime::Sel; #[inline] pub fn alloc_sel() -> Sel { __sel_inner!("alloc\0", "alloc") } #[inline] pub fn init_sel() -> Sel { __sel_inner!("init\0", "init") } #[inline] pub fn new_sel() -> Sel { __sel_inner!("new\0", "new") } #[inline] pub fn dealloc_sel() -> Sel { __sel_inner!("dealloc\0", "dealloc") } /// An undocumented selector called by the Objective-C runtime when /// initializing instance variables. #[inline] #[allow(dead_code)] // May be useful in the future fn cxx_construct_sel() -> Sel { __sel_inner!(".cxx_construct\0", ".cxx_construct") } /// Objective-C runtimes call `.cxx_destruct` as part of the final `dealloc` /// call inside `NSObject` (and has done so since macOS 10.4). /// /// While [GCC does document it somewhat][gcc-docs], this is still severely /// undocumented in clang - but since the selector is emitted into the final /// binary, it is fine to rely on it being used. /// /// Unfortunately though, this only works if the class has been defined /// statically, since in that case a flag is set to inform the runtime that it /// needs to run destructors. So unfortunately we can't use this on /// dynamically defined classes. /// /// /// # ABI /// /// The ABI of `.cxx_destruct` in Apple's runtime is actually that it does NOT /// take a selector, unlike every other Objective-C method, see: /// /// /// So the signature is `extern "C-unwind" fn(*mut AnyObject)`. /// /// This is likely because it's not a real Objective-C method that can be /// called from userspace / objc_msgSend, and it's more efficient to not pass /// the selector. /// /// Note that even if Apple decides to suddenly add the selector one day, /// ignoring it will still be sound, since the function uses the C calling /// convention, where such an ignored parameter would be allowed on all /// relevant architectures. /// /// [gcc-docs]: https://gcc.gnu.org/onlinedocs/gcc/Objective-C-and-Objective-C_002b_002b-Dialect-Options.html#index-fobjc-call-cxx-cdtors #[inline] #[allow(dead_code)] // May be useful in the future fn cxx_destruct_sel() -> Sel { __sel_inner!(".cxx_destruct\0", ".cxx_destruct") } #[cfg(test)] mod tests { use alloc::ffi::CString; use core::sync::atomic::{AtomicBool, Ordering}; use crate::rc::Retained; use crate::runtime::ClassBuilder; use crate::runtime::NSObject; use crate::{msg_send, ClassType}; use super::*; /// Test the unfortunate fact that we can't use .cxx_destruct on dynamic classes. #[test] fn test_destruct_dynamic() { static HAS_RUN: AtomicBool = AtomicBool::new(false); let name = CString::new("TestCxxDestruct").unwrap(); let mut builder = ClassBuilder::new(&name, NSObject::class()).unwrap(); unsafe extern "C" fn destruct(_: *mut NSObject, _: Sel) { HAS_RUN.store(true, Ordering::Relaxed); } // Note: The ABI is not upheld here, but its fine for this test unsafe { builder.add_method(cxx_destruct_sel(), destruct as unsafe extern "C" fn(_, _)) }; let cls = builder.register(); let obj: Retained = unsafe { msg_send![cls, new] }; drop(obj); let has_run_destruct = HAS_RUN.load(Ordering::Relaxed); // This does work on GNUStep, but unfortunately not in Apple's objc4 if cfg!(feature = "gnustep-1-7") { assert!(has_run_destruct); } else { assert!(!has_run_destruct); } } }