miette/
panic.rs

1use std::{error::Error, fmt::Display};
2
3use backtrace::Backtrace;
4
5use crate::{Context, Diagnostic, Result};
6
7/// Tells miette to render panics using its rendering engine.
8pub 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                // Padding for next lines after frame's address
57                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                        // Print symbols from this address,
73                        // if there are several addresses
74                        // we need to put it on next line
75                        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                        // See if there is debug information with file name and line
86                        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}