1use std::fmt::{self, Write};
2use std::num::NonZero;
3use std::sync::Mutex;
4
5use rustc_abi::{Align, Size};
6use rustc_data_structures::fx::{FxBuildHasher, FxHashSet};
7use rustc_errors::{Diag, DiagMessage, Level};
8use rustc_span::{DUMMY_SP, Span, SpanData, Symbol};
9
10use crate::borrow_tracker::stacked_borrows::diagnostics::TagHistory;
11use crate::borrow_tracker::tree_borrows::diagnostics as tree_diagnostics;
12use crate::*;
13
14pub enum TerminationInfo {
16 Exit {
17 code: i32,
18 leak_check: bool,
19 },
20 Abort(String),
21 Interrupted,
23 UnsupportedInIsolation(String),
24 StackedBorrowsUb {
25 msg: String,
26 help: Vec<String>,
27 history: Option<TagHistory>,
28 },
29 TreeBorrowsUb {
30 title: String,
31 details: Vec<String>,
32 history: tree_diagnostics::HistoryData,
33 },
34 Int2PtrWithStrictProvenance,
35 GlobalDeadlock,
37 LocalDeadlock,
39 MultipleSymbolDefinitions {
40 link_name: Symbol,
41 first: SpanData,
42 first_crate: Symbol,
43 second: SpanData,
44 second_crate: Symbol,
45 },
46 SymbolShimClashing {
47 link_name: Symbol,
48 span: SpanData,
49 },
50 DataRace {
51 involves_non_atomic: bool,
52 ptr: interpret::Pointer<AllocId>,
53 op1: RacingOp,
54 op2: RacingOp,
55 extra: Option<&'static str>,
56 retag_explain: bool,
57 },
58 UnsupportedForeignItem(String),
59}
60
61pub struct RacingOp {
62 pub action: String,
63 pub thread_info: String,
64 pub span: SpanData,
65}
66
67impl fmt::Display for TerminationInfo {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 use TerminationInfo::*;
70 match self {
71 Exit { code, .. } => write!(f, "the evaluated program completed with exit code {code}"),
72 Abort(msg) => write!(f, "{msg}"),
73 Interrupted => write!(f, "interpretation was interrupted"),
74 UnsupportedInIsolation(msg) => write!(f, "{msg}"),
75 Int2PtrWithStrictProvenance =>
76 write!(
77 f,
78 "integer-to-pointer casts and `ptr::with_exposed_provenance` are not supported with `-Zmiri-strict-provenance`"
79 ),
80 StackedBorrowsUb { msg, .. } => write!(f, "{msg}"),
81 TreeBorrowsUb { title, .. } => write!(f, "{title}"),
82 GlobalDeadlock => write!(f, "the evaluated program deadlocked"),
83 LocalDeadlock => write!(f, "a thread deadlocked"),
84 MultipleSymbolDefinitions { link_name, .. } =>
85 write!(f, "multiple definitions of symbol `{link_name}`"),
86 SymbolShimClashing { link_name, .. } =>
87 write!(f, "found `{link_name}` symbol definition that clashes with a built-in shim",),
88 DataRace { involves_non_atomic, ptr, op1, op2, .. } =>
89 write!(
90 f,
91 "{} detected between (1) {} on {} and (2) {} on {} at {ptr:?}",
92 if *involves_non_atomic { "Data race" } else { "Race condition" },
93 op1.action,
94 op1.thread_info,
95 op2.action,
96 op2.thread_info
97 ),
98 UnsupportedForeignItem(msg) => write!(f, "{msg}"),
99 }
100 }
101}
102
103impl fmt::Debug for TerminationInfo {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 write!(f, "{self}")
106 }
107}
108
109impl MachineStopType for TerminationInfo {
110 fn diagnostic_message(&self) -> DiagMessage {
111 self.to_string().into()
112 }
113 fn add_args(
114 self: Box<Self>,
115 _: &mut dyn FnMut(std::borrow::Cow<'static, str>, rustc_errors::DiagArgValue),
116 ) {
117 }
118}
119
120pub enum NonHaltingDiagnostic {
122 CreatedPointerTag(NonZero<u64>, Option<String>, Option<(AllocId, AllocRange, ProvenanceExtra)>),
126 PoppedPointerTag(Item, String),
128 TrackingAlloc(AllocId, Size, Align),
129 FreedAlloc(AllocId),
130 AccessedAlloc(AllocId, AllocRange, borrow_tracker::AccessKind),
131 RejectedIsolatedOp(String),
132 ProgressReport {
133 block_count: u64, },
135 Int2Ptr {
136 details: bool,
137 },
138 NativeCallSharedMem {
139 tracing: bool,
140 },
141 NativeCallFnPtr,
142 WeakMemoryOutdatedLoad {
143 ptr: Pointer,
144 },
145 ExternTypeReborrow,
146 GenmcCompareExchangeWeak,
147 GenmcCompareExchangeOrderingMismatch {
148 success_ordering: AtomicRwOrd,
149 upgraded_success_ordering: AtomicRwOrd,
150 failure_ordering: AtomicReadOrd,
151 effective_failure_ordering: AtomicReadOrd,
152 },
153}
154
155pub enum DiagLevel {
157 Error,
158 Warning,
159 Note,
160}
161
162macro_rules! note {
164 ($($tt:tt)*) => { (None, format!($($tt)*)) };
165}
166macro_rules! note_span {
168 ($span:expr, $($tt:tt)*) => { (Some($span), format!($($tt)*)) };
169}
170
171pub fn prune_stacktrace<'tcx>(
175 mut stacktrace: Vec<FrameInfo<'tcx>>,
176 machine: &MiriMachine<'tcx>,
177) -> (Vec<FrameInfo<'tcx>>, bool) {
178 match machine.backtrace_style {
179 BacktraceStyle::Off => {
180 stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx));
183 stacktrace.truncate(1);
185 (stacktrace, false)
186 }
187 BacktraceStyle::Short => {
188 let original_len = stacktrace.len();
189 stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx));
192 let has_local_frame = stacktrace.iter().any(|frame| machine.is_local(frame.instance));
196 if has_local_frame {
197 stacktrace = stacktrace
202 .into_iter()
203 .take_while(|frame| {
204 let def_id = frame.instance.def_id();
205 let path = machine.tcx.def_path_str(def_id);
206 !path.contains("__rust_begin_short_backtrace")
207 })
208 .collect::<Vec<_>>();
209
210 while stacktrace.len() > 1
216 && stacktrace.last().is_some_and(|frame| !machine.is_local(frame.instance))
217 {
218 stacktrace.pop();
219 }
220 }
221 let was_pruned = stacktrace.len() != original_len;
222 (stacktrace, was_pruned)
223 }
224 BacktraceStyle::Full => (stacktrace, false),
225 }
226}
227
228pub fn report_result<'tcx>(
233 ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
234 res: InterpErrorInfo<'tcx>,
235) -> Option<(i32, bool)> {
236 use InterpErrorKind::*;
237 use UndefinedBehaviorInfo::*;
238
239 let mut labels = vec![];
240
241 let (title, helps) = if let MachineStop(info) = res.kind() {
242 let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
243 use TerminationInfo::*;
244 let title = match info {
245 &Exit { code, leak_check } => return Some((code, leak_check)),
246 Abort(_) => Some("abnormal termination"),
247 Interrupted => None,
248 UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance | UnsupportedForeignItem(_) =>
249 Some("unsupported operation"),
250 StackedBorrowsUb { .. } | TreeBorrowsUb { .. } | DataRace { .. } =>
251 Some("Undefined Behavior"),
252 LocalDeadlock => {
253 labels.push(format!("this thread got stuck here"));
254 None
255 }
256 GlobalDeadlock => {
257 let mut any_pruned = false;
260 for (thread, stack) in ecx.machine.threads.all_blocked_stacks() {
261 let stacktrace = Frame::generate_stacktrace_from_stack(stack);
262 let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
263 any_pruned |= was_pruned;
264 report_msg(
265 DiagLevel::Error,
266 format!("the evaluated program deadlocked"),
267 vec![format!(
268 "thread `{}` got stuck here",
269 ecx.machine.threads.get_thread_display_name(thread)
270 )],
271 vec![],
272 vec![],
273 &stacktrace,
274 Some(thread),
275 &ecx.machine,
276 )
277 }
278 if any_pruned {
279 ecx.tcx.dcx().note(
280 "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace"
281 );
282 }
283 return None;
284 }
285 MultipleSymbolDefinitions { .. } | SymbolShimClashing { .. } => None,
286 };
287 #[rustfmt::skip]
288 let helps = match info {
289 UnsupportedInIsolation(_) =>
290 vec![
291 note!("set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation;"),
292 note!("or set `MIRIFLAGS=-Zmiri-isolation-error=warn` to make Miri return an error code from isolated operations (if supported for that operation) and continue with a warning"),
293 ],
294 UnsupportedForeignItem(_) => {
295 vec![
296 note!("this means the program tried to do something Miri does not support; it does not indicate a bug in the program"),
297 ]
298 }
299 StackedBorrowsUb { help, history, .. } => {
300 labels.extend(help.clone());
301 let mut helps = vec![
302 note!("this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental"),
303 note!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information"),
304 ];
305 if let Some(TagHistory {created, invalidated, protected}) = history.clone() {
306 helps.push((Some(created.1), created.0));
307 if let Some((msg, span)) = invalidated {
308 helps.push(note_span!(span, "{msg}"));
309 }
310 if let Some((protector_msg, protector_span)) = protected {
311 helps.push(note_span!(protector_span, "{protector_msg}"));
312 }
313 }
314 helps
315 },
316 TreeBorrowsUb { title: _, details, history } => {
317 let mut helps = vec![
318 note!("this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental"),
319 note!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information"),
320 ];
321 for m in details {
322 helps.push(note!("{m}"));
323 }
324 for event in history.events.clone() {
325 helps.push(event);
326 }
327 helps
328 }
329 MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
330 vec![
331 note_span!(*first, "it's first defined here, in crate `{first_crate}`"),
332 note_span!(*second, "then it's defined here again, in crate `{second_crate}`"),
333 ],
334 SymbolShimClashing { link_name, span } =>
335 vec![note_span!(*span, "the `{link_name}` symbol is defined here")],
336 Int2PtrWithStrictProvenance =>
337 vec![note!("use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead")],
338 DataRace { op1, extra, retag_explain, .. } => {
339 labels.push(format!("(2) just happened here"));
340 let mut helps = vec![note_span!(op1.span, "and (1) occurred earlier here")];
341 if let Some(extra) = extra {
342 helps.push(note!("{extra}"));
343 helps.push(note!("see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model"));
344 }
345 if *retag_explain {
346 helps.push(note!("retags occur on all (re)borrows and as well as when references are copied or moved"));
347 helps.push(note!("retags permit optimizations that insert speculative reads or writes"));
348 helps.push(note!("therefore from the perspective of data races, a retag has the same implications as a read or write"));
349 }
350 helps.push(note!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"));
351 helps.push(note!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"));
352 helps
353 }
354 ,
355 _ => vec![],
356 };
357 (title, helps)
358 } else {
359 let title = match res.kind() {
360 UndefinedBehavior(ValidationError(validation_err))
361 if matches!(
362 validation_err.kind,
363 ValidationErrorKind::PointerAsInt { .. } | ValidationErrorKind::PartialPointer
364 ) =>
365 {
366 ecx.handle_ice(); bug!(
368 "This validation error should be impossible in Miri: {}",
369 format_interp_error(ecx.tcx.dcx(), res)
370 );
371 }
372 UndefinedBehavior(_) => "Undefined Behavior",
373 ResourceExhaustion(_) => "resource exhaustion",
374 Unsupported(
375 UnsupportedOpInfo::Unsupported(_)
377 | UnsupportedOpInfo::UnsizedLocal
378 | UnsupportedOpInfo::ExternTypeField,
379 ) => "unsupported operation",
380 InvalidProgram(
381 InvalidProgramInfo::AlreadyReported(_) | InvalidProgramInfo::Layout(..),
383 ) => "post-monomorphization error",
384 _ => {
385 ecx.handle_ice(); bug!(
387 "This error should be impossible in Miri: {}",
388 format_interp_error(ecx.tcx.dcx(), res)
389 );
390 }
391 };
392 #[rustfmt::skip]
393 let helps = match res.kind() {
394 Unsupported(_) =>
395 vec![
396 note!("this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support"),
397 ],
398 ResourceExhaustion(ResourceExhaustionInfo::AddressSpaceFull) if ecx.machine.data_race.as_genmc_ref().is_some() =>
399 vec![
400 note!("in GenMC mode, the address space is limited to 4GB per thread, and addresses cannot be reused")
401 ],
402 UndefinedBehavior(AlignmentCheckFailed { .. })
403 if ecx.machine.check_alignment == AlignmentCheck::Symbolic
404 =>
405 vec![
406 note!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior"),
407 note!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives"),
408 ],
409 UndefinedBehavior(info) => {
410 let mut helps = vec![
411 note!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"),
412 note!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"),
413 ];
414 match info {
415 PointerUseAfterFree(alloc_id, _) | PointerOutOfBounds { alloc_id, .. } => {
416 if let Some(span) = ecx.machine.allocated_span(*alloc_id) {
417 helps.push(note_span!(span, "{:?} was allocated here:", alloc_id));
418 }
419 if let Some(span) = ecx.machine.deallocated_span(*alloc_id) {
420 helps.push(note_span!(span, "{:?} was deallocated here:", alloc_id));
421 }
422 }
423 AbiMismatchArgument { .. } | AbiMismatchReturn { .. } => {
424 helps.push(note!("this means these two types are not *guaranteed* to be ABI-compatible across all targets"));
425 helps.push(note!("if you think this code should be accepted anyway, please report an issue with Miri"));
426 }
427 _ => {},
428 }
429 helps
430 }
431 InvalidProgram(
432 InvalidProgramInfo::AlreadyReported(_)
433 ) => {
434 return None;
436 }
437 _ =>
438 vec![],
439 };
440 (Some(title), helps)
441 };
442
443 let stacktrace = ecx.generate_stacktrace();
444 let (stacktrace, pruned) = prune_stacktrace(stacktrace, &ecx.machine);
445
446 let mut extra = String::new();
449 match res.kind() {
450 UndefinedBehavior(InvalidUninitBytes(Some((alloc_id, access)))) => {
451 writeln!(
452 extra,
453 "Uninitialized memory occurred at {alloc_id:?}{range:?}, in this allocation:",
454 range = access.bad,
455 )
456 .unwrap();
457 writeln!(extra, "{:?}", ecx.dump_alloc(*alloc_id)).unwrap();
458 }
459 _ => {}
460 }
461
462 let mut primary_msg = String::new();
463 if let Some(title) = title {
464 write!(primary_msg, "{title}: ").unwrap();
465 }
466 write!(primary_msg, "{}", format_interp_error(ecx.tcx.dcx(), res)).unwrap();
467
468 if labels.is_empty() {
469 labels.push(format!(
470 "{} occurred {}",
471 title.unwrap_or("error"),
472 if stacktrace.is_empty() { "due to this code" } else { "here" }
473 ));
474 }
475
476 report_msg(
477 DiagLevel::Error,
478 primary_msg,
479 labels,
480 vec![],
481 helps,
482 &stacktrace,
483 Some(ecx.active_thread()),
484 &ecx.machine,
485 );
486
487 eprint!("{extra}"); if pruned {
491 ecx.tcx.dcx().note(
492 "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
493 );
494 }
495
496 for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
498 trace!("-------------------");
499 trace!("Frame {}", i);
500 trace!(" return: {:?}", frame.return_place);
501 for (i, local) in frame.locals.iter().enumerate() {
502 trace!(" local {}: {:?}", i, local);
503 }
504 }
505
506 None
507}
508
509pub fn report_leaks<'tcx>(
510 ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
511 leaks: Vec<(AllocId, MemoryKind, Allocation<Provenance, AllocExtra<'tcx>, MiriAllocBytes>)>,
512) {
513 let mut any_pruned = false;
514 for (id, kind, alloc) in leaks {
515 let mut title = format!(
516 "memory leaked: {id:?} ({}, size: {:?}, align: {:?})",
517 kind,
518 alloc.size().bytes(),
519 alloc.align.bytes()
520 );
521 let Some(backtrace) = alloc.extra.backtrace else {
522 ecx.tcx.dcx().err(title);
523 continue;
524 };
525 title.push_str(", allocated here:");
526 let (backtrace, pruned) = prune_stacktrace(backtrace, &ecx.machine);
527 any_pruned |= pruned;
528 report_msg(
529 DiagLevel::Error,
530 title,
531 vec![],
532 vec![],
533 vec![],
534 &backtrace,
535 None, &ecx.machine,
537 );
538 }
539 if any_pruned {
540 ecx.tcx.dcx().note(
541 "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
542 );
543 }
544}
545
546pub fn report_msg<'tcx>(
552 diag_level: DiagLevel,
553 title: String,
554 span_msg: Vec<String>,
555 notes: Vec<(Option<SpanData>, String)>,
556 helps: Vec<(Option<SpanData>, String)>,
557 stacktrace: &[FrameInfo<'tcx>],
558 thread: Option<ThreadId>,
559 machine: &MiriMachine<'tcx>,
560) {
561 let span = match stacktrace.first() {
562 Some(fi) => fi.span,
563 None =>
564 match thread {
565 Some(thread_id) => machine.threads.thread_ref(thread_id).origin_span,
566 None => DUMMY_SP,
567 },
568 };
569 let sess = machine.tcx.sess;
570 let level = match diag_level {
571 DiagLevel::Error => Level::Error,
572 DiagLevel::Warning => Level::Warning,
573 DiagLevel::Note => Level::Note,
574 };
575 let mut err = Diag::<()>::new(sess.dcx(), level, title);
576 err.span(span);
577
578 if !span.is_dummy() {
580 for line in span_msg {
581 err.span_label(span, line);
582 }
583 } else {
584 for line in span_msg {
586 err.note(line);
587 }
588 err.note("(no span available)");
589 }
590
591 let mut extra_span = false;
593 for (span_data, note) in notes {
594 if let Some(span_data) = span_data {
595 err.span_note(span_data.span(), note);
596 extra_span = true;
597 } else {
598 err.note(note);
599 }
600 }
601 for (span_data, help) in helps {
602 if let Some(span_data) = span_data {
603 err.span_help(span_data.span(), help);
604 extra_span = true;
605 } else {
606 err.help(help);
607 }
608 }
609
610 if stacktrace.len() > 1 {
612 let mut backtrace_title = String::from("BACKTRACE");
613 if extra_span {
614 write!(backtrace_title, " (of the first span)").unwrap();
615 }
616 if let Some(thread) = thread {
617 let thread_name = machine.threads.get_thread_display_name(thread);
618 if thread_name != "main" {
619 write!(backtrace_title, " on thread `{thread_name}`").unwrap();
621 };
622 }
623 write!(backtrace_title, ":").unwrap();
624 err.note(backtrace_title);
625 for (idx, frame_info) in stacktrace.iter().enumerate() {
626 let is_local = machine.is_local(frame_info.instance);
627 if is_local && idx > 0 {
629 err.subdiagnostic(frame_info.as_note(machine.tcx));
630 } else {
631 let sm = sess.source_map();
632 let span = sm.span_to_diagnostic_string(frame_info.span);
633 err.note(format!("{frame_info} at {span}"));
634 }
635 }
636 } else if stacktrace.len() == 0 && !span.is_dummy() {
637 err.note(format!(
638 "this {} occurred while pushing a call frame onto an empty stack",
639 level.to_str()
640 ));
641 err.note("the span indicates which code caused the function to be called, but may not be the literal call site");
642 }
643
644 err.emit();
645}
646
647impl<'tcx> MiriMachine<'tcx> {
648 pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
649 use NonHaltingDiagnostic::*;
650
651 let stacktrace = Frame::generate_stacktrace_from_stack(self.threads.active_thread_stack());
652 let (stacktrace, _was_pruned) = prune_stacktrace(stacktrace, self);
653
654 let (label, diag_level) = match &e {
655 RejectedIsolatedOp(_) =>
656 ("operation rejected by isolation".to_string(), DiagLevel::Warning),
657 Int2Ptr { .. } => ("integer-to-pointer cast".to_string(), DiagLevel::Warning),
658 NativeCallSharedMem { .. } =>
659 ("sharing memory with a native function".to_string(), DiagLevel::Warning),
660 NativeCallFnPtr =>
661 (
662 "sharing a function pointer with a native function".to_string(),
663 DiagLevel::Warning,
664 ),
665 ExternTypeReborrow =>
666 ("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning),
667 GenmcCompareExchangeWeak | GenmcCompareExchangeOrderingMismatch { .. } =>
668 ("GenMC might miss possible behaviors of this code".to_string(), DiagLevel::Warning),
669 CreatedPointerTag(..)
670 | PoppedPointerTag(..)
671 | TrackingAlloc(..)
672 | AccessedAlloc(..)
673 | FreedAlloc(..)
674 | ProgressReport { .. }
675 | WeakMemoryOutdatedLoad { .. } =>
676 ("tracking was triggered here".to_string(), DiagLevel::Note),
677 };
678
679 let title = match &e {
680 CreatedPointerTag(tag, None, _) => format!("created base tag {tag:?}"),
681 CreatedPointerTag(tag, Some(perm), None) =>
682 format!("created {tag:?} with {perm} derived from unknown tag"),
683 CreatedPointerTag(tag, Some(perm), Some((alloc_id, range, orig_tag))) =>
684 format!(
685 "created tag {tag:?} with {perm} at {alloc_id:?}{range:?} derived from {orig_tag:?}"
686 ),
687 PoppedPointerTag(item, cause) => format!("popped tracked tag for item {item:?}{cause}"),
688 TrackingAlloc(id, size, align) =>
689 format!(
690 "now tracking allocation {id:?} of {size} bytes (alignment {align} bytes)",
691 size = size.bytes(),
692 align = align.bytes(),
693 ),
694 AccessedAlloc(id, range, access_kind) =>
695 format!("{access_kind} at {id:?}[{}..{}]", range.start.bytes(), range.end().bytes()),
696 FreedAlloc(id) => format!("freed allocation {id:?}"),
697 RejectedIsolatedOp(op) => format!("{op} was made to return an error due to isolation"),
698 ProgressReport { .. } =>
699 format!("progress report: current operation being executed is here"),
700 Int2Ptr { .. } => format!("integer-to-pointer cast"),
701 NativeCallSharedMem { .. } =>
702 format!("sharing memory with a native function called via FFI"),
703 NativeCallFnPtr =>
704 format!("sharing a function pointer with a native function called via FFI"),
705 WeakMemoryOutdatedLoad { ptr } =>
706 format!("weak memory emulation: outdated value returned from load at {ptr}"),
707 ExternTypeReborrow =>
708 format!("reborrow of a reference to `extern type` is not properly supported"),
709 GenmcCompareExchangeWeak =>
710 "GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures."
711 .to_string(),
712 GenmcCompareExchangeOrderingMismatch {
713 success_ordering,
714 upgraded_success_ordering,
715 failure_ordering,
716 effective_failure_ordering,
717 } => {
718 let was_upgraded_msg = if success_ordering != upgraded_success_ordering {
719 format!("Success ordering '{success_ordering:?}' was upgraded to '{upgraded_success_ordering:?}' to match failure ordering '{failure_ordering:?}'")
720 } else {
721 assert_ne!(failure_ordering, effective_failure_ordering);
722 format!("Due to success ordering '{success_ordering:?}', the failure ordering '{failure_ordering:?}' is treated like '{effective_failure_ordering:?}'")
723 };
724 format!("GenMC currently does not model the failure ordering for `compare_exchange`. {was_upgraded_msg}. Miri with GenMC might miss bugs related to this memory access.")
725 }
726 };
727
728 let notes = match &e {
729 ProgressReport { block_count } => {
730 vec![note!("so far, {block_count} basic blocks have been executed")]
731 }
732 _ => vec![],
733 };
734
735 let helps = match &e {
736 Int2Ptr { details: true } => {
737 let mut v = vec![
738 note!(
739 "this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program"
740 ),
741 note!(
742 "see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation"
743 ),
744 note!(
745 "to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead"
746 ),
747 note!(
748 "you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics"
749 ),
750 ];
751 if self.borrow_tracker.as_ref().is_some_and(|b| {
752 matches!(
753 b.borrow().borrow_tracker_method(),
754 BorrowTrackerMethod::TreeBorrows { .. }
755 )
756 }) {
757 v.push(
758 note!("Tree Borrows does not support integer-to-pointer casts, so the program is likely to go wrong when this pointer gets used")
759 );
760 } else {
761 v.push(
762 note!("alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning")
763 );
764 }
765 v
766 }
767 NativeCallSharedMem { tracing } =>
768 if *tracing {
769 vec![
770 note!(
771 "when memory is shared with a native function call, Miri can only track initialisation and provenance on a best-effort basis"
772 ),
773 note!(
774 "in particular, Miri assumes that the native call initializes all memory it has written to"
775 ),
776 note!(
777 "Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory"
778 ),
779 note!(
780 "what this means is that Miri will easily miss Undefined Behavior related to incorrect usage of this shared memory, so you should not take a clean Miri run as a signal that your FFI code is UB-free"
781 ),
782 note!(
783 "tracing memory accesses in native code is not yet fully implemented, so there can be further imprecisions beyond what is documented here"
784 ),
785 ]
786 } else {
787 vec![
788 note!(
789 "when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory"
790 ),
791 note!(
792 "in particular, Miri assumes that the native call initializes all memory it has access to"
793 ),
794 note!(
795 "Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory"
796 ),
797 note!(
798 "what this means is that Miri will easily miss Undefined Behavior related to incorrect usage of this shared memory, so you should not take a clean Miri run as a signal that your FFI code is UB-free"
799 ),
800 ]
801 },
802 NativeCallFnPtr => {
803 vec![note!(
804 "calling Rust functions from C is not supported and will, in the best case, crash the program"
805 )]
806 }
807 ExternTypeReborrow => {
808 assert!(self.borrow_tracker.as_ref().is_some_and(|b| {
809 matches!(
810 b.borrow().borrow_tracker_method(),
811 BorrowTrackerMethod::StackedBorrows
812 )
813 }));
814 vec![
815 note!(
816 "`extern type` are not compatible with the Stacked Borrows aliasing model implemented by Miri; Miri may miss bugs in this code"
817 ),
818 note!(
819 "try running with `MIRIFLAGS=-Zmiri-tree-borrows` to use the more permissive but also even more experimental Tree Borrows aliasing checks instead"
820 ),
821 ]
822 }
823 _ => vec![],
824 };
825
826 report_msg(
827 diag_level,
828 title,
829 vec![label],
830 notes,
831 helps,
832 &stacktrace,
833 Some(self.threads.active_thread()),
834 self,
835 );
836 }
837}
838
839impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
840pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
841 fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
842 let this = self.eval_context_ref();
843 this.machine.emit_diagnostic(e);
844 }
845
846 fn handle_ice(&self) {
848 eprintln!();
849 eprintln!(
850 "Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic:"
851 );
852 let this = self.eval_context_ref();
853 let stacktrace = this.generate_stacktrace();
854 report_msg(
855 DiagLevel::Note,
856 "the place in the program where the ICE was triggered".to_string(),
857 vec![],
858 vec![],
859 vec![],
860 &stacktrace,
861 Some(this.active_thread()),
862 &this.machine,
863 );
864 }
865
866 fn dedup_diagnostic(
870 &self,
871 dedup: &SpanDedupDiagnostic,
872 f: impl FnOnce(bool) -> NonHaltingDiagnostic,
873 ) {
874 let this = self.eval_context_ref();
875 let span1 = this.machine.current_user_relevant_span();
879 let span2 = this
882 .active_thread_stack()
883 .iter()
884 .rev()
885 .find(|frame| !frame.instance().def.requires_caller_location(*this.tcx))
886 .map(|frame| frame.current_span())
887 .unwrap_or(span1);
888
889 let mut lock = dedup.0.lock().unwrap();
890 let first = lock.is_empty();
891 if !lock.contains(&span2) && lock.insert(span1) && (span1 == span2 || lock.insert(span2)) {
893 this.emit_diagnostic(f(first));
895 }
896 }
897}
898
899pub struct SpanDedupDiagnostic(Mutex<FxHashSet<Span>>);
901
902impl SpanDedupDiagnostic {
903 pub const fn new() -> Self {
904 Self(Mutex::new(FxHashSet::with_hasher(FxBuildHasher)))
905 }
906}