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