Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
Rust
at url-parser
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658//! Objective-C runtime FFI bindings and Rust abstractions.//!//! Provides safe(ish) wrappers around the Objective-C runtime for message//! dispatch, class lookup, selector registration, and dynamic class creation.//!//! # Safety//!//! This module contains `unsafe` code for FFI with `libobjc.dylib`.//! The `platform` crate is one of the few crates where `unsafe` is permitted.
use std::ffi::CStr;use std::fmt;use std::os::raw::{c_char, c_void};use std::ptr::NonNull;
// ---------------------------------------------------------------------------// Raw FFI bindings to libobjc.dylib// ---------------------------------------------------------------------------
// Opaque types matching the ObjC runtime's internal structures.// These are public because they appear in the signatures of public methods// (e.g. `as_ptr`), but they are zero-sized and cannot be constructed.#[repr(C)]pub struct ObjcClass { _private: [u8; 0],}
#[repr(C)]pub struct ObjcObject { _private: [u8; 0],}
#[repr(C)]pub struct ObjcSelector { _private: [u8; 0],}
/// An Objective-C method implementation function pointer.////// The actual signature varies per method, but the runtime treats all IMPs/// as `extern "C" fn()` for registration purposes.pub type Imp = unsafe extern "C" fn();
#[link(name = "objc", kind = "dylib")]extern "C" { fn objc_getClass(name: *const c_char) -> *mut ObjcClass; fn sel_registerName(name: *const c_char) -> *mut ObjcSelector; fn objc_allocateClassPair( superclass: *mut ObjcClass, name: *const c_char, extra_bytes: usize, ) -> *mut ObjcClass; fn objc_registerClassPair(cls: *mut ObjcClass); fn class_addMethod( cls: *mut ObjcClass, sel: *mut ObjcSelector, imp: Imp, types: *const c_char, ) -> bool; fn class_getName(cls: *mut ObjcClass) -> *const c_char; fn class_addIvar( cls: *mut ObjcClass, name: *const c_char, size: usize, alignment: u8, types: *const c_char, ) -> bool;
fn object_setInstanceVariable( obj: *mut ObjcObject, name: *const c_char, value: *mut c_void, ) -> *mut c_void; // returns Ivar (opaque, we don't use it) fn object_getInstanceVariable( obj: *mut ObjcObject, name: *const c_char, out_value: *mut *mut c_void, ) -> *mut c_void; // returns Ivar
// objc_msgSend has a variadic ABI. On AArch64 the calling convention is // the standard C calling convention (arguments in x0..x7, return in x0). // We declare it with no extra arguments; the msg_send! macro transmutes // the function pointer to the appropriate concrete signature at each call // site. fn objc_msgSend();}
// ---------------------------------------------------------------------------// Sel — a registered selector// ---------------------------------------------------------------------------
/// A registered Objective-C selector.////// Selectors are interned strings that identify a method name. Two selectors/// with the same name are pointer-equal.#[derive(Clone, Copy, PartialEq, Eq, Hash)]#[repr(transparent)]pub struct Sel(NonNull<ObjcSelector>);
impl Sel { /// Register (or look up) a selector by name. /// /// `name` must be a nul-terminated C string (use `c"foo:"` literals or /// the `sel!` helper). /// /// # Panics /// /// Panics if the runtime returns null (should never happen for valid names). pub fn register(name: &CStr) -> Sel { // SAFETY: sel_registerName is thread-safe and always returns a valid // pointer for well-formed selector names. let ptr = unsafe { sel_registerName(name.as_ptr()) }; Sel(NonNull::new(ptr).expect("sel_registerName returned null")) }
/// Return the raw pointer to the selector. #[inline] pub fn as_ptr(self) -> *mut ObjcSelector { self.0.as_ptr() }}
impl fmt::Debug for Sel { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // We can't easily get the selector name without sel_getName, // so just print the pointer. write!(f, "Sel({:?})", self.0) }}
// SAFETY: Selectors are globally interned pointers; they're safe to send// across threads.unsafe impl Send for Sel {}unsafe impl Sync for Sel {}
// ---------------------------------------------------------------------------// Class — a reference to an Obj-C class// ---------------------------------------------------------------------------
/// A reference to an Objective-C class.#[derive(Clone, Copy, PartialEq, Eq, Hash)]#[repr(transparent)]pub struct Class(NonNull<ObjcClass>);
impl Class { /// Look up a class by name. /// /// Returns `None` if no class with that name is registered. pub fn get(name: &CStr) -> Option<Class> { // SAFETY: objc_getClass is thread-safe; it returns null for unknown // class names. let ptr = unsafe { objc_getClass(name.as_ptr()) }; NonNull::new(ptr).map(Class) }
/// Allocate a new class pair (a new class and its metaclass). /// /// `superclass` is the parent class; `name` is the new class name; /// `extra_bytes` is usually 0. /// /// Returns `None` if a class with `name` already exists. /// /// You must call [`Class::register`] on the returned class after adding /// methods and ivars. pub fn allocate(superclass: Class, name: &CStr, extra_bytes: usize) -> Option<Class> { // SAFETY: objc_allocateClassPair returns null if the name is already // taken. The superclass pointer is valid because it came from a Class. let ptr = unsafe { objc_allocateClassPair(superclass.as_ptr(), name.as_ptr(), extra_bytes) }; NonNull::new(ptr).map(Class) }
/// Register a class that was previously created with [`Class::allocate`]. /// /// After this call the class is usable and no further methods/ivars can be /// added. pub fn register(self) { // SAFETY: the pointer is valid and the class has not been registered // yet (caller invariant). unsafe { objc_registerClassPair(self.as_ptr()) } }
/// Add a method to a class that has not yet been registered. /// /// `sel` is the method selector, `imp` is the implementation function /// pointer, and `types` is the ObjC type encoding string. /// /// Returns `true` if the method was added, `false` if a method with that /// selector already existed. pub fn add_method(self, sel: Sel, imp: Imp, types: &CStr) -> bool { // SAFETY: the class pointer is valid and not yet registered. unsafe { class_addMethod(self.as_ptr(), sel.as_ptr(), imp, types.as_ptr()) } }
/// Add an instance variable to a class that has not yet been registered. /// /// `name` is the ivar name, `size` is the size in bytes, `alignment` is /// the log2 alignment (e.g., 3 for 8-byte alignment), and `types` is the /// ObjC type encoding string. /// /// Returns `true` if the ivar was added. pub fn add_ivar(self, name: &CStr, size: usize, alignment: u8, types: &CStr) -> bool { // SAFETY: the class pointer is valid and not yet registered. unsafe { class_addIvar( self.as_ptr(), name.as_ptr(), size, alignment, types.as_ptr(), ) } }
/// Get the name of this class. pub fn name(self) -> &'static str { // SAFETY: class_getName always returns a valid C string for a valid // class pointer. let c_str = unsafe { CStr::from_ptr(class_getName(self.as_ptr())) }; c_str.to_str().unwrap_or("<invalid UTF-8>") }
/// Return the raw pointer to the class. #[inline] pub fn as_ptr(self) -> *mut ObjcClass { self.0.as_ptr() }}
impl fmt::Debug for Class { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Class(\"{}\")", self.name()) }}
// SAFETY: ObjC classes are globally registered and immutable after// registration; safe to share across threads.unsafe impl Send for Class {}unsafe impl Sync for Class {}
// ---------------------------------------------------------------------------// Id — a non-null pointer to an Obj-C object instance// ---------------------------------------------------------------------------
/// A non-null pointer to an Objective-C object.////// This is an *unowned* (raw) reference — no automatic retain/release./// The caller is responsible for memory management.#[derive(Clone, Copy, PartialEq, Eq, Hash)]#[repr(transparent)]pub struct Id(NonNull<ObjcObject>);
impl Id { /// Create an `Id` from a raw non-null pointer. /// /// # Safety /// /// The pointer must point to a valid Objective-C object. #[inline] pub unsafe fn from_ptr(ptr: NonNull<ObjcObject>) -> Id { Id(ptr) }
/// Try to create an `Id` from a raw pointer that might be null. /// /// # Safety /// /// If non-null, the pointer must point to a valid Objective-C object. #[inline] pub unsafe fn from_raw(ptr: *mut ObjcObject) -> Option<Id> { NonNull::new(ptr).map(Id) }
/// Return the raw pointer. #[inline] pub fn as_ptr(self) -> *mut ObjcObject { self.0.as_ptr() }
/// Cast to `*mut T` for use as a function argument. /// /// # Safety /// /// The caller must ensure the object is actually of type `T`. #[inline] pub unsafe fn cast<T>(self) -> *mut T { self.0.as_ptr().cast() }
/// Set an instance variable by name. /// /// # Safety /// /// The object must have an ivar with the given `name` of pointer type. /// The caller is responsible for the validity and lifetime of `value`. pub unsafe fn set_ivar(self, name: &CStr, value: *mut c_void) { object_setInstanceVariable(self.as_ptr(), name.as_ptr(), value); }
/// Get an instance variable by name. /// /// # Safety /// /// The object must have an ivar with the given `name` of pointer type. pub unsafe fn get_ivar(self, name: &CStr) -> *mut c_void { let mut out: *mut c_void = std::ptr::null_mut(); object_getInstanceVariable(self.as_ptr(), name.as_ptr(), &mut out); out }}
impl fmt::Debug for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Id({:?})", self.0) }}
// SAFETY: ObjC objects can be sent across threads (whether this is safe// depends on the object, but at the pointer level it's fine).unsafe impl Send for Id {}unsafe impl Sync for Id {}
// ---------------------------------------------------------------------------// msg_send! macro// ---------------------------------------------------------------------------
/// Return the raw `objc_msgSend` function pointer.////// The caller must transmute this to the correct function signature for the/// message being sent. The `msg_send!` macro does this automatically.#[inline]pub fn msg_send_fn() -> unsafe extern "C" fn() { objc_msgSend}
/// Send an Objective-C message.////// # Syntax////// ```ignore/// // No arguments, returns Id:/// let obj: Id = msg_send![class.as_ptr(), alloc];////// // With arguments, returns Id:/// let obj: Id = msg_send![obj.as_ptr(), initWithFrame: rect];////// // No return value (returns ()):/// msg_send![obj.as_ptr(), release];/// ```////// The first argument must be a `*mut T` (the receiver). The return type is/// inferred from context.////// # Safety////// This is inherently unsafe: wrong selector, wrong argument types, or wrong/// return type will cause undefined behavior.#[macro_export]macro_rules! msg_send { // No arguments: msg_send![receiver, selector] [$receiver:expr, $sel:ident] => {{ let sel_name = concat!(stringify!($sel), "\0"); let sel = $crate::objc::Sel::register(unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) }); let func: unsafe extern "C" fn(*mut std::os::raw::c_void, *mut std::os::raw::c_void) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; let receiver = $receiver as *mut std::os::raw::c_void; unsafe { func(receiver, sel.as_ptr() as *mut std::os::raw::c_void) } }};
// One argument: msg_send![receiver, selector: arg] [$receiver:expr, $sel:ident : $arg:expr] => {{ let sel_name = concat!(stringify!($sel), ":\0"); let sel = $crate::objc::Sel::register(unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) }); let func: unsafe extern "C" fn( *mut std::os::raw::c_void, *mut std::os::raw::c_void, _, ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; let receiver = $receiver as *mut std::os::raw::c_void; let arg = $arg; unsafe { func( receiver, sel.as_ptr() as *mut std::os::raw::c_void, arg, ) } }};
// Two arguments: msg_send![receiver, sel1: arg1, sel2: arg2] [$receiver:expr, $sel1:ident : $arg1:expr, $sel2:ident : $arg2:expr] => {{ let sel_name = concat!(stringify!($sel1), ":", stringify!($sel2), ":\0"); let sel = $crate::objc::Sel::register(unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) }); let func: unsafe extern "C" fn( *mut std::os::raw::c_void, *mut std::os::raw::c_void, _, _, ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; let receiver = $receiver as *mut std::os::raw::c_void; let arg1 = $arg1; let arg2 = $arg2; unsafe { func( receiver, sel.as_ptr() as *mut std::os::raw::c_void, arg1, arg2, ) } }};
// Three arguments: msg_send![receiver, sel1: arg1, sel2: arg2, sel3: arg3] [$receiver:expr, $sel1:ident : $arg1:expr, $sel2:ident : $arg2:expr, $sel3:ident : $arg3:expr] => {{ let sel_name = concat!( stringify!($sel1), ":", stringify!($sel2), ":", stringify!($sel3), ":\0" ); let sel = $crate::objc::Sel::register(unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) }); let func: unsafe extern "C" fn( *mut std::os::raw::c_void, *mut std::os::raw::c_void, _, _, _, ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; let receiver = $receiver as *mut std::os::raw::c_void; let arg1 = $arg1; let arg2 = $arg2; let arg3 = $arg3; unsafe { func( receiver, sel.as_ptr() as *mut std::os::raw::c_void, arg1, arg2, arg3, ) } }};
// Four arguments [$receiver:expr, $sel1:ident : $arg1:expr, $sel2:ident : $arg2:expr, $sel3:ident : $arg3:expr, $sel4:ident : $arg4:expr] => {{ let sel_name = concat!( stringify!($sel1), ":", stringify!($sel2), ":", stringify!($sel3), ":", stringify!($sel4), ":\0" ); let sel = $crate::objc::Sel::register(unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes()) }); let func: unsafe extern "C" fn( *mut std::os::raw::c_void, *mut std::os::raw::c_void, _, _, _, _, ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) }; let receiver = $receiver as *mut std::os::raw::c_void; let arg1 = $arg1; let arg2 = $arg2; let arg3 = $arg3; let arg4 = $arg4; unsafe { func( receiver, sel.as_ptr() as *mut std::os::raw::c_void, arg1, arg2, arg3, arg4, ) } }};}
// ---------------------------------------------------------------------------// Convenience: sel! macro// ---------------------------------------------------------------------------
/// Register a selector from a string literal.////// ```ignore/// let sel = sel!("init");/// let sel = sel!("initWithFrame:");/// ```#[macro_export]macro_rules! sel { ($name:literal) => {{ $crate::objc::Sel::register(unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes()) }) }};}
/// Look up a class by name (string literal).////// Returns `Option<Class>`.////// ```ignore/// let cls = class!("NSObject").unwrap();/// ```#[macro_export]macro_rules! class { ($name:literal) => {{ $crate::objc::Class::get(unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes()) }) }};}
// ---------------------------------------------------------------------------// Tests// ---------------------------------------------------------------------------
#[cfg(test)]mod tests { use super::*;
#[test] fn get_nsobject_class() { let cls = Class::get(c"NSObject").expect("NSObject class should exist"); assert_eq!(cls.name(), "NSObject"); }
#[test] fn get_nonexistent_class_returns_none() { let cls = Class::get(c"ThisClassDoesNotExist12345"); assert!(cls.is_none()); }
#[test] fn register_selector() { let sel1 = Sel::register(c"init"); let sel2 = Sel::register(c"init"); // Same selector name should return the same pointer. assert_eq!(sel1, sel2); }
#[test] fn different_selectors_differ() { let sel1 = Sel::register(c"init"); let sel2 = Sel::register(c"alloc"); assert_ne!(sel1, sel2); }
#[test] fn alloc_init_nsobject() { let cls = Class::get(c"NSObject").unwrap(); // alloc let obj: *mut std::os::raw::c_void = msg_send![cls.as_ptr(), alloc]; assert!(!obj.is_null()); // init let obj: *mut std::os::raw::c_void = msg_send![obj, init]; assert!(!obj.is_null()); // release let _: *mut std::os::raw::c_void = msg_send![obj, release]; }
#[test] fn class_macro_works() { let cls = class!("NSObject").expect("NSObject should exist"); assert_eq!(cls.name(), "NSObject"); }
#[test] fn sel_macro_works() { let sel = sel!("init"); let sel2 = Sel::register(c"init"); assert_eq!(sel, sel2); }
#[test] fn allocate_and_register_custom_class() { let superclass = Class::get(c"NSObject").unwrap(); // Use a unique class name to avoid conflicts with other tests. let new_cls = Class::allocate(superclass, c"WeTestCustomClass", 0) .expect("should allocate new class"); new_cls.register(); // Verify we can look it up. let found = Class::get(c"WeTestCustomClass").expect("custom class should be registered"); assert_eq!(found.name(), "WeTestCustomClass"); }
#[test] fn add_method_to_custom_class() { let superclass = Class::get(c"NSObject").unwrap(); let new_cls = Class::allocate(superclass, c"WeTestMethodClass", 0) .expect("should allocate new class");
// Define a simple method that returns an integer. extern "C" fn my_method( _this: *mut std::os::raw::c_void, _sel: *mut std::os::raw::c_void, ) -> i64 { 42 }
let sel = Sel::register(c"myMethod"); // Type encoding: returns long long (q), takes id (@) and SEL (:) let added = new_cls.add_method( sel, unsafe { std::mem::transmute::<*const (), Imp>(my_method as *const ()) }, c"q@:", ); assert!(added);
new_cls.register();
// Allocate an instance and call our method. let cls = Class::get(c"WeTestMethodClass").unwrap(); let obj: *mut std::os::raw::c_void = msg_send![cls.as_ptr(), alloc]; let obj: *mut std::os::raw::c_void = msg_send![obj, init]; let result: i64 = msg_send![obj, myMethod]; assert_eq!(result, 42); let _: *mut std::os::raw::c_void = msg_send![obj, release]; }
#[test] fn msg_send_with_argument() { let cls = Class::get(c"NSObject").unwrap(); let obj: *mut std::os::raw::c_void = msg_send![cls.as_ptr(), alloc]; let obj: *mut std::os::raw::c_void = msg_send![obj, init];
// respondsToSelector: takes a SEL, returns BOOL (i8 on AArch64). let sel = Sel::register(c"init"); let responds: bool = msg_send![obj, respondsToSelector: sel.as_ptr()]; assert!(responds);
let _: *mut std::os::raw::c_void = msg_send![obj, release]; }
#[test] fn class_debug_format() { let cls = Class::get(c"NSObject").unwrap(); let debug = format!("{:?}", cls); assert!(debug.contains("NSObject")); }
#[test] fn sel_debug_format() { let sel = Sel::register(c"init"); let debug = format!("{:?}", sel); assert!(debug.contains("Sel(")); }}