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