use std::marker::PhantomData;
use bitflags::bitflags;
use crate::{
objc::{
ObjcRuntime,
ivar::{IvarIter, ivar_list_iter},
method::{MethodIter, method_list_iter},
property::{PropertyIter, property_list_iter},
protocol::{ProtocolNameIter, protocol_name_iter},
strip_objc_symbol_prefix,
},
util::read_u32_le_at,
};
pub(crate) const CLASS_T_SIZE: usize = 40;
const CLASS_T_BITS_OFFSET: usize = 32;
const CLASS_RO_T_SIZE: usize = 72;
const FAST_DATA_MASK_64: u64 = 0x0000_007f_ffff_fff8;
const FAST_DATA_MASK_ARM64E: u64 = 0x0f00_7fff_ffff_fff8;
const FAST_FLAGS_MASK: u64 = 0x0000_0000_0000_0007;
pub const FAST_IS_SWIFT_LEGACY: u64 = 0x1;
pub const FAST_IS_SWIFT_STABLE: u64 = 0x2;
pub const FAST_HAS_DEFAULT_RR: u64 = 0x4;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RoFlags: u32 {
const META = 1 << 0;
const ROOT = 1 << 1;
const HAS_CXX_STRUCTORS = 1 << 2;
const HIDDEN = 1 << 4;
const EXCEPTION = 1 << 5;
const HAS_SWIFT_INITIALIZER = 1 << 6;
const IS_ARC = 1 << 7;
const HAS_CXX_DTOR_ONLY = 1 << 8;
const HAS_WEAK_WITHOUT_ARC = 1 << 9;
const FORBIDS_ASSOCIATED_OBJECTS = 1 << 10;
const FROM_BUNDLE = 1 << 29;
const FUTURE = 1 << 30;
const REALIZED = 1 << 31;
}
}
#[derive(Clone)]
pub struct ObjcClass<'a, 'p> {
pub(crate) rt: &'p ObjcRuntime<'a>,
pub(crate) address: u64,
pub(crate) isa: u64,
pub(crate) superclass: u64,
pub(crate) bits: u64,
pub(crate) is_meta: bool,
}
impl<'a, 'p> ObjcClass<'a, 'p> {
pub fn address(&self) -> u64 {
self.address
}
pub fn isa(&self) -> u64 {
self.isa
}
pub fn superclass_address(&self) -> u64 {
self.superclass
}
pub fn superclass_name(&self) -> Option<&'a str> {
if self.superclass != 0
&& let Some(decoded) = decode_class_name(self.rt, self.superclass)
{
return Some(decoded);
}
let super_slot_va = self.address.checked_add(8)?;
if let Some((sym, _)) = self.rt.binds_by_va.get(&super_slot_va) {
return Some(strip_objc_symbol_prefix(sym));
}
None
}
pub fn bits(&self) -> u64 {
self.bits
}
pub fn fast_flags(&self) -> u64 {
self.bits & FAST_FLAGS_MASK
}
pub fn is_swift(&self) -> bool {
(self.fast_flags() & (FAST_IS_SWIFT_LEGACY | FAST_IS_SWIFT_STABLE)) != 0
}
pub fn has_default_rr(&self) -> bool {
(self.fast_flags() & FAST_HAS_DEFAULT_RR) != 0
}
pub fn is_meta(&self) -> bool {
self.is_meta
}
pub fn ro(&self) -> Option<ClassRo<'a, 'p>> {
let ro_va = mask_class_ro_pointer(self.rt, self.bits)?;
decode_class_ro(self.rt, ro_va)
}
}
impl core::fmt::Debug for ObjcClass<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ObjcClass")
.field("address", &format_args!("0x{:x}", self.address))
.field("is_meta", &self.is_meta)
.field("isa", &format_args!("0x{:x}", self.isa))
.field("superclass", &format_args!("0x{:x}", self.superclass))
.field("bits", &format_args!("0x{:x}", self.bits))
.field("is_swift", &self.is_swift())
.field("name", &self.ro().map(|r| r.name).unwrap_or("?"))
.finish()
}
}
#[derive(Clone)]
pub struct ClassRo<'a, 'p> {
rt: &'p ObjcRuntime<'a>,
address: u64,
flags: RoFlags,
instance_start: u32,
instance_size: u32,
name: &'a str,
base_methods_va: u64,
base_protocols_va: u64,
ivars_va: u64,
base_properties_va: u64,
weak_ivar_layout_va: u64,
ivar_layout_va: u64,
}
impl<'a, 'p> ClassRo<'a, 'p> {
pub fn address(&self) -> u64 {
self.address
}
pub fn flags(&self) -> RoFlags {
self.flags
}
pub fn instance_start(&self) -> u32 {
self.instance_start
}
pub fn instance_size(&self) -> u32 {
self.instance_size
}
pub fn name(&self) -> &'a str {
self.name
}
pub fn is_meta(&self) -> bool {
self.flags.contains(RoFlags::META)
}
pub fn is_root(&self) -> bool {
self.flags.contains(RoFlags::ROOT)
}
pub fn has_cxx_structors(&self) -> bool {
self.flags.contains(RoFlags::HAS_CXX_STRUCTORS)
}
pub fn is_arc(&self) -> bool {
self.flags.contains(RoFlags::IS_ARC)
}
pub fn has_swift_initializer(&self) -> bool {
self.flags.contains(RoFlags::HAS_SWIFT_INITIALIZER)
}
pub fn is_exception(&self) -> bool {
self.flags.contains(RoFlags::EXCEPTION)
}
pub fn ivar_layout_address(&self) -> u64 {
self.ivar_layout_va
}
pub fn weak_ivar_layout_address(&self) -> u64 {
self.weak_ivar_layout_va
}
pub fn methods(&self) -> MethodIter<'a, 'p> {
method_list_iter(self.rt, self.base_methods_va)
}
pub fn ivars(&self) -> IvarIter<'a, 'p> {
ivar_list_iter(self.rt, self.ivars_va)
}
pub fn properties(&self) -> PropertyIter<'a, 'p> {
property_list_iter(self.rt, self.base_properties_va)
}
pub fn protocols(&self) -> ProtocolNameIter<'a, 'p> {
protocol_name_iter(self.rt, self.base_protocols_va)
}
}
impl core::fmt::Debug for ClassRo<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ClassRo")
.field("name", &self.name)
.field("flags", &self.flags)
.field("instance_start", &self.instance_start)
.field("instance_size", &self.instance_size)
.finish()
}
}
fn mask_class_ro_pointer(rt: &ObjcRuntime<'_>, bits: u64) -> Option<u64> {
let candidate_wide = bits & FAST_DATA_MASK_ARM64E;
if candidate_wide != 0 && rt.vm_to_file_offset(candidate_wide).is_some() {
return Some(candidate_wide);
}
let candidate_std = bits & FAST_DATA_MASK_64;
if candidate_std != 0 && rt.vm_to_file_offset(candidate_std).is_some() {
return Some(candidate_std);
}
if candidate_std != 0 {
Some(candidate_std)
} else {
None
}
}
fn decode_class_ro<'a, 'p>(rt: &'p ObjcRuntime<'a>, ro_va: u64) -> Option<ClassRo<'a, 'p>> {
let bytes = rt.read_bytes(ro_va, CLASS_RO_T_SIZE)?;
let raw_flags = read_u32_le_at(bytes, 0)?;
let flags = RoFlags::from_bits_retain(raw_flags);
let instance_start = read_u32_le_at(bytes, 4)?;
let instance_size = read_u32_le_at(bytes, 8)?;
let ivar_layout_va = rt.resolve_pointer(ro_va.checked_add(16)?).unwrap_or(0);
let name_va = rt.resolve_pointer(ro_va.checked_add(24)?)?;
let base_methods_va = rt.resolve_pointer(ro_va.checked_add(32)?).unwrap_or(0);
let base_protocols_va = rt.resolve_pointer(ro_va.checked_add(40)?).unwrap_or(0);
let ivars_va = rt.resolve_pointer(ro_va.checked_add(48)?).unwrap_or(0);
let weak_ivar_layout_va = rt.resolve_pointer(ro_va.checked_add(56)?).unwrap_or(0);
let base_properties_va = rt.resolve_pointer(ro_va.checked_add(64)?).unwrap_or(0);
let name = rt.read_cstr(name_va)?;
Some(ClassRo {
rt,
address: ro_va,
flags,
instance_start,
instance_size,
name,
base_methods_va,
base_protocols_va,
ivars_va,
base_properties_va,
weak_ivar_layout_va,
ivar_layout_va,
})
}
pub struct ClassIter<'a, 'p> {
rt: &'p ObjcRuntime<'a>,
class_addrs: std::vec::IntoIter<u64>,
pending_meta: Option<u64>,
_phantom: PhantomData<&'a ()>,
}
impl<'a, 'p> ClassIter<'a, 'p> {
pub(crate) fn new(rt: &'p ObjcRuntime<'a>) -> Self {
let mut seen: std::collections::HashSet<u64> = std::collections::HashSet::new();
let mut ordered: Vec<u64> = Vec::new();
for sec in [rt.class_list, rt.nlclslist].iter().flatten() {
let mut off = 0usize;
while off.checked_add(8).is_some_and(|end| end <= sec.body.len()) {
let slot_va = sec.vmaddr.wrapping_add(off as u64);
let va = rt.resolve_pointer(slot_va).unwrap_or(0);
off = match off.checked_add(8) {
Some(v) => v,
None => break,
};
if va == 0 {
continue;
}
if seen.insert(va) {
ordered.push(va);
}
}
}
Self {
rt,
class_addrs: ordered.into_iter(),
pending_meta: None,
_phantom: PhantomData,
}
}
}
impl<'a, 'p> Iterator for ClassIter<'a, 'p> {
type Item = ObjcClass<'a, 'p>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(meta_va) = self.pending_meta.take()
&& let Some(c) = decode_class(self.rt, meta_va, true)
{
return Some(c);
}
loop {
let va = self.class_addrs.next()?;
let Some(class) = decode_class(self.rt, va, false) else {
continue;
};
if class.isa != 0 && class.isa != va {
self.pending_meta = Some(class.isa);
}
return Some(class);
}
}
}
pub(crate) fn decode_class<'a, 'p>(
rt: &'p ObjcRuntime<'a>,
class_va: u64,
is_meta: bool,
) -> Option<ObjcClass<'a, 'p>> {
rt.read_bytes(class_va, CLASS_T_SIZE)?;
let isa_slot_va = class_va;
let super_slot_va = class_va.checked_add(8)?;
let bits_slot_va = class_va.checked_add(CLASS_T_BITS_OFFSET as u64)?;
let isa = rt.resolve_pointer(isa_slot_va).unwrap_or(0);
let superclass = rt.resolve_pointer(super_slot_va).unwrap_or(0);
let raw_bits = rt.read_u64(bits_slot_va)?;
let fast_flags = raw_bits & FAST_FLAGS_MASK;
let class_ro_va = if let Some(&target) = rt.rebases_by_va.get(&bits_slot_va) {
target
} else {
mask_class_ro_pointer(rt, raw_bits).unwrap_or(0)
};
let bits = (class_ro_va & !FAST_FLAGS_MASK) | fast_flags;
Some(ObjcClass {
rt,
address: class_va,
isa,
superclass,
bits,
is_meta,
})
}
pub(crate) fn decode_class_name<'a>(rt: &ObjcRuntime<'a>, class_va: u64) -> Option<&'a str> {
rt.read_bytes(class_va, CLASS_T_SIZE)?;
let bits_slot_va = class_va.checked_add(CLASS_T_BITS_OFFSET as u64)?;
let class_ro_va = if let Some(&target) = rt.rebases_by_va.get(&bits_slot_va) {
target
} else {
let raw_bits = rt.read_u64(bits_slot_va)?;
mask_class_ro_pointer(rt, raw_bits)?
};
let name_slot_va = class_ro_va.checked_add(24)?;
let name_va = rt.resolve_pointer(name_slot_va)?;
rt.read_cstr(name_va)
}