Skip to content

Commit 465776c

Browse files
authored
fix(backtrace): Support newer backtrace format (#141)
Backtrace parsing for recent versions of `backtrace-rs` fails to obtain inline frames. The regular expression is now tidied up a little bit and supports both the old and new backtrace format in one go, along with inline frames. Additionally, crate name parsing was broken for the new Rust mangling schema, which no longer uses underscores in front of trait implementors and uses `::` instead of `..` for crate separators.
1 parent 3f032e2 commit 465776c

File tree

3 files changed

+98
-25
lines changed

3 files changed

+98
-25
lines changed

src/backtrace_support.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,31 @@ pub static ref WELL_KNOWN_SYS_MODULES: Vec<&'static str> = {
2121
"sentry_types::",
2222
// these are not modules but things like __rust_maybe_catch_panic
2323
"__rust_",
24+
"___rust_",
2425
];
2526
#[cfg(feature = "with_failure")] {
2627
rv.push("failure::");
2728
}
29+
#[cfg(feature = "with_log")] {
30+
rv.push("log::");
31+
}
32+
#[cfg(feature = "with_error_chain")] {
33+
rv.push("error_chain::");
34+
}
2835
rv
2936
};
3037

3138
pub static ref WELL_KNOWN_BORDER_FRAMES: Vec<&'static str> = {
3239
#[allow(unused_mut)]
3340
let mut rv = vec![
3441
"std::panicking::begin_panic",
42+
"core::panicking::panic",
3543
];
3644
#[cfg(feature = "with_failure")] {
3745
rv.push("failure::error_message::err_msg");
3846
rv.push("failure::backtrace::Backtrace::new");
47+
rv.push("failure::backtrace::internal::InternalBacktrace::new");
48+
rv.push("failure::Fail::context");
3949
}
4050
#[cfg(feature = "with_log")] {
4151
rv.push("<sentry::integrations::log::Logger as log::Log>::log");

src/client.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,13 @@ impl Default for ClientOptions {
213213
}
214214

215215
lazy_static::lazy_static! {
216-
static ref CRATE_RE: Regex = Regex::new(r"^(?:_<)?([a-zA-Z0-9_]+?)(?:\.\.|::)").unwrap();
216+
static ref CRATE_RE: Regex = Regex::new(r#"(?x)
217+
^
218+
(?:_?<)? # trait impl syntax
219+
(?:\w+\ as \ )? # anonymous implementor
220+
([a-zA-Z0-9_]+?) # crate name
221+
(?:\.\.|::) # crate delimiter (.. or ::)
222+
"#).unwrap();
217223
}
218224

219225
/// Tries to parse the rust crate from a function name.
@@ -680,10 +686,23 @@ mod tests {
680686
}
681687

682688
#[test]
683-
fn test_parse_crate_name_unknown() {
689+
fn test_parse_crate_name_anonymous_impl() {
684690
assert_eq!(
685691
parse_crate_name("_<F as alloc..boxed..FnBox<A>>::call_box"),
686-
None
692+
Some("alloc".into())
693+
);
694+
}
695+
696+
#[test]
697+
fn test_parse_crate_name_none() {
698+
assert_eq!(parse_crate_name("main"), None);
699+
}
700+
701+
#[test]
702+
fn test_parse_crate_name_newstyle() {
703+
assert_eq!(
704+
parse_crate_name("<failure::error::Error as core::convert::From<F>>::from"),
705+
Some("failure".into())
687706
);
688707
}
689708
}

src/integrations/failure.rs

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,24 @@ lazy_static::lazy_static! {
4040
static ref FRAME_RE: Regex = Regex::new(
4141
r#"(?xm)
4242
^
43-
[\ ]*(?:\d+:)[\ ]* # leading frame number
43+
\s*(?:\d+:)?\s* # frame number (missing for inline)
44+
45+
(?:
46+
(?P<addr_old>0x[a-f0-9]+) # old style address prefix
47+
\s-\s
48+
)?
49+
50+
(?P<symbol>[^\r\n\(]+) # symbol name
51+
4452
(?:
45-
(?P<addr_oldsyntax>0x[a-f0-9]+) # addr
46-
[\ ]-[\ ]
47-
(?P<symbol_oldsyntax>[^\r\n]+)
48-
|
49-
(?P<symbol>[^\r\n]+)
50-
\((?P<addr>0x[a-f0-9]+)\) # addr
51-
)
53+
\s\((?P<addr_new>0x[a-f0-9]+)\) # new style address in parens
54+
)?
55+
5256
(?:
5357
\r?\n
54-
[\ \t]+at[\ ]
55-
(?P<path>[^\r\n]+?)
56-
(?::(?P<lineno>\d+))?
58+
\s+at\s # padded "at" in new line
59+
(?P<path>[^\r\n]+?) # path to source file
60+
(?::(?P<lineno>\d+))? # optional source line
5761
)?
5862
$
5963
"#
@@ -62,31 +66,33 @@ lazy_static::lazy_static! {
6266
}
6367

6468
fn parse_stacktrace(bt: &str) -> Option<Stacktrace> {
69+
let mut last_address = None;
70+
6571
let frames = FRAME_RE
6672
.captures_iter(&bt)
6773
.map(|captures| {
6874
let abs_path = captures.name("path").map(|m| m.as_str().to_string());
6975
let filename = abs_path.as_ref().map(|p| filename(p));
70-
let real_symbol = captures
71-
.name("symbol")
72-
.map_or_else(|| &captures["symbol_oldsyntax"], |m| m.as_str())
73-
.to_string();
76+
let real_symbol = captures["symbol"].to_string();
7477
let symbol = strip_symbol(&real_symbol);
7578
let function = demangle_symbol(symbol);
79+
80+
// Obtain the instruction address. A missing address usually indicates an inlined stack
81+
// frame, in which case the previous address needs to be used.
82+
last_address = captures
83+
.name("addr_new")
84+
.or_else(|| captures.name("addr_old"))
85+
.and_then(|m| m.as_str().parse().ok())
86+
.or(last_address);
87+
7688
Frame {
7789
symbol: if symbol != function {
7890
Some(symbol.into())
7991
} else {
8092
None
8193
},
8294
function: Some(function),
83-
instruction_addr: Some(
84-
captures
85-
.name("addr")
86-
.map_or_else(|| &captures["addr_oldsyntax"], |m| m.as_str())
87-
.parse()
88-
.unwrap(),
89-
),
95+
instruction_addr: last_address,
9096
abs_path,
9197
filename,
9298
lineno: captures
@@ -231,3 +237,41 @@ where
231237
}
232238
}
233239
}
240+
241+
#[test]
242+
fn test_parse_stacktrace() {
243+
use crate::protocol::Addr;
244+
245+
let backtrace = r#"
246+
2: <failure::error::error_impl::ErrorImpl as core::convert::From<F>>::from::h3bae66c036570137 (0x55a12174de62)
247+
at /root/.cargo/registry/src/github.com-1ecc6299db9ec823/failure-0.1.5/src/error/error_impl.rs:19
248+
<failure::error::Error as core::convert::From<F>>::from::hc7d0d62dae166cea
249+
at /root/.cargo/registry/src/github.com-1ecc6299db9ec823/failure-0.1.5/src/error/mod.rs:36
250+
failure::error_message::err_msg::he322d3ed9409189a
251+
at /root/.cargo/registry/src/github.com-1ecc6299db9ec823/failure-0.1.5/src/error_message.rs:12
252+
rust::inline2::h562e5687710b6a71
253+
at src/main.rs:5
254+
rust::not_inline::h16f5b6019e5f0815
255+
at src/main.rs:10
256+
7: main (0x55e3895a4dc7)
257+
"#;
258+
259+
let stacktrace = parse_stacktrace(backtrace).expect("stacktrace");
260+
assert_eq!(stacktrace.frames.len(), 6);
261+
262+
assert_eq!(stacktrace.frames[0].function, Some("main".into()));
263+
assert_eq!(
264+
stacktrace.frames[0].instruction_addr,
265+
Some(Addr(0x55e3_895a_4dc7))
266+
);
267+
268+
// Inlined frame, inherits address from parent
269+
assert_eq!(
270+
stacktrace.frames[1].function,
271+
Some("rust::not_inline".into())
272+
);
273+
assert_eq!(
274+
stacktrace.frames[1].instruction_addr,
275+
Some(Addr(0x55a1_2174_de62))
276+
);
277+
}

0 commit comments

Comments
 (0)