maybe_unwind/
unwind.rs

1use crate::{backtrace::Backtrace, context::Context};
2use std::{
3    any::Any,
4    fmt,
5    panic::{self, UnwindSafe},
6};
7
8/// Invokes a closure, capturing the cause of an unwinding panic if one occurs.
9///
10/// In addition, this function also captures the panic information if the custom
11/// panic hook is set. If the panic hook is not set, only the cause of unwinding
12/// panic captured by `catch_unwind` is returned.
13#[inline]
14pub fn maybe_unwind<F, R>(f: F) -> Result<R, Unwind>
15where
16    F: FnOnce() -> R + UnwindSafe,
17{
18    let mut captured: Option<Captured> = None;
19
20    let mut ctx = Context {
21        captured: &mut captured,
22    };
23
24    let res = with_set_ctx!(&mut ctx, { panic::catch_unwind(f) });
25
26    res.map_err(|payload| Unwind {
27        payload,
28        captured: captured.take(),
29    })
30}
31
32/// The captured information about an unwinding panic.
33#[derive(Debug)]
34pub struct Unwind {
35    payload: Box<dyn Any + Send + 'static>,
36    captured: Option<Captured>,
37}
38
39#[derive(Debug)]
40pub(crate) struct Captured {
41    pub(crate) location: Option<Location>,
42    pub(crate) backtrace: Option<Backtrace>,
43}
44
45impl Unwind {
46    /// Return the payload associated with the captured panic.
47    #[inline]
48    pub fn payload(&self) -> &(dyn Any + Send + 'static) {
49        &*self.payload
50    }
51
52    /// Return the string representation of the panic payload.
53    #[inline]
54    pub fn payload_str(&self) -> &str {
55        let payload = self.payload();
56        (payload.downcast_ref::<&str>().copied())
57            .or_else(|| payload.downcast_ref::<String>().map(|s| s.as_str()))
58            .unwrap_or_else(|| "Box<dyn Any>")
59    }
60
61    /// Convert itself into a trait object of the panic payload.
62    #[inline]
63    pub fn into_payload(self) -> Box<dyn Any + Send + 'static> {
64        self.payload
65    }
66
67    /// Return the information about the location from which the panic originated.
68    #[inline]
69    pub fn location(&self) -> Option<&Location> {
70        self.captured.as_ref()?.location.as_ref()
71    }
72
73    /// Get the stack backtrace captured by the panic hook.
74    ///
75    /// Currently this method is enabled only if the backtrace is supported.
76    #[cfg(backtrace)]
77    #[inline]
78    pub fn backtrace(&self) -> Option<&Backtrace> {
79        self.captured.as_ref()?.backtrace.as_ref()
80    }
81}
82
83impl fmt::Display for Unwind {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        let msg = self.payload_str();
86        if !f.alternate() {
87            return f.write_str(msg);
88        }
89
90        if let Some(location) = self.location() {
91            writeln!(f, "panicked at {}: {}", location, msg)?;
92        } else {
93            writeln!(f, "panicked: {}", msg)?;
94        }
95
96        #[cfg(backtrace)]
97        {
98            use std::backtrace::BacktraceStatus;
99
100            if let Some(backtrace) = self.backtrace() {
101                if let BacktraceStatus::Captured = backtrace.status() {
102                    writeln!(f, "stack backtrace:")?;
103                    writeln!(f, "{}", backtrace)?;
104                }
105            }
106        }
107
108        Ok(())
109    }
110}
111
112/// The information about the location of an unwinding panic.
113#[derive(Debug)]
114pub struct Location {
115    file: String,
116    line: u32,
117    column: u32,
118}
119
120impl Location {
121    #[inline]
122    pub(crate) fn from_std(loc: &panic::Location<'_>) -> Self {
123        Self {
124            file: loc.file().to_string(),
125            line: loc.line(),
126            column: loc.column(),
127        }
128    }
129
130    /// Return the name of the source file from which the panic originated.
131    #[inline]
132    pub fn file(&self) -> &str {
133        self.file.as_str()
134    }
135
136    /// Return the line number from which the panic originated.
137    #[inline]
138    pub fn line(&self) -> u32 {
139        self.line
140    }
141
142    /// Return the column from which the panic originated.
143    #[inline]
144    pub fn column(&self) -> u32 {
145        self.column
146    }
147}
148
149impl fmt::Display for Location {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(f, "{}:{}:{}", self.file, self.line, self.column)
152    }
153}