summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_const_eval/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_const_eval/src')
-rw-r--r--compiler/rustc_const_eval/src/const_eval/error.rs280
-rw-r--r--compiler/rustc_const_eval/src/const_eval/eval_queries.rs106
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs132
-rw-r--r--compiler/rustc_const_eval/src/const_eval/mod.rs94
-rw-r--r--compiler/rustc_const_eval/src/const_eval/valtrees.rs7
-rw-r--r--compiler/rustc_const_eval/src/errors.rs688
-rw-r--r--compiler/rustc_const_eval/src/interpret/cast.rs49
-rw-r--r--compiler/rustc_const_eval/src/interpret/eval_context.rs47
-rw-r--r--compiler/rustc_const_eval/src/interpret/intern.rs27
-rw-r--r--compiler/rustc_const_eval/src/interpret/intrinsics.rs106
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs63
-rw-r--r--compiler/rustc_const_eval/src/interpret/operand.rs4
-rw-r--r--compiler/rustc_const_eval/src/interpret/operator.rs68
-rw-r--r--compiler/rustc_const_eval/src/interpret/place.rs13
-rw-r--r--compiler/rustc_const_eval/src/interpret/projection.rs5
-rw-r--r--compiler/rustc_const_eval/src/interpret/step.rs28
-rw-r--r--compiler/rustc_const_eval/src/interpret/terminator.rs57
-rw-r--r--compiler/rustc_const_eval/src/interpret/validity.rs217
-rw-r--r--compiler/rustc_const_eval/src/lib.rs13
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/check.rs75
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/ops.rs213
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs15
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/resolver.rs8
-rw-r--r--compiler/rustc_const_eval/src/transform/promote_consts.rs33
-rw-r--r--compiler/rustc_const_eval/src/transform/validate.rs96
-rw-r--r--compiler/rustc_const_eval/src/util/check_validity_requirement.rs15
-rw-r--r--compiler/rustc_const_eval/src/util/mod.rs26
-rw-r--r--compiler/rustc_const_eval/src/util/type_name.rs1
28 files changed, 1626 insertions, 860 deletions
diff --git a/compiler/rustc_const_eval/src/const_eval/error.rs b/compiler/rustc_const_eval/src/const_eval/error.rs
index c591ff75a..7890d878d 100644
--- a/compiler/rustc_const_eval/src/const_eval/error.rs
+++ b/compiler/rustc_const_eval/src/const_eval/error.rs
@@ -1,17 +1,15 @@
-use std::error::Error;
-use std::fmt;
+use std::mem;
-use rustc_errors::Diagnostic;
+use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, IntoDiagnostic, IntoDiagnosticArg};
use rustc_middle::mir::AssertKind;
-use rustc_middle::query::TyCtxtAt;
+use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::{layout::LayoutError, ConstInt};
-use rustc_span::{Span, Symbol};
+use rustc_span::source_map::Spanned;
+use rustc_span::{ErrorGuaranteed, Span, Symbol};
use super::InterpCx;
-use crate::interpret::{
- struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType,
- UnsupportedOpInfo,
-};
+use crate::errors::{self, FrameNote, ReportErrorExt};
+use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, Machine, MachineStopType};
/// The CTFE machine has some custom error kinds.
#[derive(Clone, Debug)]
@@ -23,7 +21,35 @@ pub enum ConstEvalErrKind {
Abort(String),
}
-impl MachineStopType for ConstEvalErrKind {}
+impl MachineStopType for ConstEvalErrKind {
+ fn diagnostic_message(&self) -> DiagnosticMessage {
+ use crate::fluent_generated::*;
+ use ConstEvalErrKind::*;
+ match self {
+ ConstAccessesStatic => const_eval_const_accesses_static,
+ ModifiedGlobal => const_eval_modified_global,
+ Panic { .. } => const_eval_panic,
+ AssertFailure(x) => x.diagnostic_message(),
+ Abort(msg) => msg.to_string().into(),
+ }
+ }
+ fn add_args(
+ self: Box<Self>,
+ adder: &mut dyn FnMut(std::borrow::Cow<'static, str>, DiagnosticArgValue<'static>),
+ ) {
+ use ConstEvalErrKind::*;
+ match *self {
+ ConstAccessesStatic | ModifiedGlobal | Abort(_) => {}
+ AssertFailure(kind) => kind.add_args(adder),
+ Panic { msg, line, col, file } => {
+ adder("msg".into(), msg.into_diagnostic_arg());
+ adder("file".into(), file.into_diagnostic_arg());
+ adder("line".into(), line.into_diagnostic_arg());
+ adder("col".into(), col.into_diagnostic_arg());
+ }
+ }
+ }
+}
// The errors become `MachineStop` with plain strings when being raised.
// `ConstEvalErr` (in `librustc_middle/mir/interpret/error.rs`) knows to
@@ -34,151 +60,117 @@ impl<'tcx> Into<InterpErrorInfo<'tcx>> for ConstEvalErrKind {
}
}
-impl fmt::Display for ConstEvalErrKind {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- use self::ConstEvalErrKind::*;
- match self {
- ConstAccessesStatic => write!(f, "constant accesses static"),
- ModifiedGlobal => {
- write!(f, "modifying a static's initial value from another static's initializer")
- }
- AssertFailure(msg) => write!(f, "{:?}", msg),
- Panic { msg, line, col, file } => {
- write!(f, "the evaluated program panicked at '{}', {}:{}:{}", msg, file, line, col)
- }
- Abort(msg) => write!(f, "{}", msg),
- }
- }
-}
-
-impl Error for ConstEvalErrKind {}
+pub fn get_span_and_frames<'tcx, 'mir, M: Machine<'mir, 'tcx>>(
+ ecx: &InterpCx<'mir, 'tcx, M>,
+) -> (Span, Vec<errors::FrameNote>)
+where
+ 'tcx: 'mir,
+{
+ let mut stacktrace = ecx.generate_stacktrace();
+ // Filter out `requires_caller_location` frames.
+ stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
+ let span = stacktrace.first().map(|f| f.span).unwrap_or(ecx.tcx.span);
-/// When const-evaluation errors, this type is constructed with the resulting information,
-/// and then used to emit the error as a lint or hard error.
-#[derive(Debug)]
-pub(super) struct ConstEvalErr<'tcx> {
- pub span: Span,
- pub error: InterpError<'tcx>,
- pub stacktrace: Vec<FrameInfo<'tcx>>,
-}
+ let mut frames = Vec::new();
-impl<'tcx> ConstEvalErr<'tcx> {
- /// Turn an interpreter error into something to report to the user.
- /// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace.
- /// Should be called only if the error is actually going to be reported!
- pub fn new<'mir, M: Machine<'mir, 'tcx>>(
- ecx: &InterpCx<'mir, 'tcx, M>,
- error: InterpErrorInfo<'tcx>,
- span: Option<Span>,
- ) -> ConstEvalErr<'tcx>
- where
- 'tcx: 'mir,
- {
- error.print_backtrace();
- let mut stacktrace = ecx.generate_stacktrace();
- // Filter out `requires_caller_location` frames.
- stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
- // If `span` is missing, use topmost remaining frame, or else the "root" span from `ecx.tcx`.
- let span = span.or_else(|| stacktrace.first().map(|f| f.span)).unwrap_or(ecx.tcx.span);
- ConstEvalErr { error: error.into_kind(), stacktrace, span }
- }
-
- pub(super) fn report(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled {
- self.report_decorated(tcx, message, |_| {})
- }
-
- #[instrument(level = "trace", skip(self, decorate))]
- pub(super) fn decorate(&self, err: &mut Diagnostic, decorate: impl FnOnce(&mut Diagnostic)) {
- trace!("reporting const eval failure at {:?}", self.span);
- // Add some more context for select error types.
- match self.error {
- InterpError::Unsupported(
- UnsupportedOpInfo::ReadPointerAsBytes
- | UnsupportedOpInfo::PartialPointerOverwrite(_)
- | UnsupportedOpInfo::PartialPointerCopy(_),
- ) => {
- err.help("this code performed an operation that depends on the underlying bytes representing a pointer");
- err.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported");
+ // Add notes to the backtrace. Don't print a single-line backtrace though.
+ if stacktrace.len() > 1 {
+ // Helper closure to print duplicated lines.
+ let mut add_frame = |mut frame: errors::FrameNote| {
+ frames.push(errors::FrameNote { times: 0, ..frame.clone() });
+ // Don't print [... additional calls ...] if the number of lines is small
+ if frame.times < 3 {
+ let times = frame.times;
+ frame.times = 0;
+ frames.extend(std::iter::repeat(frame).take(times as usize));
+ } else {
+ frames.push(frame);
}
- _ => {}
- }
- // Add spans for the stacktrace. Don't print a single-line backtrace though.
- if self.stacktrace.len() > 1 {
- // Helper closure to print duplicated lines.
- let mut flush_last_line = |last_frame: Option<(String, _)>, times| {
- if let Some((line, span)) = last_frame {
- err.span_note(span, line.clone());
- // Don't print [... additional calls ...] if the number of lines is small
- if times < 3 {
- for _ in 0..times {
- err.span_note(span, line.clone());
- }
- } else {
- err.span_note(
- span,
- format!("[... {} additional calls {} ...]", times, &line),
- );
- }
- }
- };
+ };
- let mut last_frame = None;
- let mut times = 0;
- for frame_info in &self.stacktrace {
- let frame = (frame_info.to_string(), frame_info.span);
- if last_frame.as_ref() == Some(&frame) {
- times += 1;
- } else {
- flush_last_line(last_frame, times);
+ let mut last_frame: Option<errors::FrameNote> = None;
+ for frame_info in &stacktrace {
+ let frame = frame_info.as_note(*ecx.tcx);
+ match last_frame.as_mut() {
+ Some(last_frame)
+ if last_frame.span == frame.span
+ && last_frame.where_ == frame.where_
+ && last_frame.instance == frame.instance =>
+ {
+ last_frame.times += 1;
+ }
+ Some(last_frame) => {
+ add_frame(mem::replace(last_frame, frame));
+ }
+ None => {
last_frame = Some(frame);
- times = 0;
}
}
- flush_last_line(last_frame, times);
}
- // Let the caller attach any additional information it wants.
- decorate(err);
+ if let Some(frame) = last_frame {
+ add_frame(frame);
+ }
}
- /// Create a diagnostic for this const eval error.
- ///
- /// Sets the message passed in via `message` and adds span labels with detailed error
- /// information before handing control back to `decorate` to do any final annotations,
- /// after which the diagnostic is emitted.
- ///
- /// If `lint_root.is_some()` report it as a lint, else report it as a hard error.
- /// (Except that for some errors, we ignore all that -- see `must_error` below.)
- #[instrument(skip(self, tcx, decorate), level = "debug")]
- pub(super) fn report_decorated(
- &self,
- tcx: TyCtxtAt<'tcx>,
- message: &str,
- decorate: impl FnOnce(&mut Diagnostic),
- ) -> ErrorHandled {
- debug!("self.error: {:?}", self.error);
- // Special handling for certain errors
- match &self.error {
- // Don't emit a new diagnostic for these errors
- err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
- ErrorHandled::TooGeneric
- }
- err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(*error_reported),
- err_inval!(Layout(LayoutError::SizeOverflow(_))) => {
- // We must *always* hard error on these, even if the caller wants just a lint.
- // The `message` makes little sense here, this is a more serious error than the
- // caller thinks anyway.
- // See <https://github.com/rust-lang/rust/pull/63152>.
- let mut err = struct_error(tcx, &self.error.to_string());
- self.decorate(&mut err, decorate);
- ErrorHandled::Reported(err.emit().into())
- }
- _ => {
- // Report as hard error.
- let mut err = struct_error(tcx, message);
- err.span_label(self.span, self.error.to_string());
- self.decorate(&mut err, decorate);
- ErrorHandled::Reported(err.emit().into())
+ (span, frames)
+}
+
+/// Create a diagnostic for a const eval error.
+///
+/// This will use the `mk` function for creating the error which will get passed labels according to
+/// the `InterpError` and the span and a stacktrace of current execution according to
+/// `get_span_and_frames`.
+pub(super) fn report<'tcx, C, F, E>(
+ tcx: TyCtxt<'tcx>,
+ error: InterpError<'tcx>,
+ span: Option<Span>,
+ get_span_and_frames: C,
+ mk: F,
+) -> ErrorHandled
+where
+ C: FnOnce() -> (Span, Vec<FrameNote>),
+ F: FnOnce(Span, Vec<FrameNote>) -> E,
+ E: IntoDiagnostic<'tcx, ErrorGuaranteed>,
+{
+ // Special handling for certain errors
+ match error {
+ // Don't emit a new diagnostic for these errors
+ err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
+ ErrorHandled::TooGeneric
+ }
+ err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(error_reported),
+ err_inval!(Layout(layout_error @ LayoutError::SizeOverflow(_))) => {
+ // We must *always* hard error on these, even if the caller wants just a lint.
+ // The `message` makes little sense here, this is a more serious error than the
+ // caller thinks anyway.
+ // See <https://github.com/rust-lang/rust/pull/63152>.
+ let (our_span, frames) = get_span_and_frames();
+ let span = span.unwrap_or(our_span);
+ let mut err =
+ tcx.sess.create_err(Spanned { span, node: layout_error.into_diagnostic() });
+ err.code(rustc_errors::error_code!(E0080));
+ let Some((mut err, handler)) = err.into_diagnostic() else {
+ panic!("did not emit diag");
+ };
+ for frame in frames {
+ err.eager_subdiagnostic(handler, frame);
}
+
+ ErrorHandled::Reported(handler.emit_diagnostic(&mut err).unwrap().into())
+ }
+ _ => {
+ // Report as hard error.
+ let (our_span, frames) = get_span_and_frames();
+ let span = span.unwrap_or(our_span);
+ let err = mk(span, frames);
+ let mut err = tcx.sess.create_err(err);
+
+ let msg = error.diagnostic_message();
+ error.add_args(&tcx.sess.parse_sess.span_diagnostic, &mut err);
+
+ // Use *our* span to label the interp error
+ err.span_label(our_span, msg);
+ ErrorHandled::Reported(err.emit().into())
}
}
}
diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
index 046d20529..417ab78fd 100644
--- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
@@ -1,12 +1,12 @@
use crate::const_eval::CheckAlignment;
-use std::borrow::Cow;
+use crate::errors::ConstEvalError;
use either::{Left, Right};
use rustc_hir::def::DefKind;
use rustc_middle::mir;
-use rustc_middle::mir::interpret::ErrorHandled;
-use rustc_middle::mir::pretty::display_allocation;
+use rustc_middle::mir::interpret::{ErrorHandled, InterpErrorInfo};
+use rustc_middle::mir::pretty::write_allocation_bytes;
use rustc_middle::traits::Reveal;
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::print::with_no_trimmed_paths;
@@ -14,7 +14,8 @@ use rustc_middle::ty::{self, TyCtxt};
use rustc_span::source_map::Span;
use rustc_target::abi::{self, Abi};
-use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr};
+use super::{CanAccessStatics, CompileTimeEvalContext, CompileTimeInterpreter};
+use crate::errors;
use crate::interpret::eval_nullary_intrinsic;
use crate::interpret::{
intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId,
@@ -22,10 +23,6 @@ use crate::interpret::{
RefTracking, StackPopCleanup,
};
-const NOTE_ON_UNDEFINED_BEHAVIOR_ERROR: &str = "The rules on what exactly is undefined behavior aren't clear, \
- so this check might be overzealous. Please open an issue on the rustc \
- repository if you believe it should not be considered undefined behavior.";
-
// Returns a pointer to where the result lives
fn eval_body_using_ecx<'mir, 'tcx>(
ecx: &mut CompileTimeEvalContext<'mir, 'tcx>,
@@ -96,14 +93,14 @@ pub(super) fn mk_eval_cx<'mir, 'tcx>(
tcx: TyCtxt<'tcx>,
root_span: Span,
param_env: ty::ParamEnv<'tcx>,
- can_access_statics: bool,
+ can_access_statics: CanAccessStatics,
) -> CompileTimeEvalContext<'mir, 'tcx> {
debug!("mk_eval_cx: {:?}", param_env);
InterpCx::new(
tcx,
root_span,
param_env,
- CompileTimeInterpreter::new(tcx.const_eval_limit(), can_access_statics, CheckAlignment::No),
+ CompileTimeInterpreter::new(can_access_statics, CheckAlignment::No),
)
}
@@ -210,7 +207,7 @@ pub(crate) fn turn_into_const_value<'tcx>(
tcx,
tcx.def_span(key.value.instance.def_id()),
key.param_env,
- /*can_access_statics:*/ is_static,
+ CanAccessStatics::from(is_static),
);
let mplace = ecx.raw_const_to_mplace(constant).expect(
@@ -253,8 +250,14 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
};
return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| {
let span = tcx.def_span(def_id);
- let error = ConstEvalErr { error: error.into_kind(), stacktrace: vec![], span };
- error.report(tcx.at(span), "could not evaluate nullary intrinsic")
+
+ super::report(
+ tcx,
+ error.into_kind(),
+ Some(span),
+ || (span, vec![]),
+ |span, _| errors::NullaryIntrinsicError { span },
+ )
});
}
@@ -306,8 +309,7 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
// Statics (and promoteds inside statics) may access other statics, because unlike consts
// they do not have to behave "as if" they were evaluated at runtime.
CompileTimeInterpreter::new(
- tcx.const_eval_limit(),
- /*can_access_statics:*/ is_static,
+ CanAccessStatics::from(is_static),
if tcx.sess.opts.unstable_opts.extra_const_ub_checks {
CheckAlignment::Error
} else {
@@ -319,9 +321,11 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
let res = ecx.load_mir(cid.instance.def, cid.promoted);
match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, &body)) {
Err(error) => {
- let err = ConstEvalErr::new(&ecx, error, None);
- let msg = if is_static {
- Cow::from("could not evaluate static initializer")
+ let (error, backtrace) = error.into_parts();
+ backtrace.print_backtrace();
+
+ let (kind, instance) = if is_static {
+ ("static", String::new())
} else {
// If the current item has generics, we'd like to enrich the message with the
// instance and its substs: to show the actual compile-time values, in addition to
@@ -329,19 +333,29 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
let instance = &key.value.instance;
if !instance.substs.is_empty() {
let instance = with_no_trimmed_paths!(instance.to_string());
- let msg = format!("evaluation of `{}` failed", instance);
- Cow::from(msg)
+ ("const_with_path", instance)
} else {
- Cow::from("evaluation of constant value failed")
+ ("const", String::new())
}
};
- Err(err.report(ecx.tcx.at(err.span), &msg))
+ Err(super::report(
+ *ecx.tcx,
+ error,
+ None,
+ || super::get_span_and_frames(&ecx),
+ |span, frames| ConstEvalError {
+ span,
+ error_kind: kind,
+ instance,
+ frame_notes: frames,
+ },
+ ))
}
Ok(mplace) => {
// Since evaluation had no errors, validate the resulting constant.
// This is a separate `try` block to provide more targeted error reporting.
- let validation = try {
+ let validation: Result<_, InterpErrorInfo<'_>> = try {
let mut ref_tracking = RefTracking::new(mplace);
let mut inner = false;
while let Some((mplace, path)) = ref_tracking.todo.pop() {
@@ -358,23 +372,37 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
}
};
let alloc_id = mplace.ptr.provenance.unwrap();
+
+ // Validation failed, report an error. This is always a hard error.
if let Err(error) = validation {
- // Validation failed, report an error. This is always a hard error.
- let err = ConstEvalErr::new(&ecx, error, None);
- Err(err.report_decorated(
- ecx.tcx,
- "it is undefined behavior to use this value",
- |diag| {
- if matches!(err.error, InterpError::UndefinedBehavior(_)) {
- diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR);
- }
- diag.note(format!(
- "the raw bytes of the constant ({}",
- display_allocation(
- *ecx.tcx,
- ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner()
- )
- ));
+ let (error, backtrace) = error.into_parts();
+ backtrace.print_backtrace();
+
+ let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {});
+
+ let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
+ let mut bytes = String::new();
+ if alloc.size() != abi::Size::ZERO {
+ bytes = "\n".into();
+ // FIXME(translation) there might be pieces that are translatable.
+ write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, " ").unwrap();
+ }
+ let raw_bytes = errors::RawBytesNote {
+ size: alloc.size().bytes(),
+ align: alloc.align.bytes(),
+ bytes,
+ };
+
+ Err(super::report(
+ *ecx.tcx,
+ error,
+ None,
+ || super::get_span_and_frames(&ecx),
+ move |span, frames| errors::UndefinedBehavior {
+ span,
+ ub_note,
+ frames,
+ raw_bytes,
},
))
} else {
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index 58b5755af..f9f645af4 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -16,25 +16,37 @@ use std::fmt;
use rustc_ast::Mutability;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::AssertMessage;
-use rustc_session::Limit;
use rustc_span::symbol::{sym, Symbol};
use rustc_target::abi::{Align, Size};
use rustc_target::spec::abi::Abi as CallAbi;
+use crate::errors::{LongRunning, LongRunningWarn};
use crate::interpret::{
self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
InterpResult, OpTy, PlaceTy, Pointer, Scalar,
};
+use crate::{errors, fluent_generated as fluent};
use super::error::*;
+/// When hitting this many interpreted terminators we emit a deny by default lint
+/// that notfies the user that their constant takes a long time to evaluate. If that's
+/// what they intended, they can just allow the lint.
+const LINT_TERMINATOR_LIMIT: usize = 2_000_000;
+/// The limit used by `-Z tiny-const-eval-limit`. This smaller limit is useful for internal
+/// tests not needing to run 30s or more to show some behaviour.
+const TINY_LINT_TERMINATOR_LIMIT: usize = 20;
+/// After this many interpreted terminators, we start emitting progress indicators at every
+/// power of two of interpreted terminators.
+const PROGRESS_INDICATOR_START: usize = 4_000_000;
+
/// Extra machine state for CTFE, and the Machine instance
pub struct CompileTimeInterpreter<'mir, 'tcx> {
- /// For now, the number of terminators that can be evaluated before we throw a resource
- /// exhaustion error.
+ /// The number of terminators that have been evaluated.
///
- /// Setting this to `0` disables the limit and allows the interpreter to run forever.
- pub(super) steps_remaining: usize,
+ /// This is used to produce lints informing the user that the compiler is not stuck.
+ /// Set to `usize::MAX` to never report anything.
+ pub(super) num_evaluated_steps: usize,
/// The virtual call stack.
pub(super) stack: Vec<Frame<'mir, 'tcx, AllocId, ()>>,
@@ -45,7 +57,7 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> {
/// * Interning makes everything outside of statics immutable.
/// * Pointers to allocations inside of statics can never leak outside, to a non-static global.
/// This boolean here controls the second part.
- pub(super) can_access_statics: bool,
+ pub(super) can_access_statics: CanAccessStatics,
/// Whether to check alignment during evaluation.
pub(super) check_alignment: CheckAlignment,
@@ -71,14 +83,25 @@ impl CheckAlignment {
}
}
+#[derive(Copy, Clone, PartialEq)]
+pub(crate) enum CanAccessStatics {
+ No,
+ Yes,
+}
+
+impl From<bool> for CanAccessStatics {
+ fn from(value: bool) -> Self {
+ if value { Self::Yes } else { Self::No }
+ }
+}
+
impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
pub(crate) fn new(
- const_eval_limit: Limit,
- can_access_statics: bool,
+ can_access_statics: CanAccessStatics,
check_alignment: CheckAlignment,
) -> Self {
CompileTimeInterpreter {
- steps_remaining: const_eval_limit.0,
+ num_evaluated_steps: 0,
stack: Vec::new(),
can_access_statics,
check_alignment,
@@ -247,7 +270,10 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
let target_align = self.read_scalar(&args[1])?.to_target_usize(self)?;
if !target_align.is_power_of_two() {
- throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
+ throw_ub_custom!(
+ fluent::const_eval_align_offset_invalid_align,
+ target_align = target_align,
+ );
}
match self.ptr_try_get_alloc_id(ptr) {
@@ -353,15 +379,18 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
"`alignment_check_failed` called when no alignment check requested"
),
CheckAlignment::FutureIncompat => {
- let err = ConstEvalErr::new(ecx, err, None);
- ecx.tcx.struct_span_lint_hir(
+ let (_, backtrace) = err.into_parts();
+ backtrace.print_backtrace();
+ let (span, frames) = super::get_span_and_frames(&ecx);
+
+ ecx.tcx.emit_spanned_lint(
INVALID_ALIGNMENT,
ecx.stack().iter().find_map(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID),
- err.span,
- err.error.to_string(),
- |db| {
- err.decorate(db, |_| {});
- db
+ span,
+ errors::AlignmentCheckFailed {
+ has: has.bytes(),
+ required: required.bytes(),
+ frames,
},
);
Ok(())
@@ -475,7 +504,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
let align = match Align::from_bytes(align) {
Ok(a) => a,
- Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
+ Err(err) => throw_ub_custom!(
+ fluent::const_eval_invalid_align_details,
+ name = "const_allocate",
+ err_kind = err.diag_ident(),
+ align = err.align()
+ ),
};
let ptr = ecx.allocate_ptr(
@@ -493,7 +527,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
let size = Size::from_bytes(size);
let align = match Align::from_bytes(align) {
Ok(a) => a,
- Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
+ Err(err) => throw_ub_custom!(
+ fluent::const_eval_invalid_align_details,
+ name = "const_deallocate",
+ err_kind = err.diag_ident(),
+ align = err.align()
+ ),
};
// If an allocation is created in an another const,
@@ -569,13 +608,54 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
fn increment_const_eval_counter(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
// The step limit has already been hit in a previous call to `increment_const_eval_counter`.
- if ecx.machine.steps_remaining == 0 {
- return Ok(());
- }
- ecx.machine.steps_remaining -= 1;
- if ecx.machine.steps_remaining == 0 {
- throw_exhaust!(StepLimitReached)
+ if let Some(new_steps) = ecx.machine.num_evaluated_steps.checked_add(1) {
+ let (limit, start) = if ecx.tcx.sess.opts.unstable_opts.tiny_const_eval_limit {
+ (TINY_LINT_TERMINATOR_LIMIT, TINY_LINT_TERMINATOR_LIMIT)
+ } else {
+ (LINT_TERMINATOR_LIMIT, PROGRESS_INDICATOR_START)
+ };
+
+ ecx.machine.num_evaluated_steps = new_steps;
+ // By default, we have a *deny* lint kicking in after some time
+ // to ensure `loop {}` doesn't just go forever.
+ // In case that lint got reduced, in particular for `--cap-lint` situations, we also
+ // have a hard warning shown every now and then for really long executions.
+ if new_steps == limit {
+ // By default, we stop after a million steps, but the user can disable this lint
+ // to be able to run until the heat death of the universe or power loss, whichever
+ // comes first.
+ let hir_id = ecx.best_lint_scope();
+ let is_error = ecx
+ .tcx
+ .lint_level_at_node(
+ rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL,
+ hir_id,
+ )
+ .0
+ .is_error();
+ let span = ecx.cur_span();
+ ecx.tcx.emit_spanned_lint(
+ rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL,
+ hir_id,
+ span,
+ LongRunning { item_span: ecx.tcx.span },
+ );
+ // If this was a hard error, don't bother continuing evaluation.
+ if is_error {
+ let guard = ecx
+ .tcx
+ .sess
+ .delay_span_bug(span, "The deny lint should have already errored");
+ throw_inval!(AlreadyReported(guard.into()));
+ }
+ } else if new_steps > start && new_steps.is_power_of_two() {
+ // Only report after a certain number of terminators have been evaluated and the
+ // current number of evaluated terminators is a power of 2. The latter gives us a cheap
+ // way to implement exponential backoff.
+ let span = ecx.cur_span();
+ ecx.tcx.sess.emit_warning(LongRunningWarn { span, item_span: ecx.tcx.span });
+ }
}
Ok(())
@@ -634,7 +714,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
}
} else {
// Read access. These are usually allowed, with some exceptions.
- if machine.can_access_statics {
+ if machine.can_access_statics == CanAccessStatics::Yes {
// Machine configuration allows us read from anything (e.g., `static` initializer).
Ok(())
} else if static_def_id.is_some() {
diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs
index 05be45fef..a3064b53d 100644
--- a/compiler/rustc_const_eval/src/const_eval/mod.rs
+++ b/compiler/rustc_const_eval/src/const_eval/mod.rs
@@ -1,14 +1,10 @@
// Not in interpret to make sure we do not use private implementation details
use crate::errors::MaxNumNodesInConstErr;
-use crate::interpret::{
- intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, InterpResult, MemPlaceMeta,
- Scalar,
-};
-use rustc_hir::Mutability;
+use crate::interpret::{intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, Scalar};
use rustc_middle::mir;
use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId};
-use rustc_middle::ty::{self, TyCtxt};
+use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::{source_map::DUMMY_SP, symbol::Symbol};
mod error;
@@ -28,7 +24,7 @@ pub(crate) fn const_caller_location(
(file, line, col): (Symbol, u32, u32),
) -> ConstValue<'_> {
trace!("const_caller_location: {}:{}:{}", file, line, col);
- let mut ecx = mk_eval_cx(tcx, DUMMY_SP, ty::ParamEnv::reveal_all(), false);
+ let mut ecx = mk_eval_cx(tcx, DUMMY_SP, ty::ParamEnv::reveal_all(), CanAccessStatics::No);
let loc_place = ecx.alloc_caller_location(file, line, col);
if intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &loc_place).is_err() {
@@ -57,10 +53,12 @@ pub(crate) fn eval_to_valtree<'tcx>(
// FIXME Need to provide a span to `eval_to_valtree`
let ecx = mk_eval_cx(
- tcx, DUMMY_SP, param_env,
+ tcx,
+ DUMMY_SP,
+ param_env,
// It is absolutely crucial for soundness that
// we do not read from static items or other mutable memory.
- false,
+ CanAccessStatics::No,
);
let place = ecx.raw_const_to_mplace(const_alloc).unwrap();
debug!(?place);
@@ -75,17 +73,8 @@ pub(crate) fn eval_to_valtree<'tcx>(
let global_const_id = cid.display(tcx);
match err {
ValTreeCreationError::NodesOverflow => {
- let msg = format!(
- "maximum number of nodes exceeded in constant {}",
- &global_const_id
- );
- let mut diag = match tcx.hir().span_if_local(did) {
- Some(span) => {
- tcx.sess.create_err(MaxNumNodesInConstErr { span, global_const_id })
- }
- None => tcx.sess.struct_err(msg),
- };
- diag.emit();
+ let span = tcx.hir().span_if_local(did);
+ tcx.sess.emit_err(MaxNumNodesInConstErr { span, global_const_id });
Ok(None)
}
@@ -96,24 +85,24 @@ pub(crate) fn eval_to_valtree<'tcx>(
}
#[instrument(skip(tcx), level = "debug")]
-pub(crate) fn try_destructure_mir_constant<'tcx>(
+pub(crate) fn try_destructure_mir_constant_for_diagnostics<'tcx>(
tcx: TyCtxt<'tcx>,
- param_env: ty::ParamEnv<'tcx>,
- val: mir::ConstantKind<'tcx>,
-) -> InterpResult<'tcx, mir::DestructuredConstant<'tcx>> {
- trace!("destructure_mir_constant: {:?}", val);
- let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false);
- let op = ecx.eval_mir_constant(&val, None, None)?;
+ val: ConstValue<'tcx>,
+ ty: Ty<'tcx>,
+) -> Option<mir::DestructuredConstant<'tcx>> {
+ let param_env = ty::ParamEnv::reveal_all();
+ let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessStatics::No);
+ let op = ecx.const_val_to_op(val, ty, None).ok()?;
// We go to `usize` as we cannot allocate anything bigger anyway.
- let (field_count, variant, down) = match val.ty().kind() {
+ let (field_count, variant, down) = match ty.kind() {
ty::Array(_, len) => (len.eval_target_usize(tcx, param_env) as usize, None, op),
ty::Adt(def, _) if def.variants().is_empty() => {
- throw_ub!(Unreachable)
+ return None;
}
ty::Adt(def, _) => {
- let variant = ecx.read_discriminant(&op)?.1;
- let down = ecx.operand_downcast(&op, variant)?;
+ let variant = ecx.read_discriminant(&op).ok()?.1;
+ let down = ecx.operand_downcast(&op, variant).ok()?;
(def.variants()[variant].fields.len(), Some(variant), down)
}
ty::Tuple(substs) => (substs.len(), None, op),
@@ -122,47 +111,12 @@ pub(crate) fn try_destructure_mir_constant<'tcx>(
let fields_iter = (0..field_count)
.map(|i| {
- let field_op = ecx.operand_field(&down, i)?;
+ let field_op = ecx.operand_field(&down, i).ok()?;
let val = op_to_const(&ecx, &field_op);
- Ok(mir::ConstantKind::Val(val, field_op.layout.ty))
+ Some((val, field_op.layout.ty))
})
- .collect::<InterpResult<'tcx, Vec<_>>>()?;
+ .collect::<Option<Vec<_>>>()?;
let fields = tcx.arena.alloc_from_iter(fields_iter);
- Ok(mir::DestructuredConstant { variant, fields })
-}
-
-#[instrument(skip(tcx), level = "debug")]
-pub(crate) fn deref_mir_constant<'tcx>(
- tcx: TyCtxt<'tcx>,
- param_env: ty::ParamEnv<'tcx>,
- val: mir::ConstantKind<'tcx>,
-) -> mir::ConstantKind<'tcx> {
- let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false);
- let op = ecx.eval_mir_constant(&val, None, None).unwrap();
- let mplace = ecx.deref_operand(&op).unwrap();
- if let Some(alloc_id) = mplace.ptr.provenance {
- assert_eq!(
- tcx.global_alloc(alloc_id).unwrap_memory().0.0.mutability,
- Mutability::Not,
- "deref_mir_constant cannot be used with mutable allocations as \
- that could allow pattern matching to observe mutable statics",
- );
- }
-
- let ty = match mplace.meta {
- MemPlaceMeta::None => mplace.layout.ty,
- // In case of unsized types, figure out the real type behind.
- MemPlaceMeta::Meta(scalar) => match mplace.layout.ty.kind() {
- ty::Str => bug!("there's no sized equivalent of a `str`"),
- ty::Slice(elem_ty) => tcx.mk_array(*elem_ty, scalar.to_target_usize(&tcx).unwrap()),
- _ => bug!(
- "type {} should not have metadata, but had {:?}",
- mplace.layout.ty,
- mplace.meta
- ),
- },
- };
-
- mir::ConstantKind::Val(op_to_const(&ecx, &mplace.into()), ty)
+ Some(mir::DestructuredConstant { variant, fields })
}
diff --git a/compiler/rustc_const_eval/src/const_eval/valtrees.rs b/compiler/rustc_const_eval/src/const_eval/valtrees.rs
index b10f2e9f8..e574df276 100644
--- a/compiler/rustc_const_eval/src/const_eval/valtrees.rs
+++ b/compiler/rustc_const_eval/src/const_eval/valtrees.rs
@@ -1,6 +1,7 @@
use super::eval_queries::{mk_eval_cx, op_to_const};
use super::machine::CompileTimeEvalContext;
use super::{ValTreeCreationError, ValTreeCreationResult, VALTREE_MAX_NODES};
+use crate::const_eval::CanAccessStatics;
use crate::interpret::{
intern_const_alloc_recursive, ConstValue, ImmTy, Immediate, InternKind, MemPlaceMeta,
MemoryKind, PlaceTy, Scalar,
@@ -263,7 +264,11 @@ pub fn valtree_to_const_value<'tcx>(
// FIXME Does this need an example?
let (param_env, ty) = param_env_ty.into_parts();
- let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false);
+ let mut ecx: crate::interpret::InterpCx<
+ '_,
+ '_,
+ crate::const_eval::CompileTimeInterpreter<'_, '_>,
+ > = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessStatics::No);
match ty.kind() {
ty::FnDef(..) => {
diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs
index f8b7cc6d7..ca38cce71 100644
--- a/compiler/rustc_const_eval/src/errors.rs
+++ b/compiler/rustc_const_eval/src/errors.rs
@@ -1,6 +1,24 @@
+use rustc_errors::{
+ DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler,
+ IntoDiagnostic,
+};
use rustc_hir::ConstContext;
-use rustc_macros::Diagnostic;
+use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
+use rustc_middle::mir::interpret::{
+ CheckInAllocMsg, ExpectedKind, InterpError, InvalidMetaKind, InvalidProgramInfo, PointerKind,
+ ResourceExhaustionInfo, UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo,
+};
+use rustc_middle::ty::{self, Ty};
use rustc_span::Span;
+use rustc_target::abi::call::AdjustForForeignAbiError;
+use rustc_target::abi::{Size, WrappingRange};
+
+#[derive(Diagnostic)]
+#[diag(const_eval_dangling_ptr_in_final)]
+pub(crate) struct DanglingPtrInFinal {
+ #[primary_span]
+ pub span: Span,
+}
#[derive(Diagnostic)]
#[diag(const_eval_unstable_in_stable)]
@@ -92,7 +110,7 @@ pub(crate) struct TransientMutBorrowErrRaw {
#[diag(const_eval_max_num_nodes_in_const)]
pub(crate) struct MaxNumNodesInConstErr {
#[primary_span]
- pub span: Span,
+ pub span: Option<Span>,
pub global_const_id: String,
}
@@ -176,6 +194,14 @@ pub(crate) struct UnallowedInlineAsm {
}
#[derive(Diagnostic)]
+#[diag(const_eval_unsupported_untyped_pointer)]
+#[note]
+pub(crate) struct UnsupportedUntypedPointer {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(Diagnostic)]
#[diag(const_eval_interior_mutable_data_refer, code = "E0492")]
pub(crate) struct InteriorMutableDataRefer {
#[primary_span]
@@ -194,3 +220,661 @@ pub(crate) struct InteriorMutabilityBorrow {
#[primary_span]
pub span: Span,
}
+
+#[derive(LintDiagnostic)]
+#[diag(const_eval_long_running)]
+#[note]
+pub struct LongRunning {
+ #[help]
+ pub item_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_long_running)]
+pub struct LongRunningWarn {
+ #[primary_span]
+ #[label]
+ pub span: Span,
+ #[help]
+ pub item_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_erroneous_constant)]
+pub(crate) struct ErroneousConstUsed {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(Subdiagnostic)]
+#[note(const_eval_non_const_impl)]
+pub(crate) struct NonConstImplNote {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(Subdiagnostic, PartialEq, Eq, Clone)]
+#[note(const_eval_frame_note)]
+pub struct FrameNote {
+ #[primary_span]
+ pub span: Span,
+ pub times: i32,
+ pub where_: &'static str,
+ pub instance: String,
+}
+
+#[derive(Subdiagnostic)]
+#[note(const_eval_raw_bytes)]
+pub struct RawBytesNote {
+ pub size: u64,
+ pub align: u64,
+ pub bytes: String,
+}
+
+// FIXME(fee1-dead) do not use stringly typed `ConstContext`
+
+#[derive(Diagnostic)]
+#[diag(const_eval_match_eq_non_const, code = "E0015")]
+#[note]
+pub struct NonConstMatchEq<'tcx> {
+ #[primary_span]
+ pub span: Span,
+ pub ty: Ty<'tcx>,
+ pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_for_loop_into_iter_non_const, code = "E0015")]
+pub struct NonConstForLoopIntoIter<'tcx> {
+ #[primary_span]
+ pub span: Span,
+ pub ty: Ty<'tcx>,
+ pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_question_branch_non_const, code = "E0015")]
+pub struct NonConstQuestionBranch<'tcx> {
+ #[primary_span]
+ pub span: Span,
+ pub ty: Ty<'tcx>,
+ pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_question_from_residual_non_const, code = "E0015")]
+pub struct NonConstQuestionFromResidual<'tcx> {
+ #[primary_span]
+ pub span: Span,
+ pub ty: Ty<'tcx>,
+ pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_try_block_from_output_non_const, code = "E0015")]
+pub struct NonConstTryBlockFromOutput<'tcx> {
+ #[primary_span]
+ pub span: Span,
+ pub ty: Ty<'tcx>,
+ pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_await_non_const, code = "E0015")]
+pub struct NonConstAwait<'tcx> {
+ #[primary_span]
+ pub span: Span,
+ pub ty: Ty<'tcx>,
+ pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_closure_non_const, code = "E0015")]
+pub struct NonConstClosure {
+ #[primary_span]
+ pub span: Span,
+ pub kind: ConstContext,
+ #[subdiagnostic]
+ pub note: Option<NonConstClosureNote>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum NonConstClosureNote {
+ #[note(const_eval_closure_fndef_not_const)]
+ FnDef {
+ #[primary_span]
+ span: Span,
+ },
+ #[note(const_eval_fn_ptr_call)]
+ FnPtr,
+ #[note(const_eval_closure_call)]
+ Closure,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(const_eval_consider_dereferencing, applicability = "machine-applicable")]
+pub struct ConsiderDereferencing {
+ pub deref: String,
+ #[suggestion_part(code = "{deref}")]
+ pub span: Span,
+ #[suggestion_part(code = "{deref}")]
+ pub rhs_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_operator_non_const, code = "E0015")]
+pub struct NonConstOperator {
+ #[primary_span]
+ pub span: Span,
+ pub kind: ConstContext,
+ #[subdiagnostic]
+ pub sugg: Option<ConsiderDereferencing>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_deref_coercion_non_const, code = "E0015")]
+#[note]
+pub struct NonConstDerefCoercion<'tcx> {
+ #[primary_span]
+ pub span: Span,
+ pub ty: Ty<'tcx>,
+ pub kind: ConstContext,
+ pub target_ty: Ty<'tcx>,
+ #[note(const_eval_target_note)]
+ pub deref_target: Option<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_live_drop, code = "E0493")]
+pub struct LiveDrop<'tcx> {
+ #[primary_span]
+ #[label]
+ pub span: Span,
+ pub kind: ConstContext,
+ pub dropped_ty: Ty<'tcx>,
+ #[label(const_eval_dropped_at_label)]
+ pub dropped_at: Option<Span>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(const_eval_align_check_failed)]
+pub struct AlignmentCheckFailed {
+ pub has: u64,
+ pub required: u64,
+ #[subdiagnostic]
+ pub frames: Vec<FrameNote>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_error, code = "E0080")]
+pub struct ConstEvalError {
+ #[primary_span]
+ pub span: Span,
+ /// One of "const", "const_with_path", and "static"
+ pub error_kind: &'static str,
+ pub instance: String,
+ #[subdiagnostic]
+ pub frame_notes: Vec<FrameNote>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_nullary_intrinsic_fail)]
+pub struct NullaryIntrinsicError {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_undefined_behavior, code = "E0080")]
+pub struct UndefinedBehavior {
+ #[primary_span]
+ pub span: Span,
+ #[note(const_eval_undefined_behavior_note)]
+ pub ub_note: Option<()>,
+ #[subdiagnostic]
+ pub frames: Vec<FrameNote>,
+ #[subdiagnostic]
+ pub raw_bytes: RawBytesNote,
+}
+
+pub trait ReportErrorExt {
+ /// Returns the diagnostic message for this error.
+ fn diagnostic_message(&self) -> DiagnosticMessage;
+ fn add_args<G: EmissionGuarantee>(
+ self,
+ handler: &Handler,
+ builder: &mut DiagnosticBuilder<'_, G>,
+ );
+
+ fn debug(self) -> String
+ where
+ Self: Sized,
+ {
+ ty::tls::with(move |tcx| {
+ let mut builder = tcx.sess.struct_allow(DiagnosticMessage::Str(String::new().into()));
+ let handler = &tcx.sess.parse_sess.span_diagnostic;
+ let message = self.diagnostic_message();
+ self.add_args(handler, &mut builder);
+ let s = handler.eagerly_translate_to_string(message, builder.args());
+ builder.cancel();
+ s
+ })
+ }
+}
+
+fn bad_pointer_message(msg: CheckInAllocMsg, handler: &Handler) -> String {
+ use crate::fluent_generated::*;
+
+ let msg = match msg {
+ CheckInAllocMsg::DerefTest => const_eval_deref_test,
+ CheckInAllocMsg::MemoryAccessTest => const_eval_memory_access_test,
+ CheckInAllocMsg::PointerArithmeticTest => const_eval_pointer_arithmetic_test,
+ CheckInAllocMsg::OffsetFromTest => const_eval_offset_from_test,
+ CheckInAllocMsg::InboundsTest => const_eval_in_bounds_test,
+ };
+
+ handler.eagerly_translate_to_string(msg, [].into_iter())
+}
+
+impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
+ fn diagnostic_message(&self) -> DiagnosticMessage {
+ use crate::fluent_generated::*;
+ use UndefinedBehaviorInfo::*;
+ match self {
+ Ub(msg) => msg.clone().into(),
+ Unreachable => const_eval_unreachable,
+ BoundsCheckFailed { .. } => const_eval_bounds_check_failed,
+ DivisionByZero => const_eval_division_by_zero,
+ RemainderByZero => const_eval_remainder_by_zero,
+ DivisionOverflow => const_eval_division_overflow,
+ RemainderOverflow => const_eval_remainder_overflow,
+ PointerArithOverflow => const_eval_pointer_arithmetic_overflow,
+ InvalidMeta(InvalidMetaKind::SliceTooBig) => const_eval_invalid_meta_slice,
+ InvalidMeta(InvalidMetaKind::TooBig) => const_eval_invalid_meta,
+ UnterminatedCString(_) => const_eval_unterminated_c_string,
+ PointerUseAfterFree(_) => const_eval_pointer_use_after_free,
+ PointerOutOfBounds { ptr_size: Size::ZERO, .. } => const_eval_zst_pointer_out_of_bounds,
+ PointerOutOfBounds { .. } => const_eval_pointer_out_of_bounds,
+ DanglingIntPointer(0, _) => const_eval_dangling_null_pointer,
+ DanglingIntPointer(_, _) => const_eval_dangling_int_pointer,
+ AlignmentCheckFailed { .. } => const_eval_alignment_check_failed,
+ WriteToReadOnly(_) => const_eval_write_to_read_only,
+ DerefFunctionPointer(_) => const_eval_deref_function_pointer,
+ DerefVTablePointer(_) => const_eval_deref_vtable_pointer,
+ InvalidBool(_) => const_eval_invalid_bool,
+ InvalidChar(_) => const_eval_invalid_char,
+ InvalidTag(_) => const_eval_invalid_tag,
+ InvalidFunctionPointer(_) => const_eval_invalid_function_pointer,
+ InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer,
+ InvalidStr(_) => const_eval_invalid_str,
+ InvalidUninitBytes(None) => const_eval_invalid_uninit_bytes_unknown,
+ InvalidUninitBytes(Some(_)) => const_eval_invalid_uninit_bytes,
+ DeadLocal => const_eval_dead_local,
+ ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch,
+ UninhabitedEnumVariantWritten => const_eval_uninhabited_enum_variant_written,
+ Validation(e) => e.diagnostic_message(),
+ Custom(x) => (x.msg)(),
+ }
+ }
+
+ fn add_args<G: EmissionGuarantee>(
+ self,
+ handler: &Handler,
+ builder: &mut DiagnosticBuilder<'_, G>,
+ ) {
+ use UndefinedBehaviorInfo::*;
+ match self {
+ Ub(_)
+ | Unreachable
+ | DivisionByZero
+ | RemainderByZero
+ | DivisionOverflow
+ | RemainderOverflow
+ | PointerArithOverflow
+ | InvalidMeta(InvalidMetaKind::SliceTooBig)
+ | InvalidMeta(InvalidMetaKind::TooBig)
+ | InvalidUninitBytes(None)
+ | DeadLocal
+ | UninhabitedEnumVariantWritten => {}
+ BoundsCheckFailed { len, index } => {
+ builder.set_arg("len", len);
+ builder.set_arg("index", index);
+ }
+ UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
+ builder.set_arg("pointer", ptr);
+ }
+ PointerUseAfterFree(allocation) => {
+ builder.set_arg("allocation", allocation);
+ }
+ PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => {
+ builder
+ .set_arg("alloc_id", alloc_id)
+ .set_arg("alloc_size", alloc_size.bytes())
+ .set_arg("ptr_offset", ptr_offset)
+ .set_arg("ptr_size", ptr_size.bytes())
+ .set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
+ }
+ DanglingIntPointer(ptr, msg) => {
+ if ptr != 0 {
+ builder.set_arg("pointer", format!("{ptr:#x}[noalloc]"));
+ }
+
+ builder.set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
+ }
+ AlignmentCheckFailed { required, has } => {
+ builder.set_arg("required", required.bytes());
+ builder.set_arg("has", has.bytes());
+ }
+ WriteToReadOnly(alloc) | DerefFunctionPointer(alloc) | DerefVTablePointer(alloc) => {
+ builder.set_arg("allocation", alloc);
+ }
+ InvalidBool(b) => {
+ builder.set_arg("value", format!("{b:02x}"));
+ }
+ InvalidChar(c) => {
+ builder.set_arg("value", format!("{c:08x}"));
+ }
+ InvalidTag(tag) => {
+ builder.set_arg("tag", format!("{tag:x}"));
+ }
+ InvalidStr(err) => {
+ builder.set_arg("err", format!("{err}"));
+ }
+ InvalidUninitBytes(Some((alloc, info))) => {
+ builder.set_arg("alloc", alloc);
+ builder.set_arg("access", info.access);
+ builder.set_arg("uninit", info.uninit);
+ }
+ ScalarSizeMismatch(info) => {
+ builder.set_arg("target_size", info.target_size);
+ builder.set_arg("data_size", info.data_size);
+ }
+ Validation(e) => e.add_args(handler, builder),
+ Custom(custom) => {
+ (custom.add_args)(&mut |name, value| {
+ builder.set_arg(name, value);
+ });
+ }
+ }
+ }
+}
+
+impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
+ fn diagnostic_message(&self) -> DiagnosticMessage {
+ use crate::fluent_generated::*;
+ use rustc_middle::mir::interpret::ValidationErrorKind::*;
+ match self.kind {
+ PtrToUninhabited { ptr_kind: PointerKind::Box, .. } => const_eval_box_to_uninhabited,
+ PtrToUninhabited { ptr_kind: PointerKind::Ref, .. } => const_eval_ref_to_uninhabited,
+
+ PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_box_to_static,
+ PtrToStatic { ptr_kind: PointerKind::Ref } => const_eval_ref_to_static,
+
+ PtrToMut { ptr_kind: PointerKind::Box } => const_eval_box_to_mut,
+ PtrToMut { ptr_kind: PointerKind::Ref } => const_eval_ref_to_mut,
+
+ ExpectedNonPtr { .. } => const_eval_expected_non_ptr,
+ MutableRefInConst => const_eval_mutable_ref_in_const,
+ NullFnPtr => const_eval_null_fn_ptr,
+ NeverVal => const_eval_never_val,
+ NullablePtrOutOfRange { .. } => const_eval_nullable_ptr_out_of_range,
+ PtrOutOfRange { .. } => const_eval_ptr_out_of_range,
+ OutOfRange { .. } => const_eval_out_of_range,
+ UnsafeCell => const_eval_unsafe_cell,
+ UninhabitedVal { .. } => const_eval_uninhabited_val,
+ InvalidEnumTag { .. } => const_eval_invalid_enum_tag,
+ UninitEnumTag => const_eval_uninit_enum_tag,
+ UninitStr => const_eval_uninit_str,
+ Uninit { expected: ExpectedKind::Bool } => const_eval_uninit_bool,
+ Uninit { expected: ExpectedKind::Reference } => const_eval_uninit_ref,
+ Uninit { expected: ExpectedKind::Box } => const_eval_uninit_box,
+ Uninit { expected: ExpectedKind::RawPtr } => const_eval_uninit_raw_ptr,
+ Uninit { expected: ExpectedKind::InitScalar } => const_eval_uninit_init_scalar,
+ Uninit { expected: ExpectedKind::Char } => const_eval_uninit_char,
+ Uninit { expected: ExpectedKind::Float } => const_eval_uninit_float,
+ Uninit { expected: ExpectedKind::Int } => const_eval_uninit_int,
+ Uninit { expected: ExpectedKind::FnPtr } => const_eval_uninit_fn_ptr,
+ UninitVal => const_eval_uninit,
+ InvalidVTablePtr { .. } => const_eval_invalid_vtable_ptr,
+ InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => {
+ const_eval_invalid_box_slice_meta
+ }
+ InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Ref } => {
+ const_eval_invalid_ref_slice_meta
+ }
+
+ InvalidMetaTooLarge { ptr_kind: PointerKind::Box } => const_eval_invalid_box_meta,
+ InvalidMetaTooLarge { ptr_kind: PointerKind::Ref } => const_eval_invalid_ref_meta,
+ UnalignedPtr { ptr_kind: PointerKind::Ref, .. } => const_eval_unaligned_ref,
+ UnalignedPtr { ptr_kind: PointerKind::Box, .. } => const_eval_unaligned_box,
+
+ NullPtr { ptr_kind: PointerKind::Box } => const_eval_null_box,
+ NullPtr { ptr_kind: PointerKind::Ref } => const_eval_null_ref,
+ DanglingPtrNoProvenance { ptr_kind: PointerKind::Box, .. } => {
+ const_eval_dangling_box_no_provenance
+ }
+ DanglingPtrNoProvenance { ptr_kind: PointerKind::Ref, .. } => {
+ const_eval_dangling_ref_no_provenance
+ }
+ DanglingPtrOutOfBounds { ptr_kind: PointerKind::Box } => {
+ const_eval_dangling_box_out_of_bounds
+ }
+ DanglingPtrOutOfBounds { ptr_kind: PointerKind::Ref } => {
+ const_eval_dangling_ref_out_of_bounds
+ }
+ DanglingPtrUseAfterFree { ptr_kind: PointerKind::Box } => {
+ const_eval_dangling_box_use_after_free
+ }
+ DanglingPtrUseAfterFree { ptr_kind: PointerKind::Ref } => {
+ const_eval_dangling_ref_use_after_free
+ }
+ InvalidBool { .. } => const_eval_validation_invalid_bool,
+ InvalidChar { .. } => const_eval_validation_invalid_char,
+ InvalidFnPtr { .. } => const_eval_invalid_fn_ptr,
+ }
+ }
+
+ fn add_args<G: EmissionGuarantee>(self, handler: &Handler, err: &mut DiagnosticBuilder<'_, G>) {
+ use crate::fluent_generated as fluent;
+ use rustc_middle::mir::interpret::ValidationErrorKind::*;
+
+ let message = if let Some(path) = self.path {
+ handler.eagerly_translate_to_string(
+ fluent::const_eval_invalid_value_with_path,
+ [("path".into(), DiagnosticArgValue::Str(path.into()))].iter().map(|(a, b)| (a, b)),
+ )
+ } else {
+ handler.eagerly_translate_to_string(fluent::const_eval_invalid_value, [].into_iter())
+ };
+
+ err.set_arg("front_matter", message);
+
+ fn add_range_arg<G: EmissionGuarantee>(
+ r: WrappingRange,
+ max_hi: u128,
+ handler: &Handler,
+ err: &mut DiagnosticBuilder<'_, G>,
+ ) {
+ let WrappingRange { start: lo, end: hi } = r;
+ assert!(hi <= max_hi);
+ let msg = if lo > hi {
+ fluent::const_eval_range_wrapping
+ } else if lo == hi {
+ fluent::const_eval_range_singular
+ } else if lo == 0 {
+ assert!(hi < max_hi, "should not be printing if the range covers everything");
+ fluent::const_eval_range_upper
+ } else if hi == max_hi {
+ assert!(lo > 0, "should not be printing if the range covers everything");
+ fluent::const_eval_range_lower
+ } else {
+ fluent::const_eval_range
+ };
+
+ let args = [
+ ("lo".into(), DiagnosticArgValue::Str(lo.to_string().into())),
+ ("hi".into(), DiagnosticArgValue::Str(hi.to_string().into())),
+ ];
+ let args = args.iter().map(|(a, b)| (a, b));
+ let message = handler.eagerly_translate_to_string(msg, args);
+ err.set_arg("in_range", message);
+ }
+
+ match self.kind {
+ PtrToUninhabited { ty, .. } | UninhabitedVal { ty } => {
+ err.set_arg("ty", ty);
+ }
+ ExpectedNonPtr { value }
+ | InvalidEnumTag { value }
+ | InvalidVTablePtr { value }
+ | InvalidBool { value }
+ | InvalidChar { value }
+ | InvalidFnPtr { value } => {
+ err.set_arg("value", value);
+ }
+ NullablePtrOutOfRange { range, max_value } | PtrOutOfRange { range, max_value } => {
+ add_range_arg(range, max_value, handler, err)
+ }
+ OutOfRange { range, max_value, value } => {
+ err.set_arg("value", value);
+ add_range_arg(range, max_value, handler, err);
+ }
+ UnalignedPtr { required_bytes, found_bytes, .. } => {
+ err.set_arg("required_bytes", required_bytes);
+ err.set_arg("found_bytes", found_bytes);
+ }
+ DanglingPtrNoProvenance { pointer, .. } => {
+ err.set_arg("pointer", pointer);
+ }
+ NullPtr { .. }
+ | PtrToStatic { .. }
+ | PtrToMut { .. }
+ | MutableRefInConst
+ | NullFnPtr
+ | NeverVal
+ | UnsafeCell
+ | UninitEnumTag
+ | UninitStr
+ | Uninit { .. }
+ | UninitVal
+ | InvalidMetaSliceTooLarge { .. }
+ | InvalidMetaTooLarge { .. }
+ | DanglingPtrUseAfterFree { .. }
+ | DanglingPtrOutOfBounds { .. } => {}
+ }
+ }
+}
+
+impl ReportErrorExt for UnsupportedOpInfo {
+ fn diagnostic_message(&self) -> DiagnosticMessage {
+ use crate::fluent_generated::*;
+ match self {
+ UnsupportedOpInfo::Unsupported(s) => s.clone().into(),
+ UnsupportedOpInfo::PartialPointerOverwrite(_) => const_eval_partial_pointer_overwrite,
+ UnsupportedOpInfo::PartialPointerCopy(_) => const_eval_partial_pointer_copy,
+ UnsupportedOpInfo::ReadPointerAsBytes => const_eval_read_pointer_as_bytes,
+ UnsupportedOpInfo::ThreadLocalStatic(_) => const_eval_thread_local_static,
+ UnsupportedOpInfo::ReadExternStatic(_) => const_eval_read_extern_static,
+ }
+ }
+ fn add_args<G: EmissionGuarantee>(self, _: &Handler, builder: &mut DiagnosticBuilder<'_, G>) {
+ use crate::fluent_generated::*;
+
+ use UnsupportedOpInfo::*;
+ if let ReadPointerAsBytes | PartialPointerOverwrite(_) | PartialPointerCopy(_) = self {
+ builder.help(const_eval_ptr_as_bytes_1);
+ builder.help(const_eval_ptr_as_bytes_2);
+ }
+ match self {
+ Unsupported(_) | ReadPointerAsBytes => {}
+ PartialPointerOverwrite(ptr) | PartialPointerCopy(ptr) => {
+ builder.set_arg("ptr", ptr);
+ }
+ ThreadLocalStatic(did) | ReadExternStatic(did) => {
+ builder.set_arg("did", format!("{did:?}"));
+ }
+ }
+ }
+}
+
+impl<'tcx> ReportErrorExt for InterpError<'tcx> {
+ fn diagnostic_message(&self) -> DiagnosticMessage {
+ match self {
+ InterpError::UndefinedBehavior(ub) => ub.diagnostic_message(),
+ InterpError::Unsupported(e) => e.diagnostic_message(),
+ InterpError::InvalidProgram(e) => e.diagnostic_message(),
+ InterpError::ResourceExhaustion(e) => e.diagnostic_message(),
+ InterpError::MachineStop(e) => e.diagnostic_message(),
+ }
+ }
+ fn add_args<G: EmissionGuarantee>(
+ self,
+ handler: &Handler,
+ builder: &mut DiagnosticBuilder<'_, G>,
+ ) {
+ match self {
+ InterpError::UndefinedBehavior(ub) => ub.add_args(handler, builder),
+ InterpError::Unsupported(e) => e.add_args(handler, builder),
+ InterpError::InvalidProgram(e) => e.add_args(handler, builder),
+ InterpError::ResourceExhaustion(e) => e.add_args(handler, builder),
+ InterpError::MachineStop(e) => e.add_args(&mut |name, value| {
+ builder.set_arg(name, value);
+ }),
+ }
+ }
+}
+
+impl<'tcx> ReportErrorExt for InvalidProgramInfo<'tcx> {
+ fn diagnostic_message(&self) -> DiagnosticMessage {
+ use crate::fluent_generated::*;
+ match self {
+ InvalidProgramInfo::TooGeneric => const_eval_too_generic,
+ InvalidProgramInfo::AlreadyReported(_) => const_eval_already_reported,
+ InvalidProgramInfo::Layout(e) => e.diagnostic_message(),
+ InvalidProgramInfo::FnAbiAdjustForForeignAbi(_) => {
+ rustc_middle::error::middle_adjust_for_foreign_abi_error
+ }
+ InvalidProgramInfo::SizeOfUnsizedType(_) => const_eval_size_of_unsized,
+ InvalidProgramInfo::UninitUnsizedLocal => const_eval_uninit_unsized_local,
+ }
+ }
+ fn add_args<G: EmissionGuarantee>(
+ self,
+ handler: &Handler,
+ builder: &mut DiagnosticBuilder<'_, G>,
+ ) {
+ match self {
+ InvalidProgramInfo::TooGeneric
+ | InvalidProgramInfo::AlreadyReported(_)
+ | InvalidProgramInfo::UninitUnsizedLocal => {}
+ InvalidProgramInfo::Layout(e) => {
+ let diag: DiagnosticBuilder<'_, ()> = e.into_diagnostic().into_diagnostic(handler);
+ for (name, val) in diag.args() {
+ builder.set_arg(name.clone(), val.clone());
+ }
+ diag.cancel();
+ }
+ InvalidProgramInfo::FnAbiAdjustForForeignAbi(
+ AdjustForForeignAbiError::Unsupported { arch, abi },
+ ) => {
+ builder.set_arg("arch", arch);
+ builder.set_arg("abi", abi.name());
+ }
+ InvalidProgramInfo::SizeOfUnsizedType(ty) => {
+ builder.set_arg("ty", ty);
+ }
+ }
+ }
+}
+
+impl ReportErrorExt for ResourceExhaustionInfo {
+ fn diagnostic_message(&self) -> DiagnosticMessage {
+ use crate::fluent_generated::*;
+ match self {
+ ResourceExhaustionInfo::StackFrameLimitReached => const_eval_stack_frame_limit_reached,
+ ResourceExhaustionInfo::MemoryExhausted => const_eval_memory_exhausted,
+ ResourceExhaustionInfo::AddressSpaceFull => const_eval_address_space_full,
+ }
+ }
+ fn add_args<G: EmissionGuarantee>(self, _: &Handler, _: &mut DiagnosticBuilder<'_, G>) {}
+}
diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs
index 163e3f869..83a072d6f 100644
--- a/compiler/rustc_const_eval/src/interpret/cast.rs
+++ b/compiler/rustc_const_eval/src/interpret/cast.rs
@@ -4,7 +4,7 @@ use rustc_apfloat::ieee::{Double, Single};
use rustc_apfloat::{Float, FloatConvert};
use rustc_middle::mir::interpret::{InterpResult, PointerArithmetic, Scalar};
use rustc_middle::mir::CastKind;
-use rustc_middle::ty::adjustment::PointerCast;
+use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::layout::{IntegerExt, LayoutOf, TyAndLayout};
use rustc_middle::ty::{self, FloatTy, Ty, TypeAndMut};
use rustc_target::abi::Integer;
@@ -14,6 +14,8 @@ use super::{
util::ensure_monomorphic_enough, FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy,
};
+use crate::fluent_generated as fluent;
+
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
pub fn cast(
&mut self,
@@ -22,51 +24,52 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
cast_ty: Ty<'tcx>,
dest: &PlaceTy<'tcx, M::Provenance>,
) -> InterpResult<'tcx> {
- use rustc_middle::mir::CastKind::*;
// FIXME: In which cases should we trigger UB when the source is uninit?
match cast_kind {
- Pointer(PointerCast::Unsize) => {
+ CastKind::PointerCoercion(PointerCoercion::Unsize) => {
let cast_ty = self.layout_of(cast_ty)?;
self.unsize_into(src, cast_ty, dest)?;
}
- PointerExposeAddress => {
+ CastKind::PointerExposeAddress => {
let src = self.read_immediate(src)?;
let res = self.pointer_expose_address_cast(&src, cast_ty)?;
self.write_immediate(res, dest)?;
}
- PointerFromExposedAddress => {
+ CastKind::PointerFromExposedAddress => {
let src = self.read_immediate(src)?;
let res = self.pointer_from_exposed_address_cast(&src, cast_ty)?;
self.write_immediate(res, dest)?;
}
- IntToInt | IntToFloat => {
+ CastKind::IntToInt | CastKind::IntToFloat => {
let src = self.read_immediate(src)?;
let res = self.int_to_int_or_float(&src, cast_ty)?;
self.write_immediate(res, dest)?;
}
- FloatToFloat | FloatToInt => {
+ CastKind::FloatToFloat | CastKind::FloatToInt => {
let src = self.read_immediate(src)?;
let res = self.float_to_float_or_int(&src, cast_ty)?;
self.write_immediate(res, dest)?;
}
- FnPtrToPtr | PtrToPtr => {
+ CastKind::FnPtrToPtr | CastKind::PtrToPtr => {
let src = self.read_immediate(&src)?;
let res = self.ptr_to_ptr(&src, cast_ty)?;
self.write_immediate(res, dest)?;
}
- Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer) => {
+ CastKind::PointerCoercion(
+ PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer,
+ ) => {
// These are NOPs, but can be wide pointers.
let v = self.read_immediate(src)?;
self.write_immediate(*v, dest)?;
}
- Pointer(PointerCast::ReifyFnPointer) => {
+ CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer) => {
// All reifications must be monomorphic, bail out otherwise.
ensure_monomorphic_enough(*self.tcx, src.layout.ty)?;
@@ -88,7 +91,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}
- Pointer(PointerCast::UnsafeFnPointer) => {
+ CastKind::PointerCoercion(PointerCoercion::UnsafeFnPointer) => {
let src = self.read_immediate(src)?;
match cast_ty.kind() {
ty::FnPtr(_) => {
@@ -99,7 +102,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}
- Pointer(PointerCast::ClosureFnPointer(_)) => {
+ CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(_)) => {
// All reifications must be monomorphic, bail out otherwise.
ensure_monomorphic_enough(*self.tcx, src.layout.ty)?;
@@ -120,7 +123,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}
- DynStar => {
+ CastKind::DynStar => {
if let ty::Dynamic(data, _, ty::DynStar) = cast_ty.kind() {
// Initial cast from sized to dyn trait
let vtable = self.get_vtable_ptr(src.layout.ty, data.principal())?;
@@ -134,16 +137,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}
- Transmute => {
+ CastKind::Transmute => {
assert!(src.layout.is_sized());
assert!(dest.layout.is_sized());
if src.layout.size != dest.layout.size {
- throw_ub_format!(
- "transmuting from {}-byte type to {}-byte type: `{}` -> `{}`",
- src.layout.size.bytes(),
- dest.layout.size.bytes(),
- src.layout.ty,
- dest.layout.ty,
+ let src_bytes = src.layout.size.bytes();
+ let dest_bytes = dest.layout.size.bytes();
+ let src_ty = format!("{}", src.layout.ty);
+ let dest_ty = format!("{}", dest.layout.ty);
+ throw_ub_custom!(
+ fluent::const_eval_invalid_transmute,
+ src_bytes = src_bytes,
+ dest_bytes = dest_bytes,
+ src = src_ty,
+ dest = dest_ty,
);
}
@@ -363,7 +370,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let old_vptr = old_vptr.to_pointer(self)?;
let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?;
if old_trait != data_a.principal() {
- throw_ub_format!("upcast on a pointer whose vtable does not match its type");
+ throw_ub_custom!(fluent::const_eval_upcast_mismatch);
}
let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?;
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs
index 7e9457800..36606ff69 100644
--- a/compiler/rustc_const_eval/src/interpret/eval_context.rs
+++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs
@@ -1,13 +1,13 @@
use std::cell::Cell;
-use std::fmt;
-use std::mem;
+use std::{fmt, mem};
use either::{Either, Left, Right};
+use hir::CRATE_HIR_ID;
use rustc_hir::{self as hir, def_id::DefId, definitions::DefPathData};
use rustc_index::IndexVec;
use rustc_middle::mir;
-use rustc_middle::mir::interpret::{ErrorHandled, InterpError, ReportedErrorInfo};
+use rustc_middle::mir::interpret::{ErrorHandled, InterpError, InvalidMetaKind, ReportedErrorInfo};
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::layout::{
self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOf, LayoutOfHelpers,
@@ -24,6 +24,8 @@ use super::{
MemPlaceMeta, Memory, MemoryKind, Operand, Place, PlaceTy, PointerArithmetic, Provenance,
Scalar, StackPopJump,
};
+use crate::errors::{self, ErroneousConstUsed};
+use crate::fluent_generated as fluent;
use crate::util;
pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
@@ -246,6 +248,7 @@ impl<'mir, 'tcx, Prov: Provenance, Extra> Frame<'mir, 'tcx, Prov, Extra> {
}
}
+// FIXME: only used by miri, should be removed once translatable.
impl<'tcx> fmt::Display for FrameInfo<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
ty::tls::with(|tcx| {
@@ -263,6 +266,21 @@ impl<'tcx> fmt::Display for FrameInfo<'tcx> {
}
}
+impl<'tcx> FrameInfo<'tcx> {
+ pub fn as_note(&self, tcx: TyCtxt<'tcx>) -> errors::FrameNote {
+ let span = self.span;
+ if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr {
+ errors::FrameNote { where_: "closure", span, instance: String::new(), times: 0 }
+ } else {
+ let instance = format!("{}", self.instance);
+ // Note: this triggers a `good_path_bug` state, which means that if we ever get here
+ // we must emit a diagnostic. We should never display a `FrameInfo` unless we
+ // actually want to emit a warning or error to the user.
+ errors::FrameNote { where_: "instance", span, instance, times: 0 }
+ }
+ }
+}
+
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for InterpCx<'mir, 'tcx, M> {
#[inline]
fn data_layout(&self) -> &TargetDataLayout {
@@ -406,6 +424,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
#[inline(always)]
+ /// Find the first stack frame that is within the current crate, if any, otherwise return the crate's HirId
+ pub fn best_lint_scope(&self) -> hir::HirId {
+ self.stack()
+ .iter()
+ .find_map(|frame| frame.body.source.def_id().as_local())
+ .map_or(CRATE_HIR_ID, |def_id| self.tcx.hir().local_def_id_to_hir_id(def_id))
+ }
+
+ #[inline(always)]
pub(crate) fn stack(&self) -> &[Frame<'mir, 'tcx, M::Provenance, M::FrameExtra>] {
M::stack(self)
}
@@ -497,7 +524,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
.try_subst_mir_and_normalize_erasing_regions(
*self.tcx,
self.param_env,
- ty::EarlyBinder(value),
+ ty::EarlyBinder::bind(value),
)
.map_err(|_| err_inval!(TooGeneric))
}
@@ -610,7 +637,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Check if this brought us over the size limit.
if size > self.max_size_of_val() {
- throw_ub!(InvalidMeta("total size is bigger than largest supported object"));
+ throw_ub!(InvalidMeta(InvalidMetaKind::TooBig));
}
Ok(Some((size, align)))
}
@@ -628,7 +655,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let size = elem.size.bytes().saturating_mul(len); // we rely on `max_size_of_val` being smaller than `u64::MAX`.
let size = Size::from_bytes(size);
if size > self.max_size_of_val() {
- throw_ub!(InvalidMeta("slice is bigger than largest supported object"));
+ throw_ub!(InvalidMeta(InvalidMetaKind::SliceTooBig));
}
Ok(Some((size, elem.align.abi)))
}
@@ -736,7 +763,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
mir::UnwindAction::Cleanup(block) => Left(mir::Location { block, statement_index: 0 }),
mir::UnwindAction::Continue => Right(self.frame_mut().body.span),
mir::UnwindAction::Unreachable => {
- throw_ub_format!("unwinding past a stack frame that does not allow unwinding")
+ throw_ub_custom!(fluent::const_eval_unreachable_unwind);
}
mir::UnwindAction::Terminate => {
self.frame_mut().loc = Right(self.frame_mut().body.span);
@@ -775,7 +802,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
);
if unwinding && self.frame_idx() == 0 {
- throw_ub_format!("unwinding past the topmost frame of the stack");
+ throw_ub_custom!(fluent::const_eval_unwind_past_top);
}
// Copy return value. Must of course happen *before* we deallocate the locals.
@@ -863,7 +890,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// StorageLive expects the local to be dead, and marks it live.
let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
if !matches!(old, LocalValue::Dead) {
- throw_ub_format!("StorageLive on a local that was already live");
+ throw_ub_custom!(fluent::const_eval_double_storage_live);
}
Ok(())
}
@@ -906,7 +933,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
ErrorHandled::Reported(err) => {
if !err.is_tainted_by_errors() && let Some(span) = span {
// To make it easier to figure out where this error comes from, also add a note at the current location.
- self.tcx.sess.span_note_without_error(span, "erroneous constant used");
+ self.tcx.sess.emit_note(ErroneousConstUsed { span });
}
err_inval!(AlreadyReported(err))
}
diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs
index c2b82ba9b..7b11ad330 100644
--- a/compiler/rustc_const_eval/src/interpret/intern.rs
+++ b/compiler/rustc_const_eval/src/interpret/intern.rs
@@ -28,6 +28,7 @@ use super::{
ValueVisitor,
};
use crate::const_eval;
+use crate::errors::{DanglingPtrInFinal, UnsupportedUntypedPointer};
pub trait CompileTimeMachine<'mir, 'tcx, T> = Machine<
'mir,
@@ -320,10 +321,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
}
}
+/// How a constant value should be interned.
#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
pub enum InternKind {
/// The `mutability` of the static, ignoring the type which may have interior mutability.
Static(hir::Mutability),
+ /// A `const` item
Constant,
Promoted,
}
@@ -388,8 +391,7 @@ pub fn intern_const_alloc_recursive<
ecx.tcx.sess.delay_span_bug(
ecx.tcx.span,
format!(
- "error during interning should later cause validation failure: {}",
- error
+ "error during interning should later cause validation failure: {error:?}"
),
);
}
@@ -425,14 +427,16 @@ pub fn intern_const_alloc_recursive<
// immutability is so important.
alloc.mutability = Mutability::Not;
}
+ // If it's a constant, we should not have any "leftovers" as everything
+ // is tracked by const-checking.
+ // FIXME: downgrade this to a warning? It rejects some legitimate consts,
+ // such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
+ //
+ // NOTE: it looks likes this code path is only reachable when we try to intern
+ // something that cannot be promoted, which in constants means values that have
+ // drop glue, such as the example above.
InternKind::Constant => {
- // If it's a constant, we should not have any "leftovers" as everything
- // is tracked by const-checking.
- // FIXME: downgrade this to a warning? It rejects some legitimate consts,
- // such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
- ecx.tcx
- .sess
- .span_err(ecx.tcx.span, "untyped pointers are not allowed in constant");
+ ecx.tcx.sess.emit_err(UnsupportedUntypedPointer { span: ecx.tcx.span });
// For better errors later, mark the allocation as immutable.
alloc.mutability = Mutability::Not;
}
@@ -447,10 +451,7 @@ pub fn intern_const_alloc_recursive<
} else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) {
// Codegen does not like dangling pointers, and generally `tcx` assumes that
// all allocations referenced anywhere actually exist. So, make sure we error here.
- let reported = ecx
- .tcx
- .sess
- .span_err(ecx.tcx.span, "encountered dangling pointer in final constant");
+ let reported = ecx.tcx.sess.emit_err(DanglingPtrInFinal { span: ecx.tcx.span });
return Err(reported);
} else if ecx.tcx.try_get_global_alloc(alloc_id).is_none() {
// We have hit an `AllocId` that is neither in local or global memory and isn't
diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
index a77c699c2..ed64a7655 100644
--- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs
+++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
@@ -22,6 +22,8 @@ use super::{
Pointer,
};
+use crate::fluent_generated as fluent;
+
mod caller_location;
fn numeric_intrinsic<Prov>(name: Symbol, bits: u128, kind: Primitive) -> Scalar<Prov> {
@@ -70,12 +72,12 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>(
}
sym::pref_align_of => {
// Correctly handles non-monomorphic calls, so there is no need for ensure_monomorphic_enough.
- let layout = tcx.layout_of(param_env.and(tp_ty)).map_err(|e| err_inval!(Layout(e)))?;
+ let layout = tcx.layout_of(param_env.and(tp_ty)).map_err(|e| err_inval!(Layout(*e)))?;
ConstValue::from_target_usize(layout.align.pref.bytes(), &tcx)
}
sym::type_id => {
ensure_monomorphic_enough(tcx, tp_ty)?;
- ConstValue::from_u64(tcx.type_id_hash(tp_ty).as_u64())
+ ConstValue::from_u128(tcx.type_id_hash(tp_ty).as_u128())
}
sym::variant_count => match tp_ty.kind() {
// Correctly handles non-monomorphic calls, so there is no need for ensure_monomorphic_enough.
@@ -167,8 +169,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let ty = match intrinsic_name {
sym::pref_align_of | sym::variant_count => self.tcx.types.usize,
sym::needs_drop => self.tcx.types.bool,
- sym::type_id => self.tcx.types.u64,
- sym::type_name => self.tcx.mk_static_str(),
+ sym::type_id => self.tcx.types.u128,
+ sym::type_name => Ty::new_static_str(self.tcx.tcx),
_ => bug!(),
};
let val = self.ctfe_query(None, |tcx| {
@@ -198,15 +200,18 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
ty
),
};
- let (nonzero, intrinsic_name) = match intrinsic_name {
+ let (nonzero, actual_intrinsic_name) = match intrinsic_name {
sym::cttz_nonzero => (true, sym::cttz),
sym::ctlz_nonzero => (true, sym::ctlz),
other => (false, other),
};
if nonzero && bits == 0 {
- throw_ub_format!("`{}_nonzero` called on 0", intrinsic_name);
+ throw_ub_custom!(
+ fluent::const_eval_call_nonzero_intrinsic,
+ name = intrinsic_name,
+ );
}
- let out_val = numeric_intrinsic(intrinsic_name, bits, kind);
+ let out_val = numeric_intrinsic(actual_intrinsic_name, bits, kind);
self.write_scalar(out_val, dest)?;
}
sym::saturating_add | sym::saturating_sub => {
@@ -229,37 +234,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let r = self.read_immediate(&args[1])?;
self.exact_div(&l, &r, dest)?;
}
- sym::unchecked_shl
- | sym::unchecked_shr
- | sym::unchecked_add
- | sym::unchecked_sub
- | sym::unchecked_mul
- | sym::unchecked_div
- | sym::unchecked_rem => {
- let l = self.read_immediate(&args[0])?;
- let r = self.read_immediate(&args[1])?;
- let bin_op = match intrinsic_name {
- sym::unchecked_shl => BinOp::Shl,
- sym::unchecked_shr => BinOp::Shr,
- sym::unchecked_add => BinOp::Add,
- sym::unchecked_sub => BinOp::Sub,
- sym::unchecked_mul => BinOp::Mul,
- sym::unchecked_div => BinOp::Div,
- sym::unchecked_rem => BinOp::Rem,
- _ => bug!(),
- };
- let (val, overflowed, _ty) = self.overflowing_binary_op(bin_op, &l, &r)?;
- if overflowed {
- let layout = self.layout_of(substs.type_at(0))?;
- let r_val = r.to_scalar().to_bits(layout.size)?;
- if let sym::unchecked_shl | sym::unchecked_shr = intrinsic_name {
- throw_ub_format!("overflowing shift by {} in `{}`", r_val, intrinsic_name);
- } else {
- throw_ub_format!("overflow executing `{}`", intrinsic_name);
- }
- }
- self.write_scalar(val, dest)?;
- }
sym::rotate_left | sym::rotate_right => {
// rotate_left: (X << (S % BW)) | (X >> ((BW - S) % BW))
// rotate_right: (X << ((BW - S) % BW)) | (X >> (S % BW))
@@ -314,17 +288,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
(Err(_), _) | (_, Err(_)) => {
// We managed to find a valid allocation for one pointer, but not the other.
// That means they are definitely not pointing to the same allocation.
- throw_ub_format!(
- "`{}` called on pointers into different allocations",
- intrinsic_name
+ throw_ub_custom!(
+ fluent::const_eval_different_allocations,
+ name = intrinsic_name,
);
}
(Ok((a_alloc_id, a_offset, _)), Ok((b_alloc_id, b_offset, _))) => {
// Found allocation for both. They must be into the same allocation.
if a_alloc_id != b_alloc_id {
- throw_ub_format!(
- "`{}` called on pointers into different allocations",
- intrinsic_name
+ throw_ub_custom!(
+ fluent::const_eval_different_allocations,
+ name = intrinsic_name,
);
}
// Use these offsets for distance calculation.
@@ -344,11 +318,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
if overflowed {
// a < b
if intrinsic_name == sym::ptr_offset_from_unsigned {
- throw_ub_format!(
- "`{}` called when first pointer has smaller offset than second: {} < {}",
- intrinsic_name,
- a_offset,
- b_offset,
+ throw_ub_custom!(
+ fluent::const_eval_unsigned_offset_from_overflow,
+ a_offset = a_offset,
+ b_offset = b_offset,
);
}
// The signed form of the intrinsic allows this. If we interpret the
@@ -356,9 +329,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// seems *positive*, they were more than isize::MAX apart.
let dist = val.to_target_isize(self)?;
if dist >= 0 {
- throw_ub_format!(
- "`{}` called when first pointer is too far before second",
- intrinsic_name
+ throw_ub_custom!(
+ fluent::const_eval_offset_from_underflow,
+ name = intrinsic_name,
);
}
dist
@@ -368,9 +341,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// If converting to isize produced a *negative* result, we had an overflow
// because they were more than isize::MAX apart.
if dist < 0 {
- throw_ub_format!(
- "`{}` called when first pointer is too far ahead of second",
- intrinsic_name
+ throw_ub_custom!(
+ fluent::const_eval_offset_from_overflow,
+ name = intrinsic_name,
);
}
dist
@@ -513,7 +486,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let op = self.eval_operand(op, None)?;
let cond = self.read_scalar(&op)?.to_bool()?;
if !cond {
- throw_ub_format!("`assume` called with `false`");
+ throw_ub_custom!(fluent::const_eval_assume_false);
}
Ok(())
}
@@ -542,7 +515,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let (res, overflow, _ty) = self.overflowing_binary_op(BinOp::Rem, &a, &b)?;
assert!(!overflow); // All overflow is UB, so this should never return on overflow.
if res.assert_bits(a.layout.size) != 0 {
- throw_ub_format!("exact_div: {} cannot be divided by {} without remainder", a, b)
+ throw_ub_custom!(
+ fluent::const_eval_exact_div_has_remainder,
+ a = format!("{a}"),
+ b = format!("{b}")
+ )
}
// `Rem` says this is all right, so we can let `Div` do its job.
self.binop_ignore_overflow(BinOp::Div, &a, &b, dest)
@@ -638,9 +615,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
// but no actual allocation can be big enough for the difference to be noticeable.
let size = size.checked_mul(count, self).ok_or_else(|| {
- err_ub_format!(
- "overflow computing total size of `{}`",
- if nonoverlapping { "copy_nonoverlapping" } else { "copy" }
+ err_ub_custom!(
+ fluent::const_eval_size_overflow,
+ name = if nonoverlapping { "copy_nonoverlapping" } else { "copy" }
)
})?;
@@ -664,10 +641,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
// but no actual allocation can be big enough for the difference to be noticeable.
- let len = layout
- .size
- .checked_mul(count, self)
- .ok_or_else(|| err_ub_format!("overflow computing total size of `write_bytes`"))?;
+ let len = layout.size.checked_mul(count, self).ok_or_else(|| {
+ err_ub_custom!(fluent::const_eval_size_overflow, name = "write_bytes")
+ })?;
let bytes = std::iter::repeat(byte).take(len.bytes_usize());
self.write_bytes_ptr(dst, bytes)
@@ -691,7 +667,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
return Ok(&[]);
};
if alloc_ref.has_provenance() {
- throw_ub_format!("`raw_eq` on bytes with provenance");
+ throw_ub_custom!(fluent::const_eval_raw_eq_with_provenance);
}
alloc_ref.get_bytes_strip_provenance()
};
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index d5b6a581a..1125d8d1f 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -19,6 +19,7 @@ use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TyCtxt};
use rustc_target::abi::{Align, HasDataLayout, Size};
use crate::const_eval::CheckAlignment;
+use crate::fluent_generated as fluent;
use super::{
alloc_range, AllocBytes, AllocId, AllocMap, AllocRange, Allocation, CheckInAllocMsg,
@@ -200,7 +201,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
align: Align,
kind: MemoryKind<M::MemoryKind>,
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
- let alloc = Allocation::uninit(size, align, M::PANIC_ON_ALLOC_FAIL)?;
+ let alloc = if M::PANIC_ON_ALLOC_FAIL {
+ Allocation::uninit(size, align)
+ } else {
+ Allocation::try_uninit(size, align)?
+ };
self.allocate_raw_ptr(alloc, kind)
}
@@ -242,9 +247,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr)?;
if offset.bytes() != 0 {
- throw_ub_format!(
- "reallocating {:?} which does not point to the beginning of an object",
- ptr
+ throw_ub_custom!(
+ fluent::const_eval_realloc_or_alloc_with_offset,
+ ptr = format!("{ptr:?}"),
+ kind = "realloc"
);
}
@@ -280,9 +286,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
trace!("deallocating: {alloc_id:?}");
if offset.bytes() != 0 {
- throw_ub_format!(
- "deallocating {:?} which does not point to the beginning of an object",
- ptr
+ throw_ub_custom!(
+ fluent::const_eval_realloc_or_alloc_with_offset,
+ ptr = format!("{ptr:?}"),
+ kind = "dealloc",
);
}
@@ -290,13 +297,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Deallocating global memory -- always an error
return Err(match self.tcx.try_get_global_alloc(alloc_id) {
Some(GlobalAlloc::Function(..)) => {
- err_ub_format!("deallocating {alloc_id:?}, which is a function")
+ err_ub_custom!(
+ fluent::const_eval_invalid_dealloc,
+ alloc_id = alloc_id,
+ kind = "fn",
+ )
}
Some(GlobalAlloc::VTable(..)) => {
- err_ub_format!("deallocating {alloc_id:?}, which is a vtable")
+ err_ub_custom!(
+ fluent::const_eval_invalid_dealloc,
+ alloc_id = alloc_id,
+ kind = "vtable",
+ )
}
Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => {
- err_ub_format!("deallocating {alloc_id:?}, which is static memory")
+ err_ub_custom!(
+ fluent::const_eval_invalid_dealloc,
+ alloc_id = alloc_id,
+ kind = "static_mem"
+ )
}
None => err_ub!(PointerUseAfterFree(alloc_id)),
}
@@ -304,21 +323,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
};
if alloc.mutability.is_not() {
- throw_ub_format!("deallocating immutable allocation {alloc_id:?}");
+ throw_ub_custom!(fluent::const_eval_dealloc_immutable, alloc = alloc_id,);
}
if alloc_kind != kind {
- throw_ub_format!(
- "deallocating {alloc_id:?}, which is {alloc_kind} memory, using {kind} deallocation operation"
+ throw_ub_custom!(
+ fluent::const_eval_dealloc_kind_mismatch,
+ alloc = alloc_id,
+ alloc_kind = format!("{alloc_kind}"),
+ kind = format!("{kind}"),
);
}
if let Some((size, align)) = old_size_and_align {
if size != alloc.size() || align != alloc.align {
- throw_ub_format!(
- "incorrect layout on deallocation: {alloc_id:?} has size {} and alignment {}, but gave size {} and alignment {}",
- alloc.size().bytes(),
- alloc.align.bytes(),
- size.bytes(),
- align.bytes(),
+ throw_ub_custom!(
+ fluent::const_eval_dealloc_incorrect_layout,
+ alloc = alloc_id,
+ size = alloc.size().bytes(),
+ align = alloc.align.bytes(),
+ size_found = size.bytes(),
+ align_found = align.bytes(),
)
}
}
@@ -1166,7 +1189,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
if (src_offset <= dest_offset && src_offset + size > dest_offset)
|| (dest_offset <= src_offset && dest_offset + size > src_offset)
{
- throw_ub_format!("copy_nonoverlapping called on overlapping ranges")
+ throw_ub_custom!(fluent::const_eval_copy_nonoverlapping_overlapping);
}
}
diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs
index e30af1655..5f89d652f 100644
--- a/compiler/rustc_const_eval/src/interpret/operand.rs
+++ b/compiler/rustc_const_eval/src/interpret/operand.rs
@@ -106,7 +106,7 @@ impl<Prov: Provenance> std::fmt::Display for ImmTy<'_, Prov> {
// Just print the ptr value. `pretty_print_const_scalar_ptr` would also try to
// print what is points to, which would fail since it has no access to the local
// memory.
- cx.pretty_print_const_pointer(ptr, ty, true)
+ cx.pretty_print_const_pointer(ptr, ty)
}
}
}
@@ -633,7 +633,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}
- pub(super) fn const_val_to_op(
+ pub(crate) fn const_val_to_op(
&self,
val_val: ConstValue<'tcx>,
ty: Ty<'tcx>,
diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs
index 7186148da..e04764636 100644
--- a/compiler/rustc_const_eval/src/interpret/operator.rs
+++ b/compiler/rustc_const_eval/src/interpret/operator.rs
@@ -3,10 +3,13 @@ use rustc_middle::mir;
use rustc_middle::mir::interpret::{InterpResult, Scalar};
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_middle::ty::{self, FloatTy, Ty};
+use rustc_span::symbol::sym;
use rustc_target::abi::Abi;
use super::{ImmTy, Immediate, InterpCx, Machine, PlaceTy};
+use crate::fluent_generated as fluent;
+
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// Applies the binary operation `op` to the two operands and writes a tuple of the result
/// and a boolean signifying the potential overflow to the destination.
@@ -19,7 +22,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
) -> InterpResult<'tcx> {
let (val, overflowed, ty) = self.overflowing_binary_op(op, &left, &right)?;
debug_assert_eq!(
- self.tcx.mk_tup(&[ty, self.tcx.types.bool]),
+ Ty::new_tup(self.tcx.tcx, &[ty, self.tcx.types.bool]),
dest.layout.ty,
"type mismatch for result of {:?}",
op,
@@ -139,8 +142,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
) -> InterpResult<'tcx, (Scalar<M::Provenance>, bool, Ty<'tcx>)> {
use rustc_middle::mir::BinOp::*;
+ let throw_ub_on_overflow = match bin_op {
+ AddUnchecked => Some(sym::unchecked_add),
+ SubUnchecked => Some(sym::unchecked_sub),
+ MulUnchecked => Some(sym::unchecked_mul),
+ ShlUnchecked => Some(sym::unchecked_shl),
+ ShrUnchecked => Some(sym::unchecked_shr),
+ _ => None,
+ };
+
// Shift ops can have an RHS with a different numeric type.
- if bin_op == Shl || bin_op == Shr {
+ if matches!(bin_op, Shl | ShlUnchecked | Shr | ShrUnchecked) {
let size = u128::from(left_layout.size.bits());
// Even if `r` is signed, we treat it as if it was unsigned (i.e., we use its
// zero-extended form). This matches the codegen backend:
@@ -155,6 +167,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// integers are maximally 128bits wide, so negative shifts *always* overflow and we have
// consistent results for the same value represented at different bit widths.
assert!(size <= 128);
+ let original_r = r;
let overflow = r >= size;
// The shift offset is implicitly masked to the type size, to make sure this operation
// is always defined. This is the one MIR operator that does *not* directly map to a
@@ -166,19 +179,28 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let result = if left_layout.abi.is_signed() {
let l = self.sign_extend(l, left_layout) as i128;
let result = match bin_op {
- Shl => l.checked_shl(r).unwrap(),
- Shr => l.checked_shr(r).unwrap(),
+ Shl | ShlUnchecked => l.checked_shl(r).unwrap(),
+ Shr | ShrUnchecked => l.checked_shr(r).unwrap(),
_ => bug!(),
};
result as u128
} else {
match bin_op {
- Shl => l.checked_shl(r).unwrap(),
- Shr => l.checked_shr(r).unwrap(),
+ Shl | ShlUnchecked => l.checked_shl(r).unwrap(),
+ Shr | ShrUnchecked => l.checked_shr(r).unwrap(),
_ => bug!(),
}
};
let truncated = self.truncate(result, left_layout);
+
+ if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
+ throw_ub_custom!(
+ fluent::const_eval_overflow_shift,
+ val = original_r,
+ name = intrinsic_name
+ );
+ }
+
return Ok((Scalar::from_uint(truncated, left_layout.size), overflow, left_layout.ty));
}
@@ -216,9 +238,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
Rem if r == 0 => throw_ub!(RemainderByZero),
Div => Some(i128::overflowing_div),
Rem => Some(i128::overflowing_rem),
- Add => Some(i128::overflowing_add),
- Sub => Some(i128::overflowing_sub),
- Mul => Some(i128::overflowing_mul),
+ Add | AddUnchecked => Some(i128::overflowing_add),
+ Sub | SubUnchecked => Some(i128::overflowing_sub),
+ Mul | MulUnchecked => Some(i128::overflowing_mul),
_ => None,
};
if let Some(op) = op {
@@ -242,11 +264,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// If that truncation loses any information, we have an overflow.
let result = result as u128;
let truncated = self.truncate(result, left_layout);
- return Ok((
- Scalar::from_uint(truncated, size),
- oflo || self.sign_extend(truncated, left_layout) != result,
- left_layout.ty,
- ));
+ let overflow = oflo || self.sign_extend(truncated, left_layout) != result;
+ if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
+ throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name);
+ }
+ return Ok((Scalar::from_uint(truncated, size), overflow, left_layout.ty));
}
}
@@ -263,12 +285,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
BitAnd => (Scalar::from_uint(l & r, size), left_layout.ty),
BitXor => (Scalar::from_uint(l ^ r, size), left_layout.ty),
- Add | Sub | Mul | Rem | Div => {
+ Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Rem | Div => {
assert!(!left_layout.abi.is_signed());
let op: fn(u128, u128) -> (u128, bool) = match bin_op {
- Add => u128::overflowing_add,
- Sub => u128::overflowing_sub,
- Mul => u128::overflowing_mul,
+ Add | AddUnchecked => u128::overflowing_add,
+ Sub | SubUnchecked => u128::overflowing_sub,
+ Mul | MulUnchecked => u128::overflowing_mul,
Div if r == 0 => throw_ub!(DivisionByZero),
Rem if r == 0 => throw_ub!(RemainderByZero),
Div => u128::overflowing_div,
@@ -279,11 +301,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Truncate to target type.
// If that truncation loses any information, we have an overflow.
let truncated = self.truncate(result, left_layout);
- return Ok((
- Scalar::from_uint(truncated, size),
- oflo || truncated != result,
- left_layout.ty,
- ));
+ let overflow = oflo || truncated != result;
+ if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
+ throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name);
+ }
+ return Ok((Scalar::from_uint(truncated, size), overflow, left_layout.ty));
}
_ => span_bug!(
diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs
index 2a31a59ad..ca1106384 100644
--- a/compiler/rustc_const_eval/src/interpret/place.rs
+++ b/compiler/rustc_const_eval/src/interpret/place.rs
@@ -9,6 +9,7 @@ use rustc_index::IndexSlice;
use rustc_middle::mir;
use rustc_middle::ty;
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
+use rustc_middle::ty::Ty;
use rustc_target::abi::{self, Abi, Align, FieldIdx, HasDataLayout, Size, FIRST_VARIANT};
use super::{
@@ -395,7 +396,7 @@ where
// (Transmuting is okay since this is an in-memory place. We also double-check the size
// stays the same.)
let (len, e_ty) = mplace.layout.ty.simd_size_and_type(*self.tcx);
- let array = self.tcx.mk_array(e_ty, len);
+ let array = Ty::new_array(self.tcx.tcx, e_ty, len);
let layout = self.layout_of(array)?;
assert_eq!(layout.size, mplace.layout.size);
Ok((MPlaceTy { layout, ..*mplace }, len))
@@ -699,8 +700,13 @@ where
assert_eq!(src.layout.size, dest.layout.size);
}
+ // Setting `nonoverlapping` here only has an effect when we don't hit the fast-path above,
+ // but that should at least match what LLVM does where `memcpy` is also only used when the
+ // type does not have Scalar/ScalarPair layout.
+ // (Or as the `Assign` docs put it, assignments "not producing primitives" must be
+ // non-overlapping.)
self.mem_copy(
- src.ptr, src.align, dest.ptr, dest.align, dest_size, /*nonoverlapping*/ false,
+ src.ptr, src.align, dest.ptr, dest.align, dest_size, /*nonoverlapping*/ true,
)
}
@@ -775,7 +781,8 @@ where
let meta = Scalar::from_target_usize(u64::try_from(str.len()).unwrap(), self);
let mplace = MemPlace { ptr: ptr.into(), meta: MemPlaceMeta::Meta(meta) };
- let ty = self.tcx.mk_ref(
+ let ty = Ty::new_ref(
+ self.tcx.tcx,
self.tcx.lifetimes.re_static,
ty::TypeAndMut { ty: self.tcx.types.str_, mutbl },
);
diff --git a/compiler/rustc_const_eval/src/interpret/projection.rs b/compiler/rustc_const_eval/src/interpret/projection.rs
index 91da930db..d7d31fe18 100644
--- a/compiler/rustc_const_eval/src/interpret/projection.rs
+++ b/compiler/rustc_const_eval/src/interpret/projection.rs
@@ -12,6 +12,7 @@ use either::{Left, Right};
use rustc_middle::mir;
use rustc_middle::ty;
use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::Ty;
use rustc_target::abi::{self, Abi, VariantIdx};
use super::{
@@ -317,7 +318,9 @@ where
let (meta, ty) = match base.layout.ty.kind() {
// It is not nice to match on the type, but that seems to be the only way to
// implement this.
- ty::Array(inner, _) => (MemPlaceMeta::None, self.tcx.mk_array(*inner, inner_len)),
+ ty::Array(inner, _) => {
+ (MemPlaceMeta::None, Ty::new_array(self.tcx.tcx, *inner, inner_len))
+ }
ty::Slice(..) => {
let len = Scalar::from_target_usize(inner_len, self);
(MemPlaceMeta::Meta(len), base.layout.ty)
diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs
index 1e60a1e72..619da8abb 100644
--- a/compiler/rustc_const_eval/src/interpret/step.rs
+++ b/compiler/rustc_const_eval/src/interpret/step.rs
@@ -9,27 +9,7 @@ use rustc_middle::mir::interpret::{InterpResult, Scalar};
use rustc_middle::ty::layout::LayoutOf;
use super::{ImmTy, InterpCx, Machine};
-
-/// Classify whether an operator is "left-homogeneous", i.e., the LHS has the
-/// same type as the result.
-#[inline]
-fn binop_left_homogeneous(op: mir::BinOp) -> bool {
- use rustc_middle::mir::BinOp::*;
- match op {
- Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Offset | Shl | Shr => true,
- Eq | Ne | Lt | Le | Gt | Ge => false,
- }
-}
-/// Classify whether an operator is "right-homogeneous", i.e., the RHS has the
-/// same type as the LHS.
-#[inline]
-fn binop_right_homogeneous(op: mir::BinOp) -> bool {
- use rustc_middle::mir::BinOp::*;
- match op {
- Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge => true,
- Offset | Shl | Shr => false,
- }
-}
+use crate::util;
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// Returns `true` as long as there are more things to do.
@@ -179,9 +159,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
BinaryOp(bin_op, box (ref left, ref right)) => {
- let layout = binop_left_homogeneous(bin_op).then_some(dest.layout);
+ let layout = util::binop_left_homogeneous(bin_op).then_some(dest.layout);
let left = self.read_immediate(&self.eval_operand(left, layout)?)?;
- let layout = binop_right_homogeneous(bin_op).then_some(left.layout);
+ let layout = util::binop_right_homogeneous(bin_op).then_some(left.layout);
let right = self.read_immediate(&self.eval_operand(right, layout)?)?;
self.binop_ignore_overflow(bin_op, &left, &right, &dest)?;
}
@@ -189,7 +169,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
CheckedBinaryOp(bin_op, box (ref left, ref right)) => {
// Due to the extra boolean in the result, we can never reuse the `dest.layout`.
let left = self.read_immediate(&self.eval_operand(left, None)?)?;
- let layout = binop_right_homogeneous(bin_op).then_some(left.layout);
+ let layout = util::binop_right_homogeneous(bin_op).then_some(left.layout);
let right = self.read_immediate(&self.eval_operand(right, layout)?)?;
self.binop_with_overflow(bin_op, &left, &right, &dest)?;
}
diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs
index 586e8f063..15823a597 100644
--- a/compiler/rustc_const_eval/src/interpret/terminator.rs
+++ b/compiler/rustc_const_eval/src/interpret/terminator.rs
@@ -15,6 +15,7 @@ use super::{
FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, Operand,
PlaceTy, Scalar, StackPopCleanup,
};
+use crate::fluent_generated as fluent;
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
pub(super) fn eval_terminator(
@@ -61,7 +62,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
destination,
target,
unwind,
- from_hir_call: _,
+ call_source: _,
fn_span: _,
} => {
let old_stack = self.frame_idx();
@@ -172,7 +173,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
InlineAsm { template, ref operands, options, destination, .. } => {
M::eval_inline_asm(self, template, operands, options)?;
if options.contains(InlineAsmOptions::NORETURN) {
- throw_ub_format!("returned from noreturn inline assembly");
+ throw_ub_custom!(fluent::const_eval_noreturn_asm_returned);
}
self.go_to_block(
destination
@@ -288,15 +289,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
return Ok(());
}
// Find next caller arg.
- let (caller_arg, caller_abi) = caller_args.next().ok_or_else(|| {
- err_ub_format!("calling a function with fewer arguments than it requires")
- })?;
+ let Some((caller_arg, caller_abi)) = caller_args.next() else {
+ throw_ub_custom!(fluent::const_eval_not_enough_caller_args);
+ };
// Now, check
if !Self::check_argument_compat(caller_abi, callee_abi) {
- throw_ub_format!(
- "calling a function with argument of type {:?} passing data of type {:?}",
- callee_arg.layout.ty,
- caller_arg.layout.ty
+ let callee_ty = format!("{}", callee_arg.layout.ty);
+ let caller_ty = format!("{}", caller_arg.layout.ty);
+ throw_ub_custom!(
+ fluent::const_eval_incompatible_types,
+ callee_ty = callee_ty,
+ caller_ty = caller_ty,
)
}
// Special handling for unsized parameters.
@@ -398,10 +401,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
if M::enforce_abi(self) {
if caller_fn_abi.conv != callee_fn_abi.conv {
- throw_ub_format!(
- "calling a function with calling convention {:?} using calling convention {:?}",
- callee_fn_abi.conv,
- caller_fn_abi.conv
+ throw_ub_custom!(
+ fluent::const_eval_incompatible_calling_conventions,
+ callee_conv = format!("{:?}", callee_fn_abi.conv),
+ caller_conv = format!("{:?}", caller_fn_abi.conv),
)
}
}
@@ -508,15 +511,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
"mismatch between callee ABI and callee body arguments"
);
if caller_args.next().is_some() {
- throw_ub_format!("calling a function with more arguments than it expected")
+ throw_ub_custom!(fluent::const_eval_too_many_caller_args);
}
// Don't forget to check the return type!
if !Self::check_argument_compat(&caller_fn_abi.ret, &callee_fn_abi.ret) {
- throw_ub_format!(
- "calling a function with return type {:?} passing \
- return place of type {:?}",
- callee_fn_abi.ret.layout.ty,
- caller_fn_abi.ret.layout.ty,
+ let callee_ty = format!("{}", callee_fn_abi.ret.layout.ty);
+ let caller_ty = format!("{}", caller_fn_abi.ret.layout.ty);
+ throw_ub_custom!(
+ fluent::const_eval_incompatible_return_types,
+ callee_ty = callee_ty,
+ caller_ty = caller_ty,
)
}
};
@@ -587,9 +591,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let (recv, vptr) = self.unpack_dyn_star(&receiver_place.into())?;
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
if dyn_trait != data.principal() {
- throw_ub_format!(
- "`dyn*` call on a pointer whose vtable does not match its type"
- );
+ throw_ub_custom!(fluent::const_eval_dyn_star_call_vtable_mismatch);
}
let recv = recv.assert_mem_place(); // we passed an MPlaceTy to `unpack_dyn_star` so we definitely still have one
@@ -609,9 +611,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
if dyn_trait != data.principal() {
- throw_ub_format!(
- "`dyn` call on a pointer whose vtable does not match its type"
- );
+ throw_ub_custom!(fluent::const_eval_dyn_call_vtable_mismatch);
}
// It might be surprising that we use a pointer as the receiver even if this
@@ -623,7 +623,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Now determine the actual method to call. We can do that in two different ways and
// compare them to ensure everything fits.
let Some(ty::VtblEntry::Method(fn_inst)) = self.get_vtable_entries(vptr)?.get(idx).copied() else {
- throw_ub_format!("`dyn` call trying to call something that is not a method")
+ // FIXME(fee1-dead) these could be variants of the UB info enum instead of this
+ throw_ub_custom!(fluent::const_eval_dyn_call_not_a_method);
};
trace!("Virtual call dispatches to {fn_inst:#?}");
if cfg!(debug_assertions) {
@@ -649,7 +650,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Adjust receiver argument. Layout can be any (thin) ptr.
args[0] = ImmTy::from_immediate(
Scalar::from_maybe_pointer(adjusted_receiver, self).into(),
- self.layout_of(self.tcx.mk_mut_ptr(dyn_ty))?,
+ self.layout_of(Ty::new_mut_ptr(self.tcx.tcx, dyn_ty))?,
)
.into();
trace!("Patched receiver operand to {:#?}", args[0]);
@@ -702,7 +703,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let arg = ImmTy::from_immediate(
place.to_ref(self),
- self.layout_of(self.tcx.mk_mut_ptr(place.layout.ty))?,
+ self.layout_of(Ty::new_mut_ptr(self.tcx.tcx, place.layout.ty))?,
);
let ret = MPlaceTy::fake_alloc_zst(self.layout_of(self.tcx.types.unit)?);
diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs
index 01b772899..21c655988 100644
--- a/compiler/rustc_const_eval/src/interpret/validity.rs
+++ b/compiler/rustc_const_eval/src/interpret/validity.rs
@@ -4,7 +4,7 @@
//! That's useful because it means other passes (e.g. promotion) can rely on `const`s
//! to be const-safe.
-use std::fmt::{Display, Write};
+use std::fmt::Write;
use std::num::NonZeroUsize;
use either::{Left, Right};
@@ -12,7 +12,10 @@ use either::{Left, Right};
use rustc_ast::Mutability;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
-use rustc_middle::mir::interpret::InterpError;
+use rustc_middle::mir::interpret::{
+ ExpectedKind, InterpError, InvalidMetaKind, PointerKind, ValidationErrorInfo,
+ ValidationErrorKind, ValidationErrorKind::*,
+};
use rustc_middle::ty;
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_span::symbol::{sym, Symbol};
@@ -30,14 +33,7 @@ use super::{
};
macro_rules! throw_validation_failure {
- ($where:expr, { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )?) => {{
- let mut msg = String::new();
- msg.push_str("encountered ");
- write!(&mut msg, $($what_fmt)*).unwrap();
- $(
- msg.push_str(", but expected ");
- write!(&mut msg, $($expected_fmt)*).unwrap();
- )?
+ ($where:expr, $kind: expr) => {{
let where_ = &$where;
let path = if !where_.is_empty() {
let mut path = String::new();
@@ -46,7 +42,8 @@ macro_rules! throw_validation_failure {
} else {
None
};
- throw_ub!(ValidationFailure { path, msg })
+
+ throw_ub!(Validation(ValidationErrorInfo { path, kind: $kind }))
}};
}
@@ -82,22 +79,22 @@ macro_rules! throw_validation_failure {
///
macro_rules! try_validation {
($e:expr, $where:expr,
- $( $( $p:pat_param )|+ => { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )? ),+ $(,)?
+ $( $( $p:pat_param )|+ => $kind: expr ),+ $(,)?
) => {{
match $e {
Ok(x) => x,
// We catch the error and turn it into a validation failure. We are okay with
// allocation here as this can only slow down builds that fail anyway.
- Err(e) => match e.kind() {
+ Err(e) => match e.into_parts() {
$(
- InterpError::UndefinedBehavior($($p)|+) =>
+ (InterpError::UndefinedBehavior($($p)|+), _) =>
throw_validation_failure!(
$where,
- { $( $what_fmt )* } $( expected { $( $expected_fmt )* } )?
+ $kind
)
),+,
#[allow(unreachable_patterns)]
- _ => Err::<!, _>(e)?,
+ (e, rest) => Err::<!, _>($crate::interpret::InterpErrorInfo::from_parts(e, rest))?,
}
}
}};
@@ -160,6 +157,7 @@ impl<T: Copy + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH>
}
}
+// FIXME make this translatable as well?
/// Format a path
fn write_path(out: &mut String, path: &[PathElem]) {
use self::PathElem::*;
@@ -185,26 +183,6 @@ fn write_path(out: &mut String, path: &[PathElem]) {
}
}
-// Formats such that a sentence like "expected something {}" to mean
-// "expected something <in the given range>" makes sense.
-fn wrapping_range_format(r: WrappingRange, max_hi: u128) -> String {
- let WrappingRange { start: lo, end: hi } = r;
- assert!(hi <= max_hi);
- if lo > hi {
- format!("less or equal to {}, or greater or equal to {}", hi, lo)
- } else if lo == hi {
- format!("equal to {}", lo)
- } else if lo == 0 {
- assert!(hi < max_hi, "should not be printing if the range covers everything");
- format!("less or equal to {}", hi)
- } else if hi == max_hi {
- assert!(lo > 0, "should not be printing if the range covers everything");
- format!("greater or equal to {}", lo)
- } else {
- format!("in the range {:?}", r)
- }
-}
-
struct ValidityVisitor<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> {
/// The `path` may be pushed to, but the part that is present when a function
/// starts must not be changed! `visit_fields` and `visit_array` rely on
@@ -311,19 +289,19 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
fn read_immediate(
&self,
op: &OpTy<'tcx, M::Provenance>,
- expected: impl Display,
+ expected: ExpectedKind,
) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> {
Ok(try_validation!(
self.ecx.read_immediate(op),
self.path,
- InvalidUninitBytes(None) => { "uninitialized memory" } expected { "{expected}" }
+ InvalidUninitBytes(None) => Uninit { expected }
))
}
fn read_scalar(
&self,
op: &OpTy<'tcx, M::Provenance>,
- expected: impl Display,
+ expected: ExpectedKind,
) -> InterpResult<'tcx, Scalar<M::Provenance>> {
Ok(self.read_immediate(op, expected)?.to_scalar())
}
@@ -342,8 +320,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
self.ecx.get_ptr_vtable(vtable),
self.path,
DanglingIntPointer(..) |
- InvalidVTablePointer(..) =>
- { "{vtable}" } expected { "a vtable pointer" },
+ InvalidVTablePointer(..) => InvalidVTablePtr { value: format!("{vtable}") }
);
// FIXME: check if the type/trait match what ty::Dynamic says?
}
@@ -366,10 +343,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
fn check_safe_pointer(
&mut self,
value: &OpTy<'tcx, M::Provenance>,
- kind: &str,
+ ptr_kind: PointerKind,
) -> InterpResult<'tcx> {
- let place =
- self.ecx.ref_to_mplace(&self.read_immediate(value, format_args!("a {kind}"))?)?;
+ let place = self.ecx.ref_to_mplace(&self.read_immediate(value, ptr_kind.into())?)?;
// Handle wide pointers.
// Check metadata early, for better diagnostics
if place.layout.is_unsized() {
@@ -379,7 +355,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
let size_and_align = try_validation!(
self.ecx.size_and_align_of_mplace(&place),
self.path,
- InvalidMeta(msg) => { "invalid {} metadata: {}", kind, msg },
+ InvalidMeta(msg) => match msg {
+ InvalidMetaKind::SliceTooBig => InvalidMetaSliceTooLarge { ptr_kind },
+ InvalidMetaKind::TooBig => InvalidMetaTooLarge { ptr_kind },
+ }
);
let (size, align) = size_and_align
// for the purpose of validity, consider foreign types to have
@@ -395,31 +374,30 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message
),
self.path,
- AlignmentCheckFailed { required, has } =>
- {
- "an unaligned {kind} (required {} byte alignment but found {})",
- required.bytes(),
- has.bytes(),
- },
- DanglingIntPointer(0, _) =>
- { "a null {kind}" },
- DanglingIntPointer(i, _) =>
- {
- "a dangling {kind} ({pointer} has no provenance)",
- pointer = Pointer::<Option<AllocId>>::from_addr_invalid(*i),
- },
- PointerOutOfBounds { .. } =>
- { "a dangling {kind} (going beyond the bounds of its allocation)" },
+ AlignmentCheckFailed { required, has } => UnalignedPtr {
+ ptr_kind,
+ required_bytes: required.bytes(),
+ found_bytes: has.bytes()
+ },
+ DanglingIntPointer(0, _) => NullPtr { ptr_kind },
+ DanglingIntPointer(i, _) => DanglingPtrNoProvenance {
+ ptr_kind,
+ // FIXME this says "null pointer" when null but we need translate
+ pointer: format!("{}", Pointer::<Option<AllocId>>::from_addr_invalid(i))
+ },
+ PointerOutOfBounds { .. } => DanglingPtrOutOfBounds {
+ ptr_kind
+ },
// This cannot happen during const-eval (because interning already detects
// dangling pointers), but it can happen in Miri.
- PointerUseAfterFree(..) =>
- { "a dangling {kind} (use-after-free)" },
+ PointerUseAfterFree(..) => DanglingPtrUseAfterFree {
+ ptr_kind,
+ },
);
// Do not allow pointers to uninhabited types.
if place.layout.abi.is_uninhabited() {
- throw_validation_failure!(self.path,
- { "a {kind} pointing to uninhabited type {}", place.layout.ty }
- )
+ let ty = place.layout.ty;
+ throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
}
// Recursive checking
if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
@@ -441,9 +419,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
// this check is so important.
// This check is reachable when the const just referenced the static,
// but never read it (so we never entered `before_access_global`).
- throw_validation_failure!(self.path,
- { "a {} pointing to a static variable in a constant", kind }
- );
+ throw_validation_failure!(self.path, PtrToStatic { ptr_kind });
}
// We skip recursively checking other statics. These statics must be sound by
// themselves, and the only way to get broken statics here is by using
@@ -464,9 +440,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
// This should be unreachable, but if someone manages to copy a pointer
// out of a `static`, then that pointer might point to mutable memory,
// and we would catch that here.
- throw_validation_failure!(self.path,
- { "a {} pointing to mutable memory in a constant", kind }
- );
+ throw_validation_failure!(self.path, PtrToMut { ptr_kind });
}
}
// Nothing to check for these.
@@ -496,22 +470,24 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
let ty = value.layout.ty;
match ty.kind() {
ty::Bool => {
- let value = self.read_scalar(value, "a boolean")?;
+ let value = self.read_scalar(value, ExpectedKind::Bool)?;
try_validation!(
value.to_bool(),
self.path,
- InvalidBool(..) =>
- { "{:x}", value } expected { "a boolean" },
+ InvalidBool(..) => ValidationErrorKind::InvalidBool {
+ value: format!("{value:x}"),
+ }
);
Ok(true)
}
ty::Char => {
- let value = self.read_scalar(value, "a unicode scalar value")?;
+ let value = self.read_scalar(value, ExpectedKind::Char)?;
try_validation!(
value.to_char(),
self.path,
- InvalidChar(..) =>
- { "{:x}", value } expected { "a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)" },
+ InvalidChar(..) => ValidationErrorKind::InvalidChar {
+ value: format!("{value:x}"),
+ }
);
Ok(true)
}
@@ -521,16 +497,17 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
let value = self.read_scalar(
value,
if matches!(ty.kind(), ty::Float(..)) {
- "a floating point number"
+ ExpectedKind::Float
} else {
- "an integer"
+ ExpectedKind::Int
},
)?;
// As a special exception we *do* match on a `Scalar` here, since we truly want
// to know its underlying representation (and *not* cast it to an integer).
if matches!(value, Scalar::Ptr(..)) {
- throw_validation_failure!(self.path,
- { "{:x}", value } expected { "plain (non-pointer) bytes" }
+ throw_validation_failure!(
+ self.path,
+ ExpectedNonPtr { value: format!("{value:x}") }
)
}
Ok(true)
@@ -540,7 +517,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
// actually enforce the strict rules for raw pointers (mostly because
// that lets us re-use `ref_to_mplace`).
let place =
- self.ecx.ref_to_mplace(&self.read_immediate(value, "a raw pointer")?)?;
+ self.ecx.ref_to_mplace(&self.read_immediate(value, ExpectedKind::RawPtr)?)?;
if place.layout.is_unsized() {
self.check_wide_ptr_meta(place.meta, place.layout)?;
}
@@ -554,14 +531,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
// a ZST).
let layout = self.ecx.layout_of(*ty)?;
if !layout.is_zst() {
- throw_validation_failure!(self.path, { "mutable reference in a `const`" });
+ throw_validation_failure!(self.path, MutableRefInConst);
}
}
- self.check_safe_pointer(value, "reference")?;
+ self.check_safe_pointer(value, PointerKind::Ref)?;
Ok(true)
}
ty::FnPtr(_sig) => {
- let value = self.read_scalar(value, "a function pointer")?;
+ let value = self.read_scalar(value, ExpectedKind::FnPtr)?;
// If we check references recursively, also check that this points to a function.
if let Some(_) = self.ref_tracking {
@@ -570,19 +547,20 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
self.ecx.get_ptr_fn(ptr),
self.path,
DanglingIntPointer(..) |
- InvalidFunctionPointer(..) =>
- { "{ptr}" } expected { "a function pointer" },
+ InvalidFunctionPointer(..) => InvalidFnPtr {
+ value: format!("{ptr}"),
+ },
);
// FIXME: Check if the signature matches
} else {
// Otherwise (for standalone Miri), we have to still check it to be non-null.
if self.ecx.scalar_may_be_null(value)? {
- throw_validation_failure!(self.path, { "a null function pointer" });
+ throw_validation_failure!(self.path, NullFnPtr);
}
}
Ok(true)
}
- ty::Never => throw_validation_failure!(self.path, { "a value of the never type `!`" }),
+ ty::Never => throw_validation_failure!(self.path, NeverVal),
ty::Foreign(..) | ty::FnDef(..) => {
// Nothing to check.
Ok(true)
@@ -629,12 +607,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
if start == 1 && end == max_value {
// Only null is the niche. So make sure the ptr is NOT null.
if self.ecx.scalar_may_be_null(scalar)? {
- throw_validation_failure!(self.path,
- { "a potentially null pointer" }
- expected {
- "something that cannot possibly fail to be {}",
- wrapping_range_format(valid_range, max_value)
- }
+ throw_validation_failure!(
+ self.path,
+ NullablePtrOutOfRange { range: valid_range, max_value }
)
} else {
return Ok(());
@@ -645,12 +620,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
} else {
// Conservatively, we reject, because the pointer *could* have a bad
// value.
- throw_validation_failure!(self.path,
- { "a pointer" }
- expected {
- "something that cannot possibly fail to be {}",
- wrapping_range_format(valid_range, max_value)
- }
+ throw_validation_failure!(
+ self.path,
+ PtrOutOfRange { range: valid_range, max_value }
)
}
}
@@ -659,9 +631,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
if valid_range.contains(bits) {
Ok(())
} else {
- throw_validation_failure!(self.path,
- { "{}", bits }
- expected { "something {}", wrapping_range_format(valid_range, max_value) }
+ throw_validation_failure!(
+ self.path,
+ OutOfRange { value: format!("{bits}"), range: valid_range, max_value }
)
}
}
@@ -685,10 +657,11 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
Ok(try_validation!(
this.ecx.read_discriminant(op),
this.path,
- InvalidTag(val) =>
- { "{:x}", val } expected { "a valid enum tag" },
- InvalidUninitBytes(None) =>
- { "uninitialized bytes" } expected { "a valid enum tag" },
+ InvalidTag(val) => InvalidEnumTag {
+ value: format!("{val:x}"),
+ },
+
+ InvalidUninitBytes(None) => UninitEnumTag,
)
.1)
})
@@ -730,7 +703,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
// Special check preventing `UnsafeCell` inside unions in the inner part of constants.
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) {
if !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) {
- throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
+ throw_validation_failure!(self.path, UnsafeCell);
}
}
Ok(())
@@ -738,7 +711,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
#[inline]
fn visit_box(&mut self, op: &OpTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
- self.check_safe_pointer(op, "box")?;
+ self.check_safe_pointer(op, PointerKind::Box)?;
Ok(())
}
@@ -756,7 +729,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. }))
&& def.is_unsafe_cell()
{
- throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
+ throw_validation_failure!(self.path, UnsafeCell);
}
}
@@ -775,14 +748,13 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
// MyNewtype and then the scalar in there).
match op.layout.abi {
Abi::Uninhabited => {
- throw_validation_failure!(self.path,
- { "a value of uninhabited type {:?}", op.layout.ty }
- );
+ let ty = op.layout.ty;
+ throw_validation_failure!(self.path, UninhabitedVal { ty });
}
Abi::Scalar(scalar_layout) => {
if !scalar_layout.is_uninit_valid() {
// There is something to check here.
- let scalar = self.read_scalar(op, "initialized scalar value")?;
+ let scalar = self.read_scalar(op, ExpectedKind::InitScalar)?;
self.visit_scalar(scalar, scalar_layout)?;
}
}
@@ -792,7 +764,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
// the other must be init.
if !a_layout.is_uninit_valid() && !b_layout.is_uninit_valid() {
let (a, b) =
- self.read_immediate(op, "initialized scalar value")?.to_scalar_pair();
+ self.read_immediate(op, ExpectedKind::InitScalar)?.to_scalar_pair();
self.visit_scalar(a, a_layout)?;
self.visit_scalar(b, b_layout)?;
}
@@ -822,7 +794,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
try_validation!(
self.ecx.read_bytes_ptr_strip_provenance(mplace.ptr, Size::from_bytes(len)),
self.path,
- InvalidUninitBytes(..) => { "uninitialized data in `str`" },
+ InvalidUninitBytes(..) => { UninitStr },
);
}
ty::Array(tys, ..) | ty::Slice(tys)
@@ -852,7 +824,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
Left(mplace) => mplace,
Right(imm) => match *imm {
Immediate::Uninit =>
- throw_validation_failure!(self.path, { "uninitialized bytes" }),
+ throw_validation_failure!(self.path, UninitVal),
Immediate::Scalar(..) | Immediate::ScalarPair(..) =>
bug!("arrays/slices can never have Scalar/ScalarPair layout"),
}
@@ -888,7 +860,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
.unwrap();
self.path.push(PathElem::ArrayElem(i));
- throw_validation_failure!(self.path, { "uninitialized bytes" })
+ throw_validation_failure!(self.path, UninitVal)
}
// Propagate upwards (that will also check for unexpected errors).
@@ -929,12 +901,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
match visitor.visit_value(&op) {
Ok(()) => Ok(()),
// Pass through validation failures.
- Err(err) if matches!(err.kind(), err_ub!(ValidationFailure { .. })) => Err(err),
+ Err(err) if matches!(err.kind(), err_ub!(Validation { .. })) => Err(err),
// Complain about any other kind of UB error -- those are bad because we'd like to
// report them in a way that shows *where* in the value the issue lies.
Err(err) if matches!(err.kind(), InterpError::UndefinedBehavior(_)) => {
- err.print_backtrace();
- bug!("Unexpected Undefined Behavior error during validation: {}", err);
+ let (err, backtrace) = err.into_parts();
+ backtrace.print_backtrace();
+ bug!("Unexpected Undefined Behavior error during validation: {err:?}");
}
// Pass through everything else.
Err(err) => Err(err),
diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs
index c36282d5e..c126f749b 100644
--- a/compiler/rustc_const_eval/src/lib.rs
+++ b/compiler/rustc_const_eval/src/lib.rs
@@ -4,6 +4,7 @@ Rust MIR: a lowered representation of Rust.
*/
+#![deny(rustc::untranslatable_diagnostic)]
#![feature(assert_matches)]
#![feature(box_patterns)]
#![feature(decl_macro)]
@@ -33,6 +34,8 @@ pub mod interpret;
pub mod transform;
pub mod util;
+pub use errors::ReportErrorExt;
+
use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage};
use rustc_fluent_macro::fluent_messages;
use rustc_middle::query::Providers;
@@ -49,17 +52,11 @@ pub fn provide(providers: &mut Providers) {
let (param_env, raw) = param_env_and_value.into_parts();
const_eval::eval_to_valtree(tcx, param_env, raw)
};
- providers.try_destructure_mir_constant = |tcx, param_env_and_value| {
- let (param_env, value) = param_env_and_value.into_parts();
- const_eval::try_destructure_mir_constant(tcx, param_env, value).ok()
- };
+ providers.try_destructure_mir_constant_for_diagnostics =
+ |tcx, (cv, ty)| const_eval::try_destructure_mir_constant_for_diagnostics(tcx, cv, ty);
providers.valtree_to_const_val = |tcx, (ty, valtree)| {
const_eval::valtree_to_const_value(tcx, ty::ParamEnv::empty().and(ty), valtree)
};
- providers.deref_mir_constant = |tcx, param_env_and_value| {
- let (param_env, value) = param_env_and_value.into_parts();
- const_eval::deref_mir_constant(tcx, param_env, value)
- };
providers.check_validity_requirement = |tcx, (init_kind, param_env_and_ty)| {
util::check_validity_requirement(tcx, init_kind, param_env_and_ty)
};
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
index 57d939747..14540e8df 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
@@ -9,8 +9,8 @@ use rustc_infer::traits::{ImplSource, Obligation, ObligationCause};
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts};
-use rustc_middle::ty::{self, adjustment::PointerCast, Instance, InstanceDef, Ty, TyCtxt};
-use rustc_middle::ty::{Binder, TraitRef, TypeVisitableExt};
+use rustc_middle::ty::{self, adjustment::PointerCoercion, Instance, InstanceDef, Ty, TyCtxt};
+use rustc_middle::ty::{TraitRef, TypeVisitableExt};
use rustc_mir_dataflow::{self, Analysis};
use rustc_span::{sym, Span, Symbol};
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
@@ -412,7 +412,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
BorrowKind::Shallow => {
PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow)
}
- BorrowKind::Unique => PlaceContext::MutatingUse(MutatingUseContext::Borrow),
BorrowKind::Mut { .. } => {
PlaceContext::MutatingUse(MutatingUseContext::Borrow)
}
@@ -457,7 +456,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
}
}
- Rvalue::Ref(_, kind @ (BorrowKind::Mut { .. } | BorrowKind::Unique), place) => {
+ Rvalue::Ref(_, BorrowKind::Mut { .. }, place) => {
let ty = place.ty(self.body, self.tcx).ty;
let is_allowed = match ty.kind() {
// Inside a `static mut`, `&mut [...]` is allowed.
@@ -478,11 +477,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
};
if !is_allowed {
- if let BorrowKind::Mut { .. } = kind {
- self.check_mut_borrow(place.local, hir::BorrowKind::Ref)
- } else {
- self.check_op(ops::CellBorrow);
- }
+ self.check_mut_borrow(place.local, hir::BorrowKind::Ref)
}
}
@@ -526,12 +521,12 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
}
Rvalue::Cast(
- CastKind::Pointer(
- PointerCast::MutToConstPointer
- | PointerCast::ArrayToPointer
- | PointerCast::UnsafeFnPointer
- | PointerCast::ClosureFnPointer(_)
- | PointerCast::ReifyFnPointer,
+ CastKind::PointerCoercion(
+ PointerCoercion::MutToConstPointer
+ | PointerCoercion::ArrayToPointer
+ | PointerCoercion::UnsafeFnPointer
+ | PointerCoercion::ClosureFnPointer(_)
+ | PointerCoercion::ReifyFnPointer,
),
_,
_,
@@ -539,7 +534,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
// These are all okay; they only change the type, not the data.
}
- Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), _, _) => {
+ Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, _) => {
// Unsizing is implemented for CTFE.
}
@@ -617,30 +612,28 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
}
fn visit_projection_elem(
&mut self,
- place_local: Local,
- proj_base: &[PlaceElem<'tcx>],
+ place_ref: PlaceRef<'tcx>,
elem: PlaceElem<'tcx>,
context: PlaceContext,
location: Location,
) {
trace!(
- "visit_projection_elem: place_local={:?} proj_base={:?} elem={:?} \
+ "visit_projection_elem: place_ref={:?} elem={:?} \
context={:?} location={:?}",
- place_local,
- proj_base,
+ place_ref,
elem,
context,
location,
);
- self.super_projection_elem(place_local, proj_base, elem, context, location);
+ self.super_projection_elem(place_ref, elem, context, location);
match elem {
ProjectionElem::Deref => {
- let base_ty = Place::ty_from(place_local, proj_base, self.body, self.tcx).ty;
+ let base_ty = place_ref.ty(self.body, self.tcx).ty;
if base_ty.is_unsafe_ptr() {
- if proj_base.is_empty() {
- let decl = &self.body.local_decls[place_local];
+ if place_ref.projection.is_empty() {
+ let decl = &self.body.local_decls[place_ref.local];
if let LocalInfo::StaticRef { def_id, .. } = *decl.local_info() {
let span = decl.source_info.span;
self.check_static(def_id, span);
@@ -702,7 +695,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
self.super_terminator(terminator, location);
match &terminator.kind {
- TerminatorKind::Call { func, args, fn_span, from_hir_call, .. } => {
+ TerminatorKind::Call { func, args, fn_span, call_source, .. } => {
let ConstCx { tcx, body, param_env, .. } = *self.ccx;
let caller = self.def_id();
@@ -755,17 +748,16 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ call_source: *call_source,
feature: Some(sym::const_trait_impl),
});
return;
}
let trait_ref = TraitRef::from_method(tcx, trait_id, substs);
- let poly_trait_pred =
- Binder::dummy(trait_ref).with_constness(ty::BoundConstness::ConstIfConst);
+ let trait_ref = trait_ref.with_constness(ty::BoundConstness::ConstIfConst);
let obligation =
- Obligation::new(tcx, ObligationCause::dummy(), param_env, poly_trait_pred);
+ Obligation::new(tcx, ObligationCause::dummy(), param_env, trait_ref);
let implsrc = {
let infcx = tcx.infer_ctxt().build();
@@ -781,14 +773,23 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
);
return;
}
- Ok(Some(ImplSource::Closure(data))) => {
- if !tcx.is_const_fn_raw(data.closure_def_id) {
+ // Closure: Fn{Once|Mut}
+ Ok(Some(ImplSource::Builtin(_)))
+ if trait_ref.self_ty().is_closure()
+ && tcx.fn_trait_kind_from_def_id(trait_id).is_some() =>
+ {
+ let ty::Closure(closure_def_id, substs) =
+ *trait_ref.self_ty().kind()
+ else {
+ unreachable!()
+ };
+ if !tcx.is_const_fn_raw(closure_def_id) {
self.check_op(ops::FnCallNonConst {
caller,
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ call_source: *call_source,
feature: None,
});
@@ -814,7 +815,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ call_source: *call_source,
feature: None,
});
return;
@@ -838,7 +839,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
tcx,
ObligationCause::dummy_with_span(*fn_span),
param_env,
- poly_trait_pred,
+ trait_ref,
);
// improve diagnostics by showing what failed. Our requirements are stricter this time
@@ -857,7 +858,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ call_source: *call_source,
feature: None,
});
return;
@@ -917,7 +918,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
callee,
substs,
span: *fn_span,
- from_hir_call: *from_hir_call,
+ call_source: *call_source,
feature: None,
});
return;
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
index 21f3c2c89..4eb278252 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
@@ -2,18 +2,16 @@
use hir::def_id::LocalDefId;
use hir::{ConstContext, LangItem};
-use rustc_errors::{
- error_code, struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed,
-};
+use rustc_errors::{error_code, DiagnosticBuilder, ErrorGuaranteed};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::{ImplSource, Obligation, ObligationCause};
-use rustc_middle::mir;
+use rustc_middle::mir::{self, CallSource};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
+use rustc_middle::ty::TraitRef;
use rustc_middle::ty::{suggest_constraining_type_param, Adt, Closure, FnDef, FnPtr, Param, Ty};
-use rustc_middle::ty::{Binder, TraitRef};
use rustc_middle::util::{call_kind, CallDesugaringKind, CallKind};
use rustc_session::parse::feature_err;
use rustc_span::symbol::sym;
@@ -102,7 +100,7 @@ pub struct FnCallNonConst<'tcx> {
pub callee: DefId,
pub substs: SubstsRef<'tcx>,
pub span: Span,
- pub from_hir_call: bool,
+ pub call_source: CallSource,
pub feature: Option<Symbol>,
}
@@ -112,7 +110,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
ccx: &ConstCx<'_, 'tcx>,
_: Span,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
- let FnCallNonConst { caller, callee, substs, span, from_hir_call, feature } = *self;
+ let FnCallNonConst { caller, callee, substs, span, call_source, feature } = *self;
let ConstCx { tcx, param_env, .. } = *ccx;
let diag_trait = |err, self_ty: Ty<'_>, trait_id| {
@@ -139,12 +137,8 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
}
}
Adt(..) => {
- let obligation = Obligation::new(
- tcx,
- ObligationCause::dummy(),
- param_env,
- Binder::dummy(trait_ref),
- );
+ let obligation =
+ Obligation::new(tcx, ObligationCause::dummy(), param_env, trait_ref);
let infcx = tcx.infer_ctxt().build();
let mut selcx = SelectionContext::new(&infcx);
@@ -152,40 +146,45 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
if let Ok(Some(ImplSource::UserDefined(data))) = implsrc {
let span = tcx.def_span(data.impl_def_id);
- err.span_note(span, "impl defined here, but it is not `const`");
+ err.subdiagnostic(errors::NonConstImplNote { span });
}
}
_ => {}
}
};
- let call_kind = call_kind(tcx, ccx.param_env, callee, substs, span, from_hir_call, None);
+ let call_kind =
+ call_kind(tcx, ccx.param_env, callee, substs, span, call_source.from_hir_call(), None);
debug!(?call_kind);
let mut err = match call_kind {
CallKind::Normal { desugaring: Some((kind, self_ty)), .. } => {
macro_rules! error {
- ($fmt:literal) => {
- struct_span_err!(tcx.sess, span, E0015, $fmt, self_ty, ccx.const_kind())
+ ($err:ident) => {
+ tcx.sess.create_err(errors::$err {
+ span,
+ ty: self_ty,
+ kind: ccx.const_kind(),
+ })
};
}
let mut err = match kind {
CallDesugaringKind::ForLoopIntoIter => {
- error!("cannot convert `{}` into an iterator in {}s")
+ error!(NonConstForLoopIntoIter)
}
CallDesugaringKind::QuestionBranch => {
- error!("`?` cannot determine the branch of `{}` in {}s")
+ error!(NonConstQuestionBranch)
}
CallDesugaringKind::QuestionFromResidual => {
- error!("`?` cannot convert from residual of `{}` in {}s")
+ error!(NonConstQuestionFromResidual)
}
CallDesugaringKind::TryBlockFromOutput => {
- error!("`try` block cannot convert `{}` to the result in {}s")
+ error!(NonConstTryBlockFromOutput)
}
CallDesugaringKind::Await => {
- error!("cannot convert `{}` into a future in {}s")
+ error!(NonConstAwait)
}
};
@@ -193,108 +192,101 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
err
}
CallKind::FnCall { fn_trait_id, self_ty } => {
- let mut err = struct_span_err!(
- tcx.sess,
- span,
- E0015,
- "cannot call non-const closure in {}s",
- ccx.const_kind(),
- );
-
- match self_ty.kind() {
+ let note = match self_ty.kind() {
FnDef(def_id, ..) => {
let span = tcx.def_span(*def_id);
if ccx.tcx.is_const_fn_raw(*def_id) {
span_bug!(span, "calling const FnDef errored when it shouldn't");
}
- err.span_note(span, "function defined here, but it is not `const`");
- }
- FnPtr(..) => {
- err.note(format!(
- "function pointers need an RFC before allowed to be called in {}s",
- ccx.const_kind()
- ));
+ Some(errors::NonConstClosureNote::FnDef { span })
}
- Closure(..) => {
- err.note(format!(
- "closures need an RFC before allowed to be called in {}s",
- ccx.const_kind()
- ));
- }
- _ => {}
- }
+ FnPtr(..) => Some(errors::NonConstClosureNote::FnPtr),
+ Closure(..) => Some(errors::NonConstClosureNote::Closure),
+ _ => None,
+ };
+
+ let mut err = tcx.sess.create_err(errors::NonConstClosure {
+ span,
+ kind: ccx.const_kind(),
+ note,
+ });
diag_trait(&mut err, self_ty, fn_trait_id);
err
}
CallKind::Operator { trait_id, self_ty, .. } => {
- let mut err = struct_span_err!(
- tcx.sess,
- span,
- E0015,
- "cannot call non-const operator in {}s",
- ccx.const_kind()
- );
-
- if Some(trait_id) == ccx.tcx.lang_items().eq_trait() {
- match (substs[0].unpack(), substs[1].unpack()) {
- (GenericArgKind::Type(self_ty), GenericArgKind::Type(rhs_ty))
- if self_ty == rhs_ty
- && self_ty.is_ref()
- && self_ty.peel_refs().is_primitive() =>
- {
- let mut num_refs = 0;
- let mut tmp_ty = self_ty;
- while let rustc_middle::ty::Ref(_, inner_ty, _) = tmp_ty.kind() {
- num_refs += 1;
- tmp_ty = *inner_ty;
- }
- let deref = "*".repeat(num_refs);
-
- if let Ok(call_str) = ccx.tcx.sess.source_map().span_to_snippet(span) {
- if let Some(eq_idx) = call_str.find("==") {
- if let Some(rhs_idx) =
- call_str[(eq_idx + 2)..].find(|c: char| !c.is_whitespace())
- {
- let rhs_pos =
- span.lo() + BytePos::from_usize(eq_idx + 2 + rhs_idx);
- let rhs_span = span.with_lo(rhs_pos).with_hi(rhs_pos);
- err.multipart_suggestion(
- "consider dereferencing here",
- vec![
- (span.shrink_to_lo(), deref.clone()),
- (rhs_span, deref),
- ],
- Applicability::MachineApplicable,
- );
+ let mut err = if let CallSource::MatchCmp = call_source {
+ tcx.sess.create_err(errors::NonConstMatchEq {
+ span,
+ kind: ccx.const_kind(),
+ ty: self_ty,
+ })
+ } else {
+ let mut sugg = None;
+
+ if Some(trait_id) == ccx.tcx.lang_items().eq_trait() {
+ match (substs[0].unpack(), substs[1].unpack()) {
+ (GenericArgKind::Type(self_ty), GenericArgKind::Type(rhs_ty))
+ if self_ty == rhs_ty
+ && self_ty.is_ref()
+ && self_ty.peel_refs().is_primitive() =>
+ {
+ let mut num_refs = 0;
+ let mut tmp_ty = self_ty;
+ while let rustc_middle::ty::Ref(_, inner_ty, _) = tmp_ty.kind() {
+ num_refs += 1;
+ tmp_ty = *inner_ty;
+ }
+ let deref = "*".repeat(num_refs);
+
+ if let Ok(call_str) =
+ ccx.tcx.sess.source_map().span_to_snippet(span)
+ {
+ if let Some(eq_idx) = call_str.find("==") {
+ if let Some(rhs_idx) = call_str[(eq_idx + 2)..]
+ .find(|c: char| !c.is_whitespace())
+ {
+ let rhs_pos = span.lo()
+ + BytePos::from_usize(eq_idx + 2 + rhs_idx);
+ let rhs_span = span.with_lo(rhs_pos).with_hi(rhs_pos);
+ sugg = Some(errors::ConsiderDereferencing {
+ deref,
+ span: span.shrink_to_lo(),
+ rhs_span,
+ });
+ }
}
}
}
+ _ => {}
}
- _ => {}
}
- }
+ tcx.sess.create_err(errors::NonConstOperator {
+ span,
+ kind: ccx.const_kind(),
+ sugg,
+ })
+ };
diag_trait(&mut err, self_ty, trait_id);
err
}
CallKind::DerefCoercion { deref_target, deref_target_ty, self_ty } => {
- let mut err = struct_span_err!(
- tcx.sess,
- span,
- E0015,
- "cannot perform deref coercion on `{}` in {}s",
- self_ty,
- ccx.const_kind()
- );
-
- err.note(format!("attempting to deref into `{}`", deref_target_ty));
-
// Check first whether the source is accessible (issue #87060)
- if tcx.sess.source_map().is_span_accessible(deref_target) {
- err.span_note(deref_target, "deref defined here");
- }
+ let target = if tcx.sess.source_map().is_span_accessible(deref_target) {
+ Some(deref_target)
+ } else {
+ None
+ };
+
+ let mut err = tcx.sess.create_err(errors::NonConstDerefCoercion {
+ span,
+ ty: self_ty,
+ kind: ccx.const_kind(),
+ target_ty: deref_target_ty,
+ deref_target: target,
+ });
diag_trait(&mut err, self_ty, tcx.require_lang_item(LangItem::Deref, Some(span)));
err
@@ -432,21 +424,12 @@ impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> {
ccx: &ConstCx<'_, 'tcx>,
span: Span,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
- let mut err = struct_span_err!(
- ccx.tcx.sess,
- span,
- E0493,
- "destructor of `{}` cannot be evaluated at compile-time",
- self.dropped_ty,
- );
- err.span_label(
+ ccx.tcx.sess.create_err(errors::LiveDrop {
span,
- format!("the destructor for this type cannot be evaluated in {}s", ccx.const_kind()),
- );
- if let Some(span) = self.dropped_at {
- err.span_label(span, "value is dropped here");
- }
- err
+ dropped_ty: self.dropped_ty,
+ kind: ccx.const_kind(),
+ dropped_at: self.dropped_at,
+ })
}
}
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs
index 1da205790..015a4aa94 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs
@@ -172,7 +172,7 @@ impl Qualif for NeedsNonConstDrop {
if !matches!(
impl_src,
- ImplSource::ConstDestruct(_) | ImplSource::Param(_, ty::BoundConstness::ConstIfConst)
+ ImplSource::Builtin(_) | ImplSource::Param(_, ty::BoundConstness::ConstIfConst)
) {
// If our const destruct candidate is not ConstDestruct or implied by the param env,
// then it's bad
@@ -223,7 +223,7 @@ impl Qualif for CustomEq {
def: AdtDef<'tcx>,
substs: SubstsRef<'tcx>,
) -> bool {
- let ty = cx.tcx.mk_adt(def, substs);
+ let ty = Ty::new_adt(cx.tcx, def, substs);
!ty.is_structural_eq_shallow(cx.tcx)
}
}
@@ -344,15 +344,18 @@ where
};
// Check the qualifs of the value of `const` items.
- // FIXME(valtrees): check whether const qualifs should behave the same
- // way for type and mir constants.
let uneval = match constant.literal {
ConstantKind::Ty(ct)
- if matches!(ct.kind(), ty::ConstKind::Param(_) | ty::ConstKind::Error(_)) =>
+ if matches!(
+ ct.kind(),
+ ty::ConstKind::Param(_) | ty::ConstKind::Error(_) | ty::ConstKind::Value(_)
+ ) =>
{
None
}
- ConstantKind::Ty(c) => bug!("expected ConstKind::Param here, found {:?}", c),
+ ConstantKind::Ty(c) => {
+ bug!("expected ConstKind::Param or ConstKind::Value here, found {:?}", c)
+ }
ConstantKind::Unevaluated(uv, _) => Some(uv),
ConstantKind::Val(..) => None,
};
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs b/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs
index 78c74e189..3a869f7f5 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs
@@ -103,7 +103,7 @@ where
fn ref_allows_mutation(&self, kind: mir::BorrowKind, place: mir::Place<'tcx>) -> bool {
match kind {
mir::BorrowKind::Mut { .. } => true,
- mir::BorrowKind::Shared | mir::BorrowKind::Shallow | mir::BorrowKind::Unique => {
+ mir::BorrowKind::Shared | mir::BorrowKind::Shallow => {
self.shared_borrow_allows_mutation(place)
}
}
@@ -337,7 +337,7 @@ where
Q: Qualif,
{
fn apply_statement_effect(
- &self,
+ &mut self,
state: &mut Self::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
@@ -346,7 +346,7 @@ where
}
fn apply_terminator_effect(
- &self,
+ &mut self,
state: &mut Self::Domain,
terminator: &mir::Terminator<'tcx>,
location: Location,
@@ -355,7 +355,7 @@ where
}
fn apply_call_return_effect(
- &self,
+ &mut self,
state: &mut Self::Domain,
block: BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,
diff --git a/compiler/rustc_const_eval/src/transform/promote_consts.rs b/compiler/rustc_const_eval/src/transform/promote_consts.rs
index 0e2d9ee8f..1b39a76e4 100644
--- a/compiler/rustc_const_eval/src/transform/promote_consts.rs
+++ b/compiler/rustc_const_eval/src/transform/promote_consts.rs
@@ -14,11 +14,10 @@
use rustc_hir as hir;
use rustc_middle::mir;
-use rustc_middle::mir::traversal::ReversePostorderIter;
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
use rustc_middle::ty::subst::InternalSubsts;
-use rustc_middle::ty::{self, List, TyCtxt, TypeVisitableExt};
+use rustc_middle::ty::{self, List, Ty, TyCtxt, TypeVisitableExt};
use rustc_span::Span;
use rustc_index::{Idx, IndexSlice, IndexVec};
@@ -53,9 +52,8 @@ impl<'tcx> MirPass<'tcx> for PromoteTemps<'tcx> {
return;
}
- let mut rpo = traversal::reverse_postorder(body);
let ccx = ConstCx::new(tcx, body);
- let (mut temps, all_candidates) = collect_temps_and_candidates(&ccx, &mut rpo);
+ let (mut temps, all_candidates) = collect_temps_and_candidates(&ccx);
let promotable_candidates = validate_candidates(&ccx, &mut temps, &all_candidates);
@@ -166,14 +164,13 @@ impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> {
pub fn collect_temps_and_candidates<'tcx>(
ccx: &ConstCx<'_, 'tcx>,
- rpo: &mut ReversePostorderIter<'_, 'tcx>,
) -> (IndexVec<Local, TempState>, Vec<Candidate>) {
let mut collector = Collector {
temps: IndexVec::from_elem(TempState::Undefined, &ccx.body.local_decls),
candidates: vec![],
ccx,
};
- for (bb, data) in rpo {
+ for (bb, data) in traversal::reverse_postorder(ccx.body) {
collector.visit_basic_block_data(bb, data);
}
(collector.temps, collector.candidates)
@@ -457,7 +454,9 @@ impl<'tcx> Validator<'_, 'tcx> {
match kind {
// Reject these borrow types just to be safe.
// FIXME(RalfJung): could we allow them? Should we? No point in it until we have a usecase.
- BorrowKind::Shallow | BorrowKind::Unique => return Err(Unpromotable),
+ BorrowKind::Shallow | BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture } => {
+ return Err(Unpromotable);
+ }
BorrowKind::Shared => {
let has_mut_interior = self.qualif_local::<qualifs::HasMutInterior>(place.local);
@@ -466,7 +465,9 @@ impl<'tcx> Validator<'_, 'tcx> {
}
}
- BorrowKind::Mut { .. } => {
+ // FIXME: consider changing this to only promote &mut [] for default borrows,
+ // also forbidding two phase borrows
+ BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow } => {
let ty = place.ty(self.body, self.tcx).ty;
// In theory, any zero-sized value could be borrowed
@@ -572,13 +573,18 @@ impl<'tcx> Validator<'_, 'tcx> {
| BinOp::Gt
| BinOp::Offset
| BinOp::Add
+ | BinOp::AddUnchecked
| BinOp::Sub
+ | BinOp::SubUnchecked
| BinOp::Mul
+ | BinOp::MulUnchecked
| BinOp::BitXor
| BinOp::BitAnd
| BinOp::BitOr
| BinOp::Shl
- | BinOp::Shr => {}
+ | BinOp::ShlUnchecked
+ | BinOp::Shr
+ | BinOp::ShrUnchecked => {}
}
self.validate_operand(lhs)?;
@@ -795,7 +801,9 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
};
match terminator.kind {
- TerminatorKind::Call { mut func, mut args, from_hir_call, fn_span, .. } => {
+ TerminatorKind::Call {
+ mut func, mut args, call_source: desugar, fn_span, ..
+ } => {
self.visit_operand(&mut func, loc);
for arg in &mut args {
self.visit_operand(arg, loc);
@@ -811,7 +819,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
unwind: UnwindAction::Continue,
destination: Place::from(new_temp),
target: Some(new_target),
- from_hir_call,
+ call_source: desugar,
fn_span,
},
source_info: SourceInfo::outermost(terminator.source_info.span),
@@ -859,7 +867,8 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
let ty = local_decls[place.local].ty;
let span = statement.source_info.span;
- let ref_ty = tcx.mk_ref(
+ let ref_ty = Ty::new_ref(
+ tcx,
tcx.lifetimes.re_erased,
ty::TypeAndMut { ty, mutbl: borrow_kind.to_mutbl_lossy() },
);
diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs
index 3c350e25b..4cc923cd9 100644
--- a/compiler/rustc_const_eval/src/transform/validate.rs
+++ b/compiler/rustc_const_eval/src/transform/validate.rs
@@ -67,8 +67,8 @@ impl<'tcx> MirPass<'tcx> for Validator {
unwind_edge_count: 0,
reachable_blocks: traversal::reachable_as_bitset(body),
storage_liveness,
- place_cache: Vec::new(),
- value_cache: Vec::new(),
+ place_cache: FxHashSet::default(),
+ value_cache: FxHashSet::default(),
};
checker.visit_body(body);
checker.check_cleanup_control_flow();
@@ -95,8 +95,8 @@ struct TypeChecker<'a, 'tcx> {
unwind_edge_count: usize,
reachable_blocks: BitSet<BasicBlock>,
storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>,
- place_cache: Vec<PlaceRef<'tcx>>,
- value_cache: Vec<u128>,
+ place_cache: FxHashSet<PlaceRef<'tcx>>,
+ value_cache: FxHashSet<u128>,
}
impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
@@ -318,8 +318,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
fn visit_projection_elem(
&mut self,
- local: Local,
- proj_base: &[PlaceElem<'tcx>],
+ place_ref: PlaceRef<'tcx>,
elem: PlaceElem<'tcx>,
context: PlaceContext,
location: Location,
@@ -334,7 +333,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
ProjectionElem::Deref
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::PostCleanup) =>
{
- let base_ty = Place::ty_from(local, proj_base, &self.body.local_decls, self.tcx).ty;
+ let base_ty = place_ref.ty(&self.body.local_decls, self.tcx).ty;
if base_ty.is_box() {
self.fail(
@@ -344,8 +343,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
}
}
ProjectionElem::Field(f, ty) => {
- let parent = Place { local, projection: self.tcx.mk_place_elems(proj_base) };
- let parent_ty = parent.ty(&self.body.local_decls, self.tcx);
+ let parent_ty = place_ref.ty(&self.body.local_decls, self.tcx);
let fail_out_of_bounds = |this: &Self, location| {
this.fail(location, format!("Out of bounds field {:?} for {:?}", f, parent_ty));
};
@@ -355,7 +353,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
location,
format!(
"Field projection `{:?}.{:?}` specified type `{:?}`, but actual type is `{:?}`",
- parent, f, ty, f_ty
+ place_ref, f, ty, f_ty
)
)
}
@@ -434,7 +432,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
}
_ => {}
}
- self.super_projection_elem(local, proj_base, elem, context, location);
+ self.super_projection_elem(place_ref, elem, context, location);
}
fn visit_var_debug_info(&mut self, debuginfo: &VarDebugInfo<'tcx>) {
@@ -498,8 +496,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
macro_rules! check_kinds {
- ($t:expr, $text:literal, $($patterns:tt)*) => {
- if !matches!(($t).kind(), $($patterns)*) {
+ ($t:expr, $text:literal, $typat:pat) => {
+ if !matches!(($t).kind(), $typat) {
self.fail(location, format!($text, $t));
}
};
@@ -527,6 +525,25 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
use BinOp::*;
let a = vals.0.ty(&self.body.local_decls, self.tcx);
let b = vals.1.ty(&self.body.local_decls, self.tcx);
+ if crate::util::binop_right_homogeneous(*op) {
+ if let Eq | Lt | Le | Ne | Ge | Gt = op {
+ // The function pointer types can have lifetimes
+ if !self.mir_assign_valid_types(a, b) {
+ self.fail(
+ location,
+ format!("Cannot {op:?} compare incompatible types {a:?} and {b:?}"),
+ );
+ }
+ } else if a != b {
+ self.fail(
+ location,
+ format!(
+ "Cannot perform binary op {op:?} on unequal types {a:?} and {b:?}"
+ ),
+ );
+ }
+ }
+
match op {
Offset => {
check_kinds!(a, "Cannot offset non-pointer type {:?}", ty::RawPtr(..));
@@ -538,7 +555,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
for x in [a, b] {
check_kinds!(
x,
- "Cannot compare type {:?}",
+ "Cannot {op:?} compare type {:?}",
ty::Bool
| ty::Char
| ty::Int(..)
@@ -548,19 +565,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
| ty::FnPtr(..)
)
}
- // The function pointer types can have lifetimes
- if !self.mir_assign_valid_types(a, b) {
- self.fail(
- location,
- format!("Cannot compare unequal types {:?} and {:?}", a, b),
- );
- }
}
- Shl | Shr => {
+ AddUnchecked | SubUnchecked | MulUnchecked | Shl | ShlUnchecked | Shr
+ | ShrUnchecked => {
for x in [a, b] {
check_kinds!(
x,
- "Cannot shift non-integer type {:?}",
+ "Cannot {op:?} non-integer type {:?}",
ty::Uint(..) | ty::Int(..)
)
}
@@ -569,37 +580,19 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
for x in [a, b] {
check_kinds!(
x,
- "Cannot perform bitwise op on type {:?}",
+ "Cannot perform bitwise op {op:?} on type {:?}",
ty::Uint(..) | ty::Int(..) | ty::Bool
)
}
- if a != b {
- self.fail(
- location,
- format!(
- "Cannot perform bitwise op on unequal types {:?} and {:?}",
- a, b
- ),
- );
- }
}
Add | Sub | Mul | Div | Rem => {
for x in [a, b] {
check_kinds!(
x,
- "Cannot perform arithmetic on type {:?}",
+ "Cannot perform arithmetic {op:?} on type {:?}",
ty::Uint(..) | ty::Int(..) | ty::Float(..)
)
}
- if a != b {
- self.fail(
- location,
- format!(
- "Cannot perform arithmetic on unequal types {:?} and {:?}",
- a, b
- ),
- );
- }
}
}
}
@@ -657,7 +650,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
// FIXME: Add Checks for these
CastKind::PointerFromExposedAddress
| CastKind::PointerExposeAddress
- | CastKind::Pointer(_) => {}
+ | CastKind::PointerCoercion(_) => {}
CastKind::IntToInt | CastKind::IntToFloat => {
let input_valid = op_ty.is_integral() || op_ty.is_char() || op_ty.is_bool();
let target_valid = target_type.is_numeric() || target_type.is_char();
@@ -958,10 +951,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
self.value_cache.clear();
self.value_cache.extend(targets.iter().map(|(value, _)| value));
- let all_len = self.value_cache.len();
- self.value_cache.sort_unstable();
- self.value_cache.dedup();
- let has_duplicates = all_len != self.value_cache.len();
+ let has_duplicates = targets.iter().len() != self.value_cache.len();
if has_duplicates {
self.fail(
location,
@@ -994,16 +984,14 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
// passed by a reference to the callee. Consequently they must be non-overlapping.
// Currently this simply checks for duplicate places.
self.place_cache.clear();
- self.place_cache.push(destination.as_ref());
+ self.place_cache.insert(destination.as_ref());
+ let mut has_duplicates = false;
for arg in args {
if let Operand::Move(place) = arg {
- self.place_cache.push(place.as_ref());
+ has_duplicates |= !self.place_cache.insert(place.as_ref());
}
}
- let all_len = self.place_cache.len();
- let mut dedup = FxHashSet::default();
- self.place_cache.retain(|p| dedup.insert(*p));
- let has_duplicates = all_len != self.place_cache.len();
+
if has_duplicates {
self.fail(
location,
diff --git a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs
index 23fcd22c5..2d1970791 100644
--- a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs
+++ b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs
@@ -1,9 +1,8 @@
use rustc_middle::ty::layout::{LayoutCx, LayoutError, LayoutOf, TyAndLayout, ValidityRequirement};
use rustc_middle::ty::{ParamEnv, ParamEnvAnd, Ty, TyCtxt};
-use rustc_session::Limit;
use rustc_target::abi::{Abi, FieldsShape, Scalar, Variants};
-use crate::const_eval::{CheckAlignment, CompileTimeInterpreter};
+use crate::const_eval::{CanAccessStatics, CheckAlignment, CompileTimeInterpreter};
use crate::interpret::{InterpCx, MemoryKind, OpTy};
/// Determines if this type permits "raw" initialization by just transmuting some memory into an
@@ -22,7 +21,7 @@ pub fn check_validity_requirement<'tcx>(
tcx: TyCtxt<'tcx>,
kind: ValidityRequirement,
param_env_and_ty: ParamEnvAnd<'tcx, Ty<'tcx>>,
-) -> Result<bool, LayoutError<'tcx>> {
+) -> Result<bool, &'tcx LayoutError<'tcx>> {
let layout = tcx.layout_of(param_env_and_ty)?;
// There is nothing strict or lax about inhabitedness.
@@ -44,12 +43,8 @@ fn might_permit_raw_init_strict<'tcx>(
ty: TyAndLayout<'tcx>,
tcx: TyCtxt<'tcx>,
kind: ValidityRequirement,
-) -> Result<bool, LayoutError<'tcx>> {
- let machine = CompileTimeInterpreter::new(
- Limit::new(0),
- /*can_access_statics:*/ false,
- CheckAlignment::Error,
- );
+) -> Result<bool, &'tcx LayoutError<'tcx>> {
+ let machine = CompileTimeInterpreter::new(CanAccessStatics::No, CheckAlignment::Error);
let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine);
@@ -80,7 +75,7 @@ fn might_permit_raw_init_lax<'tcx>(
this: TyAndLayout<'tcx>,
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
init_kind: ValidityRequirement,
-) -> Result<bool, LayoutError<'tcx>> {
+) -> Result<bool, &'tcx LayoutError<'tcx>> {
let scalar_allows_raw_init = move |s: Scalar| -> bool {
match init_kind {
ValidityRequirement::Inhabited => {
diff --git a/compiler/rustc_const_eval/src/util/mod.rs b/compiler/rustc_const_eval/src/util/mod.rs
index 7641f5607..289e34225 100644
--- a/compiler/rustc_const_eval/src/util/mod.rs
+++ b/compiler/rustc_const_eval/src/util/mod.rs
@@ -1,3 +1,5 @@
+use rustc_middle::mir;
+
mod alignment;
mod check_validity_requirement;
mod compare_types;
@@ -7,3 +9,27 @@ pub use self::alignment::is_disaligned;
pub use self::check_validity_requirement::check_validity_requirement;
pub use self::compare_types::{is_equal_up_to_subtyping, is_subtype};
pub use self::type_name::type_name;
+
+/// Classify whether an operator is "left-homogeneous", i.e., the LHS has the
+/// same type as the result.
+#[inline]
+pub(crate) fn binop_left_homogeneous(op: mir::BinOp) -> bool {
+ use rustc_middle::mir::BinOp::*;
+ match op {
+ Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor
+ | BitAnd | BitOr | Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => true,
+ Eq | Ne | Lt | Le | Gt | Ge => false,
+ }
+}
+
+/// Classify whether an operator is "right-homogeneous", i.e., the RHS has the
+/// same type as the LHS.
+#[inline]
+pub(crate) fn binop_right_homogeneous(op: mir::BinOp) -> bool {
+ use rustc_middle::mir::BinOp::*;
+ match op {
+ Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor
+ | BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge => true,
+ Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => false,
+ }
+}
diff --git a/compiler/rustc_const_eval/src/util/type_name.rs b/compiler/rustc_const_eval/src/util/type_name.rs
index 11ad5b49d..4f01e0a24 100644
--- a/compiler/rustc_const_eval/src/util/type_name.rs
+++ b/compiler/rustc_const_eval/src/util/type_name.rs
@@ -63,6 +63,7 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
| ty::Generator(def_id, substs, _) => self.print_def_path(def_id, substs),
ty::Foreign(def_id) => self.print_def_path(def_id, &[]),
+ ty::Alias(ty::Weak, _) => bug!("type_name: unexpected weak projection"),
ty::Alias(ty::Inherent, _) => bug!("type_name: unexpected inherent projection"),
ty::GeneratorWitness(_) => bug!("type_name: unexpected `GeneratorWitness`"),
ty::GeneratorWitnessMIR(..) => bug!("type_name: unexpected `GeneratorWitnessMIR`"),