diff options
Diffstat (limited to 'compiler/rustc_const_eval')
| -rw-r--r-- | compiler/rustc_const_eval/messages.ftl | 22 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/const_eval/eval_queries.rs | 32 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/const_eval/machine.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/errors.rs | 43 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/interpret/intern.rs | 557 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/interpret/place.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/interpret/validity.rs | 145 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/util/caller_location.rs | 1 |
8 files changed, 333 insertions, 475 deletions
diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 4a426ed16e5..2728d2ca463 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -46,8 +46,8 @@ const_eval_dangling_int_pointer = {$bad_pointer_message}: {$pointer} is a dangling pointer (it has no provenance) const_eval_dangling_null_pointer = {$bad_pointer_message}: null pointer is a dangling pointer (it has no provenance) -const_eval_dangling_ptr_in_final = encountered dangling pointer in final constant +const_eval_dangling_ptr_in_final = encountered dangling pointer in final value of {const_eval_intern_kind} const_eval_dead_local = accessing a dead local variable const_eval_dealloc_immutable = @@ -134,6 +134,14 @@ const_eval_interior_mutable_data_refer = This would make multiple uses of a constant to be able to see different values and allow circumventing the `Send` and `Sync` requirements for shared mutable data, which is unsound. +const_eval_intern_kind = {$kind -> + [static] static + [static_mut] mutable static + [const] constant + [promoted] promoted + *[other] {""} +} + const_eval_invalid_align = align has to be a power of 2 @@ -205,6 +213,8 @@ const_eval_modified_global = const_eval_mut_deref = mutation through a reference is not allowed in {const_eval_const_context}s +const_eval_mutable_ptr_in_final = encountered mutable pointer in final value of {const_eval_intern_kind} + const_eval_non_const_fmt_macro_call = cannot call non-const formatting macro in {const_eval_const_context}s @@ -392,9 +402,6 @@ const_eval_unstable_in_stable = .unstable_sugg = if it is not part of the public API, make this function unstably const .bypass_sugg = otherwise `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks -const_eval_unsupported_untyped_pointer = unsupported untyped pointer in constant - .note = memory only reachable via raw pointers is not supported - const_eval_unterminated_c_string = reading a null-terminated string starting at {$pointer} with no null found before end of allocation @@ -406,7 +413,6 @@ const_eval_upcast_mismatch = ## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`. ## (We'd love to sort this differently to make that more clear but tidy won't let us...) -const_eval_validation_box_to_mut = {$front_matter}: encountered a box pointing to mutable memory in a constant const_eval_validation_box_to_static = {$front_matter}: encountered a box pointing to a static variable in a constant const_eval_validation_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty} const_eval_validation_dangling_box_no_provenance = {$front_matter}: encountered a dangling box ({$pointer} has no provenance) @@ -441,7 +447,8 @@ const_eval_validation_invalid_fn_ptr = {$front_matter}: encountered {$value}, bu const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer -const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in a `const` +const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in a `const` or `static` +const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!` const_eval_validation_null_box = {$front_matter}: encountered a null box const_eval_validation_null_fn_ptr = {$front_matter}: encountered a null function pointer @@ -451,7 +458,6 @@ const_eval_validation_out_of_range = {$front_matter}: encountered {$value}, but const_eval_validation_partial_pointer = {$front_matter}: encountered a partial pointer or a mix of pointers const_eval_validation_pointer_as_int = {$front_matter}: encountered a pointer, but {$expected} const_eval_validation_ptr_out_of_range = {$front_matter}: encountered a pointer, but expected something that cannot possibly fail to be {$in_range} -const_eval_validation_ref_to_mut = {$front_matter}: encountered a reference pointing to mutable memory in a constant const_eval_validation_ref_to_static = {$front_matter}: encountered a reference pointing to a static variable in a constant const_eval_validation_ref_to_uninhabited = {$front_matter}: encountered a reference pointing to uninhabited type {$ty} const_eval_validation_unaligned_box = {$front_matter}: encountered an unaligned box (required {$required_bytes} byte alignment but found {$found_bytes}) @@ -459,7 +465,7 @@ const_eval_validation_unaligned_ref = {$front_matter}: encountered an unaligned const_eval_validation_uninhabited_enum_variant = {$front_matter}: encountered an uninhabited enum variant const_eval_validation_uninhabited_val = {$front_matter}: encountered a value of uninhabited type `{$ty}` const_eval_validation_uninit = {$front_matter}: encountered uninitialized memory, but {$expected} -const_eval_validation_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in a `const` +const_eval_validation_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in read-only memory const_eval_write_through_immutable_pointer = writing through a pointer that was derived from a shared (immutable) reference 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 4236117d75b..6a92ed9717d 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -293,6 +293,9 @@ pub fn eval_in_interpreter<'mir, 'tcx>( cid: GlobalId<'tcx>, is_static: bool, ) -> ::rustc_middle::mir::interpret::EvalToAllocationRawResult<'tcx> { + // `is_static` just means "in static", it could still be a promoted! + debug_assert_eq!(is_static, ecx.tcx.static_mutability(cid.instance.def_id()).is_some()); + 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) => { @@ -330,8 +333,7 @@ pub fn eval_in_interpreter<'mir, 'tcx>( 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 = - const_validate_mplace(&ecx, &mplace, is_static, cid.promoted.is_some()); + let validation = const_validate_mplace(&ecx, &mplace, cid); let alloc_id = mplace.ptr().provenance.unwrap().alloc_id(); @@ -350,22 +352,26 @@ pub fn eval_in_interpreter<'mir, 'tcx>( pub fn const_validate_mplace<'mir, 'tcx>( ecx: &InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>, mplace: &MPlaceTy<'tcx>, - is_static: bool, - is_promoted: bool, + cid: GlobalId<'tcx>, ) -> InterpResult<'tcx> { let mut ref_tracking = RefTracking::new(mplace.clone()); let mut inner = false; while let Some((mplace, path)) = ref_tracking.todo.pop() { - let mode = if is_static { - if is_promoted { - // Promoteds in statics are allowed to point to statics. - CtfeValidationMode::Const { inner, allow_static_ptrs: true } - } else { - // a `static` - CtfeValidationMode::Regular + let mode = match ecx.tcx.static_mutability(cid.instance.def_id()) { + Some(_) if cid.promoted.is_some() => { + // Promoteds in statics are consts that re allowed to point to statics. + CtfeValidationMode::Const { + allow_immutable_unsafe_cell: false, + allow_static_ptrs: true, + } + } + Some(mutbl) => CtfeValidationMode::Static { mutbl }, // a `static` + None => { + // 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 } } - } else { - CtfeValidationMode::Const { inner, allow_static_ptrs: false } }; ecx.const_validate_operand(&mplace.into(), path, &mut ref_tracking, mode)?; inner = true; diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 6947ace17c5..6ee29063349 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -723,7 +723,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, && ty.is_freeze(*ecx.tcx, ecx.param_env) { let place = ecx.ref_to_mplace(val)?; - let new_place = place.map_provenance(|p| p.map(CtfeProvenance::as_immutable)); + let new_place = place.map_provenance(CtfeProvenance::as_immutable); Ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout)) } else { Ok(val.clone()) diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 171cc89d6ad..c07db2773c1 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use rustc_errors::{ DiagCtxt, DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, IntoDiagnostic, Level, @@ -13,12 +15,24 @@ use rustc_middle::ty::{self, Ty}; use rustc_span::Span; use rustc_target::abi::call::AdjustForForeignAbiError; use rustc_target::abi::{Size, WrappingRange}; +use rustc_type_ir::Mutability; + +use crate::interpret::InternKind; #[derive(Diagnostic)] #[diag(const_eval_dangling_ptr_in_final)] pub(crate) struct DanglingPtrInFinal { #[primary_span] pub span: Span, + pub kind: InternKind, +} + +#[derive(Diagnostic)] +#[diag(const_eval_mutable_ptr_in_final)] +pub(crate) struct MutablePtrInFinal { + #[primary_span] + pub span: Span, + pub kind: InternKind, } #[derive(Diagnostic)] @@ -195,14 +209,6 @@ 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] @@ -615,18 +621,16 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_static, PtrToStatic { ptr_kind: PointerKind::Ref } => const_eval_validation_ref_to_static, - PtrToMut { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_mut, - PtrToMut { ptr_kind: PointerKind::Ref } => const_eval_validation_ref_to_mut, - PointerAsInt { .. } => const_eval_validation_pointer_as_int, PartialPointer => const_eval_validation_partial_pointer, MutableRefInConst => const_eval_validation_mutable_ref_in_const, + MutableRefToImmutable => const_eval_validation_mutable_ref_to_immutable, NullFnPtr => const_eval_validation_null_fn_ptr, NeverVal => const_eval_validation_never_val, NullablePtrOutOfRange { .. } => const_eval_validation_nullable_ptr_out_of_range, PtrOutOfRange { .. } => const_eval_validation_ptr_out_of_range, OutOfRange { .. } => const_eval_validation_out_of_range, - UnsafeCell => const_eval_validation_unsafe_cell, + UnsafeCellInImmutable => const_eval_validation_unsafe_cell, UninhabitedVal { .. } => const_eval_validation_uninhabited_val, InvalidEnumTag { .. } => const_eval_validation_invalid_enum_tag, UninhabitedEnumVariant => const_eval_validation_uninhabited_enum_variant, @@ -772,11 +776,11 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { } NullPtr { .. } | PtrToStatic { .. } - | PtrToMut { .. } | MutableRefInConst + | MutableRefToImmutable | NullFnPtr | NeverVal - | UnsafeCell + | UnsafeCellInImmutable | InvalidMetaSliceTooLarge { .. } | InvalidMetaTooLarge { .. } | DanglingPtrUseAfterFree { .. } @@ -905,3 +909,14 @@ impl ReportErrorExt for ResourceExhaustionInfo { } fn add_args<G: EmissionGuarantee>(self, _: &DiagCtxt, _: &mut DiagnosticBuilder<'_, G>) {} } + +impl rustc_errors::IntoDiagnosticArg for InternKind { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Borrowed(match self { + InternKind::Static(Mutability::Not) => "static", + InternKind::Static(Mutability::Mut) => "static_mut", + InternKind::Constant => "const", + InternKind::Promoted => "promoted", + })) + } +} diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs index 202819ee633..a5a94e80b66 100644 --- a/compiler/rustc_const_eval/src/interpret/intern.rs +++ b/compiler/rustc_const_eval/src/interpret/intern.rs @@ -5,30 +5,24 @@ //! //! In principle, this is not very complicated: we recursively walk the final value, follow all the //! pointers, and move all reachable allocations to the global `tcx` memory. The only complication -//! is picking the right mutability for the allocations in a `static` initializer: we want to make -//! as many allocations as possible immutable so LLVM can put them into read-only memory. At the -//! same time, we need to make memory that could be mutated by the program mutable to avoid -//! incorrect compilations. To achieve this, we do a type-based traversal of the final value, -//! tracking mutable and shared references and `UnsafeCell` to determine the current mutability. -//! (In principle, we could skip this type-based part for `const` and promoteds, as they need to be -//! always immutable. At least for `const` however we use this opportunity to reject any `const` -//! that contains allocations whose mutability we cannot identify.) +//! is picking the right mutability: the outermost allocation generally has a clear mutability, but +//! what about the other allocations it points to that have also been created with this value? We +//! don't want to do guesswork here. The rules are: `static`, `const`, and promoted can only create +//! immutable allocations that way. `static mut` can be initialized with expressions like `&mut 42`, +//! so all inner allocations are marked mutable. Some of them could potentially be made immutable, +//! but that would require relying on type information, and given how many ways Rust has to lie +//! about type information, we want to avoid doing that. -use super::validity::RefTracking; -use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; +use rustc_ast::Mutability; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_middle::mir::interpret::{CtfeProvenance, InterpResult}; -use rustc_middle::ty::{self, layout::TyAndLayout, Ty}; - -use rustc_ast::Mutability; +use rustc_middle::ty::layout::TyAndLayout; -use super::{ - AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy, Projectable, - ValueVisitor, -}; +use super::{AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy}; use crate::const_eval; -use crate::errors::{DanglingPtrInFinal, UnsupportedUntypedPointer}; +use crate::errors::{DanglingPtrInFinal, MutablePtrInFinal}; pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine< 'mir, @@ -41,271 +35,44 @@ pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine< MemoryMap = FxIndexMap<AllocId, (MemoryKind<T>, Allocation)>, >; -struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>> { - /// The ectx from which we intern. - ecx: &'rt mut InterpCx<'mir, 'tcx, M>, - /// Previously encountered safe references. - ref_tracking: &'rt mut RefTracking<(MPlaceTy<'tcx>, InternMode)>, - /// A list of all encountered allocations. After type-based interning, we traverse this list to - /// also intern allocations that are only referenced by a raw pointer or inside a union. - leftover_allocations: &'rt mut FxIndexSet<AllocId>, - /// The root kind of the value that we're looking at. This field is never mutated for a - /// particular allocation. It is primarily used to make as many allocations as possible - /// read-only so LLVM can place them in const memory. - mode: InternMode, - /// This field stores whether we are *currently* inside an `UnsafeCell`. This can affect - /// the intern mode of references we encounter. - inside_unsafe_cell: bool, -} - -#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)] -enum InternMode { - /// A static and its current mutability. Below shared references inside a `static mut`, - /// this is *immutable*, and below mutable references inside an `UnsafeCell`, this - /// is *mutable*. - Static(hir::Mutability), - /// A `const`. - Const, -} - -/// Signalling data structure to ensure we don't recurse -/// into the memory of other constants or statics -struct IsStaticOrFn; - -/// Intern an allocation without looking at its children. -/// `mode` is the mode of the environment where we found this pointer. -/// `mutability` is the mutability of the place to be interned; even if that says -/// `immutable` things might become mutable if `ty` is not frozen. -/// `ty` can be `None` if there is no potential interior mutability -/// to account for (e.g. for vtables). -fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>( +/// Intern an allocation. Returns `Err` if the allocation does not exist in the local memory. +/// +/// `mutability` can be used to force immutable interning: if it is `Mutability::Not`, the +/// allocation is interned immutably; if it is `Mutability::Mut`, then the allocation *must be* +/// already mutable (as a sanity check). +/// +/// `recursive_alloc` is called for all recursively encountered allocations. +fn intern_shallow<'rt, 'mir, 'tcx, T, M: CompileTimeMachine<'mir, 'tcx, T>>( ecx: &'rt mut InterpCx<'mir, 'tcx, M>, - leftover_allocations: &'rt mut FxIndexSet<AllocId>, alloc_id: AllocId, - mode: InternMode, - ty: Option<Ty<'tcx>>, -) -> Option<IsStaticOrFn> { - trace!("intern_shallow {:?} with {:?}", alloc_id, mode); + mutability: Mutability, + mut recursive_alloc: impl FnMut(&InterpCx<'mir, 'tcx, M>, CtfeProvenance), +) -> Result<(), ()> { + trace!("intern_shallow {:?}", alloc_id); // remove allocation - let tcx = ecx.tcx; - let Some((kind, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else { - // Pointer not found in local memory map. It is either a pointer to the global - // map, or dangling. - // If the pointer is dangling (neither in local nor global memory), we leave it - // to validation to error -- it has the much better error messages, pointing out where - // in the value the dangling reference lies. - // The `span_delayed_bug` ensures that we don't forget such a check in validation. - if tcx.try_get_global_alloc(alloc_id).is_none() { - tcx.dcx().span_delayed_bug(ecx.tcx.span, "tried to intern dangling pointer"); - } - // treat dangling pointers like other statics - // just to stop trying to recurse into them - return Some(IsStaticOrFn); + let Some((_kind, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else { + return Err(()); }; - // This match is just a canary for future changes to `MemoryKind`, which most likely need - // changes in this function. - match kind { - MemoryKind::Stack - | MemoryKind::Machine(const_eval::MemoryKind::Heap) - | MemoryKind::CallerLocation => {} - } // Set allocation mutability as appropriate. This is used by LLVM to put things into // read-only memory, and also by Miri when evaluating other globals that // access this one. - if let InternMode::Static(mutability) = mode { - // For this, we need to take into account `UnsafeCell`. When `ty` is `None`, we assume - // no interior mutability. - let frozen = ty.map_or(true, |ty| ty.is_freeze(*ecx.tcx, ecx.param_env)); - // For statics, allocation mutability is the combination of place mutability and - // type mutability. - // The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere. - let immutable = mutability == Mutability::Not && frozen; - if immutable { + match mutability { + Mutability::Not => { alloc.mutability = Mutability::Not; - } else { - // Just making sure we are not "upgrading" an immutable allocation to mutable. + } + Mutability::Mut => { + // This must be already mutable, we won't "un-freeze" allocations ever. assert_eq!(alloc.mutability, Mutability::Mut); } - } else { - // No matter what, *constants are never mutable*. Mutating them is UB. - // See const_eval::machine::MemoryExtra::can_access_statics for why - // immutability is so important. - - // Validation will ensure that there is no `UnsafeCell` on an immutable allocation. - alloc.mutability = Mutability::Not; - }; - // link the alloc id to the actual allocation - leftover_allocations.extend(alloc.provenance().ptrs().iter().map(|&(_, prov)| prov.alloc_id())); - let alloc = tcx.mk_const_alloc(alloc); - tcx.set_alloc_id_memory(alloc_id, alloc); - None -} - -impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>> - InternVisitor<'rt, 'mir, 'tcx, M> -{ - fn intern_shallow( - &mut self, - alloc_id: AllocId, - mode: InternMode, - ty: Option<Ty<'tcx>>, - ) -> Option<IsStaticOrFn> { - intern_shallow(self.ecx, self.leftover_allocations, alloc_id, mode, ty) } -} - -impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>> - ValueVisitor<'mir, 'tcx, M> for InternVisitor<'rt, 'mir, 'tcx, M> -{ - type V = MPlaceTy<'tcx>; - - #[inline(always)] - fn ecx(&self) -> &InterpCx<'mir, 'tcx, M> { - self.ecx - } - - fn visit_value(&mut self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx> { - // Handle Reference types, as these are the only types with provenance supported by const eval. - // Raw pointers (and boxes) are handled by the `leftover_allocations` logic. - let tcx = self.ecx.tcx; - let ty = mplace.layout.ty; - if let ty::Ref(_, referenced_ty, ref_mutability) = *ty.kind() { - let value = self.ecx.read_immediate(mplace)?; - let mplace = self.ecx.ref_to_mplace(&value)?; - assert_eq!(mplace.layout.ty, referenced_ty); - // Handle trait object vtables. - if let ty::Dynamic(_, _, ty::Dyn) = - tcx.struct_tail_erasing_lifetimes(referenced_ty, self.ecx.param_env).kind() - { - let ptr = mplace.meta().unwrap_meta().to_pointer(&tcx)?; - if let Some(prov) = ptr.provenance { - // Explicitly choose const mode here, since vtables are immutable, even - // if the reference of the fat pointer is mutable. - self.intern_shallow(prov.alloc_id(), InternMode::Const, None); - } else { - // Validation will error (with a better message) on an invalid vtable pointer. - // Let validation show the error message, but make sure it *does* error. - tcx.dcx() - .span_delayed_bug(tcx.span, "vtables pointers cannot be integer pointers"); - } - } - // Check if we have encountered this pointer+layout combination before. - // Only recurse for allocation-backed pointers. - if let Some(prov) = mplace.ptr().provenance { - // Compute the mode with which we intern this. Our goal here is to make as many - // statics as we can immutable so they can be placed in read-only memory by LLVM. - let ref_mode = match self.mode { - InternMode::Static(mutbl) => { - // In statics, merge outer mutability with reference mutability and - // take into account whether we are in an `UnsafeCell`. - - // The only way a mutable reference actually works as a mutable reference is - // by being in a `static mut` directly or behind another mutable reference. - // If there's an immutable reference or we are inside a `static`, then our - // mutable reference is equivalent to an immutable one. As an example: - // `&&mut Foo` is semantically equivalent to `&&Foo` - match ref_mutability { - _ if self.inside_unsafe_cell => { - // Inside an `UnsafeCell` is like inside a `static mut`, the "outer" - // mutability does not matter. - InternMode::Static(ref_mutability) - } - Mutability::Not => { - // A shared reference, things become immutable. - // We do *not* consider `freeze` here: `intern_shallow` considers - // `freeze` for the actual mutability of this allocation; the intern - // mode for references contained in this allocation is tracked more - // precisely when traversing the referenced data (by tracking - // `UnsafeCell`). This makes sure that `&(&i32, &Cell<i32>)` still - // has the left inner reference interned into a read-only - // allocation. - InternMode::Static(Mutability::Not) - } - Mutability::Mut => { - // Mutable reference. - InternMode::Static(mutbl) - } - } - } - InternMode::Const => { - // Ignore `UnsafeCell`, everything is immutable. Validity does some sanity - // checking for mutable references that we encounter -- they must all be - // ZST. - InternMode::Const - } - }; - match self.intern_shallow(prov.alloc_id(), ref_mode, Some(referenced_ty)) { - // No need to recurse, these are interned already and statics may have - // cycles, so we don't want to recurse there - Some(IsStaticOrFn) => {} - // intern everything referenced by this value. The mutability is taken from the - // reference. It is checked above that mutable references only happen in - // `static mut` - None => self.ref_tracking.track((mplace, ref_mode), || ()), - } - } - Ok(()) - } else { - // Not a reference. Check if we want to recurse. - let is_walk_needed = |mplace: &MPlaceTy<'tcx>| -> InterpResult<'tcx, bool> { - // ZSTs cannot contain pointers, we can avoid the interning walk. - if mplace.layout.is_zst() { - return Ok(false); - } - - // Now, check whether this allocation could contain references. - // - // Note, this check may sometimes not be cheap, so we only do it when the walk we'd like - // to avoid could be expensive: on the potentially larger types, arrays and slices, - // rather than on all aggregates unconditionally. - if matches!(mplace.layout.ty.kind(), ty::Array(..) | ty::Slice(..)) { - let Some((size, _align)) = self.ecx.size_and_align_of_mplace(mplace)? else { - // We do the walk if we can't determine the size of the mplace: we may be - // dealing with extern types here in the future. - return Ok(true); - }; - - // If there is no provenance in this allocation, it does not contain references - // that point to another allocation, and we can avoid the interning walk. - if let Some(alloc) = self.ecx.get_ptr_alloc(mplace.ptr(), size)? { - if !alloc.has_provenance() { - return Ok(false); - } - } else { - // We're encountering a ZST here, and can avoid the walk as well. - return Ok(false); - } - } - - // In the general case, we do the walk. - Ok(true) - }; - - // If this allocation contains no references to intern, we avoid the potentially costly - // walk. - // - // We can do this before the checks for interior mutability below, because only references - // are relevant in that situation, and we're checking if there are any here. - if !is_walk_needed(mplace)? { - return Ok(()); - } - - if let Some(def) = mplace.layout.ty.ty_adt_def() { - if def.is_unsafe_cell() { - // We are crossing over an `UnsafeCell`, we can mutate again. This means that - // References we encounter inside here are interned as pointing to mutable - // allocations. - // Remember the `old` value to handle nested `UnsafeCell`. - let old = std::mem::replace(&mut self.inside_unsafe_cell, true); - let walked = self.walk_value(mplace); - self.inside_unsafe_cell = old; - return walked; - } - } - - self.walk_value(mplace) - } + // record child allocations + for &(_, prov) in alloc.provenance().ptrs().iter() { + recursive_alloc(ecx, prov); } + // link the alloc id to the actual allocation + let alloc = ecx.tcx.mk_const_alloc(alloc); + ecx.tcx.set_alloc_id_memory(alloc_id, alloc); + Ok(()) } /// How a constant value should be interned. @@ -332,122 +99,105 @@ pub fn intern_const_alloc_recursive< intern_kind: InternKind, ret: &MPlaceTy<'tcx>, ) -> Result<(), ErrorGuaranteed> { - let tcx = ecx.tcx; - let base_intern_mode = match intern_kind { - InternKind::Static(mutbl) => InternMode::Static(mutbl), - // `Constant` includes array lengths. - InternKind::Constant | InternKind::Promoted => InternMode::Const, - }; - - // Type based interning. - // `ref_tracking` tracks typed references we have already interned and still need to crawl for - // more typed information inside them. - // `leftover_allocations` collects *all* allocations we see, because some might not - // be available in a typed way. They get interned at the end. - let mut ref_tracking = RefTracking::empty(); - let leftover_allocations = &mut FxIndexSet::default(); - - // start with the outermost allocation - intern_shallow( - ecx, - leftover_allocations, - // The outermost allocation must exist, because we allocated it with - // `Memory::allocate`. - ret.ptr().provenance.unwrap().alloc_id(), - base_intern_mode, - Some(ret.layout.ty), - ); - - ref_tracking.track((ret.clone(), base_intern_mode), || ()); - - while let Some(((mplace, mode), _)) = ref_tracking.todo.pop() { - let res = InternVisitor { - ref_tracking: &mut ref_tracking, - ecx, - mode, - leftover_allocations, - inside_unsafe_cell: false, + // We are interning recursively, and for mutability we are distinguishing the "root" allocation + // that we are starting in, and all other allocations that we are encountering recursively. + let (base_mutability, inner_mutability) = match intern_kind { + InternKind::Constant | InternKind::Promoted => { + // Completely immutable. Interning anything mutably here can only lead to unsoundness, + // since all consts are conceptually independent values but share the same underlying + // memory. + (Mutability::Not, Mutability::Not) } - .visit_value(&mplace); - // We deliberately *ignore* interpreter errors here. When there is a problem, the remaining - // references are "leftover"-interned, and later validation will show a proper error - // and point at the right part of the value causing the problem. - match res { - Ok(()) => {} - Err(error) => { - ecx.tcx.dcx().span_delayed_bug( - ecx.tcx.span, - format!( - "error during interning should later cause validation failure: {}", - ecx.format_error(error), - ), - ); - } + InternKind::Static(Mutability::Not) => { + ( + // Outermost allocation is mutable if `!Freeze`. + if ret.layout.ty.is_freeze(*ecx.tcx, ecx.param_env) { + Mutability::Not + } else { + Mutability::Mut + }, + // Inner allocations are never mutable. They can only arise via the "tail + // expression" / "outer scope" rule, and we treat them consistently with `const`. + Mutability::Not, + ) } - } - - // Intern the rest of the allocations as mutable. These might be inside unions, padding, raw - // pointers, ... So we can't intern them according to their type rules + InternKind::Static(Mutability::Mut) => { + // Just make everything mutable. We accept code like + // `static mut X = &mut [42]`, so even inner allocations need to be mutable. + (Mutability::Mut, Mutability::Mut) + } + }; - let mut todo: Vec<_> = leftover_allocations.iter().cloned().collect(); - debug!(?todo); - debug!("dead_alloc_map: {:#?}", ecx.memory.dead_alloc_map); - while let Some(alloc_id) = todo.pop() { - if let Some((_, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) { - // We can't call the `intern_shallow` method here, as its logic is tailored to safe - // references and a `leftover_allocations` set (where we only have a todo-list here). - // So we hand-roll the interning logic here again. - match intern_kind { - // Statics may point to mutable allocations. - // Even for immutable statics it would be ok to have mutable allocations behind - // raw pointers, e.g. for `static FOO: *const AtomicUsize = &AtomicUsize::new(42)`. - InternKind::Static(_) => {} - // Raw pointers in promoteds may only point to immutable things so we mark - // everything as immutable. - // It is UB to mutate through a raw pointer obtained via an immutable reference: - // Since all references and pointers inside a promoted must by their very definition - // be created from an immutable reference (and promotion also excludes interior - // mutability), mutating through them would be UB. - // There's no way we can check whether the user is using raw pointers correctly, - // so all we can do is mark this as immutable here. - InternKind::Promoted => { - // See const_eval::machine::MemoryExtra::can_access_statics for why - // 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 => { - ecx.tcx.dcx().emit_err(UnsupportedUntypedPointer { span: ecx.tcx.span }); - // For better errors later, mark the allocation as immutable. - alloc.mutability = Mutability::Not; - } - } - let alloc = tcx.mk_const_alloc(alloc); - tcx.set_alloc_id_memory(alloc_id, alloc); - for &(_, prov) in alloc.inner().provenance().ptrs().iter() { - let alloc_id = prov.alloc_id(); - if leftover_allocations.insert(alloc_id) { - todo.push(alloc_id); + // Initialize recursive interning. + let base_alloc_id = ret.ptr().provenance.unwrap().alloc_id(); + let mut todo = vec![(base_alloc_id, base_mutability)]; + // We need to distinguish "has just been interned" from "was already in `tcx`", + // so we track this in a separate set. + let mut just_interned = FxHashSet::default(); + // Whether we encountered a bad mutable pointer. + // We want to first report "dangling" and then "mutable", so we need to delay reporting these + // errors. + let mut found_bad_mutable_pointer = false; + + // Keep interning as long as there are things to intern. + // We show errors if there are dangling pointers, or mutable pointers in immutable contexts + // (i.e., everything except for `static mut`). When these errors affect references, it is + // unfortunate that we show these errors here and not during validation, since validation can + // show much nicer errors. However, we do need these checks to be run on all pointers, including + // raw pointers, so we cannot rely on validation to catch them -- and since interning runs + // before validation, and interning doesn't know the type of anything, this means we can't show + // better errors. Maybe we should consider doing validation before interning in the future. + while let Some((alloc_id, mutability)) = todo.pop() { + if ecx.tcx.try_get_global_alloc(alloc_id).is_some() { + // Already interned. + debug_assert!(!ecx.memory.alloc_map.contains_key(&alloc_id)); + continue; + } + just_interned.insert(alloc_id); + intern_shallow(ecx, alloc_id, mutability, |ecx, prov| { + let alloc_id = prov.alloc_id(); + if intern_kind != InternKind::Promoted + && inner_mutability == Mutability::Not + && !prov.immutable() + { + if ecx.tcx.try_get_global_alloc(alloc_id).is_some() + && !just_interned.contains(&alloc_id) + { + // This is a pointer to some memory from another constant. We encounter mutable + // pointers to such memory since we do not always track immutability through + // these "global" pointers. Allowing them is harmless; the point of these checks + // during interning is to justify why we intern the *new* allocations immutably, + // so we can completely ignore existing allocations. We also don't need to add + // this to the todo list, since after all it is already interned. + return; } + // Found a mutable pointer inside a const where inner allocations should be + // immutable. We exclude promoteds from this, since things like `&mut []` and + // `&None::<Cell<i32>>` lead to promotion that can produce mutable pointers. We rely + // on the promotion analysis not screwing up to ensure that it is sound to intern + // promoteds as immutable. + found_bad_mutable_pointer = true; } - } 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.dcx().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 - // marked as dangling by local memory. That should be impossible. - span_bug!(ecx.tcx.span, "encountered unknown alloc id {:?}", alloc_id); - } + // It is tempting to intern as immutable if `prov.immutable()`. However, there + // might be multiple pointers to the same allocation, and if *at least one* of + // them is mutable, the allocation must be interned mutably. We will intern the + // allocation when we encounter the first pointer. Therefore we always intern + // with `inner_mutability`, and furthermore we ensured above that if that is + // "immutable", then there are *no* mutable pointers anywhere in the newly + // interned memory. + todo.push((alloc_id, inner_mutability)); + }) + .map_err(|()| { + ecx.tcx.dcx().emit_err(DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind }) + })?; } + if found_bad_mutable_pointer { + return Err(ecx + .tcx + .dcx() + .emit_err(MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind })); + } + Ok(()) } @@ -462,29 +212,18 @@ pub fn intern_const_alloc_for_constprop< ecx: &mut InterpCx<'mir, 'tcx, M>, alloc_id: AllocId, ) -> InterpResult<'tcx, ()> { - // Move allocation to `tcx`. - let Some((_, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else { - // Pointer not found in local memory map. It is either a pointer to the global - // map, or dangling. - if ecx.tcx.try_get_global_alloc(alloc_id).is_none() { - throw_ub!(DeadLocal) - } + if ecx.tcx.try_get_global_alloc(alloc_id).is_some() { // The constant is already in global memory. Do nothing. return Ok(()); - }; - - alloc.mutability = Mutability::Not; - - // We are not doing recursive interning, so we don't currently support provenance. - // (If this assertion ever triggers, we should just implement a - // proper recursive interning loop.) - assert!(alloc.provenance().ptrs().is_empty()); - - // Link the alloc id to the actual allocation - let alloc = ecx.tcx.mk_const_alloc(alloc); - ecx.tcx.set_alloc_id_memory(alloc_id, alloc); - - Ok(()) + } + // Move allocation to `tcx`. + intern_shallow(ecx, alloc_id, Mutability::Not, |_ecx, _| { + // We are not doing recursive interning, so we don't currently support provenance. + // (If this assertion ever triggers, we should just implement a + // proper recursive interning loop -- or just call `intern_const_alloc_recursive`. + panic!("`intern_const_alloc_for_constprop` called on allocation with nested provenance") + }) + .map_err(|()| err_ub!(DeadLocal).into()) } impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>> @@ -504,12 +243,16 @@ impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>> // `allocate` picks a fresh AllocId that we will associate with its data below. let dest = self.allocate(layout, MemoryKind::Stack)?; f(self, &dest.clone().into())?; - let mut alloc = - self.memory.alloc_map.remove(&dest.ptr().provenance.unwrap().alloc_id()).unwrap().1; - alloc.mutability = Mutability::Not; - let alloc = self.tcx.mk_const_alloc(alloc); let alloc_id = dest.ptr().provenance.unwrap().alloc_id(); // this was just allocated, it must have provenance - self.tcx.set_alloc_id_memory(alloc_id, alloc); + intern_shallow(self, alloc_id, Mutability::Not, |ecx, prov| { + // We are not doing recursive interning, so we don't currently support provenance. + // (If this assertion ever triggers, we should just implement a + // proper recursive interning loop -- or just call `intern_const_alloc_recursive`. + if !ecx.tcx.try_get_global_alloc(prov.alloc_id()).is_some() { + panic!("`intern_with_temp_alloc` with nested allocations"); + } + }) + .unwrap(); Ok(alloc_id) } } diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index 639b269ac25..b39efad61bb 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -62,8 +62,8 @@ pub(super) struct MemPlace<Prov: Provenance = CtfeProvenance> { impl<Prov: Provenance> MemPlace<Prov> { /// Adjust the provenance of the main pointer (metadata is unaffected). - pub fn map_provenance(self, f: impl FnOnce(Option<Prov>) -> Option<Prov>) -> Self { - MemPlace { ptr: self.ptr.map_provenance(f), ..self } + pub fn map_provenance(self, f: impl FnOnce(Prov) -> Prov) -> Self { + MemPlace { ptr: self.ptr.map_provenance(|p| p.map(f)), ..self } } /// Turn a mplace into a (thin or wide) pointer, as a reference, pointing to the same space. @@ -128,7 +128,7 @@ impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> { } /// Adjust the provenance of the main pointer (metadata is unaffected). - pub fn map_provenance(self, f: impl FnOnce(Option<Prov>) -> Option<Prov>) -> Self { + pub fn map_provenance(self, f: impl FnOnce(Prov) -> Prov) -> Self { MPlaceTy { mplace: self.mplace.map_provenance(f), ..self } } diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 8b44b87647d..f028d7802ae 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -9,12 +9,13 @@ use std::num::NonZeroUsize; use either::{Left, Right}; +use hir::def::DefKind; use rustc_ast::Mutability; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_middle::mir::interpret::{ - ExpectedKind, InterpError, InvalidMetaKind, Misalignment, PointerKind, ValidationErrorInfo, - ValidationErrorKind, ValidationErrorKind::*, + ExpectedKind, InterpError, InvalidMetaKind, Misalignment, PointerKind, Provenance, + ValidationErrorInfo, ValidationErrorKind, ValidationErrorKind::*, }; use rustc_middle::ty; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; @@ -123,15 +124,41 @@ pub enum PathElem { } /// Extra things to check for during validation of CTFE results. +#[derive(Copy, Clone)] pub enum CtfeValidationMode { - /// Regular validation, nothing special happening. - Regular, - /// Validation of a `const`. - /// `inner` says if this is an inner, indirect allocation (as opposed to the top-level const - /// allocation). Being an inner allocation makes a difference because the top-level allocation - /// of a `const` is copied for each use, but the inner allocations are implicitly shared. + /// Validation of a `static` + Static { mutbl: Mutability }, + /// Validation of a `const` (including promoteds). + /// `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 { inner: bool, allow_static_ptrs: bool }, + Const { allow_immutable_unsafe_cell: bool, allow_static_ptrs: bool }, +} + +impl CtfeValidationMode { + fn allow_immutable_unsafe_cell(self) -> bool { + match self { + CtfeValidationMode::Static { .. } => false, + CtfeValidationMode::Const { allow_immutable_unsafe_cell, .. } => { + allow_immutable_unsafe_cell + } + } + } + + 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, + CtfeValidationMode::Const { .. } => false, + } + } } /// State for tracking recursive validation of references @@ -418,6 +445,22 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' } // Recursive checking if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() { + // Determine whether this pointer expects to be pointing to something mutable. + let ptr_expected_mutbl = match ptr_kind { + PointerKind::Box => Mutability::Mut, + PointerKind::Ref => { + let tam = value.layout.ty.builtin_deref(false).unwrap(); + // ZST never require mutability. We do not take into account interior mutability + // here since we cannot know if there really is an `UnsafeCell` inside + // `Option<UnsafeCell>` -- so we check that in the recursive descent behind this + // reference. + if size == Size::ZERO || tam.mutbl == Mutability::Not { + Mutability::Not + } else { + Mutability::Mut + } + } + }; // Proceed recursively even for ZST, no reason to skip them! // `!` is a ZST and we want to validate it. if let Ok((alloc_id, _offset, _prov)) = self.ecx.ptr_try_get_alloc_id(place.ptr()) { @@ -428,16 +471,29 @@ 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 matches!( - self.ctfe_mode, - Some(CtfeValidationMode::Const { allow_static_ptrs: false, .. }) - ) { + 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 }); } + // 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()) + { + 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. @@ -454,14 +510,29 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' if alloc.inner().mutability == Mutability::Mut && matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) { - // 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, PtrToMut { ptr_kind }); + // 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`" + ); + } + if ptr_expected_mutbl == Mutability::Mut + && alloc.inner().mutability == Mutability::Not + { + throw_validation_failure!(self.path, MutableRefToImmutable); } } - // Nothing to check for these. - None | Some(GlobalAlloc::Function(..) | GlobalAlloc::VTable(..)) => {} + Some(GlobalAlloc::Function(..) | GlobalAlloc::VTable(..)) => { + // These are immutable, we better don't allow mutable pointers here. + if ptr_expected_mutbl == Mutability::Mut { + throw_validation_failure!(self.path, MutableRefToImmutable); + } + } + // Dangling, already handled. + None => bug!(), } } let path = &self.path; @@ -532,11 +603,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' Ok(true) } ty::Ref(_, ty, mutbl) => { - if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) + if self.ctfe_mode.is_some_and(|c| !c.may_contain_mutable_ref()) && *mutbl == Mutability::Mut { - // A mutable reference inside a const? That does not seem right (except if it is - // a ZST). let layout = self.ecx.layout_of(*ty)?; if !layout.is_zst() { throw_validation_failure!(self.path, MutableRefInConst); @@ -642,6 +711,19 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' ) } } + + fn in_mutable_memory(&self, op: &OpTy<'tcx, M::Provenance>) -> bool { + if let Some(mplace) = op.as_mplace_or_imm().left() { + if let Some(alloc_id) = mplace.ptr().provenance.and_then(|p| p.get_alloc_id()) { + if self.ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner().mutability + == Mutability::Mut + { + return true; + } + } + } + false + } } impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> @@ -705,10 +787,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> op: &OpTy<'tcx, M::Provenance>, _fields: NonZeroUsize, ) -> InterpResult<'tcx> { - // 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); + // Special check for CTFE validation, preventing `UnsafeCell` inside unions in immutable memory. + if self.ctfe_mode.is_some_and(|c| !c.allow_immutable_unsafe_cell()) { + if !op.layout.is_zst() && !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) { + if !self.in_mutable_memory(op) { + throw_validation_failure!(self.path, UnsafeCellInImmutable); + } } } Ok(()) @@ -730,11 +814,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> } // Special check preventing `UnsafeCell` in the inner part of constants - if let Some(def) = op.layout.ty.ty_adt_def() { - if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) + if self.ctfe_mode.is_some_and(|c| !c.allow_immutable_unsafe_cell()) { + if !op.layout.is_zst() + && let Some(def) = op.layout.ty.ty_adt_def() && def.is_unsafe_cell() { - throw_validation_failure!(self.path, UnsafeCell); + if !self.in_mutable_memory(op) { + throw_validation_failure!(self.path, UnsafeCellInImmutable); + } } } diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index 4a3cfd50b44..36a315b8f30 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -27,6 +27,7 @@ fn alloc_caller_location<'mir, 'tcx>( // See https://github.com/rust-lang/rust/pull/89920#discussion_r730012398 ecx.allocate_str("<redacted>", MemoryKind::CallerLocation, Mutability::Not).unwrap() }; + let file = file.map_provenance(CtfeProvenance::as_immutable); let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) }; let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) }; |
