Skip to main content

miri/
diagnostics.rs

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