hydro_lang/compile/ir/
backtrace.rs1#[cfg(feature = "build")]
5use std::cell::RefCell;
6#[cfg(feature = "build")]
7use std::fmt::Debug;
8#[cfg(feature = "build")]
9use std::sync::OnceLock;
10
11#[cfg(feature = "build")]
12use backtrace::BacktraceFrame;
13
14#[cfg(feature = "build")]
16fn strip_hash_brackets(s: &str) -> String {
17 let mut result = String::with_capacity(s.len());
18 let mut chars = s.chars().peekable();
19 while let Some(c) = chars.next() {
20 if c == '[' {
21 let bracket_content: String = chars.by_ref().take_while(|&ch| ch != ']').collect();
22 if !bracket_content.chars().all(|ch| ch.is_ascii_hexdigit()) {
23 result.push('[');
24 result.push_str(&bracket_content);
25 result.push(']');
26 }
27 } else {
28 result.push(c);
29 }
30 }
31 result
32}
33
34#[cfg(not(feature = "build"))]
35#[derive(Clone)]
37pub struct Backtrace;
38
39#[cfg(feature = "build")]
40#[derive(Clone)]
43pub struct Backtrace {
44 skip_count: usize,
45 col_offset: usize, frames: Vec<(RefCell<Option<BacktraceFrame>>, OnceLock<BacktraceFrame>)>,
47}
48
49#[cfg(stageleft_runtime)]
50#[cfg(feature = "build")]
51#[doc(hidden)]
52pub fn __macro_get_backtrace(col_offset: usize) -> Backtrace {
53 let mut out = Backtrace::get_backtrace(1);
54 out.col_offset = col_offset;
55 out
56}
57
58#[cfg(not(feature = "build"))]
59#[doc(hidden)]
60pub fn __macro_get_backtrace(_col_offset: usize) -> Backtrace {
61 panic!();
62}
63
64impl Backtrace {
65 #[cfg(feature = "build")]
66 #[inline(never)]
67 pub(crate) fn get_backtrace(skip_count: usize) -> Backtrace {
68 let backtrace = backtrace::Backtrace::new_unresolved();
69 let frames_vec: Vec<_> = backtrace.into();
70 Backtrace {
71 skip_count,
72 col_offset: 0,
73 frames: frames_vec
74 .into_iter()
75 .map(|f| (RefCell::new(Some(f)), OnceLock::new()))
76 .collect(),
77 }
78 }
79
80 #[cfg(not(feature = "build"))]
81 pub(crate) fn get_backtrace(_skip_count: usize) -> Backtrace {
82 panic!();
83 }
84
85 #[cfg(feature = "build")]
86 pub fn elements(&self) -> impl Iterator<Item = BacktraceElement> + '_ {
92 self.frames
93 .iter()
94 .map(|(frame_refcell, resolved_frame)| {
95 resolved_frame.get_or_init(|| {
96 let mut gotten_frame = frame_refcell.borrow_mut().take().unwrap();
97 gotten_frame.resolve();
98 gotten_frame
99 })
100 })
101 .skip_while(|f| {
102 !(std::ptr::eq(f.symbol_address(), Backtrace::get_backtrace as _)
103 || f.symbols()
104 .first()
105 .and_then(|s| s.name())
106 .and_then(|n| n.as_str())
107 .is_some_and(|n| n.contains("get_backtrace")))
108 })
109 .skip(1)
110 .take_while(|f| {
111 !f.symbols()
112 .last()
113 .and_then(|s| s.name())
114 .and_then(|n| n.as_str())
115 .is_some_and(|n| n.contains("__rust_begin_short_backtrace"))
116 })
117 .flat_map(move |frame| frame.symbols())
118 .skip(self.skip_count)
119 .enumerate()
120 .map(|(idx, symbol)| {
121 let full_fn_name = strip_hash_brackets(&symbol.name().unwrap().to_string());
122 let mut element = BacktraceElement {
123 fn_name: full_fn_name
124 .rfind("::")
125 .map(|idx| full_fn_name.split_at(idx).0.to_string())
126 .unwrap_or(full_fn_name),
127 filename: symbol.filename().map(|f| f.display().to_string()),
128 lineno: symbol.lineno(),
129 colno: symbol.colno(),
130 addr: symbol.addr().map(|a| a as usize),
131 };
132
133 if self.col_offset > 0 && idx == 0 {
134 element.colno = element
135 .colno
136 .map(|c| c.saturating_sub(self.col_offset as u32));
137 }
138
139 element
140 })
141 }
142}
143
144#[cfg(feature = "build")]
145#[derive(Clone)]
147pub struct BacktraceElement {
148 pub fn_name: String,
150 pub filename: Option<String>,
152 pub lineno: Option<u32>,
154 pub colno: Option<u32>,
156 pub addr: Option<usize>,
158}
159
160#[cfg(feature = "build")]
161impl Debug for BacktraceElement {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 f.debug_struct("BacktraceElement")
165 .field("fn_name", &self.fn_name)
166 .field("lineno", &self.lineno)
167 .field("colno", &self.colno)
168 .finish()
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 #[cfg(feature = "build")]
175 #[test]
176 fn test_backtrace() {
177 use super::*;
178
179 if cfg!(not(target_os = "linux")) && std::env::var_os("GITHUB_ACTIONS").is_some() {
180 eprintln!("Backtrace tests fail on non-linux Github Actions runners, skipping.");
181 return;
182 }
183
184 let backtrace = Backtrace::get_backtrace(0);
185 let elements = backtrace.elements();
186
187 hydro_build_utils::assert_debug_snapshot!(elements.collect::<Vec<_>>());
188 }
189}