1use std::any::Any;
2use std::backtrace::BacktraceStatus;
3use std::cell::Cell;
4use std::panic::Location;
5use std::sync::OnceLock;
6
7#[derive(Debug)]
8pub struct PanicError {
9 pub location: Option<String>,
10 pub payload: Payload,
11 pub backtrace: Option<std::backtrace::Backtrace>,
12 pub salsa_backtrace: Option<salsa::Backtrace>,
13}
14
15#[derive(Debug)]
16pub struct Payload(Box<dyn std::any::Any + Send>);
17
18impl Payload {
19 pub fn downcast_ref<R: Any>(&self) -> Option<&R> {
20 self.0.downcast_ref::<R>()
21 }
22}
23
24impl std::fmt::Display for Payload {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 if let Some(s) = self.0.downcast_ref::<String>() {
27 f.write_str(s)
28 } else if let Some(s) = self.0.downcast_ref::<&str>() {
29 f.write_str(s)
30 } else if let Some(s) = self.0.downcast_ref::<salsa::Cancelled>() {
31 write!(f, "{s}")
32 } else {
33 f.write_str("Box<dyn Any>")
34 }
35 }
36}
37
38impl PanicError {
39 pub fn resume_unwind(self) -> ! {
40 std::panic::resume_unwind(self.payload.0)
41 }
42
43 pub fn to_diagnostic_message(&self, path: Option<impl std::fmt::Display>) -> String {
44 use std::fmt::Write;
45
46 let mut message = String::new();
47 message.push_str("Panicked");
48
49 if let Some(location) = &self.location {
50 let _ = write!(&mut message, " at {location}");
51 }
52
53 if let Some(path) = path {
54 let _ = write!(&mut message, " when checking `{path}`");
55 }
56
57 let _ = write!(&mut message, ": `{payload}`", payload = self.payload);
58
59 message
60 }
61}
62
63impl std::fmt::Display for PanicError {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 write!(f, "panicked at")?;
66 if let Some(location) = &self.location {
67 write!(f, " {location}")?;
68 }
69
70 write!(f, ":\n{payload}", payload = self.payload)?;
71
72 if let Some(query_trace) = self.salsa_backtrace.as_ref() {
73 let _ = writeln!(f, "{query_trace}");
74 }
75
76 if let Some(backtrace) = &self.backtrace {
77 match backtrace.status() {
78 BacktraceStatus::Disabled => {
79 writeln!(
80 f,
81 "\nrun with `RUST_BACKTRACE=1` environment variable to display a backtrace"
82 )?;
83 }
84 BacktraceStatus::Captured => {
85 writeln!(f, "\nBacktrace: {backtrace}")?;
86 }
87 _ => {}
88 }
89 }
90
91 Ok(())
92 }
93}
94
95#[derive(Default)]
96struct CapturedPanicInfo {
97 backtrace: Option<std::backtrace::Backtrace>,
98 location: Option<String>,
99 salsa_backtrace: Option<salsa::Backtrace>,
100}
101
102thread_local! {
103 static CAPTURE_PANIC_INFO: Cell<bool> = const { Cell::new(false) };
104 static LAST_BACKTRACE: Cell<CapturedPanicInfo> = const {
105 Cell::new(CapturedPanicInfo { backtrace: None, location: None, salsa_backtrace: None })
106 };
107}
108
109fn install_hook() {
110 static ONCE: OnceLock<()> = OnceLock::new();
111 ONCE.get_or_init(|| {
112 let prev = std::panic::take_hook();
113 std::panic::set_hook(Box::new(move |info| {
114 let should_capture = CAPTURE_PANIC_INFO.with(Cell::get);
115 if !should_capture {
116 return (*prev)(info);
117 }
118
119 let location = info.location().map(Location::to_string);
120 let backtrace = Some(std::backtrace::Backtrace::capture());
121
122 LAST_BACKTRACE.set(CapturedPanicInfo {
123 backtrace,
124 location,
125 salsa_backtrace: salsa::Backtrace::capture(),
126 });
127 }));
128 });
129}
130
131pub fn catch_unwind<F, R>(f: F) -> Result<R, PanicError>
146where
147 F: FnOnce() -> R + std::panic::UnwindSafe,
148{
149 install_hook();
150 let prev_should_capture = CAPTURE_PANIC_INFO.replace(true);
151 let result = std::panic::catch_unwind(f).map_err(|payload| {
152 let CapturedPanicInfo {
161 location,
162 backtrace,
163 salsa_backtrace,
164 } = LAST_BACKTRACE.with(Cell::take);
165
166 PanicError {
167 location,
168 payload: Payload(payload),
169 backtrace,
170 salsa_backtrace,
171 }
172 });
173 CAPTURE_PANIC_INFO.set(prev_should_capture);
174 result
175}
176
177#[cfg(test)]
178mod tests {
179 use salsa::{Database, Durability};
180
181 #[test]
182 #[ignore = "super::catch_unwind installs a custom panic handler, which could effect test isolation"]
183 fn no_backtrace_for_salsa_cancelled() {
184 #[salsa::input]
185 struct Input {
186 #[returns(copy)]
187 value: u32,
188 }
189
190 #[salsa::tracked(returns(copy))]
191 fn test_query(db: &dyn Database, input: Input) -> u32 {
192 loop {
193 let _ = input.value(db);
195 }
196 }
197
198 let db = salsa::DatabaseImpl::new();
199
200 let input = Input::new(&db, 42);
201
202 let result = std::thread::scope(move |scope| {
203 {
204 let mut db = db.clone();
205 scope.spawn(move || {
206 db.synthetic_write(Durability::MEDIUM);
208 });
209 }
210
211 {
212 scope.spawn(move || {
213 super::catch_unwind(|| {
214 test_query(&db, input);
215 })
216 })
217 }
218 .join()
219 .unwrap()
220 });
221
222 match result {
223 Ok(_) => panic!("Expected query to panic"),
224 Err(err) => {
225 assert!(err.backtrace.is_none());
227 }
228 }
229 }
230}