use std::any::Any;
use std::cell::RefCell;
use std::fmt;
use std::panic;
use serde_::{Deserialize, Serialize};
fn serialize_panic(panic: &(dyn Any + Send + 'static)) -> PanicInfo {
PanicInfo::new(match panic.downcast_ref::<&'static str>() {
Some(s) => *s,
None => match panic.downcast_ref::<String>() {
Some(s) => &s[..],
None => "Box<Any>",
},
})
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "serde_")]
pub struct PanicInfo {
msg: String,
pub(crate) location: Option<Location>,
#[cfg(feature = "backtrace")]
pub(crate) backtrace: Option<backtrace::Backtrace>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "serde_")]
pub struct Location {
file: String,
line: u32,
column: u32,
}
impl Location {
fn from_std(loc: &std::panic::Location) -> Location {
Location {
file: loc.file().into(),
line: loc.line(),
column: loc.column(),
}
}
pub fn file(&self) -> &str {
&self.file
}
pub fn line(&self) -> u32 {
self.line
}
pub fn column(&self) -> u32 {
self.column
}
}
impl PanicInfo {
pub(crate) fn new(s: &str) -> PanicInfo {
PanicInfo {
msg: s.into(),
location: None,
#[cfg(feature = "backtrace")]
backtrace: None,
}
}
pub fn from_std(info: &std::panic::PanicInfo, capture_backtrace: bool) -> PanicInfo {
#[allow(unused_mut)]
let mut panic = serialize_panic(info.payload());
#[cfg(feature = "backtrace")]
{
if capture_backtrace {
panic.backtrace = Some(backtrace::Backtrace::new());
}
}
#[cfg(not(feature = "backtrace"))]
{
let _ = capture_backtrace;
}
panic.location = info.location().map(Location::from_std);
panic
}
pub fn message(&self) -> &str {
self.msg.as_str()
}
pub fn location(&self) -> Option<&Location> {
self.location.as_ref()
}
#[cfg(feature = "backtrace")]
pub fn backtrace(&self) -> Option<&backtrace::Backtrace> {
self.backtrace.as_ref()
}
}
impl fmt::Debug for PanicInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("PanicInfo")
.field("message", &self.message())
.field("location", &self.location())
.field("backtrace", &{
#[cfg(feature = "backtrace")]
{
self.backtrace()
}
#[cfg(not(feature = "backtrace"))]
{
None::<()>
}
})
.finish()
}
}
impl fmt::Display for PanicInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.msg)
}
}
thread_local! {
static PANIC_INFO: RefCell<Option<PanicInfo>> = RefCell::new(None);
}
fn reset_panic_info() {
PANIC_INFO.with(|pi| {
*pi.borrow_mut() = None;
});
}
fn take_panic_info(payload: &(dyn Any + Send + 'static)) -> PanicInfo {
PANIC_INFO
.with(|pi| pi.borrow_mut().take())
.unwrap_or_else(move || serialize_panic(payload))
}
fn panic_handler(info: &panic::PanicInfo<'_>, capture_backtrace: bool) {
PANIC_INFO.with(|pi| {
*pi.borrow_mut() = Some(PanicInfo::from_std(info, capture_backtrace));
});
}
pub fn catch_panic<F: FnOnce() -> R, R>(func: F) -> Result<R, PanicInfo> {
reset_panic_info();
match panic::catch_unwind(panic::AssertUnwindSafe(|| func())) {
Ok(rv) => Ok(rv),
Err(panic) => Err(take_panic_info(&*panic)),
}
}
pub fn init_panic_hook(capture_backtraces: bool) {
let next = panic::take_hook();
panic::set_hook(Box::new(move |info| {
panic_handler(info, capture_backtraces);
next(info);
}));
}
#[test]
fn test_panic_hook() {
init_panic_hook(true);
let rv = catch_panic(|| {
panic!("something went wrong");
});
let pi = rv.unwrap_err();
assert_eq!(pi.message(), "something went wrong");
#[cfg(feature = "backtrace")]
{
let bt = format!("{:?}", pi.backtrace().unwrap());
assert!(bt.contains("PanicInfo::from_std"));
}
}