1use crate::crash_rustler::CrashRustler;
2
3impl CrashRustler {
4 pub fn signal_name(&self) -> String {
25 match self.signal {
26 0 => String::new(),
27 1 => "SIGHUP".into(),
28 2 => "SIGINT".into(),
29 3 => "SIGQUIT".into(),
30 4 => "SIGILL".into(),
31 5 => "SIGTRAP".into(),
32 6 => "SIGABRT".into(),
33 7 => "SIGEMT".into(),
34 8 => "SIGFPE".into(),
35 9 => "SIGKILL".into(),
36 10 => "SIGBUS".into(),
37 11 => "SIGSEGV".into(),
38 12 => "SIGSYS".into(),
39 13 => "SIGPIPE".into(),
40 14 => "SIGALRM".into(),
41 15 => "SIGTERM".into(),
42 16 => "SIGURG".into(),
43 17 => "SIGSTOP".into(),
44 18 => "SIGTSTP".into(),
45 19 => "SIGCONT".into(),
46 20 => "SIGCHLD".into(),
47 21 => "SIGTTIN".into(),
48 22 => "SIGTTOU".into(),
49 23 => "SIGIO".into(),
50 24 => "SIGXCPU".into(),
51 25 => "SIGXFSZ".into(),
52 26 => "SIGVTALRM".into(),
53 27 => "SIGPROF".into(),
54 28 => "SIGWINCH".into(),
55 29 => "SIGINFO".into(),
56 30 => "SIGUSR1".into(),
57 31 => "SIGUSR2".into(),
58 n => format!("Signal {n}"),
59 }
60 }
61
62 pub fn exception_type_description(&self) -> String {
80 match self.exception_type {
81 1 => "EXC_BAD_ACCESS".into(),
82 2 => "EXC_BAD_INSTRUCTION".into(),
83 3 => "EXC_ARITHMETIC".into(),
84 4 => "EXC_EMULATION".into(),
85 5 => "EXC_SOFTWARE".into(),
86 6 => "EXC_BREAKPOINT".into(),
87 7 => "EXC_SYSCALL".into(),
88 8 => "EXC_MACH_SYSCALL".into(),
89 9 => "EXC_RPC_ALERT".into(),
90 10 => "EXC_CRASH".into(),
91 11 => "EXC_RESOURCE".into(),
92 12 => "EXC_GUARD".into(),
93 13 => "EXC_CORPSE_NOTIFY".into(),
94 n => format!("{n:08X}"),
95 }
96 }
97
98 pub fn exception_codes_description(&self) -> String {
125 if self.exception_code.is_empty() {
126 return String::new();
127 }
128
129 let code0 = self.exception_code[0];
130
131 if self.exception_type == 1 {
133 if code0 == 2 && self.exception_code.len() > 1 {
134 return format!(
135 "KERN_PROTECTION_FAILURE at 0x{:016x}",
136 self.exception_code[1] as u64
137 );
138 }
139 if code0 == 1 && self.exception_code.len() > 1 {
140 return format!(
141 "KERN_INVALID_ADDRESS at 0x{:016x}",
142 self.exception_code[1] as u64
143 );
144 }
145 if code0 == 0xd && self.is_x86_cpu() {
147 return "EXC_I386_GPFLT".into();
148 }
149 }
150
151 if self.exception_type == 3 {
153 if self.is_x86_cpu() && code0 == 1 {
154 return "EXC_I386_DIV (divide by zero)".into();
155 }
156 if self.is_arm_cpu() {
157 return match code0 {
158 1 => "EXC_ARM_FP_IO (invalid operation)".into(),
159 2 => "EXC_ARM_FP_DZ (divide by zero)".into(),
160 3 => "EXC_ARM_FP_OF (overflow)".into(),
161 4 => "EXC_ARM_FP_UF (underflow)".into(),
162 5 => "EXC_ARM_FP_IX (inexact)".into(),
163 6 => "EXC_ARM_FP_ID (input denormal)".into(),
164 _ => format!("0x{:016x}", code0 as u64),
165 };
166 }
167 }
168
169 self.exception_code
171 .iter()
172 .map(|c| format!("0x{:016x}", *c as u64))
173 .collect::<Vec<_>>()
174 .join(", ")
175 }
176
177 pub fn cpu_type_description(&self) -> String {
196 match self.cpu_type.0 {
197 7 => "X86".into(),
198 18 => "PPC".into(),
199 12 => "ARM".into(),
200 0x100_0007 => "X86-64".into(),
201 0x100_0012 => "PPC-64".into(),
202 0x100_000c => "ARM-64".into(),
203 n => format!("{n:08X}"),
204 }
205 }
206
207 pub fn short_arch_name(&self) -> String {
211 match self.cpu_type.0 {
212 7 => "i386".into(),
213 18 => "ppc".into(),
214 12 => "arm".into(),
215 0x100_0007 => "x86_64".into(),
216 0x100_0012 => "ppc64".into(),
217 0x100_000c => "arm64".into(),
218 _ => self.cpu_type_description(),
219 }
220 }
221
222 pub fn spacify_string(s: Option<&str>) -> String {
239 match s {
240 None => String::new(),
241 Some(s) => s.split_whitespace().collect::<Vec<_>>().join(" "),
242 }
243 }
244
245 pub fn string_by_padding_newlines(s: &str) -> String {
260 let result = s.replace('\n', "\n ");
261 if let Some(stripped) = result.strip_prefix('\n') {
262 stripped.to_string()
263 } else {
264 result
265 }
266 }
267
268 pub fn string_by_trimming_column_sensitive_whitespace(s: &str) -> String {
273 let trimmed = s.trim();
274 if trimmed.is_empty() {
275 format!("[column {}]", s.len())
276 } else {
277 trimmed.to_string()
278 }
279 }
280
281 pub fn path_is_apple(path: &str) -> bool {
285 path.starts_with("/System")
286 || path.starts_with("/usr/lib")
287 || path.starts_with("/usr/bin")
288 || path.starts_with("/usr/sbin")
289 || path.starts_with("/bin")
290 || path.starts_with("/sbin")
291 }
292
293 pub fn bundle_identifier_is_apple(bundle_id: &str) -> bool {
297 bundle_id.starts_with("com.apple.")
298 || bundle_id.starts_with("commpage")
299 || bundle_id == "Ozone"
300 || bundle_id == "Motion"
301 }
302
303 pub fn is_apple_application(&self) -> bool {
307 if let Some(path) = self.executable_path()
308 && Self::path_is_apple(path)
309 {
310 return true;
311 }
312 if let Some(bundle_id) = self.bundle_identifier() {
313 return Self::bundle_identifier_is_apple(bundle_id);
314 }
315 false
316 }
317
318 pub fn record_internal_error(&mut self, error: &str) {
322 match &mut self.internal_error {
323 Some(existing) => {
324 existing.push('\n');
325 existing.push_str(error);
326 }
327 None => {
328 self.internal_error = Some(error.to_string());
329 }
330 }
331 }
332
333 pub fn reduce_to_two_sig_figures(value: u64) -> u64 {
337 if value == 0 {
338 return 0;
339 }
340 let digits = (value as f64).log10() as u32 + 1;
341 if digits <= 2 {
342 return value;
343 }
344 let divisor = 10u64.pow(digits - 2);
345 (value / divisor) * divisor
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use crate::crash_rustler::CrashRustler;
352 use crate::test_helpers::*;
353 use crate::types::*;
354
355 mod descriptions {
359 use super::*;
360
361 #[test]
362 fn signal_name_zero() {
363 let mut cr = make_test_cr();
364 cr.signal = 0;
365 assert_eq!(cr.signal_name(), "");
366 }
367
368 #[test]
369 fn signal_name_all_named() {
370 let expected = [
371 (1, "SIGHUP"),
372 (2, "SIGINT"),
373 (3, "SIGQUIT"),
374 (4, "SIGILL"),
375 (5, "SIGTRAP"),
376 (6, "SIGABRT"),
377 (7, "SIGEMT"),
378 (8, "SIGFPE"),
379 (9, "SIGKILL"),
380 (10, "SIGBUS"),
381 (11, "SIGSEGV"),
382 (12, "SIGSYS"),
383 (13, "SIGPIPE"),
384 (14, "SIGALRM"),
385 (15, "SIGTERM"),
386 (16, "SIGURG"),
387 (17, "SIGSTOP"),
388 (18, "SIGTSTP"),
389 (19, "SIGCONT"),
390 (20, "SIGCHLD"),
391 (21, "SIGTTIN"),
392 (22, "SIGTTOU"),
393 (23, "SIGIO"),
394 (24, "SIGXCPU"),
395 (25, "SIGXFSZ"),
396 (26, "SIGVTALRM"),
397 (27, "SIGPROF"),
398 (28, "SIGWINCH"),
399 (29, "SIGINFO"),
400 (30, "SIGUSR1"),
401 (31, "SIGUSR2"),
402 ];
403 let mut cr = make_test_cr();
404 for (sig, name) in expected {
405 cr.signal = sig;
406 assert_eq!(cr.signal_name(), name, "signal={sig}");
407 }
408 }
409
410 #[test]
411 fn signal_name_unknown() {
412 let mut cr = make_test_cr();
413 cr.signal = 32;
414 assert_eq!(cr.signal_name(), "Signal 32");
415 cr.signal = 100;
416 assert_eq!(cr.signal_name(), "Signal 100");
417 }
418
419 #[test]
420 fn exception_type_description_all_known() {
421 let cases = [
422 (1, "EXC_BAD_ACCESS"),
423 (2, "EXC_BAD_INSTRUCTION"),
424 (3, "EXC_ARITHMETIC"),
425 (4, "EXC_EMULATION"),
426 (5, "EXC_SOFTWARE"),
427 (6, "EXC_BREAKPOINT"),
428 (7, "EXC_SYSCALL"),
429 (8, "EXC_MACH_SYSCALL"),
430 (9, "EXC_RPC_ALERT"),
431 (10, "EXC_CRASH"),
432 (11, "EXC_RESOURCE"),
433 (12, "EXC_GUARD"),
434 (13, "EXC_CORPSE_NOTIFY"),
435 ];
436 let mut cr = make_test_cr();
437 for (et, desc) in cases {
438 cr.exception_type = et;
439 assert_eq!(cr.exception_type_description(), desc, "type={et}");
440 }
441 }
442
443 #[test]
444 fn exception_type_description_unknown() {
445 let mut cr = make_test_cr();
446 cr.exception_type = 99;
447 assert_eq!(cr.exception_type_description(), "00000063");
448 }
449
450 #[test]
451 fn exception_codes_description_empty() {
452 let mut cr = make_test_cr();
453 cr.exception_code = vec![];
454 assert_eq!(cr.exception_codes_description(), "");
455 }
456
457 #[test]
458 fn exception_codes_bad_access_protection_failure() {
459 let mut cr = make_test_cr();
460 cr.exception_type = 1;
461 cr.exception_code = vec![2, 0x7fff_0000_1234];
462 assert_eq!(
463 cr.exception_codes_description(),
464 "KERN_PROTECTION_FAILURE at 0x00007fff00001234"
465 );
466 }
467
468 #[test]
469 fn exception_codes_bad_access_invalid_address() {
470 let mut cr = make_test_cr();
471 cr.exception_type = 1;
472 cr.exception_code = vec![1, 0x42];
473 assert_eq!(
474 cr.exception_codes_description(),
475 "KERN_INVALID_ADDRESS at 0x0000000000000042"
476 );
477 }
478
479 #[test]
480 fn exception_codes_bad_access_gpf() {
481 let mut cr = make_test_cr();
482 cr.exception_type = 1;
483 cr.exception_code = vec![0xd];
484 assert_eq!(cr.exception_codes_description(), "EXC_I386_GPFLT");
485 }
486
487 #[test]
488 fn exception_codes_arithmetic_div_zero() {
489 let mut cr = make_test_cr();
490 cr.exception_type = 3;
491 cr.exception_code = vec![1];
492 assert_eq!(
493 cr.exception_codes_description(),
494 "EXC_I386_DIV (divide by zero)"
495 );
496 }
497
498 #[test]
499 fn exception_codes_generic_hex() {
500 let mut cr = make_test_cr();
501 cr.exception_type = 5; cr.exception_code = vec![0xdead, 0xbeef];
503 assert_eq!(
504 cr.exception_codes_description(),
505 "0x000000000000dead, 0x000000000000beef"
506 );
507 }
508
509 #[test]
510 fn cpu_type_description_known() {
511 let mut cr = make_test_cr();
512 let cases = [
513 (CpuType::X86, "X86"),
514 (CpuType::POWERPC, "PPC"),
515 (CpuType::X86_64, "X86-64"),
516 (CpuType::POWERPC64, "PPC-64"),
517 ];
518 for (ct, desc) in cases {
519 cr.cpu_type = ct;
520 assert_eq!(cr.cpu_type_description(), desc);
521 }
522 }
523
524 #[test]
525 fn cpu_type_description_unknown() {
526 let mut cr = make_test_cr();
527 cr.cpu_type = CpuType(999);
528 assert_eq!(cr.cpu_type_description(), "000003E7");
529 }
530
531 #[test]
532 fn short_arch_name_known() {
533 let mut cr = make_test_cr();
534 let cases = [
535 (CpuType::X86, "i386"),
536 (CpuType::POWERPC, "ppc"),
537 (CpuType::X86_64, "x86_64"),
538 (CpuType::POWERPC64, "ppc64"),
539 ];
540 for (ct, name) in cases {
541 cr.cpu_type = ct;
542 assert_eq!(cr.short_arch_name(), name);
543 }
544 }
545
546 #[test]
547 fn short_arch_name_unknown_falls_back() {
548 let mut cr = make_test_cr();
549 cr.cpu_type = CpuType(999);
550 assert_eq!(cr.short_arch_name(), "000003E7");
552 }
553
554 #[test]
555 fn cpu_type_description_arm() {
556 let mut cr = make_test_cr();
557 cr.cpu_type = CpuType::ARM;
558 assert_eq!(cr.cpu_type_description(), "ARM");
559 cr.cpu_type = CpuType::ARM64;
560 assert_eq!(cr.cpu_type_description(), "ARM-64");
561 }
562
563 #[test]
564 fn short_arch_name_arm() {
565 let mut cr = make_test_cr();
566 cr.cpu_type = CpuType::ARM;
567 assert_eq!(cr.short_arch_name(), "arm");
568 cr.cpu_type = CpuType::ARM64;
569 assert_eq!(cr.short_arch_name(), "arm64");
570 }
571
572 #[test]
573 fn exception_codes_bad_access_gpf_not_on_arm64() {
574 let mut cr = make_test_cr_arm64();
575 cr.exception_type = 1;
576 cr.exception_code = vec![0xd];
577 assert_eq!(cr.exception_codes_description(), "0x000000000000000d");
579 }
580
581 #[test]
582 fn exception_codes_arithmetic_arm64_fp_dz() {
583 let mut cr = make_test_cr_arm64();
584 cr.exception_type = 3;
585 cr.exception_code = vec![2];
586 assert_eq!(
587 cr.exception_codes_description(),
588 "EXC_ARM_FP_DZ (divide by zero)"
589 );
590 }
591
592 #[test]
593 fn exception_codes_arithmetic_arm64_fp_io() {
594 let mut cr = make_test_cr_arm64();
595 cr.exception_type = 3;
596 cr.exception_code = vec![1];
597 assert_eq!(
598 cr.exception_codes_description(),
599 "EXC_ARM_FP_IO (invalid operation)"
600 );
601 }
602
603 #[test]
604 fn exception_codes_arithmetic_arm64_unknown() {
605 let mut cr = make_test_cr_arm64();
606 cr.exception_type = 3;
607 cr.exception_code = vec![99];
608 assert_eq!(cr.exception_codes_description(), "0x0000000000000063");
609 }
610 }
611
612 mod string_utils {
616 use super::*;
617
618 #[test]
619 fn spacify_string_none() {
620 assert_eq!(CrashRustler::spacify_string(None), "");
621 }
622
623 #[test]
624 fn spacify_string_empty() {
625 assert_eq!(CrashRustler::spacify_string(Some("")), "");
626 }
627
628 #[test]
629 fn spacify_string_normal() {
630 assert_eq!(
631 CrashRustler::spacify_string(Some("hello world")),
632 "hello world"
633 );
634 }
635
636 #[test]
637 fn spacify_string_multi_whitespace() {
638 assert_eq!(
639 CrashRustler::spacify_string(Some("hello world foo")),
640 "hello world foo"
641 );
642 }
643
644 #[test]
645 fn spacify_string_tabs_newlines() {
646 assert_eq!(
647 CrashRustler::spacify_string(Some("hello\t\nworld")),
648 "hello world"
649 );
650 }
651
652 #[test]
653 fn string_by_padding_newlines_no_newlines() {
654 assert_eq!(CrashRustler::string_by_padding_newlines("hello"), "hello");
655 }
656
657 #[test]
658 fn string_by_padding_newlines_with_newlines() {
659 assert_eq!(
660 CrashRustler::string_by_padding_newlines("line1\nline2\nline3"),
661 "line1\n line2\n line3"
662 );
663 }
664
665 #[test]
666 fn string_by_padding_newlines_leading_newline() {
667 assert_eq!(
668 CrashRustler::string_by_padding_newlines("\nline1"),
669 " line1"
670 );
671 }
672
673 #[test]
674 fn trimming_column_sensitive_normal() {
675 assert_eq!(
676 CrashRustler::string_by_trimming_column_sensitive_whitespace(" hello "),
677 "hello"
678 );
679 }
680
681 #[test]
682 fn trimming_column_sensitive_all_whitespace() {
683 assert_eq!(
684 CrashRustler::string_by_trimming_column_sensitive_whitespace(" "),
685 "[column 5]"
686 );
687 }
688
689 #[test]
690 fn trimming_column_sensitive_empty() {
691 assert_eq!(
692 CrashRustler::string_by_trimming_column_sensitive_whitespace(""),
693 "[column 0]"
694 );
695 }
696
697 #[test]
698 fn path_is_apple_system() {
699 assert!(CrashRustler::path_is_apple(
700 "/System/Library/Frameworks/AppKit.framework"
701 ));
702 }
703
704 #[test]
705 fn path_is_apple_usr_lib() {
706 assert!(CrashRustler::path_is_apple("/usr/lib/libSystem.B.dylib"));
707 }
708
709 #[test]
710 fn path_is_apple_usr_bin() {
711 assert!(CrashRustler::path_is_apple("/usr/bin/file"));
712 }
713
714 #[test]
715 fn path_is_apple_usr_sbin() {
716 assert!(CrashRustler::path_is_apple("/usr/sbin/notifyd"));
717 }
718
719 #[test]
720 fn path_is_apple_bin() {
721 assert!(CrashRustler::path_is_apple("/bin/sh"));
722 }
723
724 #[test]
725 fn path_is_apple_sbin() {
726 assert!(CrashRustler::path_is_apple("/sbin/launchd"));
727 }
728
729 #[test]
730 fn path_is_apple_applications_not_apple() {
731 assert!(!CrashRustler::path_is_apple("/Applications/Foo.app"));
732 }
733
734 #[test]
735 fn bundle_identifier_is_apple_com_apple() {
736 assert!(CrashRustler::bundle_identifier_is_apple("com.apple.Safari"));
737 }
738
739 #[test]
740 fn bundle_identifier_is_apple_commpage() {
741 assert!(CrashRustler::bundle_identifier_is_apple("commpage"));
742 assert!(CrashRustler::bundle_identifier_is_apple("commpage64"));
743 }
744
745 #[test]
746 fn bundle_identifier_is_apple_ozone() {
747 assert!(CrashRustler::bundle_identifier_is_apple("Ozone"));
748 }
749
750 #[test]
751 fn bundle_identifier_is_apple_motion() {
752 assert!(CrashRustler::bundle_identifier_is_apple("Motion"));
753 }
754
755 #[test]
756 fn bundle_identifier_is_apple_not_apple() {
757 assert!(!CrashRustler::bundle_identifier_is_apple(
758 "com.google.Chrome"
759 ));
760 assert!(!CrashRustler::bundle_identifier_is_apple(
761 "org.mozilla.firefox"
762 ));
763 }
764
765 #[test]
766 fn reduce_to_two_sig_figures_zero() {
767 assert_eq!(CrashRustler::reduce_to_two_sig_figures(0), 0);
768 }
769
770 #[test]
771 fn reduce_to_two_sig_figures_small() {
772 assert_eq!(CrashRustler::reduce_to_two_sig_figures(1), 1);
773 assert_eq!(CrashRustler::reduce_to_two_sig_figures(42), 42);
774 assert_eq!(CrashRustler::reduce_to_two_sig_figures(99), 99);
775 }
776
777 #[test]
778 fn reduce_to_two_sig_figures_three_digits() {
779 assert_eq!(CrashRustler::reduce_to_two_sig_figures(100), 100);
780 assert_eq!(CrashRustler::reduce_to_two_sig_figures(123), 120);
781 assert_eq!(CrashRustler::reduce_to_two_sig_figures(999), 990);
782 }
783
784 #[test]
785 fn reduce_to_two_sig_figures_large() {
786 assert_eq!(CrashRustler::reduce_to_two_sig_figures(12345), 12000);
787 }
788
789 #[test]
790 fn sanitize_path_user_path() {
791 assert_eq!(
792 CrashRustler::sanitize_path("/Users/kurtis/foo/bar"),
793 "/Users/USER/foo/bar"
794 );
795 }
796
797 #[test]
798 fn sanitize_path_system_unchanged() {
799 assert_eq!(
800 CrashRustler::sanitize_path("/System/Library/Frameworks/foo"),
801 "/System/Library/Frameworks/foo"
802 );
803 }
804
805 #[test]
806 fn sanitize_path_users_no_trailing_slash() {
807 assert_eq!(
809 CrashRustler::sanitize_path("/Users/kurtis"),
810 "/Users/kurtis"
811 );
812 }
813 }
814
815 mod mac_roman {
819 use crate::crash_rustler::mac_roman_to_char;
820
821 #[test]
822 fn ascii_passthrough() {
823 for b in 0..0x80u8 {
824 assert_eq!(mac_roman_to_char(b), b as char);
825 }
826 }
827
828 #[test]
829 fn known_characters() {
830 assert_eq!(mac_roman_to_char(0x80), '\u{00C4}');
832 assert_eq!(mac_roman_to_char(0xCA), '\u{00A0}');
834 assert_eq!(mac_roman_to_char(0xDB), '\u{20AC}');
836 }
837 }
838}