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();
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(feature = "build")]
39fn strip_legacy_hash_suffix(full_fn_name: &str) -> String {
40 if let Some(idx) = full_fn_name.rfind("::") {
41 let last_segment = &full_fn_name[idx + 2..];
42 if last_segment.len() == 17
43 && last_segment.starts_with('h')
44 && last_segment[1..].bytes().all(|b| b.is_ascii_hexdigit())
45 {
46 return full_fn_name[..idx].to_owned();
47 }
48 }
49 full_fn_name.to_owned()
50}
51
52#[cfg(not(feature = "build"))]
53#[derive(Clone)]
55pub struct Backtrace;
56
57#[cfg(feature = "build")]
58#[derive(Clone)]
61pub struct Backtrace {
62 skip_count: usize,
63 col_offset: usize, frames: Vec<(RefCell<Option<BacktraceFrame>>, OnceLock<BacktraceFrame>)>,
65}
66
67#[cfg(stageleft_runtime)]
68#[cfg(feature = "build")]
69#[doc(hidden)]
70pub fn __macro_get_backtrace(col_offset: usize) -> Backtrace {
71 let mut out = Backtrace::get_backtrace(1);
72 out.col_offset = col_offset;
73 out
74}
75
76#[cfg(not(feature = "build"))]
77#[doc(hidden)]
78pub fn __macro_get_backtrace(_col_offset: usize) -> Backtrace {
79 panic!();
80}
81
82impl Backtrace {
83 #[cfg(feature = "build")]
84 #[inline(never)]
85 pub(crate) fn get_backtrace(skip_count: usize) -> Backtrace {
86 let backtrace = backtrace::Backtrace::new_unresolved();
87 let frames_vec: Vec<_> = backtrace.into();
88 Backtrace {
89 skip_count,
90 col_offset: 0,
91 frames: frames_vec
92 .into_iter()
93 .map(|f| (RefCell::new(Some(f)), OnceLock::new()))
94 .collect(),
95 }
96 }
97
98 #[cfg(not(feature = "build"))]
99 pub(crate) fn get_backtrace(_skip_count: usize) -> Backtrace {
100 panic!();
101 }
102
103 #[cfg(feature = "build")]
104 pub fn elements(&self) -> impl Iterator<Item = BacktraceElement> + '_ {
110 self.frames
111 .iter()
112 .map(|(frame_refcell, resolved_frame)| {
113 resolved_frame.get_or_init(|| {
114 let mut gotten_frame = frame_refcell.borrow_mut().take().unwrap();
115 gotten_frame.resolve();
116 gotten_frame
117 })
118 })
119 .skip_while(|f| {
120 !(std::ptr::eq(f.symbol_address(), Backtrace::get_backtrace as _)
121 || f.symbols()
122 .first()
123 .and_then(|s| s.name())
124 .and_then(|n| n.as_str())
125 .is_some_and(|n| n.contains("get_backtrace")))
126 })
127 .skip(1)
128 .take_while(|f| {
129 !f.symbols()
130 .last()
131 .and_then(|s| s.name())
132 .and_then(|n| n.as_str())
133 .is_some_and(|n| n.contains("__rust_begin_short_backtrace"))
134 })
135 .flat_map(move |frame| frame.symbols())
136 .skip(self.skip_count)
137 .enumerate()
138 .map(|(idx, symbol)| {
139 let full_fn_name = strip_hash_brackets(&symbol.name().unwrap().to_string());
140 let mut element = BacktraceElement {
141 fn_name: strip_legacy_hash_suffix(&full_fn_name),
142 filename: symbol.filename().map(|f| f.display().to_string()),
143 lineno: symbol.lineno(),
144 colno: symbol.colno(),
145 addr: symbol.addr().map(|a| a as usize),
146 };
147
148 if self.col_offset > 0 && idx == 0 {
149 element.colno = element
150 .colno
151 .map(|c| c.saturating_sub(self.col_offset as u32));
152 }
153
154 element
155 })
156 }
157
158 #[cfg(feature = "build")]
159 pub fn format_span(&self) -> Option<String> {
161 let elem = self.elements().next()?;
162 let file = elem.filename.as_ref()?;
163 let line = elem.lineno?;
164 Some(format!("{file}:{line}"))
165 }
166
167 #[cfg(not(feature = "build"))]
168 pub fn format_span(&self) -> Option<String> {
170 None
171 }
172}
173
174#[cfg(feature = "build")]
175#[derive(Clone)]
177pub struct BacktraceElement {
178 pub fn_name: String,
180 pub filename: Option<String>,
182 pub lineno: Option<u32>,
184 pub colno: Option<u32>,
186 pub addr: Option<usize>,
188}
189
190#[cfg(feature = "build")]
191impl Debug for BacktraceElement {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 f.debug_struct("BacktraceElement")
195 .field("fn_name", &self.fn_name)
196 .field("lineno", &self.lineno)
197 .field("colno", &self.colno)
198 .finish()
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 #[cfg(feature = "build")]
205 #[test]
206 #[cfg_attr(not(target_os = "linux"), ignore = "backtraces only stable on Linux")]
207 fn test_backtrace() {
208 use super::*;
209
210 let backtrace = Backtrace::get_backtrace(0);
211 let elements = backtrace.elements();
212
213 hydro_build_utils::assert_debug_snapshot_unfiltered!(elements.collect::<Vec<_>>());
214 }
215}