miri/
diagnostics.rs

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