]> git.lizzy.rs Git - rust.git/blob - src/diagnostics.rs
remove no longer needed imports
[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;
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| ecx.machine.is_local(frame));
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, |frame| !ecx.machine.is_local(frame))
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.machine.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,
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.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<'mir, 'tcx>(
268     ecx: &InterpCx<'mir, 'tcx, Evaluator<'mir, '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 sess = ecx.tcx.sess;
277     let mut err = match diag_level {
278         DiagLevel::Error => sess.struct_span_err(span, title).forget_guarantee(),
279         DiagLevel::Warning => sess.struct_span_warn(span, title),
280         DiagLevel::Note => sess.diagnostic().span_note_diag(span, title),
281     };
282
283     // Show main message.
284     if span != DUMMY_SP {
285         for line in span_msg {
286             err.span_label(span, line);
287         }
288     } else {
289         // Make sure we show the message even when it is a dummy span.
290         for line in span_msg {
291             err.note(&line);
292         }
293         err.note("(no span available)");
294     }
295
296     // Show help messages.
297     if !helps.is_empty() {
298         // Add visual separator before backtrace.
299         helps.last_mut().unwrap().1.push_str("\n");
300         for (span_data, help) in helps {
301             if let Some(span_data) = span_data {
302                 err.span_help(span_data.span(), &help);
303             } else {
304                 err.help(&help);
305             }
306         }
307     }
308     // Add backtrace
309     for (idx, frame_info) in stacktrace.iter().enumerate() {
310         let is_local = ecx.machine.is_local(frame_info);
311         // No span for non-local frames and the first frame (which is the error site).
312         if is_local && idx > 0 {
313             err.span_note(frame_info.span, &frame_info.to_string());
314         } else {
315             err.note(&frame_info.to_string());
316         }
317     }
318
319     err.emit();
320 }
321
322 thread_local! {
323     static DIAGNOSTICS: RefCell<Vec<NonHaltingDiagnostic>> = RefCell::new(Vec::new());
324 }
325
326 /// Schedule a diagnostic for emitting. This function works even if you have no `InterpCx` available.
327 /// The diagnostic will be emitted after the current interpreter step is finished.
328 pub fn register_diagnostic(e: NonHaltingDiagnostic) {
329     DIAGNOSTICS.with(|diagnostics| diagnostics.borrow_mut().push(e));
330 }
331
332 /// Remember enough about the topmost frame so that we can restore the stack
333 /// after a step was taken.
334 pub struct TopFrameInfo<'tcx> {
335     stack_size: usize,
336     instance: Option<ty::Instance<'tcx>>,
337     span: Span,
338 }
339
340 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
341 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
342     fn preprocess_diagnostics(&self) -> TopFrameInfo<'tcx> {
343         // Ensure we have no lingering diagnostics.
344         DIAGNOSTICS.with(|diagnostics| assert!(diagnostics.borrow().is_empty()));
345
346         let this = self.eval_context_ref();
347         if this.active_thread_stack().is_empty() {
348             // Diagnostics can happen even with the empty stack (e.g. deallocation of thread-local statics).
349             return TopFrameInfo { stack_size: 0, instance: None, span: DUMMY_SP };
350         }
351         let frame = this.frame();
352
353         TopFrameInfo {
354             stack_size: this.active_thread_stack().len(),
355             instance: Some(frame.instance),
356             span: frame.current_span(),
357         }
358     }
359
360     /// Emit all diagnostics that were registed with `register_diagnostics`
361     fn process_diagnostics(&self, info: TopFrameInfo<'tcx>) {
362         let this = self.eval_context_ref();
363         DIAGNOSTICS.with(|diagnostics| {
364             let mut diagnostics = diagnostics.borrow_mut();
365             if diagnostics.is_empty() {
366                 return;
367             }
368             // We need to fix up the stack trace, because the machine has already
369             // stepped to the next statement.
370             let mut stacktrace = this.generate_stacktrace();
371             // Remove newly pushed frames.
372             while stacktrace.len() > info.stack_size {
373                 stacktrace.remove(0);
374             }
375             // Add popped frame back.
376             if stacktrace.len() < info.stack_size {
377                 assert!(
378                     stacktrace.len() == info.stack_size - 1,
379                     "we should never pop more than one frame at once"
380                 );
381                 let frame_info = FrameInfo {
382                     instance: info.instance.unwrap(),
383                     span: info.span,
384                     lint_root: None,
385                 };
386                 stacktrace.insert(0, frame_info);
387             } else if let Some(instance) = info.instance {
388                 // Adjust topmost frame.
389                 stacktrace[0].span = info.span;
390                 assert_eq!(
391                     stacktrace[0].instance, instance,
392                     "we should not pop and push a frame in one step"
393                 );
394             }
395
396             let (stacktrace, _was_pruned) = prune_stacktrace(this, stacktrace);
397
398             // Show diagnostics.
399             for e in diagnostics.drain(..) {
400                 use NonHaltingDiagnostic::*;
401                 let msg = match e {
402                     CreatedPointerTag(tag) => format!("created tag {:?}", tag),
403                     PoppedPointerTag(item, tag) =>
404                         match tag {
405                             None =>
406                                 format!(
407                                     "popped tracked tag for item {:?} due to deallocation",
408                                     item
409                                 ),
410                             Some((tag, access)) => {
411                                 format!(
412                                     "popped tracked tag for item {:?} due to {:?} access for {:?}",
413                                     item, access, tag
414                                 )
415                             }
416                         },
417                     CreatedCallId(id) => format!("function call with id {}", id),
418                     CreatedAlloc(AllocId(id)) => format!("created allocation with id {}", id),
419                     FreedAlloc(AllocId(id)) => format!("freed allocation with id {}", id),
420                     RejectedIsolatedOp(ref op) =>
421                         format!("{} was made to return an error due to isolation", op),
422                 };
423
424                 let (title, diag_level) = match e {
425                     RejectedIsolatedOp(_) =>
426                         ("operation rejected by isolation", DiagLevel::Warning),
427                     _ => ("tracking was triggered", DiagLevel::Note),
428                 };
429
430                 report_msg(this, diag_level, title, vec![msg], vec![], &stacktrace);
431             }
432         });
433     }
434 }