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 WeakMemoryOutdatedLoad {
142 ptr: Pointer,
143 },
144 ExternTypeReborrow,
145 GenmcCompareExchangeWeak,
146 GenmcCompareExchangeOrderingMismatch {
147 success_ordering: AtomicRwOrd,
148 upgraded_success_ordering: AtomicRwOrd,
149 failure_ordering: AtomicReadOrd,
150 effective_failure_ordering: AtomicReadOrd,
151 },
152 FileInProcOpened,
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!("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, *ecx.tcx);
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!("thread got stuck here")],
268 vec![],
269 vec![],
270 &stacktrace,
271 Some(thread),
272 &ecx.machine,
273 )
274 }
275 if any_pruned {
276 ecx.tcx.dcx().note(
277 "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace"
278 );
279 }
280 return None;
281 }
282 MultipleSymbolDefinitions { .. } | SymbolShimClashing { .. } => None,
283 };
284 #[rustfmt::skip]
285 let helps = match info {
286 UnsupportedInIsolation(_) =>
287 vec![
288 note!("set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation;"),
289 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"),
290 ],
291 UnsupportedForeignItem(_) => {
292 vec![
293 note!("this means the program tried to do something Miri does not support; it does not indicate a bug in the program"),
294 ]
295 }
296 StackedBorrowsUb { help, history, .. } => {
297 labels.extend(help.clone());
298 let mut helps = vec![
299 note!("this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental"),
300 note!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information"),
301 ];
302 if let Some(TagHistory {created, invalidated, protected}) = history.clone() {
303 helps.push((Some(created.1), created.0));
304 if let Some((msg, span)) = invalidated {
305 helps.push(note_span!(span, "{msg}"));
306 }
307 if let Some((protector_msg, protector_span)) = protected {
308 helps.push(note_span!(protector_span, "{protector_msg}"));
309 }
310 }
311 helps
312 },
313 TreeBorrowsUb { title: _, details, history } => {
314 let mut helps = vec![
315 note!("this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental"),
316 note!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information"),
317 ];
318 for m in details {
319 helps.push(note!("{m}"));
320 }
321 for event in history.events.clone() {
322 helps.push(event);
323 }
324 helps
325 }
326 MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
327 vec![
328 note_span!(*first, "it's first defined here, in crate `{first_crate}`"),
329 note_span!(*second, "then it's defined here again, in crate `{second_crate}`"),
330 ],
331 SymbolShimClashing { link_name, span } =>
332 vec![note_span!(*span, "the `{link_name}` symbol is defined here")],
333 Int2PtrWithStrictProvenance =>
334 vec![note!("use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead")],
335 DataRace { op1, extra, retag_explain, .. } => {
336 labels.push(format!("(2) just happened here"));
337 let mut helps = vec![note_span!(op1.span, "and (1) occurred earlier here")];
338 if let Some(extra) = extra {
339 helps.push(note!("{extra}"));
340 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"));
341 }
342 if *retag_explain {
343 helps.push(note!("retags occur on all (re)borrows and as well as when references are copied or moved"));
344 helps.push(note!("retags permit optimizations that insert speculative reads or writes"));
345 helps.push(note!("therefore from the perspective of data races, a retag has the same implications as a read or write"));
346 }
347 helps.push(note!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"));
348 helps.push(note!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"));
349 helps
350 }
351 ,
352 _ => vec![],
353 };
354 (title, helps)
355 } else {
356 let title = match res.kind() {
357 UndefinedBehavior(ValidationError(validation_err))
358 if matches!(
359 validation_err.kind,
360 ValidationErrorKind::PointerAsInt { .. } | ValidationErrorKind::PartialPointer
361 ) =>
362 {
363 ecx.handle_ice(); bug!(
365 "This validation error should be impossible in Miri: {}",
366 format_interp_error(ecx.tcx.dcx(), res)
367 );
368 }
369 UndefinedBehavior(_) => "Undefined Behavior",
370 ResourceExhaustion(_) => "resource exhaustion",
371 Unsupported(
372 UnsupportedOpInfo::Unsupported(_)
374 | UnsupportedOpInfo::UnsizedLocal
375 | UnsupportedOpInfo::ExternTypeField,
376 ) => "unsupported operation",
377 InvalidProgram(
378 InvalidProgramInfo::AlreadyReported(_) | InvalidProgramInfo::Layout(..),
380 ) => "post-monomorphization error",
381 _ => {
382 ecx.handle_ice(); bug!(
384 "This error should be impossible in Miri: {}",
385 format_interp_error(ecx.tcx.dcx(), res)
386 );
387 }
388 };
389 #[rustfmt::skip]
390 let helps = match res.kind() {
391 Unsupported(_) =>
392 vec![
393 note!("this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support"),
394 ],
395 ResourceExhaustion(ResourceExhaustionInfo::AddressSpaceFull) if ecx.machine.data_race.as_genmc_ref().is_some() =>
396 vec![
397 note!("in GenMC mode, the address space is limited to 4GB per thread, and addresses cannot be reused")
398 ],
399 UndefinedBehavior(AlignmentCheckFailed { .. })
400 if ecx.machine.check_alignment == AlignmentCheck::Symbolic
401 =>
402 vec![
403 note!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior"),
404 note!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives"),
405 ],
406 UndefinedBehavior(info) => {
407 let mut helps = vec![
408 note!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"),
409 note!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"),
410 ];
411 match info {
412 PointerUseAfterFree(alloc_id, _) | PointerOutOfBounds { alloc_id, .. } => {
413 if let Some(span) = ecx.machine.allocated_span(*alloc_id) {
414 helps.push(note_span!(span, "{:?} was allocated here:", alloc_id));
415 }
416 if let Some(span) = ecx.machine.deallocated_span(*alloc_id) {
417 helps.push(note_span!(span, "{:?} was deallocated here:", alloc_id));
418 }
419 }
420 AbiMismatchArgument { .. } | AbiMismatchReturn { .. } => {
421 helps.push(note!("this means these two types are not *guaranteed* to be ABI-compatible across all targets"));
422 helps.push(note!("if you think this code should be accepted anyway, please report an issue with Miri"));
423 }
424 _ => {},
425 }
426 helps
427 }
428 InvalidProgram(
429 InvalidProgramInfo::AlreadyReported(_)
430 ) => {
431 return None;
433 }
434 _ =>
435 vec![],
436 };
437 (Some(title), helps)
438 };
439
440 let stacktrace = ecx.generate_stacktrace();
441 let (stacktrace, pruned) = prune_stacktrace(stacktrace, &ecx.machine);
442
443 let mut extra = String::new();
446 match res.kind() {
447 UndefinedBehavior(InvalidUninitBytes(Some((alloc_id, access)))) => {
448 writeln!(
449 extra,
450 "Uninitialized memory occurred at {alloc_id:?}{range:?}, in this allocation:",
451 range = access.bad,
452 )
453 .unwrap();
454 writeln!(extra, "{:?}", ecx.dump_alloc(*alloc_id)).unwrap();
455 }
456 _ => {}
457 }
458
459 let mut primary_msg = String::new();
460 if let Some(title) = title {
461 write!(primary_msg, "{title}: ").unwrap();
462 }
463 write!(primary_msg, "{}", format_interp_error(ecx.tcx.dcx(), res)).unwrap();
464
465 if labels.is_empty() {
466 labels.push(format!(
467 "{} occurred {}",
468 title.unwrap_or("error"),
469 if stacktrace.is_empty() { "due to this code" } else { "here" }
470 ));
471 }
472
473 report_msg(
474 DiagLevel::Error,
475 primary_msg,
476 labels,
477 vec![],
478 helps,
479 &stacktrace,
480 Some(ecx.active_thread()),
481 &ecx.machine,
482 );
483
484 eprint!("{extra}"); if pruned {
488 ecx.tcx.dcx().note(
489 "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
490 );
491 }
492
493 for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
495 trace!("-------------------");
496 trace!("Frame {}", i);
497 trace!(" return: {:?}", frame.return_place());
498 for (i, local) in frame.locals.iter().enumerate() {
499 trace!(" local {}: {:?}", i, local);
500 }
501 }
502
503 None
504}
505
506pub fn report_leaks<'tcx>(
507 ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
508 leaks: Vec<(AllocId, MemoryKind, Allocation<Provenance, AllocExtra<'tcx>, MiriAllocBytes>)>,
509) {
510 let mut any_pruned = false;
511 for (id, kind, alloc) in leaks {
512 let mut title = format!(
513 "memory leaked: {id:?} ({}, size: {:?}, align: {:?})",
514 kind,
515 alloc.size().bytes(),
516 alloc.align.bytes()
517 );
518 let Some(backtrace) = alloc.extra.backtrace else {
519 ecx.tcx.dcx().err(title);
520 continue;
521 };
522 title.push_str(", allocated here:");
523 let (backtrace, pruned) = prune_stacktrace(backtrace, &ecx.machine);
524 any_pruned |= pruned;
525 report_msg(
526 DiagLevel::Error,
527 title,
528 vec![],
529 vec![],
530 vec![],
531 &backtrace,
532 None, &ecx.machine,
534 );
535 }
536 if any_pruned {
537 ecx.tcx.dcx().note(
538 "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
539 );
540 }
541}
542
543fn report_msg<'tcx>(
549 diag_level: DiagLevel,
550 title: String,
551 span_msg: Vec<String>,
552 notes: Vec<(Option<SpanData>, String)>,
553 helps: Vec<(Option<SpanData>, String)>,
554 stacktrace: &[FrameInfo<'tcx>],
555 thread: Option<ThreadId>,
556 machine: &MiriMachine<'tcx>,
557) {
558 let origin_span = thread.map(|t| machine.threads.thread_ref(t).origin_span).unwrap_or(DUMMY_SP);
559 let span = stacktrace.first().map(|fi| fi.span).unwrap_or(origin_span);
560 assert!(!span.is_dummy());
563
564 let tcx = machine.tcx;
565 let level = match diag_level {
566 DiagLevel::Error => Level::Error,
567 DiagLevel::Warning => Level::Warning,
568 DiagLevel::Note => Level::Note,
569 };
570 let mut err = Diag::<()>::new(tcx.sess.dcx(), level, title);
571 err.span(span);
572
573 for line in span_msg {
575 err.span_label(span, line);
576 }
577
578 for (span_data, note) in notes {
580 if let Some(span_data) = span_data {
581 err.span_note(span_data.span(), note);
582 } else {
583 err.note(note);
584 }
585 }
586 for (span_data, help) in helps {
587 if let Some(span_data) = span_data {
588 err.span_help(span_data.span(), help);
589 } else {
590 err.help(help);
591 }
592 }
593 if let Some(thread) = thread
595 && machine.threads.get_total_thread_count() > 1
596 {
597 err.note(format!(
598 "this is on thread `{}`",
599 machine.threads.get_thread_display_name(thread)
600 ));
601 }
602
603 if stacktrace.len() > 0 {
605 if stacktrace.len() > 1 {
607 let sm = tcx.sess.source_map();
608 let mut out = format!("stack backtrace:");
609 for (idx, frame_info) in stacktrace.iter().enumerate() {
610 let span = sm.span_to_diagnostic_string(frame_info.span);
611 write!(out, "\n{idx}: {}", frame_info.instance).unwrap();
612 write!(out, "\n at {span}").unwrap();
613 }
614 err.note(out);
615 }
616 if !origin_span.is_dummy() {
618 let what = if stacktrace.len() > 1 {
619 "the last function in that backtrace"
620 } else {
621 "the current function"
622 };
623 err.span_note(origin_span, format!("{what} got called indirectly due to this code"));
624 }
625 } else if !span.is_dummy() {
626 err.note(format!("this {level} occurred while pushing a call frame onto an empty stack"));
627 err.note("the span indicates which code caused the function to be called, but may not be the literal call site");
628 }
629
630 err.emit();
631}
632
633impl<'tcx> MiriMachine<'tcx> {
634 pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
635 use NonHaltingDiagnostic::*;
636
637 let stacktrace =
638 Frame::generate_stacktrace_from_stack(self.threads.active_thread_stack(), self.tcx);
639 let (stacktrace, _was_pruned) = prune_stacktrace(stacktrace, self);
640
641 let (label, diag_level) = match &e {
642 RejectedIsolatedOp(_) =>
643 ("operation rejected by isolation".to_string(), DiagLevel::Warning),
644 Int2Ptr { .. } => ("integer-to-pointer cast".to_string(), DiagLevel::Warning),
645 NativeCallSharedMem { .. } =>
646 ("sharing memory with a native function".to_string(), DiagLevel::Warning),
647 ExternTypeReborrow =>
648 ("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning),
649 GenmcCompareExchangeWeak | GenmcCompareExchangeOrderingMismatch { .. } =>
650 ("GenMC might miss possible behaviors of this code".to_string(), DiagLevel::Warning),
651 CreatedPointerTag(..)
652 | PoppedPointerTag(..)
653 | TrackingAlloc(..)
654 | AccessedAlloc(..)
655 | FreedAlloc(..)
656 | ProgressReport { .. }
657 | WeakMemoryOutdatedLoad { .. } =>
658 ("tracking was triggered here".to_string(), DiagLevel::Note),
659 FileInProcOpened => ("open a file in `/proc`".to_string(), DiagLevel::Warning),
660 };
661
662 let title = match &e {
663 CreatedPointerTag(tag, None, _) => format!("created base tag {tag:?}"),
664 CreatedPointerTag(tag, Some(perm), None) =>
665 format!("created {tag:?} with {perm} derived from unknown tag"),
666 CreatedPointerTag(tag, Some(perm), Some((alloc_id, range, orig_tag))) =>
667 format!(
668 "created tag {tag:?} with {perm} at {alloc_id:?}{range:?} derived from {orig_tag:?}"
669 ),
670 PoppedPointerTag(item, cause) => format!("popped tracked tag for item {item:?}{cause}"),
671 TrackingAlloc(id, size, align) =>
672 format!(
673 "now tracking allocation {id:?} of {size} bytes (alignment {align} bytes)",
674 size = size.bytes(),
675 align = align.bytes(),
676 ),
677 AccessedAlloc(id, range, access_kind) =>
678 format!("{access_kind} at {id:?}[{}..{}]", range.start.bytes(), range.end().bytes()),
679 FreedAlloc(id) => format!("freed allocation {id:?}"),
680 RejectedIsolatedOp(op) => format!("{op} was made to return an error due to isolation"),
681 ProgressReport { .. } =>
682 format!("progress report: current operation being executed is here"),
683 Int2Ptr { .. } => format!("integer-to-pointer cast"),
684 NativeCallSharedMem { .. } =>
685 format!("sharing memory with a native function called via FFI"),
686 WeakMemoryOutdatedLoad { ptr } =>
687 format!("weak memory emulation: outdated value returned from load at {ptr}"),
688 ExternTypeReborrow =>
689 format!("reborrow of a reference to `extern type` is not properly supported"),
690 GenmcCompareExchangeWeak =>
691 "GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures."
692 .to_string(),
693 GenmcCompareExchangeOrderingMismatch {
694 success_ordering,
695 upgraded_success_ordering,
696 failure_ordering,
697 effective_failure_ordering,
698 } => {
699 let was_upgraded_msg = if success_ordering != upgraded_success_ordering {
700 format!("Success ordering '{success_ordering:?}' was upgraded to '{upgraded_success_ordering:?}' to match failure ordering '{failure_ordering:?}'")
701 } else {
702 assert_ne!(failure_ordering, effective_failure_ordering);
703 format!("Due to success ordering '{success_ordering:?}', the failure ordering '{failure_ordering:?}' is treated like '{effective_failure_ordering:?}'")
704 };
705 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.")
706 }
707 FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri")
708 };
709
710 let notes = match &e {
711 ProgressReport { block_count } => {
712 vec![note!("so far, {block_count} basic blocks have been executed")]
713 }
714 _ => vec![],
715 };
716
717 let helps = match &e {
718 Int2Ptr { details: true } => {
719 let mut v = vec![
720 note!(
721 "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"
722 ),
723 note!(
724 "see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation"
725 ),
726 note!(
727 "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"
728 ),
729 note!(
730 "you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics"
731 ),
732 ];
733 if self.borrow_tracker.as_ref().is_some_and(|b| {
734 matches!(
735 b.borrow().borrow_tracker_method(),
736 BorrowTrackerMethod::TreeBorrows { .. }
737 )
738 }) {
739 v.push(
740 note!("Tree Borrows does not support integer-to-pointer casts, so the program is likely to go wrong when this pointer gets used")
741 );
742 } else {
743 v.push(
744 note!("alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning")
745 );
746 }
747 v
748 }
749 NativeCallSharedMem { tracing } =>
750 if *tracing {
751 vec![
752 note!(
753 "when memory is shared with a native function call, Miri can only track initialisation and provenance on a best-effort basis"
754 ),
755 note!(
756 "in particular, Miri assumes that the native call initializes all memory it has written to"
757 ),
758 note!(
759 "Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory"
760 ),
761 note!(
762 "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"
763 ),
764 note!(
765 "tracing memory accesses in native code is not yet fully implemented, so there can be further imprecisions beyond what is documented here"
766 ),
767 ]
768 } else {
769 vec![
770 note!(
771 "when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory"
772 ),
773 note!(
774 "in particular, Miri assumes that the native call initializes all memory it has access 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 ]
783 },
784 ExternTypeReborrow => {
785 assert!(self.borrow_tracker.as_ref().is_some_and(|b| {
786 matches!(
787 b.borrow().borrow_tracker_method(),
788 BorrowTrackerMethod::StackedBorrows
789 )
790 }));
791 vec![
792 note!(
793 "`extern type` are not compatible with the Stacked Borrows aliasing model implemented by Miri; Miri may miss bugs in this code"
794 ),
795 note!(
796 "try running with `MIRIFLAGS=-Zmiri-tree-borrows` to use the more permissive but also even more experimental Tree Borrows aliasing checks instead"
797 ),
798 ]
799 }
800 _ => vec![],
801 };
802
803 report_msg(
804 diag_level,
805 title,
806 vec![label],
807 notes,
808 helps,
809 &stacktrace,
810 Some(self.threads.active_thread()),
811 self,
812 );
813 }
814}
815
816impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
817pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
818 fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
819 let this = self.eval_context_ref();
820 this.machine.emit_diagnostic(e);
821 }
822
823 fn handle_ice(&self) {
825 eprintln!();
826 eprintln!(
827 "Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic:"
828 );
829 let this = self.eval_context_ref();
830 let stacktrace = this.generate_stacktrace();
831 report_msg(
832 DiagLevel::Note,
833 "the place in the program where the ICE was triggered".to_string(),
834 vec![],
835 vec![],
836 vec![],
837 &stacktrace,
838 Some(this.active_thread()),
839 &this.machine,
840 );
841 }
842
843 fn dedup_diagnostic(
847 &self,
848 dedup: &SpanDedupDiagnostic,
849 f: impl FnOnce(bool) -> NonHaltingDiagnostic,
850 ) {
851 let this = self.eval_context_ref();
852 let span1 = this.machine.current_user_relevant_span();
856 let span2 = this
859 .active_thread_stack()
860 .iter()
861 .rev()
862 .find(|frame| !frame.instance().def.requires_caller_location(*this.tcx))
863 .map(|frame| frame.current_span())
864 .unwrap_or(span1);
865
866 let mut lock = dedup.0.lock().unwrap();
867 let first = lock.is_empty();
868 if !lock.contains(&span2) && lock.insert(span1) && (span1 == span2 || lock.insert(span2)) {
870 this.emit_diagnostic(f(first));
872 }
873 }
874}
875
876pub struct SpanDedupDiagnostic(Mutex<FxHashSet<Span>>);
878
879impl SpanDedupDiagnostic {
880 pub const fn new() -> Self {
881 Self(Mutex::new(FxHashSet::with_hasher(FxBuildHasher)))
882 }
883}