diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:57:31 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:57:31 +0000 |
commit | dc0db358abe19481e475e10c32149b53370f1a1c (patch) | |
tree | ab8ce99c4b255ce46f99ef402c27916055b899ee /compiler/rustc_const_eval/src | |
parent | Releasing progress-linux version 1.71.1+dfsg1-2~progress7.99u1. (diff) | |
download | rustc-dc0db358abe19481e475e10c32149b53370f1a1c.tar.xz rustc-dc0db358abe19481e475e10c32149b53370f1a1c.zip |
Merging upstream version 1.72.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_const_eval/src')
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`"), |