miri/
diagnostics.rs

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