use crate::{backtrace::Backtrace, context::Context};
use std::{
any::Any,
fmt,
panic::{self, UnwindSafe},
};
#[inline]
pub fn maybe_unwind<F, R>(f: F) -> Result<R, Unwind>
where
F: FnOnce() -> R + UnwindSafe,
{
let mut captured: Option<Captured> = None;
let mut ctx = Context {
captured: &mut captured,
};
let res = with_set_ctx!(&mut ctx, { panic::catch_unwind(f) });
res.map_err(|payload| Unwind {
payload,
captured: captured.take(),
})
}
#[derive(Debug)]
pub struct Unwind {
payload: Box<dyn Any + Send + 'static>,
captured: Option<Captured>,
}
#[derive(Debug)]
pub(crate) struct Captured {
pub(crate) location: Option<Location>,
pub(crate) backtrace: Option<Backtrace>,
}
impl Unwind {
#[inline]
pub fn payload(&self) -> &(dyn Any + Send + 'static) {
&*self.payload
}
#[inline]
pub fn payload_str(&self) -> &str {
let payload = self.payload();
(payload.downcast_ref::<&str>().copied())
.or_else(|| payload.downcast_ref::<String>().map(|s| s.as_str()))
.unwrap_or_else(|| "Box<dyn Any>")
}
#[inline]
pub fn into_payload(self) -> Box<dyn Any + Send + 'static> {
self.payload
}
#[inline]
pub fn location(&self) -> Option<&Location> {
self.captured.as_ref()?.location.as_ref()
}
#[cfg(backtrace)]
#[inline]
pub fn backtrace(&self) -> Option<&Backtrace> {
self.captured.as_ref()?.backtrace.as_ref()
}
}
impl fmt::Display for Unwind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = self.payload_str();
if !f.alternate() {
return f.write_str(msg);
}
if let Some(location) = self.location() {
writeln!(f, "panicked at {}: {}", location, msg)?;
} else {
writeln!(f, "panicked: {}", msg)?;
}
#[cfg(backtrace)]
{
use std::backtrace::BacktraceStatus;
if let Some(backtrace) = self.backtrace() {
if let BacktraceStatus::Captured = backtrace.status() {
writeln!(f, "stack backtrace:")?;
writeln!(f, "{}", backtrace)?;
}
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct Location {
file: String,
line: u32,
column: u32,
}
impl Location {
#[inline]
pub(crate) fn from_std(loc: &panic::Location<'_>) -> Self {
Self {
file: loc.file().to_string(),
line: loc.line(),
column: loc.column(),
}
}
#[inline]
pub fn file(&self) -> &str {
self.file.as_str()
}
#[inline]
pub fn line(&self) -> u32 {
self.line
}
#[inline]
pub fn column(&self) -> u32 {
self.column
}
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", self.file, self.line, self.column)
}
}