web engine - experimental web browser
at x25519 657 lines 22 kB view raw
1//! Objective-C runtime FFI bindings and Rust abstractions. 2//! 3//! Provides safe(ish) wrappers around the Objective-C runtime for message 4//! dispatch, class lookup, selector registration, and dynamic class creation. 5//! 6//! # Safety 7//! 8//! This module contains `unsafe` code for FFI with `libobjc.dylib`. 9//! The `platform` crate is one of the few crates where `unsafe` is permitted. 10 11use std::ffi::CStr; 12use std::fmt; 13use std::os::raw::{c_char, c_void}; 14use std::ptr::NonNull; 15 16// --------------------------------------------------------------------------- 17// Raw FFI bindings to libobjc.dylib 18// --------------------------------------------------------------------------- 19 20// Opaque types matching the ObjC runtime's internal structures. 21// These are public because they appear in the signatures of public methods 22// (e.g. `as_ptr`), but they are zero-sized and cannot be constructed. 23#[repr(C)] 24pub struct ObjcClass { 25 _private: [u8; 0], 26} 27 28#[repr(C)] 29pub struct ObjcObject { 30 _private: [u8; 0], 31} 32 33#[repr(C)] 34pub struct ObjcSelector { 35 _private: [u8; 0], 36} 37 38/// An Objective-C method implementation function pointer. 39/// 40/// The actual signature varies per method, but the runtime treats all IMPs 41/// as `extern "C" fn()` for registration purposes. 42pub type Imp = unsafe extern "C" fn(); 43 44#[link(name = "objc", kind = "dylib")] 45extern "C" { 46 fn objc_getClass(name: *const c_char) -> *mut ObjcClass; 47 fn sel_registerName(name: *const c_char) -> *mut ObjcSelector; 48 fn objc_allocateClassPair( 49 superclass: *mut ObjcClass, 50 name: *const c_char, 51 extra_bytes: usize, 52 ) -> *mut ObjcClass; 53 fn objc_registerClassPair(cls: *mut ObjcClass); 54 fn class_addMethod( 55 cls: *mut ObjcClass, 56 sel: *mut ObjcSelector, 57 imp: Imp, 58 types: *const c_char, 59 ) -> bool; 60 fn class_getName(cls: *mut ObjcClass) -> *const c_char; 61 fn class_addIvar( 62 cls: *mut ObjcClass, 63 name: *const c_char, 64 size: usize, 65 alignment: u8, 66 types: *const c_char, 67 ) -> bool; 68 69 fn object_setInstanceVariable( 70 obj: *mut ObjcObject, 71 name: *const c_char, 72 value: *mut c_void, 73 ) -> *mut c_void; // returns Ivar (opaque, we don't use it) 74 fn object_getInstanceVariable( 75 obj: *mut ObjcObject, 76 name: *const c_char, 77 out_value: *mut *mut c_void, 78 ) -> *mut c_void; // returns Ivar 79 80 // objc_msgSend has a variadic ABI. On AArch64 the calling convention is 81 // the standard C calling convention (arguments in x0..x7, return in x0). 82 // We declare it with no extra arguments; the msg_send! macro transmutes 83 // the function pointer to the appropriate concrete signature at each call 84 // site. 85 fn objc_msgSend(); 86} 87 88// --------------------------------------------------------------------------- 89// Sel — a registered selector 90// --------------------------------------------------------------------------- 91 92/// A registered Objective-C selector. 93/// 94/// Selectors are interned strings that identify a method name. Two selectors 95/// with the same name are pointer-equal. 96#[derive(Clone, Copy, PartialEq, Eq, Hash)] 97#[repr(transparent)] 98pub struct Sel(NonNull<ObjcSelector>); 99 100impl Sel { 101 /// Register (or look up) a selector by name. 102 /// 103 /// `name` must be a nul-terminated C string (use `c"foo:"` literals or 104 /// the `sel!` helper). 105 /// 106 /// # Panics 107 /// 108 /// Panics if the runtime returns null (should never happen for valid names). 109 pub fn register(name: &CStr) -> Sel { 110 // SAFETY: sel_registerName is thread-safe and always returns a valid 111 // pointer for well-formed selector names. 112 let ptr = unsafe { sel_registerName(name.as_ptr()) }; 113 Sel(NonNull::new(ptr).expect("sel_registerName returned null")) 114 } 115 116 /// Return the raw pointer to the selector. 117 #[inline] 118 pub fn as_ptr(self) -> *mut ObjcSelector { 119 self.0.as_ptr() 120 } 121} 122 123impl fmt::Debug for Sel { 124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 125 // We can't easily get the selector name without sel_getName, 126 // so just print the pointer. 127 write!(f, "Sel({:?})", self.0) 128 } 129} 130 131// SAFETY: Selectors are globally interned pointers; they're safe to send 132// across threads. 133unsafe impl Send for Sel {} 134unsafe impl Sync for Sel {} 135 136// --------------------------------------------------------------------------- 137// Class — a reference to an Obj-C class 138// --------------------------------------------------------------------------- 139 140/// A reference to an Objective-C class. 141#[derive(Clone, Copy, PartialEq, Eq, Hash)] 142#[repr(transparent)] 143pub struct Class(NonNull<ObjcClass>); 144 145impl Class { 146 /// Look up a class by name. 147 /// 148 /// Returns `None` if no class with that name is registered. 149 pub fn get(name: &CStr) -> Option<Class> { 150 // SAFETY: objc_getClass is thread-safe; it returns null for unknown 151 // class names. 152 let ptr = unsafe { objc_getClass(name.as_ptr()) }; 153 NonNull::new(ptr).map(Class) 154 } 155 156 /// Allocate a new class pair (a new class and its metaclass). 157 /// 158 /// `superclass` is the parent class; `name` is the new class name; 159 /// `extra_bytes` is usually 0. 160 /// 161 /// Returns `None` if a class with `name` already exists. 162 /// 163 /// You must call [`Class::register`] on the returned class after adding 164 /// methods and ivars. 165 pub fn allocate(superclass: Class, name: &CStr, extra_bytes: usize) -> Option<Class> { 166 // SAFETY: objc_allocateClassPair returns null if the name is already 167 // taken. The superclass pointer is valid because it came from a Class. 168 let ptr = 169 unsafe { objc_allocateClassPair(superclass.as_ptr(), name.as_ptr(), extra_bytes) }; 170 NonNull::new(ptr).map(Class) 171 } 172 173 /// Register a class that was previously created with [`Class::allocate`]. 174 /// 175 /// After this call the class is usable and no further methods/ivars can be 176 /// added. 177 pub fn register(self) { 178 // SAFETY: the pointer is valid and the class has not been registered 179 // yet (caller invariant). 180 unsafe { objc_registerClassPair(self.as_ptr()) } 181 } 182 183 /// Add a method to a class that has not yet been registered. 184 /// 185 /// `sel` is the method selector, `imp` is the implementation function 186 /// pointer, and `types` is the ObjC type encoding string. 187 /// 188 /// Returns `true` if the method was added, `false` if a method with that 189 /// selector already existed. 190 pub fn add_method(self, sel: Sel, imp: Imp, types: &CStr) -> bool { 191 // SAFETY: the class pointer is valid and not yet registered. 192 unsafe { class_addMethod(self.as_ptr(), sel.as_ptr(), imp, types.as_ptr()) } 193 } 194 195 /// Add an instance variable to a class that has not yet been registered. 196 /// 197 /// `name` is the ivar name, `size` is the size in bytes, `alignment` is 198 /// the log2 alignment (e.g., 3 for 8-byte alignment), and `types` is the 199 /// ObjC type encoding string. 200 /// 201 /// Returns `true` if the ivar was added. 202 pub fn add_ivar(self, name: &CStr, size: usize, alignment: u8, types: &CStr) -> bool { 203 // SAFETY: the class pointer is valid and not yet registered. 204 unsafe { 205 class_addIvar( 206 self.as_ptr(), 207 name.as_ptr(), 208 size, 209 alignment, 210 types.as_ptr(), 211 ) 212 } 213 } 214 215 /// Get the name of this class. 216 pub fn name(self) -> &'static str { 217 // SAFETY: class_getName always returns a valid C string for a valid 218 // class pointer. 219 let c_str = unsafe { CStr::from_ptr(class_getName(self.as_ptr())) }; 220 c_str.to_str().unwrap_or("<invalid UTF-8>") 221 } 222 223 /// Return the raw pointer to the class. 224 #[inline] 225 pub fn as_ptr(self) -> *mut ObjcClass { 226 self.0.as_ptr() 227 } 228} 229 230impl fmt::Debug for Class { 231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 232 write!(f, "Class(\"{}\")", self.name()) 233 } 234} 235 236// SAFETY: ObjC classes are globally registered and immutable after 237// registration; safe to share across threads. 238unsafe impl Send for Class {} 239unsafe impl Sync for Class {} 240 241// --------------------------------------------------------------------------- 242// Id — a non-null pointer to an Obj-C object instance 243// --------------------------------------------------------------------------- 244 245/// A non-null pointer to an Objective-C object. 246/// 247/// This is an *unowned* (raw) reference — no automatic retain/release. 248/// The caller is responsible for memory management. 249#[derive(Clone, Copy, PartialEq, Eq, Hash)] 250#[repr(transparent)] 251pub struct Id(NonNull<ObjcObject>); 252 253impl Id { 254 /// Create an `Id` from a raw non-null pointer. 255 /// 256 /// # Safety 257 /// 258 /// The pointer must point to a valid Objective-C object. 259 #[inline] 260 pub unsafe fn from_ptr(ptr: NonNull<ObjcObject>) -> Id { 261 Id(ptr) 262 } 263 264 /// Try to create an `Id` from a raw pointer that might be null. 265 /// 266 /// # Safety 267 /// 268 /// If non-null, the pointer must point to a valid Objective-C object. 269 #[inline] 270 pub unsafe fn from_raw(ptr: *mut ObjcObject) -> Option<Id> { 271 NonNull::new(ptr).map(Id) 272 } 273 274 /// Return the raw pointer. 275 #[inline] 276 pub fn as_ptr(self) -> *mut ObjcObject { 277 self.0.as_ptr() 278 } 279 280 /// Cast to `*mut T` for use as a function argument. 281 /// 282 /// # Safety 283 /// 284 /// The caller must ensure the object is actually of type `T`. 285 #[inline] 286 pub unsafe fn cast<T>(self) -> *mut T { 287 self.0.as_ptr().cast() 288 } 289 290 /// Set an instance variable by name. 291 /// 292 /// # Safety 293 /// 294 /// The object must have an ivar with the given `name` of pointer type. 295 /// The caller is responsible for the validity and lifetime of `value`. 296 pub unsafe fn set_ivar(self, name: &CStr, value: *mut c_void) { 297 object_setInstanceVariable(self.as_ptr(), name.as_ptr(), value); 298 } 299 300 /// Get an instance variable by name. 301 /// 302 /// # Safety 303 /// 304 /// The object must have an ivar with the given `name` of pointer type. 305 pub unsafe fn get_ivar(self, name: &CStr) -> *mut c_void { 306 let mut out: *mut c_void = std::ptr::null_mut(); 307 object_getInstanceVariable(self.as_ptr(), name.as_ptr(), &mut out); 308 out 309 } 310} 311 312impl fmt::Debug for Id { 313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 314 write!(f, "Id({:?})", self.0) 315 } 316} 317 318// SAFETY: ObjC objects can be sent across threads (whether this is safe 319// depends on the object, but at the pointer level it's fine). 320unsafe impl Send for Id {} 321unsafe impl Sync for Id {} 322 323// --------------------------------------------------------------------------- 324// msg_send! macro 325// --------------------------------------------------------------------------- 326 327/// Return the raw `objc_msgSend` function pointer. 328/// 329/// The caller must transmute this to the correct function signature for the 330/// message being sent. The `msg_send!` macro does this automatically. 331#[inline] 332pub fn msg_send_fn() -> unsafe extern "C" fn() { 333 objc_msgSend 334} 335 336/// Send an Objective-C message. 337/// 338/// # Syntax 339/// 340/// ```ignore 341/// // No arguments, returns Id: 342/// let obj: Id = msg_send![class.as_ptr(), alloc]; 343/// 344/// // With arguments, returns Id: 345/// let obj: Id = msg_send![obj.as_ptr(), initWithFrame: rect]; 346/// 347/// // No return value (returns ()): 348/// msg_send![obj.as_ptr(), release]; 349/// ``` 350/// 351/// The first argument must be a `*mut T` (the receiver). The return type is 352/// inferred from context. 353/// 354/// # Safety 355/// 356/// This is inherently unsafe: wrong selector, wrong argument types, or wrong 357/// return type will cause undefined behavior. 358#[macro_export] 359macro_rules! msg_send { 360 // No arguments: msg_send![receiver, selector] 361 [$receiver:expr, $sel:ident] => {{ 362 let sel_name = concat!(stringify!($sel), "\0"); 363 let sel = $crate::objc::Sel::register(unsafe { 364 std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) 365 }); 366 let func: unsafe extern "C" fn(*mut std::os::raw::c_void, *mut std::os::raw::c_void) -> _ 367 = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; 368 let receiver = $receiver as *mut std::os::raw::c_void; 369 unsafe { func(receiver, sel.as_ptr() as *mut std::os::raw::c_void) } 370 }}; 371 372 // One argument: msg_send![receiver, selector: arg] 373 [$receiver:expr, $sel:ident : $arg:expr] => {{ 374 let sel_name = concat!(stringify!($sel), ":\0"); 375 let sel = $crate::objc::Sel::register(unsafe { 376 std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) 377 }); 378 let func: unsafe extern "C" fn( 379 *mut std::os::raw::c_void, 380 *mut std::os::raw::c_void, 381 _, 382 ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; 383 let receiver = $receiver as *mut std::os::raw::c_void; 384 let arg = $arg; 385 unsafe { 386 func( 387 receiver, 388 sel.as_ptr() as *mut std::os::raw::c_void, 389 arg, 390 ) 391 } 392 }}; 393 394 // Two arguments: msg_send![receiver, sel1: arg1, sel2: arg2] 395 [$receiver:expr, $sel1:ident : $arg1:expr, $sel2:ident : $arg2:expr] => {{ 396 let sel_name = concat!(stringify!($sel1), ":", stringify!($sel2), ":\0"); 397 let sel = $crate::objc::Sel::register(unsafe { 398 std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) 399 }); 400 let func: unsafe extern "C" fn( 401 *mut std::os::raw::c_void, 402 *mut std::os::raw::c_void, 403 _, 404 _, 405 ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; 406 let receiver = $receiver as *mut std::os::raw::c_void; 407 let arg1 = $arg1; 408 let arg2 = $arg2; 409 unsafe { 410 func( 411 receiver, 412 sel.as_ptr() as *mut std::os::raw::c_void, 413 arg1, 414 arg2, 415 ) 416 } 417 }}; 418 419 // Three arguments: msg_send![receiver, sel1: arg1, sel2: arg2, sel3: arg3] 420 [$receiver:expr, $sel1:ident : $arg1:expr, $sel2:ident : $arg2:expr, $sel3:ident : $arg3:expr] => {{ 421 let sel_name = concat!( 422 stringify!($sel1), ":", 423 stringify!($sel2), ":", 424 stringify!($sel3), ":\0" 425 ); 426 let sel = $crate::objc::Sel::register(unsafe { 427 std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) 428 }); 429 let func: unsafe extern "C" fn( 430 *mut std::os::raw::c_void, 431 *mut std::os::raw::c_void, 432 _, 433 _, 434 _, 435 ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; 436 let receiver = $receiver as *mut std::os::raw::c_void; 437 let arg1 = $arg1; 438 let arg2 = $arg2; 439 let arg3 = $arg3; 440 unsafe { 441 func( 442 receiver, 443 sel.as_ptr() as *mut std::os::raw::c_void, 444 arg1, 445 arg2, 446 arg3, 447 ) 448 } 449 }}; 450 451 // Four arguments 452 [$receiver:expr, $sel1:ident : $arg1:expr, $sel2:ident : $arg2:expr, $sel3:ident : $arg3:expr, $sel4:ident : $arg4:expr] => {{ 453 let sel_name = concat!( 454 stringify!($sel1), ":", 455 stringify!($sel2), ":", 456 stringify!($sel3), ":", 457 stringify!($sel4), ":\0" 458 ); 459 let sel = $crate::objc::Sel::register(unsafe { 460 std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) 461 }); 462 let func: unsafe extern "C" fn( 463 *mut std::os::raw::c_void, 464 *mut std::os::raw::c_void, 465 _, 466 _, 467 _, 468 _, 469 ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; 470 let receiver = $receiver as *mut std::os::raw::c_void; 471 let arg1 = $arg1; 472 let arg2 = $arg2; 473 let arg3 = $arg3; 474 let arg4 = $arg4; 475 unsafe { 476 func( 477 receiver, 478 sel.as_ptr() as *mut std::os::raw::c_void, 479 arg1, 480 arg2, 481 arg3, 482 arg4, 483 ) 484 } 485 }}; 486} 487 488// --------------------------------------------------------------------------- 489// Convenience: sel! macro 490// --------------------------------------------------------------------------- 491 492/// Register a selector from a string literal. 493/// 494/// ```ignore 495/// let sel = sel!("init"); 496/// let sel = sel!("initWithFrame:"); 497/// ``` 498#[macro_export] 499macro_rules! sel { 500 ($name:literal) => {{ 501 $crate::objc::Sel::register(unsafe { 502 std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes()) 503 }) 504 }}; 505} 506 507/// Look up a class by name (string literal). 508/// 509/// Returns `Option<Class>`. 510/// 511/// ```ignore 512/// let cls = class!("NSObject").unwrap(); 513/// ``` 514#[macro_export] 515macro_rules! class { 516 ($name:literal) => {{ 517 $crate::objc::Class::get(unsafe { 518 std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes()) 519 }) 520 }}; 521} 522 523// --------------------------------------------------------------------------- 524// Tests 525// --------------------------------------------------------------------------- 526 527#[cfg(test)] 528mod tests { 529 use super::*; 530 531 #[test] 532 fn get_nsobject_class() { 533 let cls = Class::get(c"NSObject").expect("NSObject class should exist"); 534 assert_eq!(cls.name(), "NSObject"); 535 } 536 537 #[test] 538 fn get_nonexistent_class_returns_none() { 539 let cls = Class::get(c"ThisClassDoesNotExist12345"); 540 assert!(cls.is_none()); 541 } 542 543 #[test] 544 fn register_selector() { 545 let sel1 = Sel::register(c"init"); 546 let sel2 = Sel::register(c"init"); 547 // Same selector name should return the same pointer. 548 assert_eq!(sel1, sel2); 549 } 550 551 #[test] 552 fn different_selectors_differ() { 553 let sel1 = Sel::register(c"init"); 554 let sel2 = Sel::register(c"alloc"); 555 assert_ne!(sel1, sel2); 556 } 557 558 #[test] 559 fn alloc_init_nsobject() { 560 let cls = Class::get(c"NSObject").unwrap(); 561 // alloc 562 let obj: *mut std::os::raw::c_void = msg_send![cls.as_ptr(), alloc]; 563 assert!(!obj.is_null()); 564 // init 565 let obj: *mut std::os::raw::c_void = msg_send![obj, init]; 566 assert!(!obj.is_null()); 567 // release 568 let _: *mut std::os::raw::c_void = msg_send![obj, release]; 569 } 570 571 #[test] 572 fn class_macro_works() { 573 let cls = class!("NSObject").expect("NSObject should exist"); 574 assert_eq!(cls.name(), "NSObject"); 575 } 576 577 #[test] 578 fn sel_macro_works() { 579 let sel = sel!("init"); 580 let sel2 = Sel::register(c"init"); 581 assert_eq!(sel, sel2); 582 } 583 584 #[test] 585 fn allocate_and_register_custom_class() { 586 let superclass = Class::get(c"NSObject").unwrap(); 587 // Use a unique class name to avoid conflicts with other tests. 588 let new_cls = Class::allocate(superclass, c"WeTestCustomClass", 0) 589 .expect("should allocate new class"); 590 new_cls.register(); 591 // Verify we can look it up. 592 let found = Class::get(c"WeTestCustomClass").expect("custom class should be registered"); 593 assert_eq!(found.name(), "WeTestCustomClass"); 594 } 595 596 #[test] 597 fn add_method_to_custom_class() { 598 let superclass = Class::get(c"NSObject").unwrap(); 599 let new_cls = Class::allocate(superclass, c"WeTestMethodClass", 0) 600 .expect("should allocate new class"); 601 602 // Define a simple method that returns an integer. 603 extern "C" fn my_method( 604 _this: *mut std::os::raw::c_void, 605 _sel: *mut std::os::raw::c_void, 606 ) -> i64 { 607 42 608 } 609 610 let sel = Sel::register(c"myMethod"); 611 // Type encoding: returns long long (q), takes id (@) and SEL (:) 612 let added = new_cls.add_method( 613 sel, 614 unsafe { std::mem::transmute::<*const (), Imp>(my_method as *const ()) }, 615 c"q@:", 616 ); 617 assert!(added); 618 619 new_cls.register(); 620 621 // Allocate an instance and call our method. 622 let cls = Class::get(c"WeTestMethodClass").unwrap(); 623 let obj: *mut std::os::raw::c_void = msg_send![cls.as_ptr(), alloc]; 624 let obj: *mut std::os::raw::c_void = msg_send![obj, init]; 625 let result: i64 = msg_send![obj, myMethod]; 626 assert_eq!(result, 42); 627 let _: *mut std::os::raw::c_void = msg_send![obj, release]; 628 } 629 630 #[test] 631 fn msg_send_with_argument() { 632 let cls = Class::get(c"NSObject").unwrap(); 633 let obj: *mut std::os::raw::c_void = msg_send![cls.as_ptr(), alloc]; 634 let obj: *mut std::os::raw::c_void = msg_send![obj, init]; 635 636 // respondsToSelector: takes a SEL, returns BOOL (i8 on AArch64). 637 let sel = Sel::register(c"init"); 638 let responds: bool = msg_send![obj, respondsToSelector: sel.as_ptr()]; 639 assert!(responds); 640 641 let _: *mut std::os::raw::c_void = msg_send![obj, release]; 642 } 643 644 #[test] 645 fn class_debug_format() { 646 let cls = Class::get(c"NSObject").unwrap(); 647 let debug = format!("{:?}", cls); 648 assert!(debug.contains("NSObject")); 649 } 650 651 #[test] 652 fn sel_debug_format() { 653 let sel = Sel::register(c"init"); 654 let debug = format!("{:?}", sel); 655 assert!(debug.contains("Sel(")); 656 } 657}