Skip to content

give some context in error messages #1250

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

Merged
merged 4 commits into from
Mar 22, 2020
Merged
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
142 changes: 97 additions & 45 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,75 +1,127 @@
use rustc_mir::interpret::InterpErrorInfo;
use std::cell::RefCell;

use rustc_span::DUMMY_SP;

use crate::*;

/// Details of premature program termination.
pub enum TerminationInfo {
Exit(i64),
Abort(Option<String>),
UnsupportedInIsolation(String),
ExperimentalUb { msg: String, url: String }
}

/// Miri specific diagnostics
pub enum NonHaltingDiagnostic {
PoppedTrackedPointerTag(Item),
CreatedAlloc(AllocId),
}

/// Emit a custom diagnostic without going through the miri-engine machinery
pub fn report_diagnostic<'tcx, 'mir>(
pub fn report_error<'tcx, 'mir>(
ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
mut e: InterpErrorInfo<'tcx>,
) -> Option<i64> {
// Special treatment for some error kinds
let msg = match e.kind {
InterpError::MachineStop(ref info) => {
use InterpError::*;

e.print_backtrace();
let (title, msg, helps) = match e.kind {
MachineStop(info) => {
let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
match info {
TerminationInfo::Exit(code) => return Some(*code),
TerminationInfo::Abort(None) => format!("the evaluated program aborted execution"),
TerminationInfo::Abort(Some(msg)) => format!("the evaluated program aborted execution: {}", msg),
}
use TerminationInfo::*;
let (title, msg) = match info {
Exit(code) => return Some(*code),
Abort(None) =>
("abnormal termination", format!("the evaluated program aborted execution")),
Abort(Some(msg)) =>
("abnormal termination", format!("the evaluated program aborted execution: {}", msg)),
UnsupportedInIsolation(msg) =>
("unsupported operation", format!("{}", msg)),
ExperimentalUb { msg, .. } =>
("Undefined Behavior", format!("{}", msg)),
};
let helps = match info {
UnsupportedInIsolation(_) =>
vec![format!("pass the flag `-Zmiri-disable-isolation` to disable isolation")],
ExperimentalUb { url, .. } =>
vec![
format!("this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental"),
format!("see {} for further information", url),
],
_ => vec![],
};
(title, msg, helps)
}
_ => {
let (title, msg) = match e.kind {
Unsupported(_) =>
("unsupported operation", e.to_string()),
UndefinedBehavior(_) =>
("Undefined Behavior", e.to_string()),
ResourceExhaustion(_) =>
("resource exhaustion", e.to_string()),
_ =>
bug!("This error should be impossible in Miri: {}", e),
};
let helps = match e.kind {
Unsupported(UnsupportedOpInfo::NoMirFor(..)) =>
vec![format!("make sure to use a Miri sysroot, which you can prepare with `cargo miri setup`")],
Unsupported(_) =>
vec![format!("this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support")],
UndefinedBehavior(_) =>
vec![
format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"),
format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"),
],
_ => vec![],
};
(title, msg, helps)
}
err_unsup!(NoMirFor(..)) => format!(
"{}. Did you set `MIRI_SYSROOT` to a Miri-enabled sysroot? You can prepare one with `cargo miri setup`.",
e
),
InterpError::InvalidProgram(_) => bug!("This error should be impossible in Miri: {}", e),
_ => e.to_string(),
};
e.print_backtrace();
report_msg(ecx, msg, true)
report_msg(ecx, &format!("{}: {}", title, msg), msg, &helps, true)
}

/// Report an error or note (depending on the `error` argument) at the current frame's current statement.
/// Also emits a full stacktrace of the interpreter stack.
pub fn report_msg<'tcx, 'mir>(
fn report_msg<'tcx, 'mir>(
ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
msg: String,
title: &str,
span_msg: String,
helps: &[String],
error: bool,
) -> Option<i64> {
if let Some(frame) = ecx.stack().last() {
let span = frame.current_source_info().unwrap().span;

let mut err = if error {
let msg = format!("Miri evaluation error: {}", msg);
ecx.tcx.sess.struct_span_err(span, msg.as_str())
let span = if let Some(frame) = ecx.stack().last() {
frame.current_source_info().unwrap().span
} else {
DUMMY_SP
};
let mut err = if error {
ecx.tcx.sess.struct_span_err(span, title)
} else {
ecx.tcx.sess.diagnostic().span_note_diag(span, title)
};
err.span_label(span, span_msg);
for help in helps {
err.help(help);
}
// Add backtrace
let frames = ecx.generate_stacktrace(None);
// We iterate with indices because we need to look at the next frame (the caller).
for idx in 0..frames.len() {
let frame_info = &frames[idx];
let call_site_is_local = frames
.get(idx + 1)
.map_or(false, |caller_info| caller_info.instance.def_id().is_local());
if call_site_is_local {
err.span_note(frame_info.call_site, &frame_info.to_string());
} else {
ecx.tcx.sess.diagnostic().span_note_diag(span, msg.as_str())
};
let frames = ecx.generate_stacktrace(None);
err.span_label(span, msg);
// We iterate with indices because we need to look at the next frame (the caller).
for idx in 0..frames.len() {
let frame_info = &frames[idx];
let call_site_is_local = frames
.get(idx + 1)
.map_or(false, |caller_info| caller_info.instance.def_id().is_local());
if call_site_is_local {
err.span_note(frame_info.call_site, &frame_info.to_string());
} else {
err.note(&frame_info.to_string());
}
err.note(&frame_info.to_string());
}
err.emit();
} else {
ecx.tcx.sess.err(&msg);
}

err.emit();

for (i, frame) in ecx.stack().iter().enumerate() {
trace!("-------------------");
trace!("Frame {}", i);
Expand Down Expand Up @@ -106,7 +158,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
CreatedAlloc(AllocId(id)) =>
format!("created allocation with id {}", id),
};
report_msg(this, msg, false);
report_msg(this, "tracking was triggered", msg, &[], false);
}
});
}
Expand Down
8 changes: 1 addition & 7 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,6 @@ impl Default for MiriConfig {
}
}

/// Details of premature program termination.
pub enum TerminationInfo {
Exit(i64),
Abort(Option<String>),
}

/// Returns a freshly created `InterpCx`, along with an `MPlaceTy` representing
/// the location where the return value of the `start` lang item will be
/// written to.
Expand Down Expand Up @@ -229,6 +223,6 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
}
Some(return_code)
}
Err(e) => report_diagnostic(&ecx, e),
Err(e) => report_error(&ecx, e),
}
}
6 changes: 3 additions & 3 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,10 +367,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
/// case.
fn check_no_isolation(&self, name: &str) -> InterpResult<'tcx> {
if !self.eval_context_ref().machine.communicate {
throw_unsup_format!(
"`{}` not available when isolation is enabled (pass the flag `-Zmiri-disable-isolation` to disable isolation)",
throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!(
"`{}` not available when isolation is enabled",
name,
)
)))
}
Ok(())
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ pub use crate::shims::tls::{EvalContextExt as TlsEvalContextExt, TlsData};
pub use crate::shims::EvalContextExt as ShimsEvalContextExt;

pub use crate::diagnostics::{
register_diagnostic, report_diagnostic, EvalContextExt as DiagnosticsEvalContextExt,
NonHaltingDiagnostic,
register_diagnostic, report_error, EvalContextExt as DiagnosticsEvalContextExt,
TerminationInfo, NonHaltingDiagnostic,
};
pub use crate::eval::{create_ecx, eval_main, MiriConfig, TerminationInfo};
pub use crate::eval::{create_ecx, eval_main, MiriConfig};
pub use crate::helpers::EvalContextExt as HelpersEvalContextExt;
pub use crate::machine::{
AllocExtra, Evaluator, FrameData, MemoryExtra, MiriEvalContext, MiriEvalContextExt,
Expand Down
29 changes: 19 additions & 10 deletions src/stacked_borrows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@ impl GlobalState {
}
}

/// Error reporting
fn err_sb_ub(msg: String) -> InterpError<'static> {
// FIXME: use `err_machine_stop!` macro, once that exists.
InterpError::MachineStop(Box::new(TerminationInfo::ExperimentalUb {
msg,
url: format!("https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md"),
}))
}

// # Stacked Borrows Core Begin

/// We need to make at least the following things true:
Expand Down Expand Up @@ -272,15 +281,15 @@ impl<'tcx> Stack {
if let Some(call) = item.protector {
if global.is_active(call) {
if let Some(tag) = tag {
throw_ub!(UbExperimental(format!(
Err(err_sb_ub(format!(
"not granting access to tag {:?} because incompatible item is protected: {:?}",
tag, item
)));
)))?
} else {
throw_ub!(UbExperimental(format!(
Err(err_sb_ub(format!(
"deallocating while item is protected: {:?}",
item
)));
)))?
}
}
}
Expand All @@ -294,10 +303,10 @@ impl<'tcx> Stack {

// Step 1: Find granting item.
let granting_idx = self.find_granting(access, tag).ok_or_else(|| {
err_ub!(UbExperimental(format!(
err_sb_ub(format!(
"no item granting {} to tag {:?} found in borrow stack.",
access, tag
),))
))
})?;

// Step 2: Remove incompatible items above them. Make sure we do not remove protected
Expand Down Expand Up @@ -338,10 +347,10 @@ impl<'tcx> Stack {
fn dealloc(&mut self, tag: Tag, global: &GlobalState) -> InterpResult<'tcx> {
// Step 1: Find granting item.
self.find_granting(AccessKind::Write, tag).ok_or_else(|| {
err_ub!(UbExperimental(format!(
err_sb_ub(format!(
"no item granting write access for deallocation to tag {:?} found in borrow stack",
tag,
)))
))
})?;

// Step 2: Remove all items. Also checks for protectors.
Expand All @@ -363,10 +372,10 @@ impl<'tcx> Stack {
// Now we figure out which item grants our parent (`derived_from`) this kind of access.
// We use that to determine where to put the new item.
let granting_idx = self.find_granting(access, derived_from)
.ok_or_else(|| err_ub!(UbExperimental(format!(
.ok_or_else(|| err_sb_ub(format!(
"trying to reborrow for {:?}, but parent tag {:?} does not have an appropriate item in the borrow stack",
new.perm, derived_from,
))))?;
)))?;

// Compute where to put the new item.
// Either way, we ensure that we insert the new item in a way such that between
Expand Down