miri/
diagnostics.rs

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