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