#[cfg(feature = "build")]
use std::cell::RefCell;
#[cfg(feature = "build")]
use std::fmt::Debug;
#[cfg(feature = "build")]
use std::sync::OnceLock;
#[cfg(feature = "build")]
use backtrace::BacktraceFrame;
#[cfg(feature = "build")]
fn strip_hash_brackets(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '[' {
let bracket_content: String = chars.by_ref().take_while(|&ch| ch != ']').collect();
if !bracket_content.chars().all(|ch| ch.is_ascii_hexdigit()) {
result.push('[');
result.push_str(&bracket_content);
result.push(']');
}
} else {
result.push(c);
}
}
result
}
#[cfg(not(feature = "build"))]
#[derive(Clone)]
pub struct Backtrace;
#[cfg(feature = "build")]
#[derive(Clone)]
pub struct Backtrace {
skip_count: usize,
col_offset: usize, frames: Vec<(RefCell<Option<BacktraceFrame>>, OnceLock<BacktraceFrame>)>,
}
#[cfg(stageleft_runtime)]
#[cfg(feature = "build")]
#[doc(hidden)]
pub fn __macro_get_backtrace(col_offset: usize) -> Backtrace {
let mut out = Backtrace::get_backtrace(1);
out.col_offset = col_offset;
out
}
#[cfg(not(feature = "build"))]
#[doc(hidden)]
pub fn __macro_get_backtrace(_col_offset: usize) -> Backtrace {
panic!();
}
impl Backtrace {
#[cfg(feature = "build")]
#[inline(never)]
pub(crate) fn get_backtrace(skip_count: usize) -> Backtrace {
let backtrace = backtrace::Backtrace::new_unresolved();
let frames_vec: Vec<_> = backtrace.into();
Backtrace {
skip_count,
col_offset: 0,
frames: frames_vec
.into_iter()
.map(|f| (RefCell::new(Some(f)), OnceLock::new()))
.collect(),
}
}
#[cfg(not(feature = "build"))]
pub(crate) fn get_backtrace(_skip_count: usize) -> Backtrace {
panic!();
}
#[cfg(feature = "build")]
pub fn elements(&self) -> impl Iterator<Item = BacktraceElement> + '_ {
self.frames
.iter()
.map(|(frame_refcell, resolved_frame)| {
resolved_frame.get_or_init(|| {
let mut gotten_frame = frame_refcell.borrow_mut().take().unwrap();
gotten_frame.resolve();
gotten_frame
})
})
.skip_while(|f| {
!(std::ptr::eq(f.symbol_address(), Backtrace::get_backtrace as _)
|| f.symbols()
.first()
.and_then(|s| s.name())
.and_then(|n| n.as_str())
.is_some_and(|n| n.contains("get_backtrace")))
})
.skip(1)
.take_while(|f| {
!f.symbols()
.last()
.and_then(|s| s.name())
.and_then(|n| n.as_str())
.is_some_and(|n| n.contains("__rust_begin_short_backtrace"))
})
.flat_map(move |frame| frame.symbols())
.skip(self.skip_count)
.enumerate()
.map(|(idx, symbol)| {
let full_fn_name = strip_hash_brackets(&symbol.name().unwrap().to_string());
let mut element = BacktraceElement {
fn_name: full_fn_name
.rfind("::")
.map(|idx| full_fn_name.split_at(idx).0.to_owned())
.unwrap_or(full_fn_name),
filename: symbol.filename().map(|f| f.display().to_string()),
lineno: symbol.lineno(),
colno: symbol.colno(),
addr: symbol.addr().map(|a| a as usize),
};
if self.col_offset > 0 && idx == 0 {
element.colno = element
.colno
.map(|c| c.saturating_sub(self.col_offset as u32));
}
element
})
}
#[cfg(feature = "build")]
pub fn format_span(&self) -> Option<String> {
let elem = self.elements().next()?;
let file = elem.filename.as_ref()?;
let line = elem.lineno?;
Some(format!("{file}:{line}"))
}
#[cfg(not(feature = "build"))]
pub fn format_span(&self) -> Option<String> {
None
}
}
#[cfg(feature = "build")]
#[derive(Clone)]
pub struct BacktraceElement {
pub fn_name: String,
pub filename: Option<String>,
pub lineno: Option<u32>,
pub colno: Option<u32>,
pub addr: Option<usize>,
}
#[cfg(feature = "build")]
impl Debug for BacktraceElement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BacktraceElement")
.field("fn_name", &self.fn_name)
.field("lineno", &self.lineno)
.field("colno", &self.colno)
.finish()
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "build")]
#[test]
#[cfg_attr(not(target_os = "linux"), ignore)]
fn test_backtrace() {
use super::*;
let backtrace = Backtrace::get_backtrace(0);
let elements = backtrace.elements();
hydro_build_utils::assert_debug_snapshot!(elements.collect::<Vec<_>>());
}
}