Skip to main content

crashrustler/
init.rs

1use crate::crash_rustler::CrashRustler;
2use crate::types::*;
3
4impl CrashRustler {
5    /// Creates a new CrashRustler from pre-gathered crash data.
6    ///
7    /// The binary (exc_handler) is responsible for all Mach/system calls.
8    /// This constructor is pure data — no FFI.
9    ///
10    /// Equivalent to -[CrashReport initWithTask:exceptionType:exceptionCode:
11    /// exceptionCodeCount:thread:threadStateFlavor:threadState:threadStateCount:]
12    pub fn new(params: CrashParams) -> Self {
13        let exception_code_count = params.exception_codes.len() as u32;
14        let mut cr = Self {
15            task: params.task,
16            pid: params.pid,
17            ppid: params.ppid,
18            uid: params.uid,
19            is_64_bit: params.is_64_bit,
20            thread: params.thread,
21            thread_state: params.thread_state,
22            exception_state: params.exception_state,
23            process_name: params.process_name,
24            executable_path: params.executable_path,
25            r_process_pid: params.r_process_pid,
26            date: params.date,
27            awake_system_uptime: params.awake_system_uptime,
28            cpu_type: params.cpu_type,
29            crashed_thread_number: -1,
30            performing_autopsy: false,
31            is_native: true,
32            exception_type: params.exception_type,
33            exception_code_count,
34            exception_code: params.exception_codes,
35            ..Default::default()
36        };
37
38        // For EXC_CRASH (type 10): extract embedded exception type and signal
39        // from the exception code bits. This is pure arithmetic — no FFI needed.
40        if cr.exception_type == 10 && !cr.exception_code.is_empty() {
41            let code0 = cr.exception_code[0] as u64;
42            let embedded_type = ((code0 >> 20) & 0xf) as i32;
43            if embedded_type != 0 {
44                cr.exception_type = embedded_type;
45            }
46            cr.signal = ((code0 >> 24) & 0xff) as u32;
47            // Mask off the signal/type bits, keeping lower 20 bits
48            cr.exception_code[0] = (cr.exception_code[0] as u64 & 0xf_ffff) as i64;
49        }
50
51        cr
52    }
53
54    /// Stub initializer for corpse-based crash reports.
55    /// Returns None — corpse handling is done elsewhere in the crash reporter daemon.
56    /// Equivalent to -[CrashReport initWithCorpse:length:task:...]
57    #[allow(clippy::too_many_arguments)]
58    pub fn new_from_corpse(
59        _corpse: u64,
60        _length: u64,
61        _task: u32,
62        _exception_type: i32,
63        _exception_codes: &[i64],
64        _thread: u32,
65        _thread_state_flavor: u32,
66        _thread_state: &[u32],
67    ) -> Option<Self> {
68        // ObjC implementation returns nil
69        None
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    fn make_params() -> CrashParams {
78        CrashParams {
79            task: 100,
80            pid: 1234,
81            ppid: 1,
82            uid: 501,
83            is_64_bit: true,
84            thread: 200,
85            thread_state: ThreadState {
86                flavor: 6,
87                registers: vec![],
88            },
89            exception_state: ExceptionState {
90                state: vec![],
91                count: 0,
92            },
93            process_name: Some("test".to_string()),
94            executable_path: Some("/usr/bin/test".to_string()),
95            r_process_pid: 0,
96            date: Some("2026-03-23".to_string()),
97            awake_system_uptime: 1000,
98            cpu_type: CpuType::ARM64,
99            exception_type: 1, // EXC_BAD_ACCESS
100            exception_codes: vec![2, 0x42],
101        }
102    }
103
104    #[test]
105    fn new_populates_basic_fields() {
106        let params = make_params();
107        let cr = CrashRustler::new(params);
108        assert_eq!(cr.task, 100);
109        assert_eq!(cr.pid, 1234);
110        assert_eq!(cr.ppid, 1);
111        assert_eq!(cr.uid, 501);
112        assert!(cr.is_64_bit);
113        assert_eq!(cr.thread, 200);
114        assert_eq!(cr.process_name, Some("test".to_string()));
115        assert_eq!(cr.executable_path, Some("/usr/bin/test".to_string()));
116        assert_eq!(cr.cpu_type, CpuType::ARM64);
117        assert_eq!(cr.exception_type, 1);
118        assert_eq!(cr.exception_code, vec![2, 0x42]);
119        assert_eq!(cr.exception_code_count, 2);
120    }
121
122    #[test]
123    fn new_sets_defaults_for_derived_fields() {
124        let cr = CrashRustler::new(make_params());
125        assert_eq!(cr.crashed_thread_number, -1);
126        assert!(!cr.performing_autopsy);
127        assert!(cr.is_native);
128        assert_eq!(cr.signal, 0);
129    }
130
131    #[test]
132    fn new_exc_crash_extracts_embedded_type() {
133        let mut params = make_params();
134        params.exception_type = 10; // EXC_CRASH
135        // Encode embedded type=1 (EXC_BAD_ACCESS) and signal=11 (SIGSEGV)
136        // code0 format: [signal:8][type:4][subcode:20]
137        params.exception_codes = vec![((11i64 << 24) | (1i64 << 20) | 0x42), 0];
138        let cr = CrashRustler::new(params);
139        assert_eq!(cr.exception_type, 1); // extracted from bits
140        assert_eq!(cr.signal, 11); // extracted from bits
141        assert_eq!(cr.exception_code[0], 0x42); // lower 20 bits preserved
142    }
143
144    #[test]
145    fn new_exc_crash_zero_embedded_type_preserves_type_10() {
146        let mut params = make_params();
147        params.exception_type = 10;
148        // Embedded type is 0 → exception_type stays 10
149        params.exception_codes = vec![((6i64 << 24) | 0), 0];
150        let cr = CrashRustler::new(params);
151        assert_eq!(cr.exception_type, 10);
152        assert_eq!(cr.signal, 6); // SIGABRT
153    }
154
155    #[test]
156    fn new_exc_crash_empty_codes_no_panic() {
157        let mut params = make_params();
158        params.exception_type = 10;
159        params.exception_codes = vec![];
160        let cr = CrashRustler::new(params);
161        assert_eq!(cr.exception_type, 10); // unchanged, no codes to extract from
162    }
163
164    #[test]
165    fn new_non_crash_exception_no_signal_extraction() {
166        let mut params = make_params();
167        params.exception_type = 1; // EXC_BAD_ACCESS, not EXC_CRASH
168        params.exception_codes = vec![0x0b10_0042, 0]; // would decode to signal 11 if misinterpreted
169        let cr = CrashRustler::new(params);
170        assert_eq!(cr.exception_type, 1); // unchanged
171        assert_eq!(cr.signal, 0); // no extraction for non-EXC_CRASH
172        assert_eq!(cr.exception_code[0], 0x0b10_0042); // codes unchanged
173    }
174
175    #[test]
176    fn new_from_corpse_returns_none() {
177        let result = CrashRustler::new_from_corpse(0, 0, 0, 0, &[], 0, 0, &[]);
178        assert!(result.is_none());
179    }
180}