Skip to main content

macho_objc/
category.rs

1use crate::error::Result;
2use crate::model::addr::Va;
3use crate::objc::class::{resolve_class_name_from_va, resolve_class_ref_name};
4use crate::objc::method::parse_method_list;
5use crate::objc::property::{parse_property_list, parse_property_list_with_kind};
6use crate::objc::protocol::parse_protocol_name_list;
7use crate::objc::resolve::ObjCResolver;
8use crate::objc::types::ObjCCategory;
9
10/// Performs parse_category.
11pub fn parse_category(resolver: &ObjCResolver<'_>, cat_va: Va) -> Result<ObjCCategory> {
12    let offset = resolver.va_to_offset(cat_va)?;
13
14    let name_ptr_offset = offset.as_usize() as u64;
15    let name = match resolver.read_pointer_at_offset(name_ptr_offset)? {
16        Some(va) => resolver.read_cstring(va).unwrap_or("<invalid>").to_string(),
17        None => "<null>".to_string(),
18    };
19
20    // Read class pointer — may be a bind (external) or rebase (internal)
21    let cls_offset = offset.as_usize() as u64 + 8;
22    let class_name = resolve_class_ref_name(resolver, cls_offset).unwrap_or_else(|| {
23        if let Ok(Some(cls_va)) = resolver.read_pointer_at_offset(cls_offset) {
24            resolve_class_name_from_va(resolver, cls_va).unwrap_or_else(|| "<class>".to_string())
25        } else {
26            "<null>".to_string()
27        }
28    });
29
30    let inst_methods_offset = offset.as_usize() as u64 + 16;
31    let instance_methods = match resolver.read_pointer_at_offset(inst_methods_offset)? {
32        Some(va) if va.0 != 0 => parse_method_list(resolver, va).unwrap_or_default(),
33        _ => Vec::new(),
34    };
35
36    let cls_methods_offset = offset.as_usize() as u64 + 24;
37    let class_methods = match resolver.read_pointer_at_offset(cls_methods_offset)? {
38        Some(va) if va.0 != 0 => parse_method_list(resolver, va).unwrap_or_default(),
39        _ => Vec::new(),
40    };
41
42    let protos_offset = offset.as_usize() as u64 + 32;
43    let protocols = match resolver.read_pointer_at_offset(protos_offset)? {
44        Some(va) if va.0 != 0 => parse_protocol_name_list(resolver, va).unwrap_or_default(),
45        _ => Vec::new(),
46    };
47
48    // Modern category_t appends instance properties at +40.
49    let props_offset = offset.as_usize() as u64 + 40;
50    let properties = match resolver.read_pointer_at_offset(props_offset) {
51        Ok(Some(va)) if va.0 != 0 => parse_property_list(resolver, va).unwrap_or_default(),
52        _ => Vec::new(),
53    };
54
55    let class_props_offset = offset.as_usize() as u64 + 48;
56    let class_properties = match resolver.read_pointer_at_offset(class_props_offset) {
57        Ok(Some(va)) if va.0 != 0 => {
58            parse_property_list_with_kind(resolver, va, true).unwrap_or_default()
59        }
60        _ => Vec::new(),
61    };
62
63    let mut properties = properties;
64    properties.extend(class_properties);
65
66    Ok(ObjCCategory {
67        name,
68        class_name,
69        instance_methods,
70        class_methods,
71        properties,
72        protocols,
73    })
74}