Prune backtraces similar to RUST_BACKTRACE=1 logic

Previously, Miri would always print a backtrace including all frames
when encountering an error. This adds -Zmiri-backtrace which defaults
to 1, internally called BacktraceStyle::Short. By default, backtraces
are pruned to start at __rust_begin_short_backtrace, similar to std.
Then we also remove non-local frames from the bottom of the trace.
This cleans up the last one or two shims outside main or a test.

Users can opt out of pruning by setting -Zmiri-backtrace=full, and will
be automatically opted out if there are no local frames because that
means the reported error is likely in the Rust runtime, which this
pruning is crafted to remove.
This commit is contained in:
Ben Kimock 2022-02-20 11:55:39 -05:00
parent 2b0078e455
commit 19ecd130b5
5 changed files with 76 additions and 2 deletions

View File

@ -29,6 +29,8 @@ use rustc_middle::{
}; };
use rustc_session::{config::ErrorOutputType, search_paths::PathKind, CtfeBacktrace}; use rustc_session::{config::ErrorOutputType, search_paths::PathKind, CtfeBacktrace};
use miri::BacktraceStyle;
struct MiriCompilerCalls { struct MiriCompilerCalls {
miri_config: miri::MiriConfig, miri_config: miri::MiriConfig,
} }
@ -462,6 +464,14 @@ fn main() {
let measureme_out = arg.strip_prefix("-Zmiri-measureme=").unwrap(); let measureme_out = arg.strip_prefix("-Zmiri-measureme=").unwrap();
miri_config.measureme_out = Some(measureme_out.to_string()); miri_config.measureme_out = Some(measureme_out.to_string());
} }
arg if arg.starts_with("-Zmiri-backtrace=") => {
miri_config.backtrace_style = match arg.strip_prefix("-Zmiri-backtrace=") {
Some("0") => BacktraceStyle::Off,
Some("1") => BacktraceStyle::Short,
Some("full") => BacktraceStyle::Full,
_ => panic!("-Zmiri-backtrace may only be 0, 1, or full"),
};
}
_ => { _ => {
// Forward to rustc. // Forward to rustc.
rustc_args.push(arg); rustc_args.push(arg);

View File

@ -157,6 +157,46 @@ pub fn report_error<'tcx, 'mir>(
} }
}; };
let mut stacktrace = ecx.generate_stacktrace();
let has_local_frame = stacktrace.iter().any(|frame| frame.instance.def_id().is_local());
match ecx.machine.backtrace_style {
BacktraceStyle::Off => {
// Retain one frame so that we can print a span for the error itself
stacktrace.truncate(1);
}
BacktraceStyle::Short => {
// Only prune frames if there is at least one local frame. This check ensures that if
// we get a backtrace that never makes it to the user code because it has detected a
// bug in the Rust runtime, we don't prune away every frame.
if has_local_frame {
// This is part of the logic that `std` uses to select the relevant part of a
// backtrace. But here, we only look for __rust_begin_short_backtrace, not
// __rust_end_short_backtrace because the end symbol comes from a call to the default
// panic handler.
stacktrace = stacktrace
.into_iter()
.take_while(|frame| {
let def_id = frame.instance.def_id();
let path = ecx.tcx.tcx.def_path_str(def_id);
!path.contains("__rust_begin_short_backtrace")
})
.collect::<Vec<_>>();
// After we prune frames from the bottom, there are a few left that are part of the
// Rust runtime. So we remove frames until we get to a local symbol, which should be
// main or a test.
// This len check ensures that we don't somehow remove every frame, as doing so breaks
// the primary error message.
while stacktrace.len() > 1
&& stacktrace.last().map_or(false, |e| !e.instance.def_id().is_local())
{
stacktrace.pop();
}
}
}
BacktraceStyle::Full => {}
}
e.print_backtrace(); e.print_backtrace();
let msg = e.to_string(); let msg = e.to_string();
report_msg( report_msg(
@ -165,9 +205,17 @@ pub fn report_error<'tcx, 'mir>(
&if let Some(title) = title { format!("{}: {}", title, msg) } else { msg.clone() }, &if let Some(title) = title { format!("{}: {}", title, msg) } else { msg.clone() },
msg, msg,
helps, helps,
&ecx.generate_stacktrace(), &stacktrace,
); );
// Include a note like `std` does for short backtraces, but since we are opt-out not opt-in, we
// do not include a note when backtraces are off.
if ecx.machine.backtrace_style == BacktraceStyle::Short && has_local_frame {
ecx.tcx.sess.diagnostic().note_without_error(
"some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
);
}
// Debug-dump all locals. // Debug-dump all locals.
for (i, frame) in ecx.active_thread_stack().iter().enumerate() { for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
trace!("-------------------"); trace!("-------------------");

View File

@ -57,6 +57,16 @@ pub enum IsolatedOp {
Allow, Allow,
} }
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum BacktraceStyle {
/// Prints a terser backtrace which ideally only contains relevant information.
Short,
/// Prints a backtrace with all possible information.
Full,
/// Prints only the frame that the error occurs in.
Off,
}
/// Configuration needed to spawn a Miri instance. /// Configuration needed to spawn a Miri instance.
#[derive(Clone)] #[derive(Clone)]
pub struct MiriConfig { pub struct MiriConfig {
@ -98,6 +108,7 @@ pub struct MiriConfig {
pub measureme_out: Option<String>, pub measureme_out: Option<String>,
/// Panic when unsupported functionality is encountered /// Panic when unsupported functionality is encountered
pub panic_on_unsupported: bool, pub panic_on_unsupported: bool,
pub backtrace_style: BacktraceStyle,
} }
impl Default for MiriConfig { impl Default for MiriConfig {
@ -121,6 +132,7 @@ impl Default for MiriConfig {
cmpxchg_weak_failure_rate: 0.8, cmpxchg_weak_failure_rate: 0.8,
measureme_out: None, measureme_out: None,
panic_on_unsupported: false, panic_on_unsupported: false,
backtrace_style: BacktraceStyle::Short,
} }
} }
} }

View File

@ -60,7 +60,7 @@ pub use crate::diagnostics::{
NonHaltingDiagnostic, TerminationInfo, NonHaltingDiagnostic, TerminationInfo,
}; };
pub use crate::eval::{ pub use crate::eval::{
create_ecx, eval_entry, AlignmentCheck, IsolatedOp, MiriConfig, RejectOpWith, create_ecx, eval_entry, AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, RejectOpWith,
}; };
pub use crate::helpers::EvalContextExt as HelpersEvalContextExt; pub use crate::helpers::EvalContextExt as HelpersEvalContextExt;
pub use crate::machine::{ pub use crate::machine::{

View File

@ -343,6 +343,9 @@ pub struct Evaluator<'mir, 'tcx> {
/// functionality is encountered. If `false`, an error is propagated in the Miri application context /// functionality is encountered. If `false`, an error is propagated in the Miri application context
/// instead (default behavior) /// instead (default behavior)
pub(crate) panic_on_unsupported: bool, pub(crate) panic_on_unsupported: bool,
/// Equivalent setting as RUST_BACKTRACE on encountering an error.
pub(crate) backtrace_style: BacktraceStyle,
} }
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> { impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
@ -374,6 +377,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
string_cache: Default::default(), string_cache: Default::default(),
exported_symbols_cache: FxHashMap::default(), exported_symbols_cache: FxHashMap::default(),
panic_on_unsupported: config.panic_on_unsupported, panic_on_unsupported: config.panic_on_unsupported,
backtrace_style: config.backtrace_style,
} }
} }