Skip to content

Filter backtrace #48702

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ version.ml
version.texi
.cargo
!src/vendor/**
/src/target/

no_llvm_build

128 changes: 98 additions & 30 deletions src/libstd/sys_common/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,14 @@ fn _print(w: &mut Write, format: PrintFormat) -> io::Result<()> {
inline_context: 0,
}; MAX_NB_FRAMES];
let (nb_frames, context) = unwind_backtrace(&mut frames)?;
let (skipped_before, skipped_after) =
filter_frames(&frames[..nb_frames], format, &context);
if skipped_before + skipped_after > 0 {
let filtered_frames = filter_frames(&frames[..nb_frames], &context, format);
if format != PrintFormat::Full {
writeln!(w, "note: Some details are omitted, \
run with `RUST_BACKTRACE=full` for a verbose backtrace.")?;
}
writeln!(w, "stack backtrace:")?;

let filtered_frames = &frames[..nb_frames - skipped_after];
for (index, frame) in filtered_frames.iter().skip(skipped_before).enumerate() {
for (index, frame) in filtered_frames {
resolve_symname(*frame, |symname| {
output(w, index, *frame, symname, format)
}, &context)?;
Expand All @@ -93,40 +91,110 @@ fn _print(w: &mut Write, format: PrintFormat) -> io::Result<()> {
Ok(())
}

/// Returns a number of frames to remove at the beginning and at the end of the
/// backtrace, according to the backtrace format.
fn filter_frames(frames: &[Frame],
format: PrintFormat,
context: &BacktraceContext) -> (usize, usize)
fn should_show_frame(frame: &Frame, context: &BacktraceContext) -> bool {
const FILTERED_SYMBOLS: &[&str] = &[
"main",
"rust_begin_unwind",
"__rust_maybe_catch_panic"
];
const FILTERED_SYMBOL_PARTS: &[&str] = &[
"_ZN4core9panicking",
"_ZN3std9panicking",
"_ZN3std3sys",
"_ZN3std10sys_common",
"_ZN3std2rt",
];
let mut should_show = true;
let _ = resolve_symname(*frame, |symname| {
if let Some(mangled_symbol_name) = symname {
for filtered_symbol in FILTERED_SYMBOLS {
if mangled_symbol_name == *filtered_symbol {
should_show = false;
return Ok(());
}
}
for filtered_symbol_part in FILTERED_SYMBOL_PARTS {
if mangled_symbol_name.starts_with(filtered_symbol_part) {
should_show = false;
return Ok(());
}
}
}
Ok(())
}, context);
should_show
}

/// Returns the frames to show as a Vec.
/// If the bool is true the frame is on the edge between showing and not showing.
fn filter_frames<'a>(frames: &'a [Frame],
context: &'a BacktraceContext,
format: PrintFormat) -> impl Iterator<Item=(usize, &'a Frame)> + 'a
{
if format == PrintFormat::Full {
return (0, 0);
return FilterFrames::Pass(frames.iter().enumerate());
}

let skipped_before = 0;

let skipped_after = frames.len() - frames.iter().position(|frame| {
let mut is_marker = false;
let _ = resolve_symname(*frame, |symname| {
if let Some(mangled_symbol_name) = symname {
// Use grep to find the concerned functions
if mangled_symbol_name.contains("__rust_begin_short_backtrace") {
is_marker = true;
let frames_iter = frames
.iter()
.take_while(move |frame| {
let mut is_after_begin_short_backtrace = false;
let _ = resolve_symname(**frame, |symname| {
if let Some(mangled_symbol_name) = symname {
// Use grep to find the concerned functions
if mangled_symbol_name.contains("__rust_begin_short_backtrace") {
is_after_begin_short_backtrace = true;
}
}
}
Ok(())
}, context);
is_marker
}).unwrap_or(frames.len());

if skipped_before + skipped_after >= frames.len() {
// Avoid showing completely empty backtraces
return (0, 0);
Ok(())
}, context);
!is_after_begin_short_backtrace
})
.enumerate()
.peekable();

FilterFrames::Filter {
show_prev_frame: false,
context: context,
iter: frames_iter,
}
}

(skipped_before, skipped_after)
enum FilterFrames<'a, I: ::iter::Iterator<Item=(usize, &'a Frame)>> {
Filter {
show_prev_frame: bool,
context: &'a BacktraceContext,
iter: ::iter::Peekable<I>,
},
Pass(::iter::Enumerate<::slice::Iter<'a, Frame>>),
}

impl<'a, I: ::iter::Iterator<Item=(usize, &'a Frame)>> ::iter::Iterator for FilterFrames<'a, I> {
type Item = (usize, &'a Frame);

fn next(&mut self) -> Option<(usize, &'a Frame)> {
match *self {
FilterFrames::Filter { ref mut show_prev_frame, ref mut context, ref mut iter } => {
while let Some((i, frame)) = iter.next() {
let show_cur_frame = should_show_frame(frame, context);
let show_next_frame = iter
.peek()
.map(|&(_, frame)| should_show_frame(frame, context))
.unwrap_or(false);
if *show_prev_frame || show_cur_frame || show_next_frame {
*show_prev_frame = show_cur_frame;
return Some((i, frame));
}
*show_prev_frame = show_cur_frame;
}
None
}
FilterFrames::Pass(ref mut iter) => {
iter.next()
}
}
}
}

/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
#[inline(never)]
Expand Down
23 changes: 23 additions & 0 deletions src/test/run-make/filter_backtrace/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-include ../tools.mk

# Test that short backtraces don't include several internal symbols

all:
$(RUSTC) main.rs -o $(TMPDIR)/main
# with short backtrace
RUST_BACKTRACE=1 $(TMPDIR)/main 2> $(TMPDIR)/short_bt.stderr || exit 0 && exit 1
cat $(TMPDIR)/short_bt.stderr # FIXME: remove this debug cat
$(CGREP) "panicked at" < $(TMPDIR)/short_bt.stderr
$(CGREP) -v "std::panicking" < $(TMPDIR)/short_bt.stderr
$(CGREP) -v "std::sys" < $(TMPDIR)/short_bt.stderr
$(CGREP) -v "__rust_maybe_catch_panic" < $(TMPDIR)/short_bt.stderr
$(CGREP) -v "__rust_begin_short_backtrace" < $(TMPDIR)/short_bt.stderr
# with long backtrace
RUST_BACKTRACE=full $(TMPDIR)/main 2> $(TMPDIR)/long_bt.stderr || exit 0 && exit 1
cat $(TMPDIR)/long_bt.stderr # FIXME: remove this debug cat
$(CGREP) "panicked at" < $(TMPDIR)/long_bt.stderr
$(CGREP) "std::panicking" < $(TMPDIR)/long_bt.stderr
$(CGREP) "std::sys" < $(TMPDIR)/long_bt.stderr
$(CGREP) "__rust_maybe_catch_panic" < $(TMPDIR)/long_bt.stderr
# FIXME: prevent tail call optimization for this
$(CGREP) -v "__rust_begin_short_backtrace" < $(TMPDIR)/long_bt.stderr
13 changes: 13 additions & 0 deletions src/test/run-make/filter_backtrace/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

fn main() {
(None as Option<()>).unwrap();
}
2 changes: 1 addition & 1 deletion src/test/run-pass/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fn expected(fn_name: &str) -> String {

fn runtest(me: &str) {
// Make sure that the stack trace is printed
let p = template(me).arg("fail").env("RUST_BACKTRACE", "1").spawn().unwrap();
let p = template(me).arg("fail").env("RUST_BACKTRACE", "full").spawn().unwrap();
let out = p.wait_with_output().unwrap();
assert!(!out.status.success());
let s = str::from_utf8(&out.stderr).unwrap();
Expand Down