1use std::{error::Error, fmt::Display};
2
3use backtrace::Backtrace;
4
5use crate::{Context, Diagnostic, Result};
6
7pub fn set_panic_hook() {
9 std::panic::set_hook(Box::new(move |info| {
10 let mut message = "Something went wrong".to_string();
11 let payload = info.payload();
12 if let Some(msg) = payload.downcast_ref::<&str>() {
13 message = msg.to_string();
14 }
15 if let Some(msg) = payload.downcast_ref::<String>() {
16 message.clone_from(msg);
17 }
18 let mut report: Result<()> = Err(Panic(message).into());
19 if let Some(loc) = info.location() {
20 report = report
21 .with_context(|| format!("at {}:{}:{}", loc.file(), loc.line(), loc.column()));
22 }
23 if let Err(err) = report.with_context(|| "Main thread panicked.".to_string()) {
24 eprintln!("Error: {:?}", err);
25 }
26 }));
27}
28
29#[derive(Debug)]
30struct Panic(String);
31
32impl Display for Panic {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 let msg = &self.0;
35 let panic = Panic::backtrace();
36 write!(f, "{msg}{panic}")
37 }
38}
39
40impl Error for Panic {}
41
42impl Diagnostic for Panic {
43 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
44 Some(Box::new(
45 "set the `RUST_BACKTRACE=1` environment variable to display a backtrace.",
46 ))
47 }
48}
49
50impl Panic {
51 fn backtrace() -> String {
52 use std::fmt::Write;
53 if let Ok(var) = std::env::var("RUST_BACKTRACE") {
54 if !var.is_empty() && var != "0" {
55 const HEX_WIDTH: usize = std::mem::size_of::<usize>() + 2;
56 const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
58 let mut backtrace = String::new();
59 let trace = Backtrace::new();
60 let frames = backtrace_ext::short_frames_strict(&trace).enumerate();
61 for (idx, (frame, sub_frames)) in frames {
62 let ip = frame.ip();
63 let _ = write!(backtrace, "\n{:4}: {:2$?}", idx, ip, HEX_WIDTH);
64
65 let symbols = frame.symbols();
66 if symbols.is_empty() {
67 let _ = write!(backtrace, " - <unresolved>");
68 continue;
69 }
70
71 for (idx, symbol) in symbols[sub_frames].iter().enumerate() {
72 if idx != 0 {
76 let _ = write!(backtrace, "\n{:1$}", "", NEXT_SYMBOL_PADDING);
77 }
78
79 if let Some(name) = symbol.name() {
80 let _ = write!(backtrace, " - {}", name);
81 } else {
82 let _ = write!(backtrace, " - <unknown>");
83 }
84
85 if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
87 let _ = write!(
88 backtrace,
89 "\n{:3$}at {}:{}",
90 "",
91 file.display(),
92 line,
93 NEXT_SYMBOL_PADDING
94 );
95 }
96 }
97 }
98 return backtrace;
99 }
100 }
101 "".into()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use std::error::Error;
108
109 use super::*;
110
111 #[test]
112 fn panic() {
113 let panic = Panic("ruh roh raggy".to_owned());
114
115 assert_eq!(panic.to_string(), "ruh roh raggy");
116 assert!(panic.source().is_none());
117 assert!(panic.code().is_none());
118 assert!(panic.severity().is_none());
119 assert_eq!(
120 panic.help().map(|h| h.to_string()),
121 Some(
122 "set the `RUST_BACKTRACE=1` environment variable to display a backtrace."
123 .to_owned()
124 )
125 );
126 assert!(panic.url().is_none());
127 assert!(panic.source_code().is_none());
128 assert!(panic.labels().is_none());
129 assert!(panic.related().is_none());
130 assert!(panic.diagnostic_source().is_none());
131 }
132}