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