tokio_unix_ipc/
panic.rs

1//! Support for cross-process panic handling.
2//!
3//! This module provides a version of the standard library's
4//! panic info object ([`PanicInfo`]) which can hold information
5//! about why a panic happened so it can be sent across processes.
6//!
7//! To capture panics instead of crashing the panic hook needs to
8//! be installed ([`init_panic_hook`]) and afterwards the panicking
9//! function needs to be invoked via [`catch_panic`].
10//!
11//! This requires the `panic-support` feature.
12use std::any::Any;
13use std::cell::RefCell;
14use std::fmt;
15use std::panic;
16
17use serde_::{Deserialize, Serialize};
18
19fn serialize_panic(panic: &dyn Any) -> PanicInfo {
20    PanicInfo::new(match panic.downcast_ref::<&'static str>() {
21        Some(s) => s,
22        None => match panic.downcast_ref::<String>() {
23            Some(s) => &s[..],
24            None => "Box<Any>",
25        },
26    })
27}
28
29/// Represents a panic caugh across processes.
30///
31/// This contains the marshalled panic information so that it can be used
32/// for other purposes.
33///
34/// This is similar to [`std::panic::PanicInfo`] but can cross process boundaries.
35#[derive(Serialize, Deserialize)]
36#[serde(crate = "serde_")]
37pub struct PanicInfo {
38    msg: String,
39    pub(crate) location: Option<Location>,
40    #[cfg(feature = "backtrace")]
41    pub(crate) backtrace: Option<backtrace::Backtrace>,
42}
43
44/// Location of a panic.
45///
46/// This is similar to `std::panic::Location` but can cross process boundaries.
47#[derive(Serialize, Deserialize, Debug)]
48#[serde(crate = "serde_")]
49pub struct Location {
50    file: String,
51    line: u32,
52    column: u32,
53}
54
55impl Location {
56    fn from_std(loc: &std::panic::Location) -> Location {
57        Location {
58            file: loc.file().into(),
59            line: loc.line(),
60            column: loc.column(),
61        }
62    }
63
64    /// Returns the name of the source file from which the panic originated.
65    pub fn file(&self) -> &str {
66        &self.file
67    }
68
69    /// Returns the line number from which the panic originated.
70    pub fn line(&self) -> u32 {
71        self.line
72    }
73
74    /// Returns the column from which the panic originated.
75    pub fn column(&self) -> u32 {
76        self.column
77    }
78}
79
80impl PanicInfo {
81    /// Creates a new panic object.
82    pub(crate) fn new(s: &str) -> PanicInfo {
83        PanicInfo {
84            msg: s.into(),
85            location: None,
86            #[cfg(feature = "backtrace")]
87            backtrace: None,
88        }
89    }
90
91    /// Creates a panic info from a standard library one.
92    ///
93    /// It will attempt to extract all information available from it
94    /// and if `capture_backtrace` is set to `true` it will also
95    /// capture a backtrace at the current location and assign it
96    /// to the panic info object.
97    ///
98    /// For the backtrace parameter to have an effect the
99    /// `backtrace` feature needs to be enabled.
100    pub fn from_std(info: &std::panic::PanicInfo, capture_backtrace: bool) -> PanicInfo {
101        #[allow(unused_mut)]
102        let mut panic = serialize_panic(info.payload());
103        #[cfg(feature = "backtrace")]
104        {
105            if capture_backtrace {
106                panic.backtrace = Some(backtrace::Backtrace::new());
107            }
108        }
109        #[cfg(not(feature = "backtrace"))]
110        {
111            let _ = capture_backtrace;
112        }
113        panic.location = info.location().map(Location::from_std);
114        panic
115    }
116
117    /// Returns the message of the panic.
118    pub fn message(&self) -> &str {
119        self.msg.as_str()
120    }
121
122    /// Returns the panic location.
123    pub fn location(&self) -> Option<&Location> {
124        self.location.as_ref()
125    }
126
127    /// Returns a reference to the backtrace.
128    ///
129    /// Typically this backtrace is already resolved because it's currently
130    /// not possible to cross the process boundaries with unresolved backtraces.
131    #[cfg(feature = "backtrace")]
132    pub fn backtrace(&self) -> Option<&backtrace::Backtrace> {
133        self.backtrace.as_ref()
134    }
135}
136
137impl fmt::Debug for PanicInfo {
138    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139        f.debug_struct("PanicInfo")
140            .field("message", &self.message())
141            .field("location", &self.location())
142            .field("backtrace", &{
143                #[cfg(feature = "backtrace")]
144                {
145                    self.backtrace()
146                }
147                #[cfg(not(feature = "backtrace"))]
148                {
149                    None::<()>
150                }
151            })
152            .finish()
153    }
154}
155
156impl fmt::Display for PanicInfo {
157    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158        write!(f, "{}", self.msg)
159    }
160}
161
162thread_local! {
163    static PANIC_INFO: RefCell<Option<PanicInfo>> = const { RefCell::new(None) };
164}
165
166fn reset_panic_info() {
167    PANIC_INFO.with(|pi| {
168        *pi.borrow_mut() = None;
169    });
170}
171
172fn take_panic_info(payload: &dyn Any) -> PanicInfo {
173    PANIC_INFO
174        .with(|pi| pi.borrow_mut().take())
175        .unwrap_or_else(move || serialize_panic(payload))
176}
177
178fn panic_handler(info: &panic::PanicInfo<'_>, capture_backtrace: bool) {
179    PANIC_INFO.with(|pi| {
180        *pi.borrow_mut() = Some(PanicInfo::from_std(info, capture_backtrace));
181    });
182}
183
184/// Invokes a function and captures the panic.
185///
186/// The captured panic info will only be fully filled if the panic hook
187/// has been installed (see [`init_panic_hook`]).
188pub fn catch_panic<F: FnOnce() -> R, R>(func: F) -> Result<R, PanicInfo> {
189    reset_panic_info();
190    match panic::catch_unwind(panic::AssertUnwindSafe(func)) {
191        Ok(rv) => Ok(rv),
192        Err(panic) => Err(take_panic_info(&*panic)),
193    }
194}
195
196/// Initializes the panic hook for IPC usage.
197///
198/// When enabled the default panic handler is disabled and panics are stored in a
199/// thread local storage. This needs to be called for the [`catch_panic`]
200/// function in this module to provide the correct panic info.
201pub fn init_panic_hook(capture_backtraces: bool) {
202    let next = panic::take_hook();
203    panic::set_hook(Box::new(move |info| {
204        panic_handler(info, capture_backtraces);
205        next(info);
206    }));
207}
208
209#[test]
210fn test_panic_hook() {
211    init_panic_hook(true);
212    let rv = catch_panic(|| {
213        panic!("something went wrong");
214    });
215    let pi = rv.unwrap_err();
216    assert_eq!(pi.message(), "something went wrong");
217    #[cfg(feature = "backtrace")]
218    {
219        let bt = format!("{:?}", pi.backtrace().unwrap());
220        assert!(bt.contains("PanicInfo::from_std"));
221    }
222}