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, 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
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    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
120/// Miri specific diagnostics
121pub enum NonHaltingDiagnostic {
122    /// (new_tag, new_perm, (alloc_id, base_offset, orig_tag))
123    ///
124    /// new_perm is `None` for base tags.
125    CreatedPointerTag(NonZero<u64>, Option<String>, Option<(AllocId, AllocRange, ProvenanceExtra)>),
126    /// This `Item` was popped from the borrow stack. The string explains the reason.
127    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, // how many basic blocks have been run so far
134    },
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
155/// Level of Miri specific diagnostics
156pub enum DiagLevel {
157    Error,
158    Warning,
159    Note,
160}
161
162/// Generate a note/help text without a span.
163macro_rules! note {
164    ($($tt:tt)*) => { (None, format!($($tt)*)) };
165}
166/// Generate a note/help text with a span.
167macro_rules! note_span {
168    ($span:expr, $($tt:tt)*) => { (Some($span), format!($($tt)*)) };
169}
170
171/// Attempts to prune a stacktrace to omit the Rust runtime, and returns a bool indicating if any
172/// frames were pruned. If the stacktrace does not have any local frames, we conclude that it must
173/// be pointing to a problem in the Rust runtime itself, and do not prune it at all.
174pub 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            // 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            // Retain one frame so that we can print a span for the error itself
184            stacktrace.truncate(1);
185            (stacktrace, false)
186        }
187        BacktraceStyle::Short => {
188            let original_len = stacktrace.len();
189            // Remove all frames marked with `caller_location` -- that attribute indicates we
190            // usually want to point at the caller, not them.
191            stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(machine.tcx));
192            // Only prune further frames if there is at least one local frame. This check ensures
193            // that if we get a backtrace that never makes it to the user code because it has
194            // detected a bug in the Rust runtime, we don't prune away every frame.
195            let has_local_frame = stacktrace.iter().any(|frame| machine.is_local(frame.instance));
196            if has_local_frame {
197                // This is part of the logic that `std` uses to select the relevant part of a
198                // backtrace. But here, we only look for __rust_begin_short_backtrace, not
199                // __rust_end_short_backtrace because the end symbol comes from a call to the default
200                // panic handler.
201                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                // After we prune frames from the bottom, there are a few left that are part of the
211                // Rust runtime. So we remove frames until we get to a local symbol, which should be
212                // main or a test.
213                // This len check ensures that we don't somehow remove every frame, as doing so breaks
214                // the primary error message.
215                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
228/// Report the result of a Miri execution.
229///
230/// Returns `Some` if this was regular program termination with a given exit code and a `bool`
231/// indicating whether a leak check should happen; `None` otherwise.
232pub 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                // Global deadlocks are reported differently: just show all blocked threads.
258                // The "active" thread might actually be terminated, so we ignore it.
259                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(); // print interpreter backtrace (this is outside the eval `catch_unwind`)
364                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                // We list only the ones that can actually happen.
373                UnsupportedOpInfo::Unsupported(_)
374                | UnsupportedOpInfo::UnsizedLocal
375                | UnsupportedOpInfo::ExternTypeField,
376            ) => "unsupported operation",
377            InvalidProgram(
378                // We list only the ones that can actually happen.
379                InvalidProgramInfo::AlreadyReported(_) | InvalidProgramInfo::Layout(..),
380            ) => "post-monomorphization error",
381            _ => {
382                ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`)
383                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                // This got already reported. No point in reporting it again.
432                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    // We want to dump the allocation if this is `InvalidUninitBytes`.
444    // Since `format_interp_error` consumes `e`, we compute the outut early.
445    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}"); // newlines are already in the string
485
486    // Include a note like `std` does when we omit frames from a backtrace
487    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    // Debug-dump all locals.
494    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, // we don't know the thread this is from
533            &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
543/// Report an error or note (depending on the `error` argument) with the given stacktrace.
544/// Also emits a full stacktrace of the interpreter stack.
545/// We want to present a multi-line span message for some errors. Diagnostics do not support this
546/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
547/// additional `span_label` or `note` call.
548fn 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    // The only time we do not have an origin span is for `main`, and there we check the signature
561    // upfront. So we should always have a span here.
562    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    // Show main message.
574    for line in span_msg {
575        err.span_label(span, line);
576    }
577
578    // Show note and help messages.
579    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    // Only print thread name if there are multiple threads.
594    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    // Add backtrace
604    if stacktrace.len() > 0 {
605        // Skip it if we'd only shpw the span we have already shown
606        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        // For TLS dtors and non-main threads, show the "origin"
617        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    /// We had a panic in Miri itself, try to print something useful.
824    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    /// Call `f` only if this is the first time we are seeing this span.
844    /// The `first` parameter indicates whether this is the first time *ever* that this diagnostic
845    /// is emitted.
846    fn dedup_diagnostic(
847        &self,
848        dedup: &SpanDedupDiagnostic,
849        f: impl FnOnce(/*first*/ bool) -> NonHaltingDiagnostic,
850    ) {
851        let this = self.eval_context_ref();
852        // We want to deduplicate both based on where the error seems to be located "from the user
853        // perspective", and the location of the actual operation (to avoid warning about the same
854        // operation called from different places in the local code).
855        let span1 = this.machine.current_user_relevant_span();
856        // For the "location of the operation", we still skip `track_caller` frames, to match the
857        // span that the diagnostic will point at.
858        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        // Avoid mutating the hashset unless both spans are new.
869        if !lock.contains(&span2) && lock.insert(span1) && (span1 == span2 || lock.insert(span2)) {
870            // Both of the two spans were newly inserted.
871            this.emit_diagnostic(f(first));
872        }
873    }
874}
875
876/// Helps deduplicate a diagnostic to ensure it is only shown once per span.
877pub struct SpanDedupDiagnostic(Mutex<FxHashSet<Span>>);
878
879impl SpanDedupDiagnostic {
880    pub const fn new() -> Self {
881        Self(Mutex::new(FxHashSet::with_hasher(FxBuildHasher)))
882    }
883}