bon_macros/error/
panic_context.rs1#[rustversion::since(1.81.0)]
3use std::panic::PanicHookInfo as StdPanicHookInfo;
4
5#[rustversion::before(1.81.0)]
7use std::panic::PanicInfo as StdPanicHookInfo;
8
9use std::any::Any;
10use std::cell::RefCell;
11use std::fmt;
12use std::rc::Rc;
13
14fn with_global_panic_context<T>(f: impl FnOnce(&mut GlobalPanicContext) -> T) -> T {
15 thread_local! {
16 static GLOBAL: RefCell<GlobalPanicContext> = const {
26 RefCell::new(GlobalPanicContext {
27 last_panic: None,
28 initialized: false,
29 })
30 };
31 }
32
33 GLOBAL.with(|global| f(&mut global.borrow_mut()))
34}
35
36struct GlobalPanicContext {
37 last_panic: Option<PanicContext>,
38 initialized: bool,
39}
40
41#[derive(Default)]
44pub(super) struct PanicListener {
45 _private: (),
48}
49
50impl PanicListener {
51 pub(super) fn register() -> Self {
52 with_global_panic_context(Self::register_with_global)
53 }
54
55 fn register_with_global(global: &mut GlobalPanicContext) -> Self {
56 if global.initialized {
57 return Self { _private: () };
58 }
59
60 let prev_panic_hook = std::panic::take_hook();
61
62 std::panic::set_hook(Box::new(move |panic_info| {
63 with_global_panic_context(|global| {
64 let panics_count = global.last_panic.as_ref().map(|p| p.0.panics_count);
65 let panics_count = panics_count.unwrap_or(0) + 1;
66
67 global.last_panic = Some(PanicContext::from_std(panic_info, panics_count));
68 });
69
70 prev_panic_hook(panic_info);
71 }));
72
73 global.initialized = true;
74
75 Self { _private: () }
76 }
77
78 #[allow(clippy::unused_self)]
82 pub(super) fn get_last_panic(&self) -> Option<PanicContext> {
83 with_global_panic_context(|global| global.last_panic.clone())
84 }
85}
86
87#[derive(Clone)]
89pub(super) struct PanicContext(Rc<PanicContextShared>);
90
91struct PanicContextShared {
92 #[allow(clippy::incompatible_msrv)]
93 backtrace: backtrace::Backtrace,
94
95 location: Option<PanicLocation>,
96 thread: String,
97
98 panics_count: usize,
102}
103
104impl PanicContext {
105 #[allow(clippy::incompatible_msrv)]
106 fn from_std(std_panic_info: &StdPanicHookInfo<'_>, panics_count: usize) -> Self {
107 let location = std_panic_info.location();
108 let current_thread = std::thread::current();
109 let thread_ = current_thread
110 .name()
111 .map(String::from)
112 .unwrap_or_else(|| format!("{:?}", current_thread.id()));
113
114 Self(Rc::new(PanicContextShared {
115 #[allow(clippy::incompatible_msrv)]
118 backtrace: backtrace::Backtrace::capture(),
119 location: location.map(PanicLocation::from_std),
120 thread: thread_,
121 panics_count,
122 }))
123 }
124}
125
126impl fmt::Debug for PanicContext {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 fmt::Display::fmt(self, f)
129 }
130}
131
132impl fmt::Display for PanicContext {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 let PanicContextShared {
135 location,
136 backtrace,
137 thread,
138 panics_count,
139 } = &*self.0;
140
141 write!(f, "panic occurred")?;
142
143 if let Some(location) = location {
144 write!(f, " at {location}")?;
145 }
146
147 write!(f, " in thread '{thread}'")?;
148
149 if *panics_count > 1 {
150 write!(f, " (total panics observed: {panics_count})")?;
151 }
152
153 #[allow(clippy::incompatible_msrv)]
154 if backtrace.status() == backtrace::BacktraceStatus::Captured {
155 write!(f, "\nbacktrace:\n{backtrace}")?;
156 }
157
158 Ok(())
159 }
160}
161
162pub(super) fn message_from_panic_payload(payload: &dyn Any) -> Option<String> {
164 if let Some(str_slice) = payload.downcast_ref::<&str>() {
165 return Some((*str_slice).to_owned());
166 }
167 if let Some(owned_string) = payload.downcast_ref::<String>() {
168 return Some(owned_string.clone());
169 }
170
171 None
172}
173
174#[derive(Clone)]
176struct PanicLocation {
177 file: String,
178 line: u32,
179 col: u32,
180}
181
182impl PanicLocation {
183 fn from_std(loc: &std::panic::Location<'_>) -> Self {
184 Self {
185 file: loc.file().to_owned(),
186 line: loc.line(),
187 col: loc.column(),
188 }
189 }
190}
191
192impl fmt::Display for PanicLocation {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 write!(f, "{}:{}:{}", self.file, self.line, self.col)
195 }
196}
197
198#[rustversion::since(1.65.0)]
199mod backtrace {
200 pub(super) use std::backtrace::{Backtrace, BacktraceStatus};
201}
202
203#[rustversion::before(1.65.0)]
204mod backtrace {
205 #[derive(PartialEq)]
206 pub(super) enum BacktraceStatus {
207 Captured,
208 }
209
210 pub(super) struct Backtrace;
211
212 impl Backtrace {
213 pub(super) fn capture() -> Self {
214 Self
215 }
216 pub(super) fn status(&self) -> BacktraceStatus {
217 BacktraceStatus::Captured
218 }
219 }
220
221 impl std::fmt::Display for Backtrace {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 f.write_str("{update your Rust compiler to >=1.65.0 to see the backtrace}")
224 }
225 }
226}