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