1use std::collections::BTreeMap;
2
3use crate::crash_rustler::CrashRustler;
4use crate::types::*;
5
6impl CrashRustler {
7 pub fn crashed_due_to_bad_memory_access(&self) -> bool {
28 if self.exception_type != 1 {
29 return false;
30 }
31 if self.is_x86_cpu()
33 && self.exception_code.first() == Some(&0xd)
34 && self.exception_code.get(1) == Some(&0)
35 {
36 return false;
37 }
38 true
39 }
40
41 pub fn extract_crashing_address(&mut self) {
46 if self.crashed_due_to_bad_memory_access() {
47 if let Some(&addr) = self.exception_code.get(1) {
48 self.crashing_address = addr as u64;
49 }
50 } else if self.is_code_sign_killed() {
51 let state = &self.exception_state.state;
52 if self.is_arm_cpu() {
53 if state.len() >= 2 {
55 self.crashing_address = (state[0] as u64) | ((state[1] as u64) << 32);
56 }
57 } else {
58 if !state.is_empty() {
60 if state[0] == 1 {
61 if let Some(&cr2) = state.get(4) {
63 self.crashing_address = cr2 as u64;
64 }
65 } else {
66 if state.len() > 5 {
68 self.crashing_address = (state[4] as u64) | ((state[5] as u64) << 32);
69 }
70 }
71 }
72 }
73 }
74 }
75
76 pub fn sanitize_path(path: &str) -> String {
95 if let Some(rest) = path.strip_prefix("/Users/")
96 && let Some(slash_pos) = rest.find('/')
97 {
98 return format!("/Users/USER/{}", &rest[slash_pos + 1..]);
99 }
100 path.to_string()
101 }
102
103 pub fn cleanse_paths(&mut self) {
108 if self.executable_path.is_some() && self.reopen_path.is_none() {
110 self.reopen_path = self.executable_path.clone();
111 }
112
113 if let Some(ref path) = self.executable_path {
115 let sanitized = Self::sanitize_path(path);
116 if sanitized != *path {
117 self.executable_path = Some(sanitized);
118 }
119 }
120
121 for image in &mut self.binary_images {
123 image.path = Self::sanitize_path(&image.path);
124 }
125
126 if let Some(ref vm_map) = self.vm_map_string {
128 let sanitized_lines: Vec<String> = vm_map
129 .lines()
130 .map(|line| {
131 if let Some(slash_pos) = line.find('/') {
132 let path = &line[slash_pos..];
133 let sanitized = Self::sanitize_path(path);
134 format!("{}{}", &line[..slash_pos], sanitized)
135 } else {
136 line.to_string()
137 }
138 })
139 .collect();
140 self.vm_map_string = Some(sanitized_lines.join("\n"));
141 }
142 }
143
144 pub fn extract_work_queue_limits_from_flags(
151 &mut self,
152 flags: u32,
153 wq_max_constrained_threads: Option<u32>,
154 wq_max_threads: Option<u32>,
155 ) {
156 if flags & 3 == 0 {
157 return;
158 }
159 let mut limits = WorkQueueLimits {
160 min_threads: None,
161 max_threads: None,
162 };
163 if flags & 1 != 0 {
164 limits.min_threads = wq_max_constrained_threads;
165 }
166 if flags & 2 != 0 {
167 limits.max_threads = wq_max_threads;
168 }
169 self.work_queue_limits = Some(limits);
170 }
171
172 pub fn set_app_store_receipt(&mut self, adam_id: Option<String>, version_id: Option<String>) {
176 if adam_id.is_some() {
177 self.has_receipt = true;
178 self.adam_id = adam_id;
179 self.software_version_external_identifier = version_id;
180 }
181 }
182
183 pub fn problem_dictionary(&self) -> BTreeMap<String, PlistValue> {
192 let mut d = BTreeMap::new();
193
194 if let Some(name) = self.process_name()
195 && !name.is_empty()
196 {
197 d.insert("app_name".into(), PlistValue::String(name.into()));
198 }
199 if self.pid != 0 {
200 d.insert("app_pid".into(), PlistValue::Int(self.pid as i64));
201 }
202 if let Some(path) = self.executable_path()
203 && !path.is_empty()
204 {
205 d.insert("app_path".into(), PlistValue::String(path.into()));
206 }
207 if let Some(id) = self.process_identifier()
208 && !id.is_empty()
209 {
210 d.insert("app_bundle_id".into(), PlistValue::String(id.into()));
211 }
212 let ver = self.app_version();
213 if !ver.is_empty() {
214 d.insert("app_version".into(), PlistValue::String(ver));
215 }
216 let build_ver = self.app_build_version();
217 if !build_ver.is_empty() {
218 d.insert("app_build_version".into(), PlistValue::String(build_ver));
219 }
220
221 if let Some(v) = self.build_version_dictionary.get("ProjectName")
223 && !v.is_empty()
224 {
225 d.insert("project_name".into(), PlistValue::String(v.clone()));
226 }
227 if let Some(v) = self.build_version_dictionary.get("SourceVersion")
228 && !v.is_empty()
229 {
230 d.insert(
231 "project_source_version".into(),
232 PlistValue::String(v.clone()),
233 );
234 }
235 if let Some(v) = self.build_version_dictionary.get("BuildVersion")
236 && !v.is_empty()
237 {
238 d.insert(
239 "project_build_version".into(),
240 PlistValue::String(v.clone()),
241 );
242 }
243
244 d.insert("arch".into(), PlistValue::String(self.short_arch_name()));
245 d.insert("arch_translated".into(), PlistValue::Bool(!self.is_native));
246 d.insert("arch_64".into(), PlistValue::Bool(self.is_64_bit));
247
248 if let Some(name) = self.parent_process_name()
249 && !name.is_empty()
250 {
251 d.insert("parent_name".into(), PlistValue::String(name.into()));
252 }
253 if self.ppid != 0 {
254 d.insert("parent_pid".into(), PlistValue::Int(self.ppid as i64));
255 }
256 if self.r_process_pid != -1 {
257 if let Some(name) = self.responsible_process_name()
258 && !name.is_empty()
259 {
260 d.insert(
261 "responsible_process_name".into(),
262 PlistValue::String(name.into()),
263 );
264 }
265 d.insert(
266 "responsible_process_pid".into(),
267 PlistValue::Int(self.r_process_pid as i64),
268 );
269 }
270
271 if let Some(ref date) = self.date {
272 d.insert("date".into(), PlistValue::String(date.clone()));
273 }
274
275 if let Some(v) = self.os_version_dictionary.get("ProductVersion")
277 && !v.is_empty()
278 {
279 d.insert("os_version".into(), PlistValue::String(v.clone()));
280 }
281 if let Some(v) = self.os_version_dictionary.get("BuildVersion")
282 && !v.is_empty()
283 {
284 d.insert("os_build".into(), PlistValue::String(v.clone()));
285 }
286 if let Some(v) = self.os_version_dictionary.get("ProductName")
287 && !v.is_empty()
288 {
289 d.insert("os_product".into(), PlistValue::String(v.clone()));
290 }
291
292 d.insert("report_version".into(), PlistValue::String("12".into()));
293
294 if self.awake_system_uptime != 0 {
295 d.insert(
296 "awake_system_uptime".into(),
297 PlistValue::Int(Self::reduce_to_two_sig_figures(self.awake_system_uptime) as i64),
298 );
299 }
300
301 let sig_name = self.signal_name();
302 if !sig_name.is_empty() {
303 d.insert("signal_name".into(), PlistValue::String(sig_name));
304 }
305 if self.crashed_thread_number >= 0 {
306 d.insert(
307 "crashing_thread_index".into(),
308 PlistValue::Int(self.crashed_thread_number as i64),
309 );
310 }
311
312 let exc_type = self.exception_type_description();
313 if !exc_type.is_empty() {
314 d.insert("exception_type".into(), PlistValue::String(exc_type));
315 }
316 let exc_codes = self.exception_codes_description();
317 if !exc_codes.is_empty() {
318 d.insert("exception_codes".into(), PlistValue::String(exc_codes));
319 }
320
321 if self.performing_autopsy {
322 d.insert(
323 "exception_notes".into(),
324 PlistValue::Array(vec![{
325 let mut m = BTreeMap::new();
326 m.insert(
327 "note".into(),
328 PlistValue::String("EXC_CORPSE_NOTIFY".into()),
329 );
330 m
331 }]),
332 );
333 }
334
335 if self.is_code_sign_killed() {
336 d.insert("cs_killed".into(), PlistValue::Bool(true));
337 if let Some(ref msgs) = self.code_sign_invalid_messages_description {
338 d.insert("kernel_messages".into(), PlistValue::String(msgs.clone()));
339 }
340 }
341
342 d.insert(
343 "system_integrity_protection".into(),
344 PlistValue::Bool(self.is_rootless_enabled()),
345 );
346
347 if let Some(ref info) = self.application_specific_info
348 && !info.is_empty()
349 {
350 d.insert(
351 "crash_info_message".into(),
352 PlistValue::String(info.clone()),
353 );
354 }
355 if let Some(ref sel) = self.objc_selector_name
356 && !sel.is_empty()
357 {
358 d.insert("objc_selector".into(), PlistValue::String(sel.clone()));
359 }
360 if !self.application_specific_signature_strings.is_empty() {
361 let arr = self
362 .application_specific_signature_strings
363 .iter()
364 .map(|s| {
365 let mut m = BTreeMap::new();
366 m.insert("signature".into(), PlistValue::String(s.clone()));
367 m
368 })
369 .collect();
370 d.insert("crash_info_signatures".into(), PlistValue::Array(arr));
371 }
372 if !self.application_specific_backtraces.is_empty() {
373 let arr = self
374 .application_specific_backtraces
375 .iter()
376 .map(|s| {
377 let mut m = BTreeMap::new();
378 m.insert("backtrace".into(), PlistValue::String(s.clone()));
379 m
380 })
381 .collect();
382 d.insert("crash_info_thread_strings".into(), PlistValue::Array(arr));
383 }
384
385 if let Some(ref err) = self.internal_error
386 && !err.is_empty()
387 {
388 d.insert("internal_error".into(), PlistValue::String(err.clone()));
389 }
390 if let Some(ref err) = self.dyld_error_string
391 && !err.is_empty()
392 {
393 d.insert("dyld_error".into(), PlistValue::String(err.clone()));
394 }
395 if let Some(ref info) = self.dyld_error_info {
396 d.insert("dyld_error_info".into(), PlistValue::String(info.clone()));
397 }
398
399 let ts = self.thread_state_description();
401 d.insert("crashing_thread_state".into(), PlistValue::String(ts));
402
403 if let Some(ref vm) = self.vm_map_string
405 && !vm.is_empty()
406 {
407 d.insert("vm_map".into(), PlistValue::String(vm.clone()));
408 }
409 if let Some(ref vs) = self.vm_summary_string
410 && !vs.is_empty()
411 {
412 d.insert("vm_summary".into(), PlistValue::String(vs.clone()));
413 }
414
415 if let Some(ref uuid) = self.sleep_wake_uuid
416 && !uuid.is_empty()
417 {
418 d.insert(
419 "sleep_wake_uuid_string".into(),
420 PlistValue::String(uuid.clone()),
421 );
422 }
423 if let Some(ref uuid) = self.anon_uuid {
424 d.insert("anon_uuid".into(), PlistValue::String(uuid.clone()));
425 }
426 if !self.ext_mod_info.dictionary.is_empty() {
427 let mut ext = BTreeMap::new();
428 for (k, v) in &self.ext_mod_info.dictionary {
429 ext.insert(k.clone(), PlistValue::String(v.clone()));
430 }
431 d.insert(
432 "external_modification_summary".into(),
433 PlistValue::Dict(ext),
434 );
435 }
436 if let Some(ref rosetta) = self.rosetta_info
437 && !rosetta.is_empty()
438 {
439 d.insert(
440 "rosetta_threads_string".into(),
441 PlistValue::String(rosetta.clone()),
442 );
443 }
444
445 d
446 }
447
448 pub fn pre_signature_dictionary(&self) -> BTreeMap<String, PlistValue> {
453 let mut d = BTreeMap::new();
454
455 if let Some(name) = self.process_name()
456 && !name.is_empty()
457 {
458 d.insert("app_name".into(), PlistValue::String(name.into()));
459 }
460 if let Some(id) = self.process_identifier()
461 && !id.is_empty()
462 {
463 d.insert("app_bundle_id".into(), PlistValue::String(id.into()));
464 }
465 let build_ver = self.app_build_version();
466 if !build_ver.is_empty() {
467 d.insert("app_build_version".into(), PlistValue::String(build_ver));
468 }
469 let ver = self.app_version();
470 if !ver.is_empty() {
471 d.insert("app_version".into(), PlistValue::String(ver));
472 }
473 d.insert("arch".into(), PlistValue::String(self.short_arch_name()));
474
475 if let Some(v) = self.os_version_dictionary.get("BuildVersion")
476 && !v.is_empty()
477 {
478 d.insert("os_build".into(), PlistValue::String(v.clone()));
479 }
480
481 d.insert("report_version".into(), PlistValue::String("12".into()));
482
483 let sig_name = self.signal_name();
484 if !sig_name.is_empty() {
485 d.insert("signal_name".into(), PlistValue::String(sig_name));
486 }
487 let exc_type = self.exception_type_description();
488 if !exc_type.is_empty() {
489 d.insert("exception_type".into(), PlistValue::String(exc_type));
490 }
491 if let Some(ref sel) = self.objc_selector_name
492 && !sel.is_empty()
493 {
494 d.insert("objc_selector".into(), PlistValue::String(sel.clone()));
495 }
496 if !self.application_specific_signature_strings.is_empty() {
497 let arr = self
498 .application_specific_signature_strings
499 .iter()
500 .map(|s| {
501 let mut m = BTreeMap::new();
502 m.insert("signature".into(), PlistValue::String(s.clone()));
503 m
504 })
505 .collect();
506 d.insert("crash_info_signatures".into(), PlistValue::Array(arr));
507 }
508 if let Some(ref err) = self.internal_error
509 && !err.is_empty()
510 {
511 d.insert("internal_error".into(), PlistValue::String(err.clone()));
512 }
513 if let Some(ref info) = self.dyld_error_info {
514 d.insert("dyld_error_info".into(), PlistValue::String(info.clone()));
515 }
516 if let Some(ref adam) = self.adam_id
517 && !adam.is_empty()
518 {
519 d.insert("mas_adam_id".into(), PlistValue::String(adam.clone()));
520 }
521 if let Some(ref ext_id) = self.software_version_external_identifier
522 && !ext_id.is_empty()
523 {
524 d.insert("mas_external_id".into(), PlistValue::String(ext_id.clone()));
525 }
526 if self.work_queue_limits.is_some() {
527 let mut wq = BTreeMap::new();
528 if let Some(ref limits) = self.work_queue_limits {
529 if let Some(min) = limits.min_threads {
530 wq.insert("min_threads".into(), PlistValue::Int(min as i64));
531 }
532 if let Some(max) = limits.max_threads {
533 wq.insert("max_threads".into(), PlistValue::Int(max as i64));
534 }
535 }
536 d.insert("wq_limits_reached".into(), PlistValue::Dict(wq));
537 }
538
539 if !self.fatal_dyld_error_on_launch
541 && self.crashed_thread_number >= 0
542 && (self.crashed_thread_number as usize) < self.backtraces.len()
543 {
544 let bt = &self.backtraces[self.crashed_thread_number as usize];
545 let filtered = self.filter_thread_for_presignature(bt);
546 d.insert("crashed_thread".into(), PlistValue::Dict(filtered));
547 }
548
549 d
550 }
551
552 pub fn context_dictionary(&self) -> BTreeMap<String, PlistValue> {
555 let mut d = BTreeMap::new();
556 if let Some(ref date) = self.date {
557 d.insert("date".into(), PlistValue::String(date.clone()));
558 }
559 if let Some(ref uuid) = self.sleep_wake_uuid
560 && !uuid.is_empty()
561 {
562 d.insert(
563 "sleep_wake_uuid_string".into(),
564 PlistValue::String(uuid.clone()),
565 );
566 }
567 d
568 }
569
570 pub fn description_dictionary(&self) -> BTreeMap<String, PlistValue> {
574 let mut d = BTreeMap::new();
575 d.insert("report".into(), PlistValue::Dict(self.problem_dictionary()));
576 d.insert(
577 "presignature".into(),
578 PlistValue::Dict(self.pre_signature_dictionary()),
579 );
580 d.insert(
581 "context".into(),
582 PlistValue::Dict(self.context_dictionary()),
583 );
584 d
585 }
586
587 fn filter_thread_for_presignature(&self, bt: &ThreadBacktrace) -> BTreeMap<String, PlistValue> {
592 let mut result = BTreeMap::new();
593 let mut frames = Vec::new();
594
595 for frame in &bt.frames {
596 let mut fd = BTreeMap::new();
597 if let Some(ref sym) = frame.symbol_name {
598 fd.insert("symbol".into(), PlistValue::String(sym.clone()));
599 }
600 if frame.symbol_offset != 0 {
601 fd.insert(
602 "symbol_offset".into(),
603 PlistValue::Int(frame.symbol_offset as i64),
604 );
605 }
606 if let Some(img) = self.binary_image_for_address(frame.address) {
607 let offset = frame.address - img.base_address;
608 fd.insert("binary_image_offset".into(), PlistValue::Int(offset as i64));
609 if let Some(ref id) = img.identifier {
610 fd.insert(
611 "binary_image_identifier".into(),
612 PlistValue::String(id.clone()),
613 );
614 }
615 }
616 frames.push(fd);
617 }
618
619 result.insert("backtrace".into(), PlistValue::Array(frames));
620 if let Some(ref name) = bt.thread_name {
621 result.insert("thread_name".into(), PlistValue::String(name.clone()));
622 }
623 result
624 }
625
626 pub fn filtered_binary_image_for_presignature(
630 &self,
631 image: &BinaryImage,
632 index: usize,
633 ) -> BTreeMap<String, PlistValue> {
634 let mut d = BTreeMap::new();
635 d.insert("index".into(), PlistValue::Int(index as i64));
636
637 if let Some(ref uuid) = image.uuid {
638 d.insert("uuid".into(), PlistValue::String(uuid.clone()));
639 } else {
640 if let Some(ref ver) = image.version {
641 d.insert("bundle_version".into(), PlistValue::String(ver.clone()));
642 }
643 if let Some(ref id) = image.identifier {
644 d.insert("bundle_id".into(), PlistValue::String(id.clone()));
645 }
646 d.insert("path".into(), PlistValue::String(image.path.clone()));
647 }
648 d
649 }
650
651 pub fn binary_images_plist(&self) -> Vec<BTreeMap<String, PlistValue>> {
654 self.binary_images
655 .iter()
656 .enumerate()
657 .map(|(i, img)| {
658 let mut d = BTreeMap::new();
659 d.insert("index".into(), PlistValue::Int(i as i64));
660 d.insert(
661 "StartAddress".into(),
662 PlistValue::Int(img.base_address as i64),
663 );
664 d.insert(
665 "Size".into(),
666 PlistValue::Int((img.end_address - img.base_address) as i64),
667 );
668 d.insert("path".into(), PlistValue::String(img.path.clone()));
669 d.insert("name".into(), PlistValue::String(img.name.clone()));
670 if let Some(ref uuid) = img.uuid {
671 d.insert("uuid".into(), PlistValue::String(uuid.clone()));
672 }
673 if let Some(ref id) = img.identifier {
674 d.insert("bundle_id".into(), PlistValue::String(id.clone()));
675 }
676 if let Some(ref ver) = img.version {
677 d.insert("bundle_version".into(), PlistValue::String(ver.clone()));
678 }
679 if let Some(ref arch) = img.arch {
680 d.insert("arch".into(), PlistValue::String(arch.clone()));
681 }
682 d
683 })
684 .collect()
685 }
686
687 pub fn rosetta_threads_plist(&self) -> Vec<BTreeMap<String, PlistValue>> {
692 let rosetta = match &self.rosetta_info {
693 Some(r) if !r.is_empty() => r,
694 _ => return Vec::new(),
695 };
696
697 let mut threads: Vec<BTreeMap<String, PlistValue>> = Vec::new();
698 let mut current_frames: Vec<BTreeMap<String, PlistValue>> = Vec::new();
699 let mut current_thread = BTreeMap::new();
700 current_thread.insert("backtrace".into(), PlistValue::Array(Vec::new()));
701
702 for line in rosetta.lines() {
703 if line.starts_with("Thread") {
704 if (!current_frames.is_empty() || threads.is_empty()) && !current_frames.is_empty()
706 {
707 current_thread.insert("backtrace".into(), PlistValue::Array(current_frames));
708 threads.push(current_thread);
709 }
710 current_frames = Vec::new();
711 current_thread = BTreeMap::new();
712 if line.contains("Crashed") {
713 current_thread.insert("crashed".into(), PlistValue::Bool(true));
714 }
715 } else {
716 let trimmed = line.trim();
718 if trimmed.is_empty() {
719 continue;
720 }
721 if let Some(hex_str) = trimmed.strip_prefix("0x") {
723 let addr_end = hex_str
724 .find(|c: char| !c.is_ascii_hexdigit())
725 .unwrap_or(hex_str.len());
726 if let Ok(addr) = u64::from_str_radix(&hex_str[..addr_end], 16) {
727 let mut frame = BTreeMap::new();
728 frame.insert("address".into(), PlistValue::Int(addr as i64));
729
730 let rest = hex_str[addr_end..].trim_start();
732 if let Some(space_pos) = rest.find(' ') {
733 let image_path = &rest[..space_pos];
734 let after_image = rest[space_pos..].trim_start();
735
736 if let Some(last_component) = image_path.rsplit('/').next() {
737 frame.insert(
738 "binary_image_identifier".into(),
739 PlistValue::String(last_component.into()),
740 );
741 }
742
743 if let Some(plus_pos) = after_image.rfind(" + ") {
745 let symbol = after_image[..plus_pos].trim();
746 let offset_str = after_image[plus_pos + 3..].trim();
747 if !symbol.is_empty() {
748 frame
749 .insert("symbol".into(), PlistValue::String(symbol.into()));
750 if let Ok(off) = offset_str.parse::<u64>() {
751 frame.insert(
752 "symbol_offset".into(),
753 PlistValue::Int(off as i64),
754 );
755 }
756 } else if let Ok(off) = offset_str.parse::<u64>() {
757 frame.insert(
758 "binary_image_offset".into(),
759 PlistValue::Int(off as i64),
760 );
761 }
762 }
763 }
764 current_frames.push(frame);
765 }
766 }
767 }
768 }
769
770 if !current_frames.is_empty() {
772 current_thread.insert("backtrace".into(), PlistValue::Array(current_frames));
773 threads.push(current_thread);
774 }
775
776 if threads.is_empty() {
777 return Vec::new();
778 }
779 threads
780 }
781}
782
783#[cfg(test)]
784mod tests {
785 use crate::test_helpers::*;
786 use crate::types::*;
787
788 mod crash_analysis {
792 use super::*;
793
794 #[test]
795 fn crashed_due_to_bad_memory_access_not_type1() {
796 let mut cr = make_test_cr();
797 cr.exception_type = 3; assert!(!cr.crashed_due_to_bad_memory_access());
799 }
800
801 #[test]
802 fn crashed_due_to_bad_memory_access_type1() {
803 let mut cr = make_test_cr();
804 cr.exception_type = 1;
805 cr.exception_code = vec![2, 0x42];
806 assert!(cr.crashed_due_to_bad_memory_access());
807 }
808
809 #[test]
810 fn crashed_due_to_bad_memory_access_gpf_null_false() {
811 let mut cr = make_test_cr();
812 cr.exception_type = 1;
813 cr.exception_code = vec![0xd, 0]; assert!(!cr.crashed_due_to_bad_memory_access());
815 }
816
817 #[test]
818 fn extract_crashing_address_bad_memory_access() {
819 let mut cr = make_test_cr();
820 cr.exception_type = 1;
821 cr.exception_code = vec![2, 0xDEAD];
822 cr.extract_crashing_address();
823 assert_eq!(cr.crashing_address, 0xDEAD);
824 }
825
826 #[test]
827 fn extract_crashing_address_code_sign_killed_32bit() {
828 let mut cr = make_test_cr();
829 cr.exception_type = 5; cr.cs_status = 0x100_0000;
831 cr.exception_state = ExceptionState {
832 state: vec![1, 0, 0, 0, 0xCAFE, 0], count: 6,
834 };
835 cr.extract_crashing_address();
836 assert_eq!(cr.crashing_address, 0xCAFE);
837 }
838
839 #[test]
840 fn extract_crashing_address_code_sign_killed_64bit() {
841 let mut cr = make_test_cr();
842 cr.exception_type = 5;
843 cr.cs_status = 0x100_0000;
844 cr.exception_state = ExceptionState {
845 state: vec![4, 0, 0, 0, 0xBEEF, 0x0001], count: 6,
847 };
848 cr.extract_crashing_address();
849 assert_eq!(cr.crashing_address, (0x0001u64 << 32) | 0xBEEF);
850 }
851
852 #[test]
853 fn crashed_due_to_bad_memory_access_gpf_null_arm64_is_true() {
854 let mut cr = make_test_cr_arm64();
856 cr.exception_type = 1;
857 cr.exception_code = vec![0xd, 0];
858 assert!(cr.crashed_due_to_bad_memory_access());
859 }
860
861 #[test]
862 fn extract_crashing_address_code_sign_killed_arm64() {
863 let mut cr = make_test_cr_arm64();
864 cr.exception_type = 5;
865 cr.cs_status = 0x100_0000;
866 cr.exception_state = ExceptionState {
868 state: vec![0xDEAD_0000, 0x0000_FFFF, 0, 0],
869 count: 4,
870 };
871 cr.extract_crashing_address();
872 assert_eq!(cr.crashing_address, 0x0000_FFFF_DEAD_0000);
873 }
874
875 #[test]
876 fn cleanse_paths_sanitizes_executable_path() {
877 let mut cr = make_test_cr();
878 cr.executable_path = Some("/Users/kurtis/app/bin".into());
879 cr.cleanse_paths();
880 assert_eq!(cr.executable_path.as_deref(), Some("/Users/USER/app/bin"));
881 }
882
883 #[test]
884 fn cleanse_paths_preserves_reopen_path() {
885 let mut cr = make_test_cr();
886 cr.executable_path = Some("/Users/kurtis/app/bin".into());
887 cr.reopen_path = None;
888 cr.cleanse_paths();
889 assert_eq!(cr.reopen_path.as_deref(), Some("/Users/kurtis/app/bin"));
891 }
892
893 #[test]
894 fn cleanse_paths_sanitizes_binary_image_paths() {
895 let mut cr = make_test_cr();
896 cr.binary_images.push(BinaryImage {
897 name: "libfoo.dylib".into(),
898 path: "/Users/kurtis/lib/libfoo.dylib".into(),
899 uuid: None,
900 base_address: 0x1000,
901 end_address: 0x2000,
902 arch: None,
903 identifier: None,
904 version: None,
905 });
906 cr.cleanse_paths();
907 assert_eq!(cr.binary_images[0].path, "/Users/USER/lib/libfoo.dylib");
908 }
909
910 #[test]
911 fn cleanse_paths_sanitizes_vm_map_lines() {
912 let mut cr = make_test_cr();
913 cr.vm_map_string =
914 Some("region 0x1000 /Users/kurtis/lib/libfoo.dylib\nother line".into());
915 cr.cleanse_paths();
916 let vm = cr.vm_map_string.as_ref().unwrap();
917 assert!(vm.contains("/Users/USER/lib/libfoo.dylib"));
918 assert_eq!(vm.lines().nth(1).unwrap(), "other line");
919 }
920
921 #[test]
922 fn set_app_store_receipt_with_adam_id() {
923 let mut cr = make_test_cr();
924 cr.set_app_store_receipt(Some("12345".into()), Some("67890".into()));
925 assert!(cr.has_receipt);
926 assert_eq!(cr.adam_id, Some("12345".into()));
927 assert_eq!(
928 cr.software_version_external_identifier,
929 Some("67890".into())
930 );
931 }
932
933 #[test]
934 fn set_app_store_receipt_without_adam_id() {
935 let mut cr = make_test_cr();
936 cr.set_app_store_receipt(None, Some("67890".into()));
937 assert!(!cr.has_receipt);
938 assert!(cr.adam_id.is_none());
939 }
940 }
941
942 mod dictionary_methods {
946 use super::*;
947
948 #[test]
949 fn problem_dictionary_has_expected_keys() {
950 let cr = make_test_cr();
951 let d = cr.problem_dictionary();
952 assert!(d.contains_key("app_name"));
953 assert!(d.contains_key("app_pid"));
954 assert!(d.contains_key("app_path"));
955 assert!(d.contains_key("arch"));
956 assert!(d.contains_key("arch_translated"));
957 assert!(d.contains_key("arch_64"));
958 assert!(d.contains_key("report_version"));
959 assert!(d.contains_key("system_integrity_protection"));
960 assert!(d.contains_key("crashing_thread_state"));
961 }
962
963 #[test]
964 fn problem_dictionary_values() {
965 let cr = make_test_cr();
966 let d = cr.problem_dictionary();
967 match d.get("app_name") {
968 Some(PlistValue::String(s)) => assert_eq!(s, "TestApp"),
969 _ => panic!("unexpected app_name type"),
970 }
971 match d.get("app_pid") {
972 Some(PlistValue::Int(n)) => assert_eq!(*n, 1234),
973 _ => panic!("unexpected app_pid type"),
974 }
975 }
976
977 #[test]
978 fn pre_signature_dictionary_has_expected_keys() {
979 let cr = make_test_cr();
980 let d = cr.pre_signature_dictionary();
981 assert!(d.contains_key("app_name"));
982 assert!(d.contains_key("arch"));
983 assert!(d.contains_key("report_version"));
984 assert!(d.contains_key("exception_type"));
985 assert!(d.contains_key("signal_name"));
986 }
987
988 #[test]
989 fn pre_signature_dictionary_filtered() {
990 let cr = make_test_cr();
991 let d = cr.pre_signature_dictionary();
992 assert!(!d.contains_key("app_pid"));
994 assert!(!d.contains_key("crashing_thread_state"));
995 assert!(!d.contains_key("vm_map"));
996 }
997
998 #[test]
999 fn context_dictionary_has_date_and_uuid() {
1000 let mut cr = make_test_cr();
1001 cr.date = Some("2024-01-01".into());
1002 cr.sleep_wake_uuid = Some("UUID-123".into());
1003 let d = cr.context_dictionary();
1004 assert!(d.contains_key("date"));
1005 assert!(d.contains_key("sleep_wake_uuid_string"));
1006 assert_eq!(d.len(), 2);
1007 }
1008
1009 #[test]
1010 fn context_dictionary_date_only() {
1011 let mut cr = make_test_cr();
1012 cr.date = Some("2024-01-01".into());
1013 let d = cr.context_dictionary();
1014 assert!(d.contains_key("date"));
1015 assert!(!d.contains_key("sleep_wake_uuid_string"));
1016 }
1017
1018 #[test]
1019 fn description_dictionary_has_three_keys() {
1020 let cr = make_test_cr();
1021 let d = cr.description_dictionary();
1022 assert!(d.contains_key("report"));
1023 assert!(d.contains_key("presignature"));
1024 assert!(d.contains_key("context"));
1025 }
1026
1027 #[test]
1028 fn binary_images_plist_empty() {
1029 let cr = make_test_cr();
1030 assert!(cr.binary_images_plist().is_empty());
1031 }
1032
1033 #[test]
1034 fn binary_images_plist_populated() {
1035 let mut cr = make_test_cr();
1036 cr.binary_images.push(BinaryImage {
1037 name: "libfoo.dylib".into(),
1038 path: "/usr/lib/libfoo.dylib".into(),
1039 uuid: Some("UUID".into()),
1040 base_address: 0x1000,
1041 end_address: 0x2000,
1042 arch: Some("x86_64".into()),
1043 identifier: Some("libfoo.dylib".into()),
1044 version: Some("1.0".into()),
1045 });
1046 let plist = cr.binary_images_plist();
1047 assert_eq!(plist.len(), 1);
1048 assert!(plist[0].contains_key("StartAddress"));
1049 assert!(plist[0].contains_key("Size"));
1050 assert!(plist[0].contains_key("uuid"));
1051 assert!(plist[0].contains_key("bundle_id"));
1052 }
1053
1054 #[test]
1055 fn filtered_binary_image_for_presignature_with_uuid() {
1056 let cr = make_test_cr();
1057 let img = BinaryImage {
1058 name: "libfoo.dylib".into(),
1059 path: "/usr/lib/libfoo.dylib".into(),
1060 uuid: Some("UUID-123".into()),
1061 base_address: 0x1000,
1062 end_address: 0x2000,
1063 arch: None,
1064 identifier: Some("libfoo.dylib".into()),
1065 version: Some("1.0".into()),
1066 };
1067 let d = cr.filtered_binary_image_for_presignature(&img, 0);
1068 assert!(d.contains_key("uuid"));
1069 assert!(!d.contains_key("path"));
1070 }
1071
1072 #[test]
1073 fn filtered_binary_image_for_presignature_without_uuid() {
1074 let cr = make_test_cr();
1075 let img = BinaryImage {
1076 name: "libfoo.dylib".into(),
1077 path: "/usr/lib/libfoo.dylib".into(),
1078 uuid: None,
1079 base_address: 0x1000,
1080 end_address: 0x2000,
1081 arch: None,
1082 identifier: Some("libfoo.dylib".into()),
1083 version: Some("1.0".into()),
1084 };
1085 let d = cr.filtered_binary_image_for_presignature(&img, 0);
1086 assert!(!d.contains_key("uuid"));
1087 assert!(d.contains_key("path"));
1088 assert!(d.contains_key("bundle_id"));
1089 assert!(d.contains_key("bundle_version"));
1090 }
1091
1092 #[test]
1093 fn rosetta_threads_plist_empty() {
1094 let cr = make_test_cr();
1095 assert!(cr.rosetta_threads_plist().is_empty());
1096 }
1097
1098 #[test]
1099 fn rosetta_threads_plist_single_thread() {
1100 let mut cr = make_test_cr();
1101 cr.rosetta_info = Some("Thread 0:\n0x1000 /usr/lib/libfoo.dylib main + 42\n".into());
1102 let threads = cr.rosetta_threads_plist();
1103 assert_eq!(threads.len(), 1);
1104 }
1105
1106 #[test]
1107 fn rosetta_threads_plist_crashed_marker() {
1108 let mut cr = make_test_cr();
1109 cr.rosetta_info =
1110 Some("Thread 0 Crashed:\n0x1000 /usr/lib/libfoo.dylib main + 42\n".into());
1111 let threads = cr.rosetta_threads_plist();
1112 assert_eq!(threads.len(), 1);
1113 assert!(threads[0].contains_key("crashed"));
1114 }
1115
1116 #[test]
1117 fn rosetta_threads_plist_frame_with_symbol_offset() {
1118 let mut cr = make_test_cr();
1119 cr.rosetta_info = Some("Thread 0:\n0x1000 /usr/lib/libfoo.dylib main + 42\n".into());
1120 let threads = cr.rosetta_threads_plist();
1121 let bt = threads[0].get("backtrace").unwrap();
1122 if let PlistValue::Array(frames) = bt {
1123 assert_eq!(frames.len(), 1);
1124 assert!(frames[0].contains_key("symbol"));
1125 match frames[0].get("symbol") {
1126 Some(PlistValue::String(s)) => assert_eq!(s, "main"),
1127 _ => panic!("expected symbol string"),
1128 }
1129 match frames[0].get("symbol_offset") {
1130 Some(PlistValue::Int(n)) => assert_eq!(*n, 42),
1131 _ => panic!("expected symbol_offset int"),
1132 }
1133 } else {
1134 panic!("expected backtrace array");
1135 }
1136 }
1137 }
1138}