1use 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#[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#[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 pub fn file(&self) -> &str {
66 &self.file
67 }
68
69 pub fn line(&self) -> u32 {
71 self.line
72 }
73
74 pub fn column(&self) -> u32 {
76 self.column
77 }
78}
79
80impl PanicInfo {
81 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 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 pub fn message(&self) -> &str {
119 self.msg.as_str()
120 }
121
122 pub fn location(&self) -> Option<&Location> {
124 self.location.as_ref()
125 }
126
127 #[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
184pub 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
196pub 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}