rustc_const_eval/const_eval/
error.rs

1use std::mem;
2
3use rustc_errors::{Diag, DiagArgName, DiagArgValue, DiagMessage, IntoDiagArg};
4use rustc_middle::mir::AssertKind;
5use rustc_middle::mir::interpret::{AllocId, Provenance, ReportedErrorInfo, UndefinedBehaviorInfo};
6use rustc_middle::query::TyCtxtAt;
7use rustc_middle::ty::ConstInt;
8use rustc_middle::ty::layout::LayoutError;
9use rustc_span::{Span, Symbol};
10
11use super::CompileTimeMachine;
12use crate::errors::{self, FrameNote, ReportErrorExt};
13use crate::interpret::{
14    CtfeProvenance, ErrorHandled, Frame, InterpCx, InterpErrorInfo, InterpErrorKind,
15    MachineStopType, Pointer, err_inval, err_machine_stop,
16};
17
18/// The CTFE machine has some custom error kinds.
19#[derive(Clone, Debug)]
20pub enum ConstEvalErrKind {
21    ConstAccessesMutGlobal,
22    ModifiedGlobal,
23    RecursiveStatic,
24    AssertFailure(AssertKind<ConstInt>),
25    Panic {
26        msg: Symbol,
27        line: u32,
28        col: u32,
29        file: Symbol,
30    },
31    WriteThroughImmutablePointer,
32    /// Called `const_make_global` twice.
33    ConstMakeGlobalPtrAlreadyMadeGlobal(AllocId),
34    /// Called `const_make_global` on a non-heap pointer.
35    ConstMakeGlobalPtrIsNonHeap(Pointer<Option<CtfeProvenance>>),
36    /// Called `const_make_global` on a dangling pointer.
37    ConstMakeGlobalWithDanglingPtr(Pointer<Option<CtfeProvenance>>),
38    /// Called `const_make_global` on a pointer that does not start at the
39    /// beginning of an object.
40    ConstMakeGlobalWithOffset(Pointer<Option<CtfeProvenance>>),
41}
42
43impl MachineStopType for ConstEvalErrKind {
44    fn diagnostic_message(&self) -> DiagMessage {
45        use ConstEvalErrKind::*;
46
47        use crate::fluent_generated::*;
48        match self {
49            ConstAccessesMutGlobal => const_eval_const_accesses_mut_global,
50            ModifiedGlobal => const_eval_modified_global,
51            Panic { .. } => const_eval_panic,
52            RecursiveStatic => const_eval_recursive_static,
53            AssertFailure(x) => x.diagnostic_message(),
54            WriteThroughImmutablePointer => const_eval_write_through_immutable_pointer,
55            ConstMakeGlobalPtrAlreadyMadeGlobal { .. } => {
56                const_eval_const_make_global_ptr_already_made_global
57            }
58            ConstMakeGlobalPtrIsNonHeap(_) => const_eval_const_make_global_ptr_is_non_heap,
59            ConstMakeGlobalWithDanglingPtr(_) => const_eval_const_make_global_with_dangling_ptr,
60            ConstMakeGlobalWithOffset(_) => const_eval_const_make_global_with_offset,
61        }
62    }
63    fn add_args(self: Box<Self>, adder: &mut dyn FnMut(DiagArgName, DiagArgValue)) {
64        use ConstEvalErrKind::*;
65        match *self {
66            RecursiveStatic
67            | ConstAccessesMutGlobal
68            | ModifiedGlobal
69            | WriteThroughImmutablePointer => {}
70            AssertFailure(kind) => kind.add_args(adder),
71            Panic { msg, .. } => {
72                adder("msg".into(), msg.into_diag_arg(&mut None));
73            }
74            ConstMakeGlobalPtrIsNonHeap(ptr)
75            | ConstMakeGlobalWithOffset(ptr)
76            | ConstMakeGlobalWithDanglingPtr(ptr) => {
77                adder("ptr".into(), format!("{ptr:?}").into_diag_arg(&mut None));
78            }
79            ConstMakeGlobalPtrAlreadyMadeGlobal(alloc) => {
80                adder("alloc".into(), alloc.into_diag_arg(&mut None));
81            }
82        }
83    }
84}
85
86/// The errors become [`InterpErrorKind::MachineStop`] when being raised.
87impl<'tcx> Into<InterpErrorInfo<'tcx>> for ConstEvalErrKind {
88    fn into(self) -> InterpErrorInfo<'tcx> {
89        err_machine_stop!(self).into()
90    }
91}
92
93pub fn get_span_and_frames<'tcx>(
94    tcx: TyCtxtAt<'tcx>,
95    stack: &[Frame<'tcx, impl Provenance, impl Sized>],
96) -> (Span, Vec<errors::FrameNote>) {
97    let mut stacktrace = Frame::generate_stacktrace_from_stack(stack);
98    // Filter out `requires_caller_location` frames.
99    stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*tcx));
100    let span = stacktrace.last().map(|f| f.span).unwrap_or(tcx.span);
101
102    let mut frames = Vec::new();
103
104    // Add notes to the backtrace. Don't print a single-line backtrace though.
105    if stacktrace.len() > 1 {
106        // Helper closure to print duplicated lines.
107        let mut add_frame = |mut frame: errors::FrameNote| {
108            frames.push(errors::FrameNote { times: 0, ..frame.clone() });
109            // Don't print [... additional calls ...] if the number of lines is small
110            if frame.times < 3 {
111                let times = frame.times;
112                frame.times = 0;
113                frames.extend(std::iter::repeat(frame).take(times as usize));
114            } else {
115                frames.push(frame);
116            }
117        };
118
119        let mut last_frame: Option<errors::FrameNote> = None;
120        for frame_info in &stacktrace {
121            let frame = frame_info.as_note(*tcx);
122            match last_frame.as_mut() {
123                Some(last_frame)
124                    if last_frame.span == frame.span
125                        && last_frame.where_ == frame.where_
126                        && last_frame.instance == frame.instance =>
127                {
128                    last_frame.times += 1;
129                }
130                Some(last_frame) => {
131                    add_frame(mem::replace(last_frame, frame));
132                }
133                None => {
134                    last_frame = Some(frame);
135                }
136            }
137        }
138        if let Some(frame) = last_frame {
139            add_frame(frame);
140        }
141    }
142
143    // In `rustc`, we present const-eval errors from the outer-most place first to the inner-most.
144    // So we reverse the frames here. The first frame will be the same as the span from the current
145    // `TyCtxtAt<'_>`, so we remove it as it would be redundant.
146    frames.reverse();
147    if frames.len() > 0 {
148        frames.remove(0);
149    }
150    if let Some(last) = frames.last_mut()
151        // If the span is not going to be printed, we don't want the span label for `is_last`.
152        && tcx.sess.source_map().span_to_snippet(last.span.source_callsite()).is_ok()
153    {
154        last.has_label = true;
155    }
156
157    (span, frames)
158}
159
160/// Create a diagnostic for a const eval error.
161///
162/// This will use the `mk` function for adding more information to the error.
163/// You can use it to add a stacktrace of current execution according to
164/// `get_span_and_frames` or just give context on where the const eval error happened.
165pub(super) fn report<'tcx, C, F>(
166    ecx: &InterpCx<'tcx, CompileTimeMachine<'tcx>>,
167    error: InterpErrorKind<'tcx>,
168    span: Span,
169    get_span_and_frames: C,
170    mk: F,
171) -> ErrorHandled
172where
173    C: FnOnce() -> (Span, Vec<FrameNote>),
174    F: FnOnce(&mut Diag<'_>, Span, Vec<FrameNote>),
175{
176    let tcx = ecx.tcx.tcx;
177    // Special handling for certain errors
178    match error {
179        // Don't emit a new diagnostic for these errors, they are already reported elsewhere or
180        // should remain silent.
181        err_inval!(AlreadyReported(info)) => ErrorHandled::Reported(info, span),
182        err_inval!(Layout(LayoutError::TooGeneric(_))) | err_inval!(TooGeneric) => {
183            ErrorHandled::TooGeneric(span)
184        }
185        err_inval!(Layout(LayoutError::ReferencesError(guar))) => {
186            // This can occur in infallible promoteds e.g. when a non-existent type or field is
187            // encountered.
188            ErrorHandled::Reported(ReportedErrorInfo::allowed_in_infallible(guar), span)
189        }
190        // Report remaining errors.
191        _ => {
192            let (our_span, frames) = get_span_and_frames();
193            let span = span.substitute_dummy(our_span);
194            let mut err = tcx.dcx().struct_span_err(our_span, error.diagnostic_message());
195            // We allow invalid programs in infallible promoteds since invalid layouts can occur
196            // anyway (e.g. due to size overflow). And we allow OOM as that can happen any time.
197            let allowed_in_infallible = matches!(
198                error,
199                InterpErrorKind::ResourceExhaustion(_) | InterpErrorKind::InvalidProgram(_)
200            );
201
202            if let InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(
203                Some((alloc_id, _access)),
204            )) = error
205            {
206                let bytes = ecx.print_alloc_bytes_for_diagnostics(alloc_id);
207                let info = ecx.get_alloc_info(alloc_id);
208                let raw_bytes = errors::RawBytesNote {
209                    size: info.size.bytes(),
210                    align: info.align.bytes(),
211                    bytes,
212                };
213                err.subdiagnostic(raw_bytes);
214            }
215
216            error.add_args(&mut err);
217
218            mk(&mut err, span, frames);
219            let g = err.emit();
220            let reported = if allowed_in_infallible {
221                ReportedErrorInfo::allowed_in_infallible(g)
222            } else {
223                ReportedErrorInfo::const_eval_error(g)
224            };
225            ErrorHandled::Reported(reported, span)
226        }
227    }
228}
229
230/// Emit a lint from a const-eval situation, with a backtrace.
231// Even if this is unused, please don't remove it -- chances are we will need to emit a lint during const-eval again in the future!
232#[allow(unused)]
233pub(super) fn lint<'tcx, L>(
234    tcx: TyCtxtAt<'tcx>,
235    machine: &CompileTimeMachine<'tcx>,
236    lint: &'static rustc_session::lint::Lint,
237    decorator: impl FnOnce(Vec<errors::FrameNote>) -> L,
238) where
239    L: for<'a> rustc_errors::LintDiagnostic<'a, ()>,
240{
241    let (span, frames) = get_span_and_frames(tcx, &machine.stack);
242
243    tcx.emit_node_span_lint(lint, machine.best_lint_scope(*tcx), span, decorator(frames));
244}