Skip to main content

miri/
diagnostics.rs

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, 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
14/// Details of premature program termination.
15pub enum TerminationInfo {
16    Exit {
17        code: i32,
18        leak_check: bool,
19    },
20    Abort(String),
21    /// Miri was interrupted by a Ctrl+C from the user
22    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    /// All threads are blocked.
36    GlobalDeadlock,
37    /// Some thread discovered a deadlock condition (e.g. in a mutex with reentrancy checking).
38    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
111/// Miri specific diagnostics
112pub enum NonHaltingDiagnostic {
113    /// (new_tag, new_perm, (alloc_id, base_offset, orig_tag))
114    ///
115    /// new_perm is `None` for base tags.
116    CreatedPointerTag(NonZero<u64>, Option<String>, Option<(AllocId, AllocRange, ProvenanceExtra)>),
117    /// This `Item` was popped from the borrow stack. The string explains the reason.
118    PoppedPointerTag(Item, String),
119    TrackingAlloc(AllocId, Size, Align),
120    FreedAlloc(AllocId),
121    AccessedAlloc(AllocId, AllocRange, borrow_tracker::AccessKind),
122    RejectedIsolatedOp(String),
123    ProgressReport {
124        block_count: u64, // how many basic blocks have been run so far
125    },
126    Int2Ptr {
127        details: bool,
128    },
129    NativeCallSharedMem {
130        tracing: bool,
131    },
132    WeakMemoryOutdatedLoad {
133        ptr: Pointer,
134    },
135    ExternTypeReborrow,
136    GenmcCompareExchangeWeak,
137    GenmcCompareExchangeOrderingMismatch {
138        success_ordering: AtomicRwOrd,
139        upgraded_success_ordering: AtomicRwOrd,
140        failure_ordering: AtomicReadOrd,
141        effective_failure_ordering: AtomicReadOrd,
142    },
143    FileInProcOpened,
144}
145
146/// Level of Miri specific diagnostics
147pub enum DiagLevel {
148    Error,
149    Warning,
150    Note,
151}
152
153/// Generate a note/help text without a span.
154macro_rules! note {
155    ($($tt:tt)*) => { (None, format!($($tt)*)) };
156}
157/// Generate a note/help text with a span.
158macro_rules! note_span {
159    ($span:expr, $($tt:tt)*) => { (Some($span), format!($($tt)*)) };
160}
161
162/// Attempts to prune a stacktrace to omit the Rust runtime, and returns a bool indicating if any
163/// frames were pruned. If the stacktrace does not have any local frames, we conclude that it must
164/// be pointing to a problem in the Rust runtime itself, and do not prune it at all.
165pub fn prune_stacktrace<'tcx>(
166    mut stacktrace: Vec<FrameInfo<'tcx>>,
167    machine: &MiriMachine<'tcx>,
168) -> (Vec<FrameInfo<'tcx>>, bool) {
169    match machine.backtrace_style {
170        BacktraceStyle::Off => {
171            // Remove all frames marked with `caller_location` -- that attribute indicates we
172            // usually want to point at the caller, not them.
173            stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx));
174            // Retain one frame so that we can print a span for the error itself
175            stacktrace.truncate(1);
176            (stacktrace, false)
177        }
178        BacktraceStyle::Short => {
179            let original_len = stacktrace.len();
180            // Remove all frames marked with `caller_location` -- that attribute indicates we
181            // usually want to point at the caller, not them.
182            stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx));
183            // Only prune further frames if there is at least one local frame. This check ensures
184            // that if we get a backtrace that never makes it to the user code because it has
185            // detected a bug in the Rust runtime, we don't prune away every frame.
186            let has_local_frame = stacktrace.iter().any(|frame| machine.is_local(frame.instance));
187            if has_local_frame {
188                // This is part of the logic that `std` uses to select the relevant part of a
189                // backtrace. But here, we only look for __rust_begin_short_backtrace, not
190                // __rust_end_short_backtrace because the end symbol comes from a call to the default
191                // panic handler.
192                stacktrace = stacktrace
193                    .into_iter()
194                    .take_while(|frame| {
195                        let def_id = frame.instance.def_id();
196                        let path = machine.tcx.def_path_str(def_id);
197                        !path.contains("__rust_begin_short_backtrace")
198                    })
199                    .collect::<Vec<_>>();
200
201                // After we prune frames from the bottom, there are a few left that are part of the
202                // Rust runtime. So we remove frames until we get to a local symbol, which should be
203                // main or a test.
204                // This len check ensures that we don't somehow remove every frame, as doing so breaks
205                // the primary error message.
206                while stacktrace.len() > 1
207                    && stacktrace.last().is_some_and(|frame| !machine.is_local(frame.instance))
208                {
209                    stacktrace.pop();
210                }
211            }
212            let was_pruned = stacktrace.len() != original_len;
213            (stacktrace, was_pruned)
214        }
215        BacktraceStyle::Full => (stacktrace, false),
216    }
217}
218
219/// Report the result of a Miri execution.
220///
221/// Returns `Some` if this was regular program termination with a given exit code and a `bool`
222/// indicating whether a leak check should happen; `None` otherwise.
223pub fn report_result<'tcx>(
224    ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
225    res: InterpErrorInfo<'tcx>,
226) -> Option<(i32, bool)> {
227    use InterpErrorKind::*;
228    use UndefinedBehaviorInfo::*;
229
230    let mut labels = vec![];
231
232    let (title, helps) = if let MachineStop(info) = res.kind() {
233        let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
234        use TerminationInfo::*;
235        let title = match info {
236            &Exit { code, leak_check } => return Some((code, leak_check)),
237            Abort(_) => Some("abnormal termination"),
238            Interrupted => None,
239            UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance | UnsupportedForeignItem(_) =>
240                Some("unsupported operation"),
241            StackedBorrowsUb { .. } | TreeBorrowsUb { .. } | DataRace { .. } =>
242                Some("Undefined Behavior"),
243            LocalDeadlock => {
244                labels.push(format!("thread got stuck here"));
245                None
246            }
247            GlobalDeadlock => {
248                // Global deadlocks are reported differently: just show all blocked threads.
249                // The "active" thread might actually be terminated, so we ignore it.
250                let mut any_pruned = false;
251                for (thread, stack) in ecx.machine.threads.all_blocked_stacks() {
252                    let stacktrace = Frame::generate_stacktrace_from_stack(stack, *ecx.tcx);
253                    let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
254                    any_pruned |= was_pruned;
255                    report_msg(
256                        DiagLevel::Error,
257                        format!("the evaluated program deadlocked"),
258                        vec![format!("thread got stuck here")],
259                        vec![],
260                        vec![],
261                        &stacktrace,
262                        Some(thread),
263                        &ecx.machine,
264                    )
265                }
266                if any_pruned {
267                    ecx.tcx.dcx().note(
268                        "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace"
269                    );
270                }
271                return None;
272            }
273            MultipleSymbolDefinitions { .. } | SymbolShimClashing { .. } => None,
274        };
275        #[rustfmt::skip]
276        let helps = match info {
277            UnsupportedInIsolation(_) =>
278                vec![
279                    note!("set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation;"),
280                    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"),
281                ],
282            UnsupportedForeignItem(_) => {
283                vec![
284                    note!("this means the program tried to do something Miri does not support; it does not indicate a bug in the program"),
285                ]
286            }
287            StackedBorrowsUb { help, history, .. } => {
288                labels.extend(help.clone());
289                let mut helps = vec![
290                    note!("this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental"),
291                    note!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information"),
292                ];
293                if let Some(TagHistory {created, invalidated, protected}) = history.clone() {
294                    helps.push((Some(created.1), created.0));
295                    if let Some((msg, span)) = invalidated {
296                        helps.push(note_span!(span, "{msg}"));
297                    }
298                    if let Some((protector_msg, protector_span)) = protected {
299                        helps.push(note_span!(protector_span, "{protector_msg}"));
300                    }
301                }
302                helps
303            },
304            TreeBorrowsUb { title: _, details, history } => {
305                let mut helps = vec![
306                    note!("this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental"),
307                    note!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information"),
308                ];
309                for m in details {
310                    helps.push(note!("{m}"));
311                }
312                for event in history.events.clone() {
313                    helps.push(event);
314                }
315                helps
316            }
317            MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
318                vec![
319                    note_span!(*first, "it's first defined here, in crate `{first_crate}`"),
320                    note_span!(*second, "then it's defined here again, in crate `{second_crate}`"),
321                ],
322            SymbolShimClashing { link_name, span } =>
323                vec![note_span!(*span, "the `{link_name}` symbol is defined here")],
324            Int2PtrWithStrictProvenance =>
325                vec![note!("use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead")],
326            DataRace { op1, extra, retag_explain, .. } => {
327                labels.push(format!("(2) just happened here"));
328                let mut helps = vec![note_span!(op1.span, "and (1) occurred earlier here")];
329                if let Some(extra) = extra {
330                    helps.push(note!("{extra}"));
331                    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"));
332                }
333                if *retag_explain {
334                    helps.push(note!("retags occur on all (re)borrows and as well as when references are copied or moved"));
335                    helps.push(note!("retags permit optimizations that insert speculative reads or writes"));
336                    helps.push(note!("therefore from the perspective of data races, a retag has the same implications as a read or write"));
337                }
338                helps.push(note!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"));
339                helps.push(note!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"));
340                helps
341            }
342                ,
343            _ => vec![],
344        };
345        (title, helps)
346    } else {
347        let title = match res.kind() {
348            UndefinedBehavior(UndefinedBehaviorInfo::ValidationError {
349                ptr_bytes_warning: true,
350                ..
351            }) => {
352                ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`)
353                bug!(
354                    "This validation error should be impossible in Miri: {}",
355                    format_interp_error(res)
356                );
357            }
358            UndefinedBehavior(_) => "Undefined Behavior",
359            ResourceExhaustion(_) => "resource exhaustion",
360            Unsupported(
361                // We list only the ones that can actually happen.
362                UnsupportedOpInfo::Unsupported(_)
363                | UnsupportedOpInfo::UnsizedLocal
364                | UnsupportedOpInfo::ExternTypeField,
365            ) => "unsupported operation",
366            InvalidProgram(
367                // We list only the ones that can actually happen.
368                InvalidProgramInfo::AlreadyReported(_) | InvalidProgramInfo::Layout(..),
369            ) => "post-monomorphization error",
370            _ => {
371                ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`)
372                bug!("This error should be impossible in Miri: {}", format_interp_error(res));
373            }
374        };
375        #[rustfmt::skip]
376        let helps = match res.kind() {
377            Unsupported(_) =>
378                vec![
379                    note!("this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support"),
380                ],
381            ResourceExhaustion(ResourceExhaustionInfo::AddressSpaceFull) if ecx.machine.data_race.as_genmc_ref().is_some() =>
382                vec![
383                    note!("in GenMC mode, the address space is limited to 4GB per thread, and addresses cannot be reused")
384                ],
385            UndefinedBehavior(AlignmentCheckFailed { .. })
386                if ecx.machine.check_alignment == AlignmentCheck::Symbolic
387            =>
388                vec![
389                    note!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior"),
390                    note!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives"),
391                ],
392            UndefinedBehavior(info) => {
393                let mut helps = vec![
394                    note!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"),
395                    note!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"),
396                ];
397                match info {
398                    PointerUseAfterFree(alloc_id, _) | PointerOutOfBounds { alloc_id, .. } => {
399                        if let Some(span) = ecx.machine.allocated_span(*alloc_id) {
400                            helps.push(note_span!(span, "{alloc_id} was allocated here:"));
401                        }
402                        if let Some(span) = ecx.machine.deallocated_span(*alloc_id) {
403                            helps.push(note_span!(span, "{alloc_id} was deallocated here:"));
404                        }
405                    }
406                    AbiMismatchArgument { .. } | AbiMismatchReturn { .. } => {
407                        helps.push(note!("this means these two types are not *guaranteed* to be ABI-compatible across all targets"));
408                        helps.push(note!("if you think this code should be accepted anyway, please report an issue with Miri"));
409                    }
410                    _ => {},
411                }
412                helps
413            }
414            InvalidProgram(
415                InvalidProgramInfo::AlreadyReported(_)
416            ) => {
417                // This got already reported. No point in reporting it again.
418                return None;
419            }
420            _ =>
421                vec![],
422        };
423        (Some(title), helps)
424    };
425
426    let stacktrace = ecx.generate_stacktrace();
427    let (stacktrace, pruned) = prune_stacktrace(stacktrace, &ecx.machine);
428
429    // We want to dump the allocation if this is `InvalidUninitBytes`.
430    // Since `format_interp_error` consumes `e`, we compute the outut early.
431    let mut extra = String::new();
432    match res.kind() {
433        UndefinedBehavior(InvalidUninitBytes(Some((alloc_id, access)))) => {
434            writeln!(
435                extra,
436                "Uninitialized memory occurred at {alloc_id}{range}, in this allocation:",
437                range = access.bad,
438            )
439            .unwrap();
440            writeln!(extra, "{:?}", ecx.dump_alloc(*alloc_id)).unwrap();
441        }
442        _ => {}
443    }
444
445    let mut primary_msg = String::new();
446    if let Some(title) = title {
447        write!(primary_msg, "{title}: ").unwrap();
448    }
449    write!(primary_msg, "{}", format_interp_error(res)).unwrap();
450
451    if labels.is_empty() {
452        labels.push(format!(
453            "{} occurred {}",
454            title.unwrap_or("error"),
455            if stacktrace.is_empty() { "due to this code" } else { "here" }
456        ));
457    }
458
459    report_msg(
460        DiagLevel::Error,
461        primary_msg,
462        labels,
463        vec![],
464        helps,
465        &stacktrace,
466        Some(ecx.active_thread()),
467        &ecx.machine,
468    );
469
470    eprint!("{extra}"); // newlines are already in the string
471
472    // Include a note like `std` does when we omit frames from a backtrace
473    if pruned {
474        ecx.tcx.dcx().note(
475            "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
476        );
477    }
478
479    // Debug-dump all locals.
480    for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
481        trace!("-------------------");
482        trace!("Frame {}", i);
483        trace!("    return: {:?}", frame.return_place());
484        for (i, local) in frame.locals.iter().enumerate() {
485            trace!("    local {}: {:?}", i, local);
486        }
487    }
488
489    None
490}
491
492pub fn report_leaks<'tcx>(
493    ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
494    leaks: Vec<(AllocId, MemoryKind, Allocation<Provenance, AllocExtra<'tcx>, MiriAllocBytes>)>,
495) {
496    let mut any_pruned = false;
497    for (id, kind, alloc) in leaks {
498        let mut title = format!(
499            "memory leaked: {id:?} ({}, size: {}, align: {})",
500            kind,
501            alloc.size().bytes(),
502            alloc.align.bytes()
503        );
504        let Some(backtrace) = alloc.extra.backtrace else {
505            ecx.tcx.dcx().err(title);
506            continue;
507        };
508        title.push_str(", allocated here:");
509        let (backtrace, pruned) = prune_stacktrace(backtrace, &ecx.machine);
510        any_pruned |= pruned;
511        report_msg(
512            DiagLevel::Error,
513            title,
514            vec![],
515            vec![],
516            vec![],
517            &backtrace,
518            None, // we don't know the thread this is from
519            &ecx.machine,
520        );
521    }
522    if any_pruned {
523        ecx.tcx.dcx().note(
524            "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
525        );
526    }
527}
528
529/// Report an error or note (depending on the `error` argument) with the given stacktrace.
530/// Also emits a full stacktrace of the interpreter stack.
531/// We want to present a multi-line span message for some errors. Diagnostics do not support this
532/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
533/// additional `span_label` or `note` call.
534fn report_msg<'tcx>(
535    diag_level: DiagLevel,
536    title: String,
537    span_msg: Vec<String>,
538    notes: Vec<(Option<SpanData>, String)>,
539    helps: Vec<(Option<SpanData>, String)>,
540    stacktrace: &[FrameInfo<'tcx>],
541    thread: Option<ThreadId>,
542    machine: &MiriMachine<'tcx>,
543) {
544    let origin_span = thread.map(|t| machine.threads.thread_ref(t).origin_span).unwrap_or(DUMMY_SP);
545    let span = stacktrace.first().map(|fi| fi.span).unwrap_or(origin_span);
546    // The only time we do not have an origin span is for `main`, and there we check the signature
547    // upfront. So we should always have a span here.
548    assert!(!span.is_dummy());
549
550    let tcx = machine.tcx;
551    let level = match diag_level {
552        DiagLevel::Error => Level::Error,
553        DiagLevel::Warning => Level::Warning,
554        DiagLevel::Note => Level::Note,
555    };
556    let mut err = Diag::<()>::new(tcx.sess.dcx(), level, title);
557    err.span(span);
558
559    // Show main message.
560    for line in span_msg {
561        err.span_label(span, line);
562    }
563
564    // Show note and help messages.
565    for (span_data, note) in notes {
566        if let Some(span_data) = span_data {
567            err.span_note(span_data.span(), note);
568        } else {
569            err.note(note);
570        }
571    }
572    for (span_data, help) in helps {
573        if let Some(span_data) = span_data {
574            err.span_help(span_data.span(), help);
575        } else {
576            err.help(help);
577        }
578    }
579    // Only print thread name if there are multiple threads.
580    if let Some(thread) = thread
581        && machine.threads.get_total_thread_count() > 1
582    {
583        err.note(format!(
584            "this is on thread `{}`",
585            machine.threads.get_thread_display_name(thread)
586        ));
587    }
588
589    // Add backtrace
590    if stacktrace.len() > 0 {
591        // Skip it if we'd only shpw the span we have already shown
592        if stacktrace.len() > 1 {
593            let sm = tcx.sess.source_map();
594            let mut out = format!("stack backtrace:");
595            for (idx, frame_info) in stacktrace.iter().enumerate() {
596                let span = sm.span_to_diagnostic_string(frame_info.span);
597                write!(out, "\n{idx}: {}", frame_info.instance).unwrap();
598                write!(out, "\n    at {span}").unwrap();
599            }
600            err.note(out);
601        }
602        // For TLS dtors and non-main threads, show the "origin"
603        if !origin_span.is_dummy() {
604            let what = if stacktrace.len() > 1 {
605                "the last function in that backtrace"
606            } else {
607                "the current function"
608            };
609            err.span_note(origin_span, format!("{what} got called indirectly due to this code"));
610        }
611    } else if !span.is_dummy() {
612        err.note(format!("this {level} occurred while pushing a call frame onto an empty stack"));
613        err.note("the span indicates which code caused the function to be called, but may not be the literal call site");
614    }
615
616    err.emit();
617}
618
619impl<'tcx> MiriMachine<'tcx> {
620    pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
621        use NonHaltingDiagnostic::*;
622
623        let stacktrace =
624            Frame::generate_stacktrace_from_stack(self.threads.active_thread_stack(), self.tcx);
625        let (stacktrace, _was_pruned) = prune_stacktrace(stacktrace, self);
626
627        let (label, diag_level) = match &e {
628            RejectedIsolatedOp(_) =>
629                ("operation rejected by isolation".to_string(), DiagLevel::Warning),
630            Int2Ptr { .. } => ("integer-to-pointer cast".to_string(), DiagLevel::Warning),
631            NativeCallSharedMem { .. } =>
632                ("sharing memory with a native function".to_string(), DiagLevel::Warning),
633            ExternTypeReborrow =>
634                ("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning),
635            GenmcCompareExchangeWeak | GenmcCompareExchangeOrderingMismatch { .. } =>
636                ("GenMC might miss possible behaviors of this code".to_string(), DiagLevel::Warning),
637            CreatedPointerTag(..)
638            | PoppedPointerTag(..)
639            | TrackingAlloc(..)
640            | AccessedAlloc(..)
641            | FreedAlloc(..)
642            | ProgressReport { .. }
643            | WeakMemoryOutdatedLoad { .. } =>
644                ("tracking was triggered here".to_string(), DiagLevel::Note),
645            FileInProcOpened => ("open a file in `/proc`".to_string(), DiagLevel::Warning),
646        };
647
648        let title = match &e {
649            CreatedPointerTag(tag, None, _) => format!("created base tag {tag:?}"),
650            CreatedPointerTag(tag, Some(perm), None) =>
651                format!("created {tag:?} with {perm} derived from unknown tag"),
652            CreatedPointerTag(tag, Some(perm), Some((alloc_id, range, orig_tag))) =>
653                format!(
654                    "created tag {tag:?} with {perm} at {alloc_id}{range} derived from {orig_tag:?}"
655                ),
656            PoppedPointerTag(item, cause) => format!("popped tracked tag for item {item:?}{cause}"),
657            TrackingAlloc(id, size, align) =>
658                format!(
659                    "now tracking allocation {id} of {size} bytes (alignment {align} bytes)",
660                    size = size.bytes(),
661                    align = align.bytes(),
662                ),
663            AccessedAlloc(id, range, access_kind) =>
664                format!("{access_kind} at {id}{range}"),
665            FreedAlloc(id) => format!("freed allocation {id:?}"),
666            RejectedIsolatedOp(op) => format!("{op} was made to return an error due to isolation"),
667            ProgressReport { .. } =>
668                format!("progress report: current operation being executed is here"),
669            Int2Ptr { .. } => format!("integer-to-pointer cast"),
670            NativeCallSharedMem { .. } =>
671                format!("sharing memory with a native function called via FFI"),
672            WeakMemoryOutdatedLoad { ptr } =>
673                format!("weak memory emulation: outdated value returned from load at {ptr}"),
674            ExternTypeReborrow =>
675                format!("reborrow of a reference to `extern type` is not properly supported"),
676            GenmcCompareExchangeWeak =>
677                "GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures."
678                    .to_string(),
679            GenmcCompareExchangeOrderingMismatch {
680                success_ordering,
681                upgraded_success_ordering,
682                failure_ordering,
683                effective_failure_ordering,
684            } => {
685                let was_upgraded_msg = if success_ordering != upgraded_success_ordering {
686                    format!("Success ordering '{success_ordering:?}' was upgraded to '{upgraded_success_ordering:?}' to match failure ordering '{failure_ordering:?}'")
687                } else {
688                    assert_ne!(failure_ordering, effective_failure_ordering);
689                    format!("Due to success ordering '{success_ordering:?}', the failure ordering '{failure_ordering:?}' is treated like '{effective_failure_ordering:?}'")
690                };
691                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.")
692            }
693            FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri"),
694        };
695
696        let notes = match &e {
697            ProgressReport { block_count } => {
698                vec![note!("so far, {block_count} basic blocks have been executed")]
699            }
700            _ => vec![],
701        };
702
703        let helps = match &e {
704            Int2Ptr { details: true } => {
705                let mut v = vec![
706                    note!(
707                        "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"
708                    ),
709                    note!(
710                        "see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation"
711                    ),
712                    note!(
713                        "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"
714                    ),
715                    note!(
716                        "you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics"
717                    ),
718                ];
719                if self.borrow_tracker.as_ref().is_some_and(|b| {
720                    matches!(
721                        b.borrow().borrow_tracker_method(),
722                        BorrowTrackerMethod::TreeBorrows { .. }
723                    )
724                }) {
725                    v.push(
726                        note!("Tree Borrows does not support integer-to-pointer casts, so the program is likely to go wrong when this pointer gets used")
727                    );
728                } else {
729                    v.push(
730                        note!("alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning")
731                    );
732                }
733                v
734            }
735            NativeCallSharedMem { tracing } =>
736                if *tracing {
737                    vec![
738                        note!(
739                            "when memory is shared with a native function call, Miri can only track initialisation and provenance on a best-effort basis"
740                        ),
741                        note!(
742                            "in particular, Miri assumes that the native call initializes all memory it has written to"
743                        ),
744                        note!(
745                            "Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory"
746                        ),
747                        note!(
748                            "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"
749                        ),
750                        note!(
751                            "tracing memory accesses in native code is not yet fully implemented, so there can be further imprecisions beyond what is documented here"
752                        ),
753                    ]
754                } else {
755                    vec![
756                        note!(
757                            "when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory"
758                        ),
759                        note!(
760                            "in particular, Miri assumes that the native call initializes all memory it has access to"
761                        ),
762                        note!(
763                            "Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory"
764                        ),
765                        note!(
766                            "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"
767                        ),
768                    ]
769                },
770            ExternTypeReborrow => {
771                assert!(self.borrow_tracker.as_ref().is_some_and(|b| {
772                    matches!(
773                        b.borrow().borrow_tracker_method(),
774                        BorrowTrackerMethod::StackedBorrows
775                    )
776                }));
777                vec![
778                    note!(
779                        "`extern type` are not compatible with the Stacked Borrows aliasing model implemented by Miri; Miri may miss bugs in this code"
780                    ),
781                    note!(
782                        "try running with `MIRIFLAGS=-Zmiri-tree-borrows` to use the more permissive but also even more experimental Tree Borrows aliasing checks instead"
783                    ),
784                ]
785            }
786            _ => vec![],
787        };
788
789        report_msg(
790            diag_level,
791            title,
792            vec![label],
793            notes,
794            helps,
795            &stacktrace,
796            Some(self.threads.active_thread()),
797            self,
798        );
799    }
800}
801
802impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
803pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
804    fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
805        let this = self.eval_context_ref();
806        this.machine.emit_diagnostic(e);
807    }
808
809    /// We had a panic in Miri itself, try to print something useful.
810    fn handle_ice(&self) {
811        eprintln!();
812        eprintln!(
813            "Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic:"
814        );
815        let this = self.eval_context_ref();
816        let stacktrace = this.generate_stacktrace();
817        report_msg(
818            DiagLevel::Note,
819            "the place in the program where the ICE was triggered".to_string(),
820            vec![],
821            vec![],
822            vec![],
823            &stacktrace,
824            Some(this.active_thread()),
825            &this.machine,
826        );
827    }
828
829    /// Call `f` only if this is the first time we are seeing this span.
830    /// The `first` parameter indicates whether this is the first time *ever* that this diagnostic
831    /// is emitted.
832    fn dedup_diagnostic(
833        &self,
834        dedup: &SpanDedupDiagnostic,
835        f: impl FnOnce(/*first*/ bool) -> NonHaltingDiagnostic,
836    ) {
837        let this = self.eval_context_ref();
838        // We want to deduplicate both based on where the error seems to be located "from the user
839        // perspective", and the location of the actual operation (to avoid warning about the same
840        // operation called from different places in the local code).
841        let span1 = this.machine.current_user_relevant_span();
842        // For the "location of the operation", we still skip `track_caller` frames, to match the
843        // span that the diagnostic will point at.
844        let span2 = this
845            .active_thread_stack()
846            .iter()
847            .rev()
848            .find(|frame| !frame.instance().def.requires_caller_location(*this.tcx))
849            .map(|frame| frame.current_span())
850            .unwrap_or(span1);
851
852        let mut lock = dedup.0.lock().unwrap();
853        let first = lock.is_empty();
854        // Avoid mutating the hashset unless both spans are new.
855        if !lock.contains(&span2) && lock.insert(span1) && (span1 == span2 || lock.insert(span2)) {
856            // Both of the two spans were newly inserted.
857            this.emit_diagnostic(f(first));
858        }
859    }
860}
861
862/// Helps deduplicate a diagnostic to ensure it is only shown once per span.
863pub struct SpanDedupDiagnostic(Mutex<FxHashSet<Span>>);
864
865impl SpanDedupDiagnostic {
866    pub const fn new() -> Self {
867        Self(Mutex::new(FxHashSet::with_hasher(FxBuildHasher)))
868    }
869}