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!("{} occurred here", title.unwrap_or("error")));
448    }
449
450    report_msg(
451        DiagLevel::Error,
452        primary_msg,
453        labels,
454        vec![],
455        helps,
456        &stacktrace,
457        Some(ecx.active_thread()),
458        &ecx.machine,
459    );
460
461    eprint!("{extra}"); // newlines are already in the string
462
463    if show_all_threads {
464        for (thread, stack) in ecx.machine.threads.all_blocked_stacks() {
465            if thread != ecx.active_thread() {
466                let stacktrace = Frame::generate_stacktrace_from_stack(stack);
467                let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
468                any_pruned |= was_pruned;
469                report_msg(
470                    DiagLevel::Error,
471                    format!("the evaluated program deadlocked"),
472                    vec![format!("this thread got stuck here")],
473                    vec![],
474                    vec![],
475                    &stacktrace,
476                    Some(thread),
477                    &ecx.machine,
478                )
479            }
480        }
481    }
482
483    // Include a note like `std` does when we omit frames from a backtrace
484    if any_pruned {
485        ecx.tcx.dcx().note(
486            "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
487        );
488    }
489
490    // Debug-dump all locals.
491    for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
492        trace!("-------------------");
493        trace!("Frame {}", i);
494        trace!("    return: {:?}", frame.return_place);
495        for (i, local) in frame.locals.iter().enumerate() {
496            trace!("    local {}: {:?}", i, local);
497        }
498    }
499
500    None
501}
502
503pub fn report_leaks<'tcx>(
504    ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
505    leaks: Vec<(AllocId, MemoryKind, Allocation<Provenance, AllocExtra<'tcx>, MiriAllocBytes>)>,
506) {
507    let mut any_pruned = false;
508    for (id, kind, alloc) in leaks {
509        let mut title = format!(
510            "memory leaked: {id:?} ({}, size: {:?}, align: {:?})",
511            kind,
512            alloc.size().bytes(),
513            alloc.align.bytes()
514        );
515        let Some(backtrace) = alloc.extra.backtrace else {
516            ecx.tcx.dcx().err(title);
517            continue;
518        };
519        title.push_str(", allocated here:");
520        let (backtrace, pruned) = prune_stacktrace(backtrace, &ecx.machine);
521        any_pruned |= pruned;
522        report_msg(
523            DiagLevel::Error,
524            title,
525            vec![],
526            vec![],
527            vec![],
528            &backtrace,
529            None, // we don't know the thread this is from
530            &ecx.machine,
531        );
532    }
533    if any_pruned {
534        ecx.tcx.dcx().note(
535            "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
536        );
537    }
538}
539
540/// Report an error or note (depending on the `error` argument) with the given stacktrace.
541/// Also emits a full stacktrace of the interpreter stack.
542/// We want to present a multi-line span message for some errors. Diagnostics do not support this
543/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
544/// additional `span_label` or `note` call.
545pub fn report_msg<'tcx>(
546    diag_level: DiagLevel,
547    title: String,
548    span_msg: Vec<String>,
549    notes: Vec<(Option<SpanData>, String)>,
550    helps: Vec<(Option<SpanData>, String)>,
551    stacktrace: &[FrameInfo<'tcx>],
552    thread: Option<ThreadId>,
553    machine: &MiriMachine<'tcx>,
554) {
555    let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
556    let sess = machine.tcx.sess;
557    let level = match diag_level {
558        DiagLevel::Error => Level::Error,
559        DiagLevel::Warning => Level::Warning,
560        DiagLevel::Note => Level::Note,
561    };
562    let mut err = Diag::<()>::new(sess.dcx(), level, title);
563    err.span(span);
564
565    // Show main message.
566    if span != DUMMY_SP {
567        for line in span_msg {
568            err.span_label(span, line);
569        }
570    } else {
571        // Make sure we show the message even when it is a dummy span.
572        for line in span_msg {
573            err.note(line);
574        }
575        err.note("(no span available)");
576    }
577
578    // Show note and help messages.
579    let mut extra_span = false;
580    for (span_data, note) in notes {
581        if let Some(span_data) = span_data {
582            err.span_note(span_data.span(), note);
583            extra_span = true;
584        } else {
585            err.note(note);
586        }
587    }
588    for (span_data, help) in helps {
589        if let Some(span_data) = span_data {
590            err.span_help(span_data.span(), help);
591            extra_span = true;
592        } else {
593            err.help(help);
594        }
595    }
596
597    // Add backtrace
598    if stacktrace.len() > 1 {
599        let mut backtrace_title = String::from("BACKTRACE");
600        if extra_span {
601            write!(backtrace_title, " (of the first span)").unwrap();
602        }
603        if let Some(thread) = thread {
604            let thread_name = machine.threads.get_thread_display_name(thread);
605            if thread_name != "main" {
606                // Only print thread name if it is not `main`.
607                write!(backtrace_title, " on thread `{thread_name}`").unwrap();
608            };
609        }
610        write!(backtrace_title, ":").unwrap();
611        err.note(backtrace_title);
612        for (idx, frame_info) in stacktrace.iter().enumerate() {
613            let is_local = machine.is_local(frame_info.instance);
614            // No span for non-local frames and the first frame (which is the error site).
615            if is_local && idx > 0 {
616                err.subdiagnostic(frame_info.as_note(machine.tcx));
617            } else {
618                let sm = sess.source_map();
619                let span = sm.span_to_embeddable_string(frame_info.span);
620                err.note(format!("{frame_info} at {span}"));
621            }
622        }
623    }
624
625    err.emit();
626}
627
628impl<'tcx> MiriMachine<'tcx> {
629    pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
630        use NonHaltingDiagnostic::*;
631
632        let stacktrace = Frame::generate_stacktrace_from_stack(self.threads.active_thread_stack());
633        let (stacktrace, _was_pruned) = prune_stacktrace(stacktrace, self);
634
635        let (label, diag_level) = match &e {
636            RejectedIsolatedOp(_) =>
637                ("operation rejected by isolation".to_string(), DiagLevel::Warning),
638            Int2Ptr { .. } => ("integer-to-pointer cast".to_string(), DiagLevel::Warning),
639            NativeCallSharedMem { .. } =>
640                ("sharing memory with a native function".to_string(), DiagLevel::Warning),
641            NativeCallFnPtr =>
642                (
643                    "sharing a function pointer with a native function".to_string(),
644                    DiagLevel::Warning,
645                ),
646            ExternTypeReborrow =>
647                ("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning),
648            GenmcCompareExchangeWeak | GenmcCompareExchangeOrderingMismatch { .. } =>
649                ("GenMC might miss possible behaviors of this code".to_string(), DiagLevel::Warning),
650            CreatedPointerTag(..)
651            | PoppedPointerTag(..)
652            | TrackingAlloc(..)
653            | AccessedAlloc(..)
654            | FreedAlloc(..)
655            | ProgressReport { .. }
656            | WeakMemoryOutdatedLoad { .. } =>
657                ("tracking was triggered here".to_string(), DiagLevel::Note),
658        };
659
660        let title = match &e {
661            CreatedPointerTag(tag, None, _) => format!("created base tag {tag:?}"),
662            CreatedPointerTag(tag, Some(perm), None) =>
663                format!("created {tag:?} with {perm} derived from unknown tag"),
664            CreatedPointerTag(tag, Some(perm), Some((alloc_id, range, orig_tag))) =>
665                format!(
666                    "created tag {tag:?} with {perm} at {alloc_id:?}{range:?} derived from {orig_tag:?}"
667                ),
668            PoppedPointerTag(item, cause) => format!("popped tracked tag for item {item:?}{cause}"),
669            TrackingAlloc(id, size, align) =>
670                format!(
671                    "now tracking allocation {id:?} of {size} bytes (alignment {align} bytes)",
672                    size = size.bytes(),
673                    align = align.bytes(),
674                ),
675            AccessedAlloc(id, range, access_kind) =>
676                format!("{access_kind} at {id:?}[{}..{}]", range.start.bytes(), range.end().bytes()),
677            FreedAlloc(id) => format!("freed allocation {id:?}"),
678            RejectedIsolatedOp(op) => format!("{op} was made to return an error due to isolation"),
679            ProgressReport { .. } =>
680                format!("progress report: current operation being executed is here"),
681            Int2Ptr { .. } => format!("integer-to-pointer cast"),
682            NativeCallSharedMem { .. } =>
683                format!("sharing memory with a native function called via FFI"),
684            NativeCallFnPtr =>
685                format!("sharing a function pointer with a native function called via FFI"),
686            WeakMemoryOutdatedLoad { ptr } =>
687                format!("weak memory emulation: outdated value returned from load at {ptr}"),
688            ExternTypeReborrow =>
689                format!("reborrow of a reference to `extern type` is not properly supported"),
690            GenmcCompareExchangeWeak =>
691                "GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures."
692                    .to_string(),
693            GenmcCompareExchangeOrderingMismatch {
694                success_ordering,
695                upgraded_success_ordering,
696                failure_ordering,
697                effective_failure_ordering,
698            } => {
699                let was_upgraded_msg = if success_ordering != upgraded_success_ordering {
700                    format!("Success ordering '{success_ordering:?}' was upgraded to '{upgraded_success_ordering:?}' to match failure ordering '{failure_ordering:?}'")
701                } else {
702                    assert_ne!(failure_ordering, effective_failure_ordering);
703                    format!("Due to success ordering '{success_ordering:?}', the failure ordering '{failure_ordering:?}' is treated like '{effective_failure_ordering:?}'")
704                };
705                format!("GenMC currently does not model the failure ordering for `compare_exchange`. {was_upgraded_msg}. Miri with GenMC might miss bugs related to this memory access.")
706            }
707        };
708
709        let notes = match &e {
710            ProgressReport { block_count } => {
711                vec![note!("so far, {block_count} basic blocks have been executed")]
712            }
713            _ => vec![],
714        };
715
716        let helps = match &e {
717            Int2Ptr { details: true } => {
718                let mut v = vec![
719                    note!(
720                        "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"
721                    ),
722                    note!(
723                        "see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation"
724                    ),
725                    note!(
726                        "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"
727                    ),
728                    note!(
729                        "you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics"
730                    ),
731                ];
732                if self.borrow_tracker.as_ref().is_some_and(|b| {
733                    matches!(
734                        b.borrow().borrow_tracker_method(),
735                        BorrowTrackerMethod::TreeBorrows { .. }
736                    )
737                }) {
738                    v.push(
739                        note!("Tree Borrows does not support integer-to-pointer casts, so the program is likely to go wrong when this pointer gets used")
740                    );
741                } else {
742                    v.push(
743                        note!("alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning")
744                    );
745                }
746                v
747            }
748            NativeCallSharedMem { tracing } =>
749                if *tracing {
750                    vec![
751                        note!(
752                            "when memory is shared with a native function call, Miri can only track initialisation and provenance on a best-effort basis"
753                        ),
754                        note!(
755                            "in particular, Miri assumes that the native call initializes all memory it has written to"
756                        ),
757                        note!(
758                            "Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory"
759                        ),
760                        note!(
761                            "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"
762                        ),
763                        note!(
764                            "tracing memory accesses in native code is not yet fully implemented, so there can be further imprecisions beyond what is documented here"
765                        ),
766                    ]
767                } else {
768                    vec![
769                        note!(
770                            "when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory"
771                        ),
772                        note!(
773                            "in particular, Miri assumes that the native call initializes all memory it has access to"
774                        ),
775                        note!(
776                            "Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory"
777                        ),
778                        note!(
779                            "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"
780                        ),
781                    ]
782                },
783            NativeCallFnPtr => {
784                vec![note!(
785                    "calling Rust functions from C is not supported and will, in the best case, crash the program"
786                )]
787            }
788            ExternTypeReborrow => {
789                assert!(self.borrow_tracker.as_ref().is_some_and(|b| {
790                    matches!(
791                        b.borrow().borrow_tracker_method(),
792                        BorrowTrackerMethod::StackedBorrows
793                    )
794                }));
795                vec![
796                    note!(
797                        "`extern type` are not compatible with the Stacked Borrows aliasing model implemented by Miri; Miri may miss bugs in this code"
798                    ),
799                    note!(
800                        "try running with `MIRIFLAGS=-Zmiri-tree-borrows` to use the more permissive but also even more experimental Tree Borrows aliasing checks instead"
801                    ),
802                ]
803            }
804            _ => vec![],
805        };
806
807        report_msg(
808            diag_level,
809            title,
810            vec![label],
811            notes,
812            helps,
813            &stacktrace,
814            Some(self.threads.active_thread()),
815            self,
816        );
817    }
818}
819
820impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
821pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
822    fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
823        let this = self.eval_context_ref();
824        this.machine.emit_diagnostic(e);
825    }
826
827    /// We had a panic in Miri itself, try to print something useful.
828    fn handle_ice(&self) {
829        eprintln!();
830        eprintln!(
831            "Miri caused an ICE during evaluation. Here's the interpreter backtrace at the time of the panic:"
832        );
833        let this = self.eval_context_ref();
834        let stacktrace = this.generate_stacktrace();
835        report_msg(
836            DiagLevel::Note,
837            "the place in the program where the ICE was triggered".to_string(),
838            vec![],
839            vec![],
840            vec![],
841            &stacktrace,
842            Some(this.active_thread()),
843            &this.machine,
844        );
845    }
846
847    /// Call `f` only if this is the first time we are seeing this span.
848    /// The `first` parameter indicates whether this is the first time *ever* that this diagnostic
849    /// is emitted.
850    fn dedup_diagnostic(
851        &self,
852        dedup: &SpanDedupDiagnostic,
853        f: impl FnOnce(/*first*/ bool) -> NonHaltingDiagnostic,
854    ) {
855        let this = self.eval_context_ref();
856        // We want to deduplicate both based on where the error seems to be located "from the user
857        // perspective", and the location of the actual operation (to avoid warning about the same
858        // operation called from different places in the local code).
859        let span1 = this.machine.current_user_relevant_span();
860        // For the "location of the operation", we still skip `track_caller` frames, to match the
861        // span that the diagnostic will point at.
862        let span2 = this
863            .active_thread_stack()
864            .iter()
865            .rev()
866            .find(|frame| !frame.instance().def.requires_caller_location(*this.tcx))
867            .map(|frame| frame.current_span())
868            .unwrap_or(span1);
869
870        let mut lock = dedup.0.lock().unwrap();
871        let first = lock.is_empty();
872        // Avoid mutating the hashset unless both spans are new.
873        if !lock.contains(&span2) && lock.insert(span1) && (span1 == span2 || lock.insert(span2)) {
874            // Both of the two spans were newly inserted.
875            this.emit_diagnostic(f(first));
876        }
877    }
878}
879
880/// Helps deduplicate a diagnostic to ensure it is only shown once per span.
881pub struct SpanDedupDiagnostic(Mutex<FxHashSet<Span>>);
882
883impl SpanDedupDiagnostic {
884    pub const fn new() -> Self {
885        Self(Mutex::new(FxHashSet::with_hasher(rustc_hash::FxBuildHasher)))
886    }
887}