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