1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::fmt::Write;
use std::path::Path;

const DEPENDENCY_FILE_PREFIXES: &[&str] = &["/rustc/", "src/libstd/", "src/libpanic_unwind/", "src/libtest/"];

const DEPENDENCY_FILE_CONTAINS: &[&str] = &["/.cargo/registry/src/"];

const DEPENDENCY_SYM_PREFIXES: &[&str] = &[
    "std::",
    "core::",
    "witcher::error::",
    "<witcher::error::",
    "witcher::backtrace::",
    "backtrace::backtrace::",
    "_rust_begin_unwind",
    "color_traceback::",
    "__rust_",
    "___rust_",
    "__pthread",
    "_main",
    "main",
    "__scrt_common_main_seh",
    "BaseThreadInitThunk",
    "_start",
    "__libc_start_main",
    "start_thread",
    "__GI__",
];

const DEPENDENCY_SYM_CONTAINS: &[&str] = &["as witcher::wrapper::Wrapper"];

// Process the given backtrace return a simplified Frame collection
pub(crate) fn new() -> Vec<Frame> {
    let bt = backtrace::Backtrace::new();

    bt.frames()
        .iter()
        .flat_map(|x| x.symbols())
        .map(|sym| Frame {
            symbol: match sym.name() {
                Some(name) => format!("{:#}", name),
                None => String::from("<unknown>"),
            },
            filename: simple_path(sym.filename()),
            lineno: sym.lineno(),
            column: sym.colno(),
        })
        .collect()
}

// Provide a convenient way to work with frame information
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Frame {
    pub symbol: String,      // name of the symbol or '<unknown>'
    pub filename: String,    // filename the symbole occurred in
    pub lineno: Option<u32>, // line number the symbol occurred on
    pub column: Option<u32>, // column number the symbol occurred on
}
impl Frame {
    // Check if this is a known rust dependency
    pub fn is_dependency(&self) -> bool {
        if DEPENDENCY_SYM_PREFIXES.iter().any(|x| self.symbol.starts_with(x))
            || DEPENDENCY_SYM_CONTAINS.iter().any(|x| self.symbol.contains(x))
            || DEPENDENCY_FILE_PREFIXES.iter().any(|x| self.filename.starts_with(x))
            || DEPENDENCY_FILE_CONTAINS.iter().any(|x| self.filename.contains(x))
        {
            return true;
        }
        false
    }
}

// Write out a shortened simplified path if possible
fn simple_path(filename: Option<&Path>) -> String {
    let mut f = String::new();
    if let Some(file) = filename {
        // Strip off the current working directory to simplify the path
        let cwd = std::env::current_dir();
        if let Ok(cwd) = &cwd {
            if let Ok(suffix) = file.strip_prefix(cwd) {
                write!(f, "{}", suffix.display()).omit();
                return f;
            }
        }
        write!(f, "{}", file.display()).omit();
    } else {
        write!(f, "<unknown>").omit();
    }
    f
}

// Helper to suppress unwanted result checks
// -------------------------------------------------------------------------------------------------
trait Omit {
    fn omit(&self);
}
impl Omit for std::fmt::Result {
    fn omit(&self) {}
}

// Unit tests
// -------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_frame_equality() {
        let mut frame1 = Frame { symbol: String::from("symbol"), filename: String::from("filename"), lineno: Some(1), column: Some(2) };

        let frame2 = Frame { symbol: String::from("symbol"), filename: String::from("filename"), lineno: Some(1), column: Some(2) };

        assert_eq!(true, frame1 == frame2);
        assert_eq!(frame1, frame2);

        frame1.lineno = Some(3);
        assert_eq!(true, frame1 != frame2);
        assert_ne!(frame1, frame2);
    }

    #[test]
    fn test_simple_path() {
        let cwd = std::env::current_dir().unwrap();
        assert_eq!("foo", simple_path(Some(Path::new(&cwd).join("foo").as_ref())));
        assert_eq!("foobar", simple_path(Some(Path::new(&cwd).join("foobar").as_ref())));
        assert_eq!("/rustc/123/src/libstd/foobar", simple_path(Some(Path::new("/rustc/123/src/libstd").join("foobar").as_ref())));
        assert_eq!("<unknown>", simple_path(None));
    }

    #[test]
    fn test_omit() {
        let mut w = String::new();
        write!(&mut w, "foobar").omit();
    }
}