use std::{collections::HashSet, marker::PhantomData};
use crate::objc::{
ObjcRuntime,
class::decode_class_name,
method::{MethodIter, method_list_iter},
property::{PropertyIter, property_list_iter},
protocol::{ProtocolNameIter, protocol_name_iter},
strip_objc_symbol_prefix,
};
const CATEGORY_BASE_SIZE: usize = 48;
const CATEGORY_WITH_CLASS_PROPS_SIZE: usize = 56;
pub struct ObjcCategory<'a, 'p> {
rt: &'p ObjcRuntime<'a>,
address: u64,
name: &'a str,
class_va: u64,
cls_slot_va: u64,
instance_methods_va: u64,
class_methods_va: u64,
protocols_va: u64,
instance_properties_va: u64,
class_properties_va: Option<u64>,
}
impl<'a, 'p> ObjcCategory<'a, 'p> {
pub fn address(&self) -> u64 {
self.address
}
pub fn name(&self) -> &'a str {
self.name
}
pub fn class_address(&self) -> u64 {
self.class_va
}
pub fn class_name(&self) -> Option<&'a str> {
if self.class_va != 0
&& let Some(n) = decode_class_name(self.rt, self.class_va)
{
return Some(n);
}
if let Some((sym, _)) = self.rt.binds_by_va.get(&self.cls_slot_va) {
return Some(strip_objc_symbol_prefix(sym));
}
None
}
pub fn instance_methods(&self) -> MethodIter<'a, 'p> {
method_list_iter(self.rt, self.instance_methods_va)
}
pub fn class_methods(&self) -> MethodIter<'a, 'p> {
method_list_iter(self.rt, self.class_methods_va)
}
pub fn protocols(&self) -> ProtocolNameIter<'a, 'p> {
protocol_name_iter(self.rt, self.protocols_va)
}
pub fn instance_properties(&self) -> PropertyIter<'a, 'p> {
property_list_iter(self.rt, self.instance_properties_va)
}
pub fn class_properties(&self) -> PropertyIter<'a, 'p> {
match self.class_properties_va {
Some(va) => property_list_iter(self.rt, va),
None => PropertyIter::empty(self.rt),
}
}
}
impl core::fmt::Debug for ObjcCategory<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ObjcCategory")
.field("address", &format_args!("0x{:x}", self.address))
.field("name", &self.name)
.field("class_name", &self.class_name().unwrap_or("?"))
.finish()
}
}
pub struct CategoryIter<'a, 'p> {
rt: &'p ObjcRuntime<'a>,
cat_addrs: std::vec::IntoIter<u64>,
_phantom: PhantomData<&'a ()>,
}
impl<'a, 'p> CategoryIter<'a, 'p> {
pub(crate) fn new(rt: &'p ObjcRuntime<'a>) -> Self {
let mut seen: HashSet<u64> = HashSet::new();
let mut ordered: Vec<u64> = Vec::new();
for sec in [rt.cat_list, rt.nlcat_list].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,
cat_addrs: ordered.into_iter(),
_phantom: PhantomData,
}
}
}
impl<'a, 'p> Iterator for CategoryIter<'a, 'p> {
type Item = ObjcCategory<'a, 'p>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let va = self.cat_addrs.next()?;
if let Some(c) = decode_category(self.rt, va) {
return Some(c);
}
#[cfg(feature = "tracing")]
tracing::debug!(
"darwinscope::objc: category at 0x{:x} skipped - decode failed",
va
);
}
}
}
fn decode_category<'a, 'p>(rt: &'p ObjcRuntime<'a>, cat_va: u64) -> Option<ObjcCategory<'a, 'p>> {
let want_class_props = rt.image_info.has_category_class_properties();
let want_size = if want_class_props {
CATEGORY_WITH_CLASS_PROPS_SIZE
} else {
CATEGORY_BASE_SIZE
};
rt.read_bytes(cat_va, want_size)?;
let name_va = rt.resolve_pointer(cat_va)?;
let cls_slot_va = cat_va.checked_add(8)?;
let class_va = rt.resolve_pointer(cls_slot_va).unwrap_or(0);
let instance_methods_va = rt.resolve_pointer(cat_va.checked_add(16)?).unwrap_or(0);
let class_methods_va = rt.resolve_pointer(cat_va.checked_add(24)?).unwrap_or(0);
let protocols_va = rt.resolve_pointer(cat_va.checked_add(32)?).unwrap_or(0);
let instance_properties_va = rt.resolve_pointer(cat_va.checked_add(40)?).unwrap_or(0);
let class_properties_va = if want_class_props {
let resolved = rt.resolve_pointer(cat_va.checked_add(48)?).unwrap_or(0);
if resolved == 0 { None } else { Some(resolved) }
} else {
None
};
let name = rt.read_cstr(name_va)?;
Some(ObjcCategory {
rt,
address: cat_va,
name,
class_va,
cls_slot_va,
instance_methods_va,
class_methods_va,
protocols_va,
instance_properties_va,
class_properties_va,
})
}