diff options
Diffstat (limited to 'compiler/rustc_const_eval/src')
20 files changed, 256 insertions, 241 deletions
diff --git a/compiler/rustc_const_eval/src/const_eval/error.rs b/compiler/rustc_const_eval/src/const_eval/error.rs index 62af21396ab..71085c2b2a5 100644 --- a/compiler/rustc_const_eval/src/const_eval/error.rs +++ b/compiler/rustc_const_eval/src/const_eval/error.rs @@ -17,7 +17,7 @@ use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, MachineStopTy /// The CTFE machine has some custom error kinds. #[derive(Clone, Debug)] pub enum ConstEvalErrKind { - ConstAccessesStatic, + ConstAccessesMutGlobal, ModifiedGlobal, AssertFailure(AssertKind<ConstInt>), Panic { msg: Symbol, line: u32, col: u32, file: Symbol }, @@ -28,7 +28,7 @@ impl MachineStopType for ConstEvalErrKind { use crate::fluent_generated::*; use ConstEvalErrKind::*; match self { - ConstAccessesStatic => const_eval_const_accesses_static, + ConstAccessesMutGlobal => const_eval_const_accesses_mut_global, ModifiedGlobal => const_eval_modified_global, Panic { .. } => const_eval_panic, AssertFailure(x) => x.diagnostic_message(), @@ -37,7 +37,7 @@ impl MachineStopType for ConstEvalErrKind { fn add_args(self: Box<Self>, adder: &mut dyn FnMut(DiagnosticArgName, DiagnosticArgValue)) { use ConstEvalErrKind::*; match *self { - ConstAccessesStatic | ModifiedGlobal => {} + ConstAccessesMutGlobal | ModifiedGlobal => {} AssertFailure(kind) => kind.add_args(adder), Panic { msg, line, col, file } => { adder("msg".into(), msg.into_diagnostic_arg()); 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 a2d0f1c5583..0844cdbe99b 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -11,7 +11,7 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_span::Span; use rustc_target::abi::{self, Abi}; -use super::{CanAccessStatics, CompileTimeEvalContext, CompileTimeInterpreter}; +use super::{CanAccessMutGlobal, CompileTimeEvalContext, CompileTimeInterpreter}; use crate::const_eval::CheckAlignment; use crate::errors; use crate::errors::ConstEvalError; @@ -90,14 +90,14 @@ pub(crate) fn mk_eval_cx<'mir, 'tcx>( tcx: TyCtxt<'tcx>, root_span: Span, param_env: ty::ParamEnv<'tcx>, - can_access_statics: CanAccessStatics, + can_access_mut_global: CanAccessMutGlobal, ) -> CompileTimeEvalContext<'mir, 'tcx> { debug!("mk_eval_cx: {:?}", param_env); InterpCx::new( tcx, root_span, param_env, - CompileTimeInterpreter::new(can_access_statics, CheckAlignment::No), + CompileTimeInterpreter::new(can_access_mut_global, CheckAlignment::No), ) } @@ -200,7 +200,7 @@ pub(crate) fn turn_into_const_value<'tcx>( tcx, tcx.def_span(key.value.instance.def_id()), key.param_env, - CanAccessStatics::from(is_static), + CanAccessMutGlobal::from(is_static), ); let mplace = ecx.raw_const_to_mplace(constant).expect( @@ -277,9 +277,11 @@ pub fn eval_to_allocation_raw_provider<'tcx>( tcx, tcx.def_span(def), key.param_env, - // Statics (and promoteds inside statics) may access other statics, because unlike consts + // Statics (and promoteds inside statics) may access mutable global memory, because unlike consts // they do not have to behave "as if" they were evaluated at runtime. - CompileTimeInterpreter::new(CanAccessStatics::from(is_static), CheckAlignment::Error), + // For consts however we want to ensure they behave "as if" they were evaluated at runtime, + // so we have to reject reading mutable global memory. + CompileTimeInterpreter::new(CanAccessMutGlobal::from(is_static), CheckAlignment::Error), ); eval_in_interpreter(ecx, cid, is_static) } @@ -358,7 +360,7 @@ pub fn const_validate_mplace<'mir, 'tcx>( // Promoteds in statics are consts that re allowed to point to statics. CtfeValidationMode::Const { allow_immutable_unsafe_cell: false, - allow_static_ptrs: true, + allow_extern_static_ptrs: true, } } Some(mutbl) => CtfeValidationMode::Static { mutbl }, // a `static` @@ -366,7 +368,10 @@ pub fn const_validate_mplace<'mir, 'tcx>( // In normal `const` (not promoted), the outermost allocation is always only copied, // so having `UnsafeCell` in there is okay despite them being in immutable memory. let allow_immutable_unsafe_cell = cid.promoted.is_none() && !inner; - CtfeValidationMode::Const { allow_immutable_unsafe_cell, allow_static_ptrs: false } + CtfeValidationMode::Const { + allow_immutable_unsafe_cell, + allow_extern_static_ptrs: false, + } } }; ecx.const_validate_operand(&mplace.into(), path, &mut ref_tracking, mode)?; diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 274ff25fb9b..d08985edb76 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -51,13 +51,10 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> { /// The virtual call stack. pub(super) stack: Vec<Frame<'mir, 'tcx>>, - /// We need to make sure consts never point to anything mutable, even recursively. That is - /// relied on for pattern matching on consts with references. - /// To achieve this, two pieces have to work together: - /// * 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: CanAccessStatics, + /// Pattern matching on consts with references would be unsound if those references + /// could point to anything mutable. Therefore, when evaluating consts and when constructing valtrees, + /// we ensure that only immutable global memory can be accessed. + pub(super) can_access_mut_global: CanAccessMutGlobal, /// Whether to check alignment during evaluation. pub(super) check_alignment: CheckAlignment, @@ -73,12 +70,12 @@ pub enum CheckAlignment { } #[derive(Copy, Clone, PartialEq)] -pub(crate) enum CanAccessStatics { +pub(crate) enum CanAccessMutGlobal { No, Yes, } -impl From<bool> for CanAccessStatics { +impl From<bool> for CanAccessMutGlobal { fn from(value: bool) -> Self { if value { Self::Yes } else { Self::No } } @@ -86,13 +83,13 @@ impl From<bool> for CanAccessStatics { impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> { pub(crate) fn new( - can_access_statics: CanAccessStatics, + can_access_mut_global: CanAccessMutGlobal, check_alignment: CheckAlignment, ) -> Self { CompileTimeInterpreter { num_evaluated_steps: 0, stack: Vec::new(), - can_access_statics, + can_access_mut_global, check_alignment, } } @@ -680,7 +677,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, machine: &Self, alloc_id: AllocId, alloc: ConstAllocation<'tcx>, - static_def_id: Option<DefId>, + _static_def_id: Option<DefId>, is_write: bool, ) -> InterpResult<'tcx> { let alloc = alloc.inner(); @@ -692,22 +689,15 @@ 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 == CanAccessStatics::Yes { + if machine.can_access_mut_global == CanAccessMutGlobal::Yes { // Machine configuration allows us read from anything (e.g., `static` initializer). Ok(()) - } else if static_def_id.is_some() { - // Machine configuration does not allow us to read statics - // (e.g., `const` initializer). - // See const_eval::machine::MemoryExtra::can_access_statics for why - // this check is so important: if we could read statics, we could read pointers - // to mutable allocations *inside* statics. These allocations are not themselves - // statics, so pointers to them can get around the check in `validity.rs`. - Err(ConstEvalErrKind::ConstAccessesStatic.into()) + } else if alloc.mutability == Mutability::Mut { + // Machine configuration does not allow us to read statics (e.g., `const` + // initializer). + Err(ConstEvalErrKind::ConstAccessesMutGlobal.into()) } else { // Immutable global, this read is fine. - // But make sure we never accept a read from something mutable, that would be - // unsound. The reason is that as the content of this allocation may be different - // now and at run-time, so if we permit reading now we might return the wrong value. assert_eq!(alloc.mutability, Mutability::Not); Ok(()) } diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs index 29cbb7f07e8..cd50701040e 100644 --- a/compiler/rustc_const_eval/src/const_eval/mod.rs +++ b/compiler/rustc_const_eval/src/const_eval/mod.rs @@ -1,12 +1,11 @@ // Not in interpret to make sure we do not use private implementation details -use crate::errors::MaxNumNodesInConstErr; -use crate::interpret::InterpCx; use rustc_middle::mir; -use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId}; +use rustc_middle::mir::interpret::InterpErrorInfo; use rustc_middle::query::TyCtxtAt; -use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_span::DUMMY_SP; +use rustc_middle::ty::{self, Ty}; + +use crate::interpret::{format_interp_error, InterpCx}; mod error; mod eval_queries; @@ -18,56 +17,26 @@ pub use error::*; pub use eval_queries::*; pub use fn_queries::*; pub use machine::*; -pub(crate) use valtrees::{const_to_valtree_inner, valtree_to_const_value}; +pub(crate) use valtrees::{eval_to_valtree, valtree_to_const_value}; // We forbid type-level constants that contain more than `VALTREE_MAX_NODES` nodes. const VALTREE_MAX_NODES: usize = 100000; pub(crate) enum ValTreeCreationError { NodesOverflow, + /// Values of this type, or this particular value, are not supported as valtrees. NonSupportedType, - Other, } pub(crate) type ValTreeCreationResult<'tcx> = Result<ty::ValTree<'tcx>, ValTreeCreationError>; -/// Evaluates a constant and turns it into a type-level constant value. -pub(crate) fn eval_to_valtree<'tcx>( - tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - cid: GlobalId<'tcx>, -) -> EvalToValTreeResult<'tcx> { - let const_alloc = tcx.eval_to_allocation_raw(param_env.and(cid))?; - - // FIXME Need to provide a span to `eval_to_valtree` - let ecx = mk_eval_cx( - tcx, - DUMMY_SP, - param_env, - // It is absolutely crucial for soundness that - // we do not read from static items or other mutable memory. - CanAccessStatics::No, - ); - let place = ecx.raw_const_to_mplace(const_alloc).unwrap(); - debug!(?place); - - let mut num_nodes = 0; - let valtree_result = const_to_valtree_inner(&ecx, &place, &mut num_nodes); - - match valtree_result { - Ok(valtree) => Ok(Some(valtree)), - Err(err) => { - let did = cid.instance.def_id(); - let global_const_id = cid.display(tcx); - match err { - ValTreeCreationError::NodesOverflow => { - let span = tcx.hir().span_if_local(did); - tcx.dcx().emit_err(MaxNumNodesInConstErr { span, global_const_id }); - - Ok(None) - } - ValTreeCreationError::NonSupportedType | ValTreeCreationError::Other => Ok(None), - } - } +impl From<InterpErrorInfo<'_>> for ValTreeCreationError { + fn from(err: InterpErrorInfo<'_>) -> Self { + ty::tls::with(|tcx| { + bug!( + "Unexpected Undefined Behavior error during valtree construction: {}", + format_interp_error(tcx.dcx(), err), + ) + }) } } @@ -78,7 +47,7 @@ pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>( ty: Ty<'tcx>, ) -> Option<mir::DestructuredConstant<'tcx>> { let param_env = ty::ParamEnv::reveal_all(); - let ecx = mk_eval_cx(tcx.tcx, tcx.span, param_env, CanAccessStatics::No); + let ecx = mk_eval_cx(tcx.tcx, tcx.span, param_env, CanAccessMutGlobal::No); let op = ecx.const_val_to_op(val, ty, None).ok()?; // We go to `usize` as we cannot allocate anything bigger anyway. diff --git a/compiler/rustc_const_eval/src/const_eval/valtrees.rs b/compiler/rustc_const_eval/src/const_eval/valtrees.rs index 5c2bf4626c4..514a6a7df76 100644 --- a/compiler/rustc_const_eval/src/const_eval/valtrees.rs +++ b/compiler/rustc_const_eval/src/const_eval/valtrees.rs @@ -1,17 +1,20 @@ +use rustc_middle::mir; +use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId}; +use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout}; +use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt}; +use rustc_span::DUMMY_SP; +use rustc_target::abi::{Abi, VariantIdx}; + 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::const_eval::CanAccessMutGlobal; +use crate::errors::MaxNumNodesInConstErr; use crate::interpret::MPlaceTy; use crate::interpret::{ intern_const_alloc_recursive, ImmTy, Immediate, InternKind, MemPlaceMeta, MemoryKind, PlaceTy, Projectable, Scalar, }; -use rustc_middle::mir; -use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout}; -use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt}; -use rustc_span::DUMMY_SP; -use rustc_target::abi::{Abi, VariantIdx}; #[instrument(skip(ecx), level = "debug")] fn branches<'tcx>( @@ -70,7 +73,7 @@ fn slice_branches<'tcx>( } #[instrument(skip(ecx), level = "debug")] -pub(crate) fn const_to_valtree_inner<'tcx>( +fn const_to_valtree_inner<'tcx>( ecx: &CompileTimeEvalContext<'tcx, 'tcx>, place: &MPlaceTy<'tcx>, num_nodes: &mut usize, @@ -88,9 +91,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>( Ok(ty::ValTree::zst()) } ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => { - let Ok(val) = ecx.read_immediate(place) else { - return Err(ValTreeCreationError::Other); - }; + let val = ecx.read_immediate(place)?; let val = val.to_scalar(); *num_nodes += 1; @@ -102,19 +103,17 @@ pub(crate) fn const_to_valtree_inner<'tcx>( // equality at compile-time (see `ptr_guaranteed_cmp`). // However we allow those that are just integers in disguise. // First, get the pointer. Remember it might be wide! - let Ok(val) = ecx.read_immediate(place) else { - return Err(ValTreeCreationError::Other); - }; + let val = ecx.read_immediate(place)?; // We could allow wide raw pointers where both sides are integers in the future, // but for now we reject them. if matches!(val.layout.abi, Abi::ScalarPair(..)) { - return Err(ValTreeCreationError::Other); + return Err(ValTreeCreationError::NonSupportedType); } let val = val.to_scalar(); // We are in the CTFE machine, so ptr-to-int casts will fail. // This can only be `Ok` if `val` already is an integer. let Ok(val) = val.try_to_int() else { - return Err(ValTreeCreationError::Other); + return Err(ValTreeCreationError::NonSupportedType); }; // It's just a ScalarInt! Ok(ty::ValTree::Leaf(val)) @@ -125,11 +124,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>( ty::FnPtr(_) => Err(ValTreeCreationError::NonSupportedType), ty::Ref(_, _, _) => { - let Ok(derefd_place)= ecx.deref_pointer(place) else { - return Err(ValTreeCreationError::Other); - }; - debug!(?derefd_place); - + let derefd_place = ecx.deref_pointer(place)?; const_to_valtree_inner(ecx, &derefd_place, num_nodes) } @@ -153,9 +148,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>( bug!("uninhabited types should have errored and never gotten converted to valtree") } - let Ok(variant) = ecx.read_discriminant(place) else { - return Err(ValTreeCreationError::Other); - }; + let variant = ecx.read_discriminant(place)?; branches(ecx, place, def.variant(variant).fields.len(), def.is_enum().then_some(variant), num_nodes) } @@ -221,6 +214,47 @@ fn create_valtree_place<'tcx>( ecx.allocate_dyn(layout, MemoryKind::Stack, meta).unwrap() } +/// Evaluates a constant and turns it into a type-level constant value. +pub(crate) fn eval_to_valtree<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + cid: GlobalId<'tcx>, +) -> EvalToValTreeResult<'tcx> { + let const_alloc = tcx.eval_to_allocation_raw(param_env.and(cid))?; + + // FIXME Need to provide a span to `eval_to_valtree` + let ecx = mk_eval_cx( + tcx, + DUMMY_SP, + param_env, + // It is absolutely crucial for soundness that + // we do not read from mutable memory. + CanAccessMutGlobal::No, + ); + let place = ecx.raw_const_to_mplace(const_alloc).unwrap(); + debug!(?place); + + let mut num_nodes = 0; + let valtree_result = const_to_valtree_inner(&ecx, &place, &mut num_nodes); + + match valtree_result { + Ok(valtree) => Ok(Some(valtree)), + Err(err) => { + let did = cid.instance.def_id(); + let global_const_id = cid.display(tcx); + let span = tcx.hir().span_if_local(did); + match err { + ValTreeCreationError::NodesOverflow => { + let handled = + tcx.dcx().emit_err(MaxNumNodesInConstErr { span, global_const_id }); + Err(handled.into()) + } + ValTreeCreationError::NonSupportedType => Ok(None), + } + } + } +} + /// Converts a `ValTree` to a `ConstValue`, which is needed after mir /// construction has finished. // FIXME Merge `valtree_to_const_value` and `valtree_into_mplace` into one function @@ -253,7 +287,7 @@ pub fn valtree_to_const_value<'tcx>( } } ty::Ref(_, inner_ty, _) => { - let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessStatics::No); + let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessMutGlobal::No); let imm = valtree_to_ref(&mut ecx, valtree, *inner_ty); let imm = ImmTy::from_immediate(imm, tcx.layout_of(param_env_ty).unwrap()); op_to_const(&ecx, &imm.into(), /* for diagnostics */ false) @@ -280,7 +314,7 @@ pub fn valtree_to_const_value<'tcx>( bug!("could not find non-ZST field during in {layout:#?}"); } - let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessStatics::No); + let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessMutGlobal::No); // Need to create a place for this valtree. let place = create_valtree_place(&mut ecx, layout, valtree); diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 4d2b1ba3eec..a649526c196 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -56,24 +56,12 @@ pub(crate) struct UnstableInStable { #[derive(Diagnostic)] #[diag(const_eval_thread_local_access, code = E0625)] -pub(crate) struct NonConstOpErr { +pub(crate) struct ThreadLocalAccessErr { #[primary_span] pub span: Span, } #[derive(Diagnostic)] -#[diag(const_eval_static_access, code = E0013)] -#[help] -pub(crate) struct StaticAccessErr { - #[primary_span] - pub span: Span, - pub kind: ConstContext, - #[note(const_eval_teach_note)] - #[help(const_eval_teach_help)] - pub teach: Option<()>, -} - -#[derive(Diagnostic)] #[diag(const_eval_raw_ptr_to_int)] #[note] #[note(const_eval_note2)] @@ -509,6 +497,9 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch, UninhabitedEnumVariantWritten(_) => const_eval_uninhabited_enum_variant_written, UninhabitedEnumVariantRead(_) => const_eval_uninhabited_enum_variant_read, + InvalidNichedEnumVariantWritten { .. } => { + const_eval_invalid_niched_enum_variant_written + } AbiMismatchArgument { .. } => const_eval_incompatible_types, AbiMismatchReturn { .. } => const_eval_incompatible_return_types, } @@ -597,6 +588,9 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { builder.arg("target_size", info.target_size); builder.arg("data_size", info.data_size); } + InvalidNichedEnumVariantWritten { enum_ty } => { + builder.arg("ty", enum_ty.to_string()); + } AbiMismatchArgument { caller_ty, callee_ty } | AbiMismatchReturn { caller_ty, callee_ty } => { builder.arg("caller_ty", caller_ty.to_string()); @@ -623,6 +617,8 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { PointerAsInt { .. } => const_eval_validation_pointer_as_int, PartialPointer => const_eval_validation_partial_pointer, + ConstRefToMutable => const_eval_validation_const_ref_to_mutable, + ConstRefToExtern => const_eval_validation_const_ref_to_extern, MutableRefInConst => const_eval_validation_mutable_ref_in_const, MutableRefToImmutable => const_eval_validation_mutable_ref_to_immutable, NullFnPtr => const_eval_validation_null_fn_ptr, @@ -777,6 +773,8 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { NullPtr { .. } | PtrToStatic { .. } | MutableRefInConst + | ConstRefToMutable + | ConstRefToExtern | MutableRefToImmutable | NullFnPtr | NeverVal @@ -801,7 +799,7 @@ impl ReportErrorExt for UnsupportedOpInfo { UnsupportedOpInfo::ReadPartialPointer(_) => const_eval_partial_pointer_copy, UnsupportedOpInfo::ReadPointerAsInt(_) => const_eval_read_pointer_as_int, UnsupportedOpInfo::ThreadLocalStatic(_) => const_eval_thread_local_static, - UnsupportedOpInfo::ReadExternStatic(_) => const_eval_read_extern_static, + UnsupportedOpInfo::ExternStatic(_) => const_eval_extern_static, } } fn add_args<G: EmissionGuarantee>(self, _: &DiagCtxt, builder: &mut DiagnosticBuilder<'_, G>) { @@ -820,7 +818,7 @@ impl ReportErrorExt for UnsupportedOpInfo { OverwritePartialPointer(ptr) | ReadPartialPointer(ptr) => { builder.arg("ptr", ptr); } - ThreadLocalStatic(did) | ReadExternStatic(did) => { + ThreadLocalStatic(did) | ExternStatic(did) => { builder.arg("did", format!("{did:?}")); } } diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs index 0cb5c634b22..6d470ff162e 100644 --- a/compiler/rustc_const_eval/src/interpret/cast.rs +++ b/compiler/rustc_const_eval/src/interpret/cast.rs @@ -415,7 +415,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } - fn unsize_into( + pub fn unsize_into( &mut self, src: &OpTy<'tcx, M::Provenance>, cast_ty: TyAndLayout<'tcx>, diff --git a/compiler/rustc_const_eval/src/interpret/discriminant.rs b/compiler/rustc_const_eval/src/interpret/discriminant.rs index bb8c17cf779..e951a77611c 100644 --- a/compiler/rustc_const_eval/src/interpret/discriminant.rs +++ b/compiler/rustc_const_eval/src/interpret/discriminant.rs @@ -85,6 +85,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Write result. let niche_dest = self.project_field(dest, tag_field)?; self.write_immediate(*tag_val, &niche_dest)?; + } else { + // The untagged variant is implicitly encoded simply by having a value that is + // outside the niche variants. But what if the data stored here does not + // actually encode this variant? That would be bad! So let's double-check... + let actual_variant = self.read_discriminant(&dest.to_op(self)?)?; + if actual_variant != variant_index { + throw_ub!(InvalidNichedEnumVariantWritten { enum_ty: dest.layout().ty }); + } } } } diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index dd989ab80fd..dd9dfe3fe79 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -4,6 +4,7 @@ use std::{fmt, mem}; use either::{Either, Left, Right}; use hir::CRATE_HIR_ID; +use rustc_errors::DiagCtxt; use rustc_hir::{self as hir, def_id::DefId, definitions::DefPathData}; use rustc_index::IndexVec; use rustc_middle::mir; @@ -430,6 +431,26 @@ pub(super) fn from_known_layout<'tcx>( } } +/// Turn the given error into a human-readable string. Expects the string to be printed, so if +/// `RUSTC_CTFE_BACKTRACE` is set this will show a backtrace of the rustc internals that +/// triggered the error. +/// +/// This is NOT the preferred way to render an error; use `report` from `const_eval` instead. +/// However, this is useful when error messages appear in ICEs. +pub fn format_interp_error<'tcx>(dcx: &DiagCtxt, e: InterpErrorInfo<'tcx>) -> String { + let (e, backtrace) = e.into_parts(); + backtrace.print_backtrace(); + // FIXME(fee1-dead), HACK: we want to use the error as title therefore we can just extract the + // label and arguments from the InterpError. + #[allow(rustc::untranslatable_diagnostic)] + let mut diag = dcx.struct_allow(""); + let msg = e.diagnostic_message(); + e.add_args(dcx, &mut diag); + let s = dcx.eagerly_translate_to_string(msg, diag.args()); + diag.cancel(); + s +} + impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { pub fn new( tcx: TyCtxt<'tcx>, @@ -462,27 +483,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { .map_or(CRATE_HIR_ID, |def_id| self.tcx.local_def_id_to_hir_id(def_id)) } - /// Turn the given error into a human-readable string. Expects the string to be printed, so if - /// `RUSTC_CTFE_BACKTRACE` is set this will show a backtrace of the rustc internals that - /// triggered the error. - /// - /// This is NOT the preferred way to render an error; use `report` from `const_eval` instead. - /// However, this is useful when error messages appear in ICEs. - pub fn format_error(&self, e: InterpErrorInfo<'tcx>) -> String { - let (e, backtrace) = e.into_parts(); - backtrace.print_backtrace(); - // FIXME(fee1-dead), HACK: we want to use the error as title therefore we can just extract the - // label and arguments from the InterpError. - let dcx = self.tcx.dcx(); - #[allow(rustc::untranslatable_diagnostic)] - let mut diag = dcx.struct_allow(""); - let msg = e.diagnostic_message(); - e.add_args(dcx, &mut diag); - let s = dcx.eagerly_translate_to_string(msg, diag.args()); - diag.cancel(); - s - } - #[inline(always)] pub(crate) fn stack(&self) -> &[Frame<'mir, 'tcx, M::Provenance, M::FrameExtra>] { M::stack(self) diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 38ad8cbf3a6..4acf4ed893c 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -557,7 +557,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { if self.tcx.is_foreign_item(def_id) { // This is unreachable in Miri, but can happen in CTFE where we actually *do* support // referencing arbitrary (declared) extern statics. - throw_unsup!(ReadExternStatic(def_id)); + throw_unsup!(ExternStatic(def_id)); } // We don't give a span -- statics don't need that, they cannot be generic or associated. diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs index 7d286d103ad..c1b6ce4eb4e 100644 --- a/compiler/rustc_const_eval/src/interpret/mod.rs +++ b/compiler/rustc_const_eval/src/interpret/mod.rs @@ -20,7 +20,7 @@ mod visitor; pub use rustc_middle::mir::interpret::*; // have all the `interpret` symbols in one place: here -pub use self::eval_context::{Frame, FrameInfo, InterpCx, StackPopCleanup}; +pub use self::eval_context::{format_interp_error, Frame, FrameInfo, InterpCx, StackPopCleanup}; pub use self::intern::{ intern_const_alloc_for_constprop, intern_const_alloc_recursive, InternKind, }; diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index d48329b6c69..f0f1008aba8 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -246,13 +246,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ); } let val = match null_op { - mir::NullOp::SizeOf => layout.size.bytes(), - mir::NullOp::AlignOf => layout.align.abi.bytes(), + mir::NullOp::SizeOf => { + let val = layout.size.bytes(); + Scalar::from_target_usize(val, self) + } + mir::NullOp::AlignOf => { + let val = layout.align.abi.bytes(); + Scalar::from_target_usize(val, self) + } mir::NullOp::OffsetOf(fields) => { - layout.offset_of_subfield(self, fields.iter()).bytes() + let val = layout.offset_of_subfield(self, fields.iter()).bytes(); + Scalar::from_target_usize(val, self) + } + mir::NullOp::DebugAssertions => { + // The checks hidden behind this are always better done by the interpreter + // itself, because it knows the runtime state better. + Scalar::from_bool(false) } }; - self.write_scalar(Scalar::from_target_usize(val, self), &dest)?; + self.write_scalar(val, &dest)?; } ShallowInitBox(ref operand, _) => { diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 85a2e4778d2..ff20fc5092c 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -377,12 +377,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // to fields, which can yield non-normalized types. So we need to provide a // normalization function. let normalize = |ty| self.tcx.normalize_erasing_regions(self.param_env, ty); - let (meta, only_if_sized) = ty.ptr_metadata_ty(*self.tcx, normalize); - assert!( - !only_if_sized, - "there should be no more 'maybe has that metadata' types during interpretation" - ); - meta + ty.ptr_metadata_ty(*self.tcx, normalize) }; return Ok(meta_ty(caller) == meta_ty(callee)); } diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 90cde81c018..38aeace02ba 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -27,8 +27,9 @@ use rustc_target::abi::{ use std::hash::Hash; use super::{ - AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, - Machine, MemPlaceMeta, OpTy, Pointer, Projectable, Scalar, ValueVisitor, + format_interp_error, AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, + InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Pointer, Projectable, Scalar, + ValueVisitor, }; // for the validation errors @@ -132,8 +133,7 @@ pub enum CtfeValidationMode { /// `allow_immutable_unsafe_cell` says whether we allow `UnsafeCell` in immutable memory (which is the /// case for the top-level allocation of a `const`, where this is fine because the allocation will be /// copied at each use site). - /// `allow_static_ptrs` says if pointers to statics are permitted (which is the case for promoteds in statics). - Const { allow_immutable_unsafe_cell: bool, allow_static_ptrs: bool }, + Const { allow_immutable_unsafe_cell: bool, allow_extern_static_ptrs: bool }, } impl CtfeValidationMode { @@ -146,13 +146,6 @@ impl CtfeValidationMode { } } - fn allow_static_ptrs(self) -> bool { - match self { - CtfeValidationMode::Static { .. } => true, // statics can point to statics - CtfeValidationMode::Const { allow_static_ptrs, .. } => allow_static_ptrs, - } - } - fn may_contain_mutable_ref(self) -> bool { match self { CtfeValidationMode::Static { mutbl } => mutbl == Mutability::Mut, @@ -468,53 +461,59 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' // Special handling for pointers to statics (irrespective of their type). assert!(!self.ecx.tcx.is_thread_local_static(did)); assert!(self.ecx.tcx.is_static(did)); - if self.ctfe_mode.is_some_and(|c| !c.allow_static_ptrs()) { - // See const_eval::machine::MemoryExtra::can_access_statics for why - // 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, PtrToStatic { ptr_kind }); - } + let is_mut = + matches!(self.ecx.tcx.def_kind(did), DefKind::Static(Mutability::Mut)) + || !self + .ecx + .tcx + .type_of(did) + .no_bound_vars() + .expect("statics should not have generic parameters") + .is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all()); // Mutability check. if ptr_expected_mutbl == Mutability::Mut { - if matches!( - self.ecx.tcx.def_kind(did), - DefKind::Static(Mutability::Not) - ) && self - .ecx - .tcx - .type_of(did) - .no_bound_vars() - .expect("statics should not have generic parameters") - .is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all()) - { + if !is_mut { throw_validation_failure!(self.path, MutableRefToImmutable); } } - // 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 - // unsafe code. - // The reasons we don't check other statics is twofold. For one, in all - // sound cases, the static was already validated on its own, and second, we - // trigger cycle errors if we try to compute the value of the other static - // and that static refers back to us. - // We might miss const-invalid data, - // but things are still sound otherwise (in particular re: consts - // referring to statics). - return Ok(()); + match self.ctfe_mode { + Some(CtfeValidationMode::Static { .. }) => { + // 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 + // unsafe code. + // The reasons we don't check other statics is twofold. For one, in all + // sound cases, the static was already validated on its own, and second, we + // trigger cycle errors if we try to compute the value of the other static + // and that static refers back to us. + // This could miss some UB, but that's fine. + return Ok(()); + } + Some(CtfeValidationMode::Const { + allow_extern_static_ptrs, .. + }) => { + // For consts on the other hand we have to recursively check; + // pattern matching assumes a valid value. However we better make + // sure this is not mutable. + if is_mut { + throw_validation_failure!(self.path, ConstRefToMutable); + } + if self.ecx.tcx.is_foreign_item(did) { + if !allow_extern_static_ptrs { + throw_validation_failure!(self.path, ConstRefToExtern); + } else { + // We can't validate this... + return Ok(()); + } + } + } + None => {} + } } GlobalAlloc::Memory(alloc) => { if alloc.inner().mutability == Mutability::Mut && matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) { - // This is impossible: this can only be some inner allocation of a - // `static mut` (everything else either hits the `GlobalAlloc::Static` - // case or is interned immutably). To get such a pointer we'd have to - // load it from a static, but such loads lead to a CTFE error. - span_bug!( - self.ecx.tcx.span, - "encountered reference to mutable memory inside a `const`" - ); + throw_validation_failure!(self.path, ConstRefToMutable); } if ptr_expected_mutbl == Mutability::Mut && alloc.inner().mutability == Mutability::Not @@ -993,7 +992,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Complain about any other kind of 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) => { - bug!("Unexpected error during validation: {}", self.format_error(err)); + bug!( + "Unexpected error during validation: {}", + format_interp_error(self.tcx.dcx(), err) + ); } } } diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index 946a49982aa..839cfd8d85a 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -5,9 +5,9 @@ Rust MIR: a lowered representation of Rust. */ #![allow(internal_features)] +#![allow(rustc::diagnostic_outside_of_impl)] #![feature(rustdoc_internals)] #![doc(rust_logo)] -#![deny(rustc::untranslatable_diagnostic)] #![feature(assert_matches)] #![feature(box_patterns)] #![feature(decl_macro)] 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 5ff81615552..28dc69859fd 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs @@ -449,35 +449,27 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } } - 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. - ty::Array(..) | ty::Slice(_) - if self.const_kind() == hir::ConstContext::Static(hir::Mutability::Mut) => - { - true - } - - // FIXME(ecstaticmorse): We could allow `&mut []` inside a const context given - // that this is merely a ZST and it is already eligible for promotion. - // This may require an RFC? - /* - ty::Array(_, len) if len.try_eval_target_usize(cx.tcx, cx.param_env) == Some(0) - => true, - */ - _ => false, - }; + Rvalue::Ref(_, BorrowKind::Mut { .. }, place) + | Rvalue::AddressOf(Mutability::Mut, place) => { + // Inside mutable statics, we allow arbitrary mutable references. + // We've allowed `static mut FOO = &mut [elements];` for a long time (the exact + // reasons why are lost to history), and there is no reason to restrict that to + // arrays and slices. + let is_allowed = + self.const_kind() == hir::ConstContext::Static(hir::Mutability::Mut); if !is_allowed { - self.check_mut_borrow(place.local, hir::BorrowKind::Ref) + self.check_mut_borrow( + place.local, + if matches!(rvalue, Rvalue::Ref(..)) { + hir::BorrowKind::Ref + } else { + hir::BorrowKind::Raw + }, + ); } } - Rvalue::AddressOf(Mutability::Mut, place) => { - self.check_mut_borrow(place.local, hir::BorrowKind::Raw) - } - Rvalue::Ref(_, BorrowKind::Shared | BorrowKind::Fake, place) | Rvalue::AddressOf(Mutability::Not, place) => { let borrowed_place_has_mut_interior = qualifs::in_place::<HasMutInterior, _>( @@ -544,7 +536,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { Rvalue::Cast(_, _, _) => {} - Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_), _) => {} + Rvalue::NullaryOp( + NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::DebugAssertions, + _, + ) => {} Rvalue::ShallowInitBox(_, _) => {} Rvalue::UnaryOp(_, operand) => { 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 fb705d91977..a9d472d377c 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs @@ -580,16 +580,21 @@ impl<'tcx> NonConstOp<'tcx> for StaticAccess { if let hir::ConstContext::Static(_) = ccx.const_kind() { Status::Allowed } else { - Status::Forbidden + Status::Unstable(sym::const_refs_to_static) } } fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> { - ccx.dcx().create_err(errors::StaticAccessErr { + let mut err = feature_err( + &ccx.tcx.sess, + sym::const_refs_to_static, span, - kind: ccx.const_kind(), - teach: ccx.tcx.sess.teach(E0013).then_some(()), - }) + format!("referencing statics in {}s is unstable", ccx.const_kind(),), + ); + err + .note("`static` and `const` variables can refer to other `const` variables. A `const` variable, however, cannot refer to a `static` variable.") + .help("to fix this, the value can be extracted to a `const` and then used."); + err } } @@ -598,7 +603,7 @@ impl<'tcx> NonConstOp<'tcx> for StaticAccess { pub struct ThreadLocalAccess; impl<'tcx> NonConstOp<'tcx> for ThreadLocalAccess { fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> { - ccx.dcx().create_err(errors::NonConstOpErr { span }) + ccx.dcx().create_err(errors::ThreadLocalAccessErr { span }) } } diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs index c4542aaa7b2..5ce6a71c4bd 100644 --- a/compiler/rustc_const_eval/src/transform/validate.rs +++ b/compiler/rustc_const_eval/src/transform/validate.rs @@ -60,6 +60,8 @@ impl<'tcx> MirPass<'tcx> for Validator { ty::Closure(..) => Abi::RustCall, ty::CoroutineClosure(..) => Abi::RustCall, ty::Coroutine(..) => Abi::Rust, + // No need to do MIR validation on error bodies + ty::Error(_) => return, _ => { span_bug!(body.span, "unexpected body ty: {:?} phase {:?}", body_ty, mir_phase) } @@ -1139,7 +1141,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { Rvalue::Repeat(_, _) | Rvalue::ThreadLocalRef(_) | Rvalue::AddressOf(_, _) - | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) + | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::DebugAssertions, _) | Rvalue::Discriminant(_) => {} } self.super_rvalue(rvalue, location); diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index 36a315b8f30..d1c2d22b5a9 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -6,7 +6,7 @@ use rustc_middle::ty::layout::LayoutOf; use rustc_span::symbol::Symbol; use rustc_type_ir::Mutability; -use crate::const_eval::{mk_eval_cx, CanAccessStatics, CompileTimeEvalContext}; +use crate::const_eval::{mk_eval_cx, CanAccessMutGlobal, CompileTimeEvalContext}; use crate::interpret::*; /// Allocate a `const core::panic::Location` with the provided filename and line/column numbers. @@ -57,7 +57,7 @@ pub(crate) fn const_caller_location_provider( col: u32, ) -> mir::ConstValue<'_> { trace!("const_caller_location: {}:{}:{}", file, line, col); - let mut ecx = mk_eval_cx(tcx.tcx, tcx.span, ty::ParamEnv::reveal_all(), CanAccessStatics::No); + let mut ecx = mk_eval_cx(tcx.tcx, tcx.span, ty::ParamEnv::reveal_all(), CanAccessMutGlobal::No); let loc_place = alloc_caller_location(&mut ecx, file, line, col); if intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &loc_place).is_err() { 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 e9e0690f07d..8c4af5e5132 100644 --- a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs +++ b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs @@ -2,7 +2,7 @@ use rustc_middle::ty::layout::{LayoutCx, LayoutError, LayoutOf, TyAndLayout, Val use rustc_middle::ty::{ParamEnv, ParamEnvAnd, Ty, TyCtxt}; use rustc_target::abi::{Abi, FieldsShape, Scalar, Variants}; -use crate::const_eval::{CanAccessStatics, CheckAlignment, CompileTimeInterpreter}; +use crate::const_eval::{CanAccessMutGlobal, CheckAlignment, CompileTimeInterpreter}; use crate::interpret::{InterpCx, MemoryKind, OpTy}; /// Determines if this type permits "raw" initialization by just transmuting some memory into an @@ -44,7 +44,7 @@ fn might_permit_raw_init_strict<'tcx>( tcx: TyCtxt<'tcx>, kind: ValidityRequirement, ) -> Result<bool, &'tcx LayoutError<'tcx>> { - let machine = CompileTimeInterpreter::new(CanAccessStatics::No, CheckAlignment::Error); + let machine = CompileTimeInterpreter::new(CanAccessMutGlobal::No, CheckAlignment::Error); let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); |
