1use core::panic::AssertUnwindSafe;
13use core::sync::atomic::{AtomicBool, Ordering};
14use std::panic::catch_unwind;
15
16static REPORTED: AtomicBool = AtomicBool::new(false);
17
18pub fn guard<T>(what: &'static str, fallback: T, body: impl FnOnce() -> T) -> T {
25 match catch_unwind(AssertUnwindSafe(body)) {
26 Ok(value) => value,
27 Err(payload) => {
28 report(what, &payload);
29 fallback
30 }
31 }
32}
33
34fn report(what: &'static str, payload: &Box<dyn core::any::Any + Send>) {
35 if REPORTED.swap(true, Ordering::Relaxed) {
38 return;
39 }
40 let msg = payload
41 .downcast_ref::<&str>()
42 .map(|s| (*s).to_string())
43 .or_else(|| payload.downcast_ref::<String>().cloned())
44 .unwrap_or_else(|| "unknown panic".to_string());
45
46 eprintln!(
47 "libasdf-rs: internal error: a panic escaped {what}: {msg}\n\
48 libasdf-rs: this is a bug; the call returned a failure value instead.\n\
49 libasdf-rs: please report it at https://github.com/cruzzil/asdf/issues"
50 );
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn returns_the_value_when_nothing_panics() {
59 assert_eq!(guard("test", -1, || 42), 42);
60 }
61
62 #[test]
63 fn returns_the_fallback_on_panic() {
64 let prev = std::panic::take_hook();
66 std::panic::set_hook(Box::new(|_| {}));
67 let got = guard("test", -1, || panic!("boom"));
68 std::panic::set_hook(prev);
69 assert_eq!(got, -1);
70 }
71
72 #[test]
73 fn null_pointers_are_a_valid_fallback() {
74 let prev = std::panic::take_hook();
75 std::panic::set_hook(Box::new(|_| {}));
76 let got: *mut u8 = guard("test", core::ptr::null_mut(), || panic!("boom"));
77 std::panic::set_hook(prev);
78 assert!(got.is_null());
79 }
80}