X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fdiagnostics.rs;h=45c0996355bffd43238dd1b4a7436aa395708d1e;hb=2670839e1af540a496a0d889fce9ad42529ecc11;hp=504863b13427c97d9a1831b65f91d982c140857b;hpb=96d6efdf32f346c45f58897b2f7fff38a476cfbe;p=rust.git diff --git a/src/diagnostics.rs b/src/diagnostics.rs index 504863b1342..45c0996355b 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -1,55 +1,129 @@ -use rustc_mir::interpret::InterpErrorInfo; +use std::cell::RefCell; +use std::fmt; +use std::num::NonZeroU64; + +use log::trace; + +use rustc_middle::ty::{self, TyCtxt}; +use rustc_span::{source_map::DUMMY_SP, Span}; use crate::*; -pub fn report_err<'tcx, 'mir>( - ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>, - mut e: InterpErrorInfo<'tcx>, +/// Details of premature program termination. +pub enum TerminationInfo { + Exit(i64), + Abort(String), + UnsupportedInIsolation(String), + ExperimentalUb { msg: String, url: String }, + Deadlock, +} + +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(msg) => + write!(f, "{}", msg), + UnsupportedInIsolation(msg) => + write!(f, "{}", msg), + ExperimentalUb { msg, .. } => + write!(f, "{}", msg), + Deadlock => + write!(f, "the evaluated program deadlocked"), + } + } +} + +impl MachineStopType for TerminationInfo {} + +/// Miri specific diagnostics +pub enum NonHaltingDiagnostic { + 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<'mir, 'tcx>>, + e: InterpErrorInfo<'tcx>, ) -> Option { - // Special treatment for some error kinds - let msg = match e.kind { - InterpError::MachineStop(ref info) => { + use InterpError::*; + + let (title, helps) = match &e.kind() { + MachineStop(info) => { let info = info.downcast_ref::().expect("invalid MachineStop payload"); - match info { - TerminationInfo::Exit(code) => return Some(*code), - TerminationInfo::PoppedTrackedPointerTag(item) => - format!("popped tracked tag for item {:?}", item), - TerminationInfo::Abort => format!("the evaluated program aborted execution"), - } + use TerminationInfo::*; + let title = match info { + Exit(code) => return Some(*code), + Abort(_) => + "abnormal termination", + UnsupportedInIsolation(_) => + "unsupported operation", + ExperimentalUb { .. } => + "Undefined Behavior", + Deadlock => "deadlock", + }; + 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, helps) + } + _ => { + 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() { + 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"), + format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"), + ], + _ => vec![], + }; + (title, 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(); - if let Some(frame) = ecx.stack().last() { - let span = frame.current_source_info().unwrap().span; - - let msg = format!("Miri evaluation error: {}", msg); - let mut err = ecx.tcx.sess.struct_span_err(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.emit(); - } else { - ecx.tcx.sess.err(&msg); - } + let msg = e.to_string(); + report_msg(*ecx.tcx, /*error*/true, &format!("{}: {}", title, msg), msg, helps, &ecx.generate_stacktrace()); - for (i, frame) in ecx.stack().iter().enumerate() { + // 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)); @@ -57,26 +131,153 @@ pub fn report_err<'tcx, 'mir>( trace!(" local {}: {:?}", i, local.value); } } - // Let the reported error determine the return code. - return None; + + // 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) with the given stacktrace. +/// Also emits a full stacktrace of the interpreter stack. +fn report_msg<'tcx>( + tcx: TyCtxt<'tcx>, + error: bool, + title: &str, + span_msg: String, + mut helps: Vec, + stacktrace: &[FrameInfo<'tcx>], +) { + let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span); + let mut err = if error { + tcx.sess.struct_span_err(span, title) + } else { + tcx.sess.diagnostic().span_note_diag(span, title) + }; + // 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 + 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 { + err.span_note(frame_info.span, &frame_info.to_string()); + } else { + err.note(&frame_info.to_string()); + } + } + + err.emit(); } -use std::cell::RefCell; thread_local! { - static ECX: RefCell>> = RefCell::new(Vec::new()); + static DIAGNOSTICS: RefCell> = RefCell::new(Vec::new()); +} + +/// Schedule a diagnostic for emitting. This function works even if you have no `InterpCx` available. +/// The diagnostic will be emitted after the current interpreter step is finished. +pub fn register_diagnostic(e: NonHaltingDiagnostic) { + DIAGNOSTICS.with(|diagnostics| diagnostics.borrow_mut().push(e)); } -pub fn register_err(e: InterpErrorInfo<'static>) { - ECX.with(|ecx| ecx.borrow_mut().push(e)); +/// 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> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> { - fn process_errors(&self) { + 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, info: TopFrameInfo<'tcx>) { let this = self.eval_context_ref(); - ECX.with(|ecx| { - for e in ecx.borrow_mut().drain(..) { - report_err(this, e); + DIAGNOSTICS.with(|diagnostics| { + 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 { + 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.tcx, /*error*/false, "tracking was triggered", msg, vec![], &stacktrace); } }); }