chj_util/
partialbacktrace.rs

1//! Wrapper around the `backtrace` crate to show only part of the
2//! stack frames (skip some at the beginning and end).
3
4use std::fmt::Write;
5
6use backtrace::Backtrace;
7
8
9pub struct PartialBacktrace {
10    bt: Backtrace
11}
12
13// Cut away last part from e.g.
14//  "website::ahtml::HtmlAllocator::new_element::h63d71c1114df562b"
15fn cut_hex(mut s: String) -> String {
16    let err = |s, _msg| -> String {
17        // warn!("could not cut end of {s:?}: {msg}");
18        // Happens for "__GI___clone3", "start_thread"
19        s
20    };
21    let mut cs = s.char_indices().rev();
22    while let Some((_, c)) = cs.next() {
23        if ! c.is_ascii_hexdigit() {
24            if c != 'h' { return err(s, "expecting 'h' left of hex digits") }
25            if let Some((_, c)) = cs.next() {
26                if c != ':' { return err(s, "expecting ':' left of 'h'") }
27                if let Some((pos, c)) = cs.next() {
28                    if c != ':' { return err(s, "expecting ':' left of 'h'") }
29                    s.truncate(pos);
30                    return s
31                } else {
32                    return err(s, "premature end left of ':'")
33                }
34            } else {
35                return err(s, "expecting :: left of 'h'")
36            }
37        }
38    }
39    return err(s, "string ends early left of hex digits")
40}
41
42impl PartialBacktrace {
43    pub fn new() -> Self {
44        Self { bt: Backtrace::new() }
45    }
46
47    /// Show the stack frames after the first `skip` ones, until
48    /// reaching one (excluding it) that refers to a file with a path
49    /// that ends in `end_file`.
50    pub fn part_to_string(&self, skip: usize, end_file: &str) -> String {
51        let mut bt_str = String::new();
52        let frames = &self.bt.frames()[skip..];
53        let mut frameno = 0; // starts counting after the skipped area
54        'outer: for frame in frames.iter() {
55            let mut subframeno = 0;
56            for sym in frame.symbols() {
57                // Have to reimplement everything as Backtrace's frames
58                // don't have the formatting code, only Backtrace as a
59                // whole has.
60                if let Some(path) = sym.filename() {
61                    let p = path.to_string_lossy();
62                    if p.ends_with(end_file) {
63                        break 'outer;
64                    }
65                    let name = sym.name().map(|s| cut_hex(s.to_string()))
66                        .unwrap_or_else(|| " XX missing name ".into());
67                    if subframeno == 0 {
68                        write!(&mut bt_str, "{frameno:4}").unwrap();
69                    } else {
70                        bt_str.push_str("      ");
71                    }
72                    let indent_at = "             at ";
73                    write!(&mut bt_str, ": {name}\n\
74                                         {indent_at}{p}").unwrap();
75                    if let Some(line) = sym.lineno() {
76                        write!(&mut bt_str, ":{line}").unwrap();
77                        if let Some(col) = sym.colno() {
78                            write!(&mut bt_str, ":{col}").unwrap();
79                        }
80                    }
81                    bt_str.push('\n');
82                    subframeno += 1;
83                }
84            }
85            frameno += 1;
86        }
87        writeln!(&mut bt_str, " ({frameno}..{} skipped)",
88                 frames.len() - 1).unwrap();
89        bt_str
90    }
91}