]> git.lizzy.rs Git - rust.git/blob - src/diagnostics.rs
Auto merge of #2029 - RalfJung:simd-bitmask, r=RalfJung
[rust.git] / src / diagnostics.rs
1 use std::cell::RefCell;
2 use std::fmt;
3 use std::num::NonZeroU64;
4
5 use log::trace;
6
7 use rustc_middle::ty::{self, TyCtxt};
8 use rustc_span::{source_map::DUMMY_SP, Span, SpanData, Symbol};
9
10 use crate::stacked_borrows::{AccessKind, SbTag};
11 use crate::*;
12
13 /// Details of premature program termination.
14 pub enum TerminationInfo {
15     Exit(i64),
16     Abort(String),
17     UnsupportedInIsolation(String),
18     ExperimentalUb {
19         msg: String,
20         help: Option<String>,
21         url: String,
22     },
23     Deadlock,
24     MultipleSymbolDefinitions {
25         link_name: Symbol,
26         first: SpanData,
27         first_crate: Symbol,
28         second: SpanData,
29         second_crate: Symbol,
30     },
31     SymbolShimClashing {
32         link_name: Symbol,
33         span: SpanData,
34     },
35 }
36
37 impl fmt::Display for TerminationInfo {
38     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39         use TerminationInfo::*;
40         match self {
41             Exit(code) => write!(f, "the evaluated program completed with exit code {}", code),
42             Abort(msg) => write!(f, "{}", msg),
43             UnsupportedInIsolation(msg) => write!(f, "{}", msg),
44             ExperimentalUb { msg, .. } => write!(f, "{}", msg),
45             Deadlock => write!(f, "the evaluated program deadlocked"),
46             MultipleSymbolDefinitions { link_name, .. } =>
47                 write!(f, "multiple definitions of symbol `{}`", link_name),
48             SymbolShimClashing { link_name, .. } =>
49                 write!(
50                     f,
51                     "found `{}` symbol definition that clashes with a built-in shim",
52                     link_name
53                 ),
54         }
55     }
56 }
57
58 impl MachineStopType for TerminationInfo {}
59
60 /// Miri specific diagnostics
61 pub enum NonHaltingDiagnostic {
62     CreatedPointerTag(NonZeroU64),
63     /// This `Item` was popped from the borrow stack, either due to a grant of
64     /// `AccessKind` to `SbTag` or a deallocation when the second argument is `None`.
65     PoppedPointerTag(Item, Option<(SbTag, AccessKind)>),
66     CreatedCallId(CallId),
67     CreatedAlloc(AllocId),
68     FreedAlloc(AllocId),
69     RejectedIsolatedOp(String),
70 }
71
72 /// Level of Miri specific diagnostics
73 enum DiagLevel {
74     Error,
75     Warning,
76     Note,
77 }
78
79 /// Attempts to prune a stacktrace to omit the Rust runtime, and returns a bool indicating if any
80 /// frames were pruned. If the stacktrace does not have any local frames, we conclude that it must
81 /// be pointing to a problem in the Rust runtime itself, and do not prune it at all.
82 fn prune_stacktrace<'mir, 'tcx>(
83     ecx: &InterpCx<'mir, 'tcx, Evaluator<'mir, 'tcx>>,
84     mut stacktrace: Vec<FrameInfo<'tcx>>,
85 ) -> (Vec<FrameInfo<'tcx>>, bool) {
86     match ecx.machine.backtrace_style {
87         BacktraceStyle::Off => {
88             // Retain one frame so that we can print a span for the error itself
89             stacktrace.truncate(1);
90             (stacktrace, false)
91         }
92         BacktraceStyle::Short => {
93             let original_len = stacktrace.len();
94             // Only prune frames if there is at least one local frame. This check ensures that if
95             // we get a backtrace that never makes it to the user code because it has detected a
96             // bug in the Rust runtime, we don't prune away every frame.
97             let has_local_frame = stacktrace.iter().any(|frame| frame.instance.def_id().is_local());
98             if has_local_frame {
99                 // This is part of the logic that `std` uses to select the relevant part of a
100                 // backtrace. But here, we only look for __rust_begin_short_backtrace, not
101                 // __rust_end_short_backtrace because the end symbol comes from a call to the default
102                 // panic handler.
103                 stacktrace = stacktrace
104                     .into_iter()
105                     .take_while(|frame| {
106                         let def_id = frame.instance.def_id();
107                         let path = ecx.tcx.tcx.def_path_str(def_id);
108                         !path.contains("__rust_begin_short_backtrace")
109                     })
110                     .collect::<Vec<_>>();
111
112                 // After we prune frames from the bottom, there are a few left that are part of the
113                 // Rust runtime. So we remove frames until we get to a local symbol, which should be
114                 // main or a test.
115                 // This len check ensures that we don't somehow remove every frame, as doing so breaks
116                 // the primary error message.
117                 while stacktrace.len() > 1
118                     && stacktrace.last().map_or(false, |e| !e.instance.def_id().is_local())
119                 {
120                     stacktrace.pop();
121                 }
122             }
123             let was_pruned = stacktrace.len() != original_len;
124             (stacktrace, was_pruned)
125         }
126         BacktraceStyle::Full => (stacktrace, false),
127     }
128 }
129
130 /// Emit a custom diagnostic without going through the miri-engine machinery
131 pub fn report_error<'tcx, 'mir>(
132     ecx: &InterpCx<'mir, 'tcx, Evaluator<'mir, 'tcx>>,
133     e: InterpErrorInfo<'tcx>,
134 ) -> Option<i64> {
135     use InterpError::*;
136
137     let mut msg = vec![];
138
139     let (title, helps) = match &e.kind() {
140         MachineStop(info) => {
141             let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
142             use TerminationInfo::*;
143             let title = match info {
144                 Exit(code) => return Some(*code),
145                 Abort(_) => Some("abnormal termination"),
146                 UnsupportedInIsolation(_) => Some("unsupported operation"),
147                 ExperimentalUb { .. } => Some("Undefined Behavior"),
148                 Deadlock => Some("deadlock"),
149                 MultipleSymbolDefinitions { .. } | SymbolShimClashing { .. } => None,
150             };
151             #[rustfmt::skip]
152             let helps = match info {
153                 UnsupportedInIsolation(_) =>
154                     vec![
155                         (None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation;")),
156                         (None, format!("or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning")),
157                     ],
158                 ExperimentalUb { url, help, .. } => {
159                     msg.extend(help.clone());
160                     vec![
161                         (None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental")),
162                         (None, format!("see {} for further information", url))
163                     ]
164                 }
165                 MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
166                     vec![
167                         (Some(*first), format!("it's first defined here, in crate `{}`", first_crate)),
168                         (Some(*second), format!("then it's defined here again, in crate `{}`", second_crate)),
169                     ],
170                 SymbolShimClashing { link_name, span } =>
171                     vec![(Some(*span), format!("the `{}` symbol is defined here", link_name))],
172                 _ => vec![],
173             };
174             (title, helps)
175         }
176         _ => {
177             #[rustfmt::skip]
178             let title = match e.kind() {
179                 Unsupported(_) =>
180                     "unsupported operation",
181                 UndefinedBehavior(_) =>
182                     "Undefined Behavior",
183                 ResourceExhaustion(_) =>
184                     "resource exhaustion",
185                 InvalidProgram(InvalidProgramInfo::ReferencedConstant) =>
186                     "post-monomorphization error",
187                 InvalidProgram(InvalidProgramInfo::AlreadyReported(_)) =>
188                     "error occurred",
189                 kind =>
190                     bug!("This error should be impossible in Miri: {:?}", kind),
191             };
192             #[rustfmt::skip]
193             let helps = match e.kind() {
194                 Unsupported(UnsupportedOpInfo::ThreadLocalStatic(_) | UnsupportedOpInfo::ReadExternStatic(_)) =>
195                     panic!("Error should never be raised by Miri: {:?}", e.kind()),
196                 Unsupported(_) =>
197                     vec![(None, format!("this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support"))],
198                 UndefinedBehavior(UndefinedBehaviorInfo::AlignmentCheckFailed { .. })
199                     if ecx.memory.extra.check_alignment == AlignmentCheck::Symbolic
200                 =>
201                     vec![
202                         (None, format!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior")),
203                         (None, format!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives")),
204                     ],
205                 UndefinedBehavior(_) =>
206                     vec![
207                         (None, format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior")),
208                         (None, format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information")),
209                     ],
210                 _ => vec![],
211             };
212             (Some(title), helps)
213         }
214     };
215
216     let stacktrace = ecx.generate_stacktrace();
217     let (stacktrace, was_pruned) = prune_stacktrace(ecx, stacktrace);
218     e.print_backtrace();
219     msg.insert(0, e.to_string());
220     report_msg(
221         *ecx.tcx,
222         DiagLevel::Error,
223         &if let Some(title) = title { format!("{}: {}", title, msg[0]) } else { msg[0].clone() },
224         msg,
225         helps,
226         &stacktrace,
227     );
228
229     // Include a note like `std` does when we omit frames from a backtrace
230     if was_pruned {
231         ecx.tcx.sess.diagnostic().note_without_error(
232             "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
233         );
234     }
235
236     // Debug-dump all locals.
237     for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
238         trace!("-------------------");
239         trace!("Frame {}", i);
240         trace!("    return: {:?}", frame.return_place.map(|p| *p));
241         for (i, local) in frame.locals.iter().enumerate() {
242             trace!("    local {}: {:?}", i, local.value);
243         }
244     }
245
246     // Extra output to help debug specific issues.
247     match e.kind() {
248         UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some((alloc_id, access)))) => {
249             eprintln!(
250                 "Uninitialized read occurred at offsets 0x{:x}..0x{:x} into this allocation:",
251                 access.uninit_offset.bytes(),
252                 access.uninit_offset.bytes() + access.uninit_size.bytes(),
253             );
254             eprintln!("{:?}", ecx.memory.dump_alloc(*alloc_id));
255         }
256         _ => {}
257     }
258
259     None
260 }
261
262 /// Report an error or note (depending on the `error` argument) with the given stacktrace.
263 /// Also emits a full stacktrace of the interpreter stack.
264 /// We want to present a multi-line span message for some errors. Diagnostics do not support this
265 /// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
266 /// additional `span_label` or `note` call.
267 fn report_msg<'tcx>(
268     tcx: TyCtxt<'tcx>,
269     diag_level: DiagLevel,
270     title: &str,
271     span_msg: Vec<String>,
272     mut helps: Vec<(Option<SpanData>, String)>,
273     stacktrace: &[FrameInfo<'tcx>],
274 ) {
275     let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
276     let mut err = match diag_level {
277         DiagLevel::Error => tcx.sess.struct_span_err(span, title).forget_guarantee(),
278         DiagLevel::Warning => tcx.sess.struct_span_warn(span, title),
279         DiagLevel::Note => tcx.sess.diagnostic().span_note_diag(span, title),
280     };
281
282     // Show main message.
283     if span != DUMMY_SP {
284         for line in span_msg {
285             err.span_label(span, line);
286         }
287     } else {
288         // Make sure we show the message even when it is a dummy span.
289         for line in span_msg {
290             err.note(&line);
291         }
292         err.note("(no span available)");
293     }
294
295     // Show help messages.
296     if !helps.is_empty() {
297         // Add visual separator before backtrace.
298         helps.last_mut().unwrap().1.push_str("\n");
299         for (span_data, help) in helps {
300             if let Some(span_data) = span_data {
301                 err.span_help(span_data.span(), &help);
302             } else {
303                 err.help(&help);
304             }
305         }
306     }
307     // Add backtrace
308     for (idx, frame_info) in stacktrace.iter().enumerate() {
309         let is_local = frame_info.instance.def_id().is_local();
310         // No span for non-local frames and the first frame (which is the error site).
311         if is_local && idx > 0 {
312             err.span_note(frame_info.span, &frame_info.to_string());
313         } else {
314             err.note(&frame_info.to_string());
315         }
316     }
317
318     err.emit();
319 }
320
321 thread_local! {
322     static DIAGNOSTICS: RefCell<Vec<NonHaltingDiagnostic>> = RefCell::new(Vec::new());
323 }
324
325 /// Schedule a diagnostic for emitting. This function works even if you have no `InterpCx` available.
326 /// The diagnostic will be emitted after the current interpreter step is finished.
327 pub fn register_diagnostic(e: NonHaltingDiagnostic) {
328     DIAGNOSTICS.with(|diagnostics| diagnostics.borrow_mut().push(e));
329 }
330
331 /// Remember enough about the topmost frame so that we can restore the stack
332 /// after a step was taken.
333 pub struct TopFrameInfo<'tcx> {
334     stack_size: usize,
335     instance: Option<ty::Instance<'tcx>>,
336     span: Span,
337 }
338
339 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
340 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
341     fn preprocess_diagnostics(&self) -> TopFrameInfo<'tcx> {
342         // Ensure we have no lingering diagnostics.
343         DIAGNOSTICS.with(|diagnostics| assert!(diagnostics.borrow().is_empty()));
344
345         let this = self.eval_context_ref();
346         if this.active_thread_stack().is_empty() {
347             // Diagnostics can happen even with the empty stack (e.g. deallocation of thread-local statics).
348             return TopFrameInfo { stack_size: 0, instance: None, span: DUMMY_SP };
349         }
350         let frame = this.frame();
351
352         TopFrameInfo {
353             stack_size: this.active_thread_stack().len(),
354             instance: Some(frame.instance),
355             span: frame.current_span(),
356         }
357     }
358
359     /// Emit all diagnostics that were registed with `register_diagnostics`
360     fn process_diagnostics(&self, info: TopFrameInfo<'tcx>) {
361         let this = self.eval_context_ref();
362         DIAGNOSTICS.with(|diagnostics| {
363             let mut diagnostics = diagnostics.borrow_mut();
364             if diagnostics.is_empty() {
365                 return;
366             }
367             // We need to fix up the stack trace, because the machine has already
368             // stepped to the next statement.
369             let mut stacktrace = this.generate_stacktrace();
370             // Remove newly pushed frames.
371             while stacktrace.len() > info.stack_size {
372                 stacktrace.remove(0);
373             }
374             // Add popped frame back.
375             if stacktrace.len() < info.stack_size {
376                 assert!(
377                     stacktrace.len() == info.stack_size - 1,
378                     "we should never pop more than one frame at once"
379                 );
380                 let frame_info = FrameInfo {
381                     instance: info.instance.unwrap(),
382                     span: info.span,
383                     lint_root: None,
384                 };
385                 stacktrace.insert(0, frame_info);
386             } else if let Some(instance) = info.instance {
387                 // Adjust topmost frame.
388                 stacktrace[0].span = info.span;
389                 assert_eq!(
390                     stacktrace[0].instance, instance,
391                     "we should not pop and push a frame in one step"
392                 );
393             }
394
395             let (stacktrace, _was_pruned) = prune_stacktrace(this, stacktrace);
396
397             // Show diagnostics.
398             for e in diagnostics.drain(..) {
399                 use NonHaltingDiagnostic::*;
400                 let msg = match e {
401                     CreatedPointerTag(tag) => format!("created tag {:?}", tag),
402                     PoppedPointerTag(item, tag) =>
403                         match tag {
404                             None =>
405                                 format!(
406                                     "popped tracked tag for item {:?} due to deallocation",
407                                     item
408                                 ),
409                             Some((tag, access)) => {
410                                 format!(
411                                     "popped tracked tag for item {:?} due to {:?} access for {:?}",
412                                     item, access, tag
413                                 )
414                             }
415                         },
416                     CreatedCallId(id) => format!("function call with id {}", id),
417                     CreatedAlloc(AllocId(id)) => format!("created allocation with id {}", id),
418                     FreedAlloc(AllocId(id)) => format!("freed allocation with id {}", id),
419                     RejectedIsolatedOp(ref op) =>
420                         format!("{} was made to return an error due to isolation", op),
421                 };
422
423                 let (title, diag_level) = match e {
424                     RejectedIsolatedOp(_) =>
425                         ("operation rejected by isolation", DiagLevel::Warning),
426                     _ => ("tracking was triggered", DiagLevel::Note),
427                 };
428
429                 report_msg(*this.tcx, diag_level, title, vec![msg], vec![], &stacktrace);
430             }
431         });
432     }
433 }