hydro_lang/
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    inner: RefCell<backtrace::Backtrace>,
21    resolved: RefCell<Option<Vec<BacktraceElement>>>,
22}
23
24impl Backtrace {
25    #[cfg(feature = "build")]
26    #[inline(never)]
27    pub(crate) fn get_backtrace(skip_count: usize) -> Backtrace {
28        let backtrace = backtrace::Backtrace::new_unresolved();
29        Backtrace {
30            skip_count,
31            inner: RefCell::new(backtrace),
32            resolved: RefCell::new(None),
33        }
34    }
35
36    #[cfg(not(feature = "build"))]
37    pub(crate) fn get_backtrace(_skip_count: usize) -> Backtrace {
38        panic!();
39    }
40
41    #[cfg(feature = "build")]
42    /// Gets the elements of the backtrace including inlined frames.
43    ///
44    /// Excludes all backtrace elements up to the original `get_backtrace` call as
45    /// well as additional skipped frames from that call. Also drops the suffix
46    /// of frames from `__rust_begin_short_backtrace` onwards.
47    pub fn elements(&self) -> Vec<BacktraceElement> {
48        self.resolved
49            .borrow_mut()
50            .get_or_insert_with(|| {
51                let mut inner_borrow = self.inner.borrow_mut();
52                inner_borrow.resolve();
53                inner_borrow
54                    .frames()
55                    .iter()
56                    .skip_while(|f| {
57                        !(f.symbol_address() as usize == Backtrace::get_backtrace as usize
58                            || f.symbols()
59                                .first()
60                                .and_then(|s| s.name())
61                                .and_then(|n| n.as_str())
62                                .is_some_and(|n| n.contains("get_backtrace")))
63                    })
64                    .skip(1)
65                    .take_while(|f| {
66                        !f.symbols()
67                            .last()
68                            .and_then(|s| s.name())
69                            .and_then(|n| n.as_str())
70                            .is_some_and(|n| n.contains("__rust_begin_short_backtrace"))
71                    })
72                    .flat_map(|frame| frame.symbols())
73                    .skip(self.skip_count)
74                    .map(|symbol| {
75                        let full_fn_name = symbol.name().unwrap().to_string();
76                        BacktraceElement {
77                            fn_name: full_fn_name
78                                .rfind("::")
79                                .map(|idx| full_fn_name.split_at(idx).0.to_string())
80                                .unwrap_or(full_fn_name),
81                            filename: symbol.filename().map(|f| f.display().to_string()),
82                            lineno: symbol.lineno(),
83                            colno: symbol.colno(),
84                            addr: symbol.addr().map(|a| a as usize),
85                        }
86                    })
87                    .collect()
88            })
89            .clone()
90    }
91}
92
93#[cfg(feature = "build")]
94/// A single frame of a backtrace, corresponding to a single function call.
95#[derive(Clone)]
96pub struct BacktraceElement {
97    /// The name of the function that was called.
98    pub fn_name: String,
99    /// The path to the file where this call occured.
100    pub filename: Option<String>,
101    /// The line number of the function call.
102    pub lineno: Option<u32>,
103    /// The column number of the function call.
104    pub colno: Option<u32>,
105    /// The address of the instruction corresponding to this function call.
106    pub addr: Option<usize>,
107}
108
109#[cfg(feature = "build")]
110impl Debug for BacktraceElement {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        // filename / addr is unstable across platforms so we drop it
113        f.debug_struct("BacktraceElement")
114            .field("fn_name", &self.fn_name)
115            .field("lineno", &self.lineno)
116            .field("colno", &self.colno)
117            .finish()
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    #[cfg(unix)]
124    use super::*;
125
126    #[cfg(unix)]
127    #[test]
128    fn test_backtrace() {
129        let backtrace = Backtrace::get_backtrace(0);
130        let elements = backtrace.elements();
131
132        hydro_build_utils::assert_debug_snapshot!(elements);
133    }
134}