hydro_lang/compile/ir/
backtrace.rs

1//! Platform-independent interface for collecting backtraces, used in the Hydro IR to
2//! trace the origin of each node.
3
4#[cfg(feature = "build")]
5use std::cell::RefCell;
6#[cfg(feature = "build")]
7use std::fmt::Debug;
8
9#[cfg(not(feature = "build"))]
10/// A dummy backtrace element with no data. Enable the `build` feature to collect backtraces.
11#[derive(Clone)]
12pub struct Backtrace;
13
14#[cfg(feature = "build")]
15/// Captures an entire backtrace, whose elements will be lazily resolved. See
16/// [`Backtrace::elements`] for more information.
17#[derive(Clone)]
18pub struct Backtrace {
19    skip_count: usize,
20    col_offset: usize, // whether this is from `sliced!` which requires an offset
21    inner: RefCell<backtrace::Backtrace>,
22    resolved: RefCell<Option<Vec<BacktraceElement>>>,
23}
24
25#[cfg(stageleft_runtime)]
26#[cfg(feature = "build")]
27#[doc(hidden)]
28pub fn __macro_get_backtrace(col_offset: usize) -> Backtrace {
29    let mut out = Backtrace::get_backtrace(1);
30    out.col_offset = col_offset;
31    out
32}
33
34#[cfg(not(feature = "build"))]
35#[doc(hidden)]
36pub fn __macro_get_backtrace(_col_offset: usize) -> Backtrace {
37    panic!();
38}
39
40impl Backtrace {
41    #[cfg(feature = "build")]
42    #[inline(never)]
43    pub(crate) fn get_backtrace(skip_count: usize) -> Backtrace {
44        let backtrace = backtrace::Backtrace::new_unresolved();
45        Backtrace {
46            skip_count,
47            col_offset: 0,
48            inner: RefCell::new(backtrace),
49            resolved: RefCell::new(None),
50        }
51    }
52
53    #[cfg(not(feature = "build"))]
54    pub(crate) fn get_backtrace(_skip_count: usize) -> Backtrace {
55        panic!();
56    }
57
58    /// Gets the elements of the backtrace including inlined frames.
59    ///
60    /// Excludes all backtrace elements up to the original `get_backtrace` call as
61    /// well as additional skipped frames from that call. Also drops the suffix
62    /// of frames from `__rust_begin_short_backtrace` onwards.
63    #[cfg(feature = "build")]
64    pub fn elements(&self) -> Vec<BacktraceElement> {
65        self.resolved
66            .borrow_mut()
67            .get_or_insert_with(|| {
68                let mut inner_borrow = self.inner.borrow_mut();
69                inner_borrow.resolve();
70                let mut collected: Vec<_> = inner_borrow
71                    .frames()
72                    .iter()
73                    .skip_while(|f| {
74                        !(std::ptr::eq(f.symbol_address(), Backtrace::get_backtrace as _)
75                            || f.symbols()
76                                .first()
77                                .and_then(|s| s.name())
78                                .and_then(|n| n.as_str())
79                                .is_some_and(|n| n.contains("get_backtrace")))
80                    })
81                    .skip(1)
82                    .take_while(|f| {
83                        !f.symbols()
84                            .last()
85                            .and_then(|s| s.name())
86                            .and_then(|n| n.as_str())
87                            .is_some_and(|n| n.contains("__rust_begin_short_backtrace"))
88                    })
89                    .flat_map(|frame| frame.symbols())
90                    .skip(self.skip_count)
91                    .map(|symbol| {
92                        let full_fn_name = symbol.name().unwrap().to_string();
93                        BacktraceElement {
94                            fn_name: full_fn_name
95                                .rfind("::")
96                                .map(|idx| full_fn_name.split_at(idx).0.to_string())
97                                .unwrap_or(full_fn_name),
98                            filename: symbol.filename().map(|f| f.display().to_string()),
99                            lineno: symbol.lineno(),
100                            colno: symbol.colno(),
101                            addr: symbol.addr().map(|a| a as usize),
102                        }
103                    })
104                    .collect();
105
106                if self.col_offset > 0
107                    && let Some(first) = collected.first_mut()
108                {
109                    first.colno = first
110                        .colno
111                        .map(|c| c.saturating_sub(self.col_offset as u32));
112                }
113
114                collected
115            })
116            .clone()
117    }
118}
119
120#[cfg(feature = "build")]
121/// A single frame of a backtrace, corresponding to a single function call.
122#[derive(Clone)]
123pub struct BacktraceElement {
124    /// The name of the function that was called.
125    pub fn_name: String,
126    /// The path to the file where this call occured.
127    pub filename: Option<String>,
128    /// The line number of the function call.
129    pub lineno: Option<u32>,
130    /// The column number of the function call.
131    pub colno: Option<u32>,
132    /// The address of the instruction corresponding to this function call.
133    pub addr: Option<usize>,
134}
135
136#[cfg(feature = "build")]
137impl Debug for BacktraceElement {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        // filename / addr is unstable across platforms so we drop it
140        f.debug_struct("BacktraceElement")
141            .field("fn_name", &self.fn_name)
142            .field("lineno", &self.lineno)
143            .field("colno", &self.colno)
144            .finish()
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    #[cfg(feature = "build")]
151    use super::*;
152
153    #[cfg(feature = "build")]
154    #[test]
155    fn test_backtrace() {
156        if cfg!(not(target_os = "linux")) && std::env::var_os("GITHUB_ACTIONS").is_some() {
157            eprintln!("Backtrace tests fail on non-linux Github Actions runners, skipping.");
158            return;
159        }
160
161        let backtrace = Backtrace::get_backtrace(0);
162        let elements = backtrace.elements();
163
164        hydro_build_utils::assert_debug_snapshot!(elements);
165    }
166}