web engine - experimental web browser
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}