use std::{
panic,
sync::{Arc, Mutex},
};
pub(crate) struct PanicGuard {
message: Arc<Mutex<String>>,
old_hook: Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>,
}
impl PanicGuard {
pub(crate) fn new() -> Self {
let message = Arc::new(Mutex::new(String::new()));
let old_hook = panic::take_hook();
panic::set_hook({
let message = message.clone();
Box::new(move |info: &panic::PanicInfo<'_>| {
let panic_payload = if let Some(s) = info.payload().downcast_ref::<&str>() {
*s
} else if let Some(s) = info.payload().downcast_ref::<String>() {
s
} else {
"<Non-String Panic Payload>"
};
let panic_message = if let Some(location) = info.location() {
format!(
"function panicked in file '{}' at line {}: {}\n",
location.file(),
location.line(),
panic_payload
)
} else {
format!(
"function panicked, but location unavailable: {}\n",
panic_payload
)
};
if let Ok(mut message) = message.lock() {
message.push_str(&panic_message);
}
})
});
Self { message, old_hook }
}
pub(crate) fn get_panic(&self) -> String {
self.message
.lock()
.map(|message| message.clone())
.unwrap_or_else(|_| {
"function panicked, but unable to retrieve further information".to_string()
})
}
}
impl Drop for PanicGuard {
fn drop(&mut self) {
let mut current_hook = panic::take_hook();
std::mem::swap(&mut self.old_hook, &mut current_hook);
panic::set_hook(current_hook);
}
}
#[cfg(test)]
mod test {
use std::panic::catch_unwind;
#[test]
fn panic_message_redirect() {
let guard = super::PanicGuard::new();
let _ = catch_unwind(|| {
panic!("panic message");
});
assert!(guard.get_panic().contains("panic message"));
}
#[test]
fn panic_hook_restore() {
let outer_guard = super::PanicGuard::new();
{
let inner_guard = super::PanicGuard::new();
let _ = catch_unwind(|| {
panic!("inner panic message");
});
assert!(inner_guard.get_panic().contains("inner panic message"));
}
let _ = catch_unwind(|| {
panic!("outer panic message");
});
assert!(outer_guard.get_panic().contains("outer panic message"));
}
}