bon_macros/error/
panic_context.rs
1#[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 backtrace: backtrace::Backtrace,
93
94 location: Option<PanicLocation>,
95 thread: String,
96
97 panics_count: usize,
101}
102
103impl PanicContext {
104 fn from_std(std_panic_info: &StdPanicHookInfo<'_>, panics_count: usize) -> Self {
105 let location = std_panic_info.location();
106 let current_thread = std::thread::current();
107 let thread_ = current_thread
108 .name()
109 .map(String::from)
110 .unwrap_or_else(|| format!("{:?}", current_thread.id()));
111
112 Self(Rc::new(PanicContextShared {
113 backtrace: backtrace::Backtrace::capture(),
114 location: location.map(PanicLocation::from_std),
115 thread: thread_,
116 panics_count,
117 }))
118 }
119}
120
121impl fmt::Debug for PanicContext {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 fmt::Display::fmt(self, f)
124 }
125}
126
127impl fmt::Display for PanicContext {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 let PanicContextShared {
130 location,
131 backtrace,
132 thread,
133 panics_count,
134 } = &*self.0;
135
136 write!(f, "panic occurred")?;
137
138 if let Some(location) = location {
139 write!(f, " at {location}")?;
140 }
141
142 write!(f, " in thread '{thread}'")?;
143
144 if *panics_count > 1 {
145 write!(f, " (total panics observed: {panics_count})")?;
146 }
147
148 #[allow(clippy::incompatible_msrv)]
149 if backtrace.status() == backtrace::BacktraceStatus::Captured {
150 write!(f, "\nbacktrace:\n{backtrace}")?;
151 }
152
153 Ok(())
154 }
155}
156
157pub(super) fn message_from_panic_payload(payload: &dyn Any) -> Option<String> {
159 if let Some(str_slice) = payload.downcast_ref::<&str>() {
160 return Some((*str_slice).to_owned());
161 }
162 if let Some(owned_string) = payload.downcast_ref::<String>() {
163 return Some(owned_string.clone());
164 }
165
166 None
167}
168
169#[derive(Clone)]
171struct PanicLocation {
172 file: String,
173 line: u32,
174 col: u32,
175}
176
177impl PanicLocation {
178 fn from_std(loc: &std::panic::Location<'_>) -> Self {
179 Self {
180 file: loc.file().to_owned(),
181 line: loc.line(),
182 col: loc.column(),
183 }
184 }
185}
186
187impl fmt::Display for PanicLocation {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 write!(f, "{}:{}:{}", self.file, self.line, self.col)
190 }
191}
192
193#[rustversion::since(1.65.0)]
194mod backtrace {
195 pub(super) use std::backtrace::{Backtrace, BacktraceStatus};
196}
197
198#[rustversion::before(1.65.0)]
199mod backtrace {
200 #[derive(PartialEq)]
201 pub(super) enum BacktraceStatus {
202 Captured,
203 }
204
205 pub(super) struct Backtrace;
206
207 impl Backtrace {
208 pub(super) fn capture() -> Self {
209 Self
210 }
211 pub(super) fn status(&self) -> BacktraceStatus {
212 BacktraceStatus::Captured
213 }
214 }
215
216 impl std::fmt::Display for Backtrace {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 f.write_str("{update your Rust compiler to >=1.65.0 to see the backtrace}")
219 }
220 }
221}