#[derive(Debug)]
pub struct ScriptPanic {
pub file: String,
pub line: u32,
pub col: u32,
pub message: String,
pub trace: String,
}
impl ScriptPanic {
pub fn header(&self, thread: &str) -> String {
let tid = os_thread_id();
let mut out = if self.file.is_empty() {
format!("\nthread '{thread}' ({tid}) panicked:\n{}\n", self.message)
} else {
format!(
"\nthread '{thread}' ({tid}) panicked at {}:{}:{}:\n{}\n",
self.file, self.line, self.col, self.message
)
};
if std::env::var_os("RUST_BACKTRACE").is_some_and(|v| v != "0") {
out.push_str("stack backtrace:\n");
out.push_str(&self.trace);
out.push('\n');
} else {
out.push_str(
"note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n",
);
}
out
}
}
impl std::fmt::Display for ScriptPanic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for ScriptPanic {}
pub fn os_thread_id() -> u64 {
#[cfg(target_os = "macos")]
{
let mut id: u64 = 0;
let rc = unsafe { libc::pthread_threadid_np(0, &raw mut id) };
if rc == 0 { id } else { 0 }
}
#[cfg(any(target_os = "linux", target_os = "android"))]
{
let id = unsafe { libc::gettid() };
u64::try_from(id).unwrap_or(0)
}
#[cfg(windows)]
{
u64::from(unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() })
}
#[cfg(not(any(
target_os = "macos",
target_os = "linux",
target_os = "android",
windows
)))]
{
0
}
}
#[derive(Debug)]
pub struct ErrReturn(pub String);
impl std::fmt::Display for ErrReturn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Error: {}", self.0)
}
}
impl std::error::Error for ErrReturn {}
pub struct FrameSite {
pub func: String,
pub file: String,
pub line: u32,
pub col: u32,
}
pub(super) fn trace_error(
e: anyhow::Error,
frames: impl Iterator<Item = FrameSite>,
) -> anyhow::Error {
const SHOWN: usize = 15;
let (message, mut origin, mut trace) = match e.downcast_ref::<ScriptPanic>() {
Some(p) => (
p.message.clone(),
Some((p.file.clone(), p.line, p.col)),
p.trace.clone(),
),
None => (format!("{e:#}"), None, String::new()),
};
let mut hidden = 0usize;
for (i, site) in frames.enumerate() {
if origin.is_none() {
origin = Some((site.file.clone(), site.line, site.col));
}
if i >= SHOWN {
hidden += 1;
continue;
}
if !trace.is_empty() {
trace.push('\n');
}
if site.file.is_empty() {
trace.push_str(&format!(" at {}", site.func));
} else if site.line == 0 {
trace.push_str(&format!(" at {} ({})", site.func, site.file));
} else {
trace.push_str(&format!(
" at {} ({}:{}:{})",
site.func, site.file, site.line, site.col
));
}
}
if hidden > 0 {
trace.push_str(&format!("\n ... {hidden} more frames"));
}
let (file, line, col) = origin.unwrap_or_default();
anyhow::Error::new(ScriptPanic {
file,
line,
col,
message,
trace,
})
}