X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fdiagnostics.rs;h=45c0996355bffd43238dd1b4a7436aa395708d1e;hb=2670839e1af540a496a0d889fce9ad42529ecc11;hp=9ff434021150f12fba18587374d1f9bce49dc211;hpb=0e8a1a40f2c81c2318230c4d9c1947310578cbe0;p=rust.git diff --git a/src/diagnostics.rs b/src/diagnostics.rs index 9ff43402115..45c0996355b 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -1,34 +1,37 @@ use std::cell::RefCell; use std::fmt; +use std::num::NonZeroU64; use log::trace; -use rustc_span::DUMMY_SP; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_span::{source_map::DUMMY_SP, Span}; use crate::*; /// Details of premature program termination. pub enum TerminationInfo { Exit(i64), - Abort(Option), + Abort(String), UnsupportedInIsolation(String), - ExperimentalUb { msg: String, url: String } + ExperimentalUb { msg: String, url: String }, + Deadlock, } -impl fmt::Debug for TerminationInfo { +impl fmt::Display for TerminationInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use TerminationInfo::*; match self { Exit(code) => write!(f, "the evaluated program completed with exit code {}", code), - Abort(None) => - write!(f, "the evaluated program aborted execution"), - Abort(Some(msg)) => - write!(f, "the evaluated program aborted execution: {}", msg), + Abort(msg) => + write!(f, "{}", msg), UnsupportedInIsolation(msg) => write!(f, "{}", msg), ExperimentalUb { msg, .. } => write!(f, "{}", msg), + Deadlock => + write!(f, "the evaluated program deadlocked"), } } } @@ -37,19 +40,22 @@ impl MachineStopType for TerminationInfo {} /// Miri specific diagnostics pub enum NonHaltingDiagnostic { - PoppedTrackedPointerTag(Item), + CreatedPointerTag(NonZeroU64), + PoppedPointerTag(Item), + CreatedCallId(CallId), CreatedAlloc(AllocId), + FreedAlloc(AllocId), } /// Emit a custom diagnostic without going through the miri-engine machinery pub fn report_error<'tcx, 'mir>( - ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>, - mut e: InterpErrorInfo<'tcx>, + ecx: &InterpCx<'mir, 'tcx, Evaluator<'mir, 'tcx>>, + e: InterpErrorInfo<'tcx>, ) -> Option { use InterpError::*; - let (title, helps) = match e.kind { - MachineStop(ref info) => { + let (title, helps) = match &e.kind() { + MachineStop(info) => { let info = info.downcast_ref::().expect("invalid MachineStop payload"); use TerminationInfo::*; let title = match info { @@ -60,6 +66,7 @@ pub fn report_error<'tcx, 'mir>( "unsupported operation", ExperimentalUb { .. } => "Undefined Behavior", + Deadlock => "deadlock", }; let helps = match info { UnsupportedInIsolation(_) => @@ -74,21 +81,32 @@ pub fn report_error<'tcx, 'mir>( (title, helps) } _ => { - let title = match e.kind { + let title = match e.kind() { Unsupported(_) => "unsupported operation", UndefinedBehavior(_) => "Undefined Behavior", ResourceExhaustion(_) => "resource exhaustion", + InvalidProgram(InvalidProgramInfo::ReferencedConstant) => + "post-monomorphization error", _ => bug!("This error should be impossible in Miri: {}", e), }; - let helps = match e.kind { + 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(UnsupportedOpInfo::ReadBytesAsPointer | UnsupportedOpInfo::ThreadLocalStatic(_) | UnsupportedOpInfo::ReadExternStatic(_)) => + panic!("Error should never be raised by Miri: {:?}", e.kind()), 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(UndefinedBehaviorInfo::AlignmentCheckFailed { .. }) + if ecx.memory.extra.check_alignment == AlignmentCheck::Symbolic + => + vec![ + format!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior"), + format!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives"), + ], UndefinedBehavior(_) => vec![ format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"), @@ -102,35 +120,68 @@ pub fn report_error<'tcx, 'mir>( e.print_backtrace(); let msg = e.to_string(); - report_msg(ecx, &format!("{}: {}", title, msg), msg, &helps, true) + report_msg(*ecx.tcx, /*error*/true, &format!("{}: {}", title, msg), msg, helps, &ecx.generate_stacktrace()); + + // Debug-dump all locals. + for (i, frame) in ecx.active_thread_stack().iter().enumerate() { + trace!("-------------------"); + trace!("Frame {}", i); + trace!(" return: {:?}", frame.return_place.map(|p| *p)); + for (i, local) in frame.locals.iter().enumerate() { + trace!(" local {}: {:?}", i, local.value); + } + } + + // Extra output to help debug specific issues. + match e.kind() { + UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some(access))) => { + eprintln!( + "Uninitialized read occurred at offsets 0x{:x}..0x{:x} into this allocation:", + access.uninit_ptr.offset.bytes(), + access.uninit_ptr.offset.bytes() + access.uninit_size.bytes(), + ); + eprintln!("{:?}", ecx.memory.dump_alloc(access.uninit_ptr.alloc_id)); + } + _ => {} + } + + None } -/// Report an error or note (depending on the `error` argument) at the current frame's current statement. +/// Report an error or note (depending on the `error` argument) with the given stacktrace. /// Also emits a full stacktrace of the interpreter stack. -fn report_msg<'tcx, 'mir>( - ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>, +fn report_msg<'tcx>( + tcx: TyCtxt<'tcx>, + error: bool, title: &str, span_msg: String, - helps: &[String], - error: bool, -) -> Option { - let span = if let Some(frame) = ecx.stack().last() { - frame.current_source_info().unwrap().span - } else { - DUMMY_SP - }; + mut helps: Vec, + stacktrace: &[FrameInfo<'tcx>], +) { + let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span); let mut err = if error { - ecx.tcx.sess.struct_span_err(span, title) + tcx.sess.struct_span_err(span, title) } else { - ecx.tcx.sess.diagnostic().span_note_diag(span, title) + tcx.sess.diagnostic().span_note_diag(span, title) }; - err.span_label(span, span_msg); - for help in helps { - err.help(help); + // Show main message. + if span != DUMMY_SP { + err.span_label(span, span_msg); + } else { + // Make sure we show the message even when it is a dummy span. + err.note(&span_msg); + err.note("(no span available)"); + } + // Show help messages. + if !helps.is_empty() { + // Add visual separator before backtrace. + helps.last_mut().unwrap().push_str("\n"); + for help in helps { + err.help(&help); + } } // Add backtrace - let frames = ecx.generate_stacktrace(); - for (idx, frame_info) in frames.iter().enumerate() { + for (idx, frame_info) in stacktrace.iter().enumerate() { let is_local = frame_info.instance.def_id().is_local(); // No span for non-local frames and the first frame (which is the error site). if is_local && idx > 0 { @@ -141,17 +192,6 @@ fn report_msg<'tcx, 'mir>( } err.emit(); - - for (i, frame) in ecx.stack().iter().enumerate() { - trace!("-------------------"); - trace!("Frame {}", i); - trace!(" return: {:?}", frame.return_place.map(|p| *p)); - for (i, local) in frame.locals.iter().enumerate() { - trace!(" local {}: {:?}", i, local.value); - } - } - // Let the reported error determine the return code. - return None; } thread_local! { @@ -164,21 +204,80 @@ pub fn register_diagnostic(e: NonHaltingDiagnostic) { DIAGNOSTICS.with(|diagnostics| diagnostics.borrow_mut().push(e)); } -impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} +/// Remember enough about the topmost frame so that we can restore the stack +/// after a step was taken. +pub struct TopFrameInfo<'tcx> { + stack_size: usize, + instance: Option>, + span: Span, +} + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> { + fn preprocess_diagnostics(&self) -> TopFrameInfo<'tcx> { + // Ensure we have no lingering diagnostics. + DIAGNOSTICS.with(|diagnostics| assert!(diagnostics.borrow().is_empty())); + + let this = self.eval_context_ref(); + if this.active_thread_stack().is_empty() { + // Diagnostics can happen even with the empty stack (e.g. deallocation of thread-local statics). + return TopFrameInfo { stack_size: 0, instance: None, span: DUMMY_SP }; + } + let frame = this.frame(); + + TopFrameInfo { + stack_size: this.active_thread_stack().len(), + instance: Some(frame.instance), + span: frame.current_span(), + } + } + /// Emit all diagnostics that were registed with `register_diagnostics` - fn process_diagnostics(&self) { + fn process_diagnostics(&self, info: TopFrameInfo<'tcx>) { let this = self.eval_context_ref(); DIAGNOSTICS.with(|diagnostics| { - for e in diagnostics.borrow_mut().drain(..) { + let mut diagnostics = diagnostics.borrow_mut(); + if diagnostics.is_empty() { + return; + } + // We need to fix up the stack trace, because the machine has already + // stepped to the next statement. + let mut stacktrace = this.generate_stacktrace(); + // Remove newly pushed frames. + while stacktrace.len() > info.stack_size { + stacktrace.remove(0); + } + // Add popped frame back. + if stacktrace.len() < info.stack_size { + assert!(stacktrace.len() == info.stack_size-1, "we should never pop more than one frame at once"); + let frame_info = FrameInfo { + instance: info.instance.unwrap(), + span: info.span, + lint_root: None, + }; + stacktrace.insert(0, frame_info); + } else if let Some(instance) = info.instance { + // Adjust topmost frame. + stacktrace[0].span = info.span; + assert_eq!(stacktrace[0].instance, instance, "we should not pop and push a frame in one step"); + } + + // Show diagnostics. + for e in diagnostics.drain(..) { use NonHaltingDiagnostic::*; let msg = match e { - PoppedTrackedPointerTag(item) => + CreatedPointerTag(tag) => + format!("created tag {:?}", tag), + PoppedPointerTag(item) => format!("popped tracked tag for item {:?}", item), + CreatedCallId(id) => + format!("function call with id {}", id), CreatedAlloc(AllocId(id)) => format!("created allocation with id {}", id), + FreedAlloc(AllocId(id)) => + format!("freed allocation with id {}", id), }; - report_msg(this, "tracking was triggered", msg, &[], false); + report_msg(*this.tcx, /*error*/false, "tracking was triggered", msg, vec![], &stacktrace); } }); }