From 6d0220daee66e242c2f88e547748341b46dd5c31 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 14 Oct 2023 22:50:35 +0200 Subject: [PATCH 1/3] interning: decide about mutability purely based on the kind of interning, not the types we see --- .../rustc_const_eval/src/interpret/intern.rs | 121 ++++++++++-------- 1 file changed, 67 insertions(+), 54 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs index 8c0009cfdfd02..5f4cf2bc01aef 100644 --- a/compiler/rustc_const_eval/src/interpret/intern.rs +++ b/compiler/rustc_const_eval/src/interpret/intern.rs @@ -5,21 +5,20 @@ //! //! 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_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_middle::mir::interpret::InterpResult; -use rustc_middle::ty::{self, layout::TyAndLayout, Ty}; +use rustc_middle::ty::{self, layout::TyAndLayout}; use rustc_ast::Mutability; @@ -56,6 +55,8 @@ struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_ev /// This field stores whether we are *currently* inside an `UnsafeCell`. This can affect /// the intern mode of references we encounter. inside_unsafe_cell: bool, + /// The mutability with which to intern the pointers we find. + intern_mutability: Mutability, } #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)] @@ -82,10 +83,9 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval: ecx: &'rt mut InterpCx<'mir, 'tcx, M>, leftover_allocations: &'rt mut FxIndexSet, alloc_id: AllocId, - mode: InternMode, - ty: Option>, + mutability: Mutability, ) -> Option { - trace!("intern_shallow {:?} with {:?}", alloc_id, mode); + trace!("intern_shallow {:?}", alloc_id); // remove allocation let tcx = ecx.tcx; let Some((kind, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else { @@ -112,28 +112,15 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval: // 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(|&(_, alloc_id)| alloc_id)); let alloc = tcx.mk_const_alloc(alloc); @@ -144,13 +131,8 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval: 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>, - ) -> Option { - intern_shallow(self.ecx, self.leftover_allocations, alloc_id, mode, ty) + fn intern_shallow(&mut self, alloc_id: AllocId) -> Option { + intern_shallow(self.ecx, self.leftover_allocations, alloc_id, self.intern_mutability) } } @@ -181,7 +163,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory if let Some(alloc_id) = ptr.provenance { // Explicitly choose const mode here, since vtables are immutable, even // if the reference of the fat pointer is mutable. - self.intern_shallow(alloc_id, InternMode::Const, None); + self.intern_shallow(alloc_id); } 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. @@ -234,7 +216,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory InternMode::Const } }; - match self.intern_shallow(alloc_id, ref_mode, Some(referenced_ty)) { + match self.intern_shallow(alloc_id) { // No need to recurse, these are interned already and statics may have // cycles, so we don't want to recurse there Some(IsStaticOrFn) => {} @@ -332,12 +314,36 @@ 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, }; + // 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. + (Mutability::Not, Mutability::Not) + } + 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. + Mutability::Not, + ) + } + 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) + } + }; // Type based interning. // `ref_tracking` tracks typed references we have already interned and still need to crawl for @@ -354,18 +360,22 @@ pub fn intern_const_alloc_recursive< // The outermost allocation must exist, because we allocated it with // `Memory::allocate`. ret.ptr().provenance.unwrap(), - base_intern_mode, - Some(ret.layout.ty), + base_mutability, ); ref_tracking.track((ret.clone(), base_intern_mode), || ()); + // We do a type-based traversal to find more allocations to intern. The interner is currently + // mid-refactoring; eventually the type-based traversal will be replaced but a simple traversal + // of all provenance we see in the allocations, but for now we avoid changing rustc error + // messages or accepting extra programs by keeping the old type-based interner around. while let Some(((mplace, mode), _)) = ref_tracking.todo.pop() { let res = InternVisitor { ref_tracking: &mut ref_tracking, ecx, mode, leftover_allocations, + intern_mutability: inner_mutability, inside_unsafe_cell: false, } .visit_value(&mplace); @@ -394,9 +404,9 @@ pub fn intern_const_alloc_recursive< 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. + // This still needs to be interned. We keep the old logic around here to avoid changing + // rustc behavior; eventually this should all be removed in favor of ignroing all types + // and interning everything with a simple `intern_shallow` loop. match intern_kind { // Statics may point to mutable allocations. // Even for immutable statics it would be ok to have mutable allocations behind @@ -410,11 +420,7 @@ pub fn intern_const_alloc_recursive< // 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; - } + InternKind::Promoted => {} // 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, @@ -425,12 +431,19 @@ pub fn intern_const_alloc_recursive< // drop glue, such as the example above. InternKind::Constant => { ecx.tcx.sess.emit_err(UnsupportedUntypedPointer { span: ecx.tcx.span }); - // For better errors later, mark the allocation as immutable. + } + } + match inner_mutability { + Mutability::Not => { alloc.mutability = Mutability::Not; } + Mutability::Mut => { + // This must be already mutable, we won't "un-freeze" allocations ever. + assert_eq!(alloc.mutability, Mutability::Mut); + } } - let alloc = tcx.mk_const_alloc(alloc); - tcx.set_alloc_id_memory(alloc_id, alloc); + let alloc = ecx.tcx.mk_const_alloc(alloc); + ecx.tcx.set_alloc_id_memory(alloc_id, alloc); for &(_, alloc_id) in alloc.inner().provenance().ptrs().iter() { if leftover_allocations.insert(alloc_id) { todo.push(alloc_id); From 3fd5624f35463cb3f10faf1a2f409e2a2aa98592 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 14 Oct 2023 23:07:59 +0200 Subject: [PATCH 2/3] we don't need intern_mode any more --- .../rustc_const_eval/src/interpret/intern.rs | 89 ++----------------- 1 file changed, 5 insertions(+), 84 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs index 5f4cf2bc01aef..8e691243e6479 100644 --- a/compiler/rustc_const_eval/src/interpret/intern.rs +++ b/compiler/rustc_const_eval/src/interpret/intern.rs @@ -44,31 +44,14 @@ struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_ev /// 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)>, + ref_tracking: &'rt mut RefTracking>, /// 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, - /// 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, /// The mutability with which to intern the pointers we find. intern_mutability: Mutability, } -#[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; @@ -151,7 +134,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory // 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() { + 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); @@ -174,48 +157,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory // Check if we have encountered this pointer+layout combination before. // Only recurse for allocation-backed pointers. if let Some(alloc_id) = 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)` 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(alloc_id) { // No need to recurse, these are interned already and statics may have // cycles, so we don't want to recurse there @@ -223,7 +164,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory // 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), || ()), + None => self.ref_tracking.track(mplace, || ()), } } Ok(()) @@ -272,19 +213,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory 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) } } @@ -314,11 +242,6 @@ pub fn intern_const_alloc_recursive< intern_kind: InternKind, ret: &MPlaceTy<'tcx>, ) -> Result<(), ErrorGuaranteed> { - let base_intern_mode = match intern_kind { - InternKind::Static(mutbl) => InternMode::Static(mutbl), - // `Constant` includes array lengths. - InternKind::Constant | InternKind::Promoted => InternMode::Const, - }; // 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 { @@ -363,20 +286,18 @@ pub fn intern_const_alloc_recursive< base_mutability, ); - ref_tracking.track((ret.clone(), base_intern_mode), || ()); + ref_tracking.track(ret.clone(), || ()); // We do a type-based traversal to find more allocations to intern. The interner is currently // mid-refactoring; eventually the type-based traversal will be replaced but a simple traversal // of all provenance we see in the allocations, but for now we avoid changing rustc error // messages or accepting extra programs by keeping the old type-based interner around. - while let Some(((mplace, mode), _)) = ref_tracking.todo.pop() { + while let Some((mplace, _)) = ref_tracking.todo.pop() { let res = InternVisitor { ref_tracking: &mut ref_tracking, ecx, - mode, leftover_allocations, intern_mutability: inner_mutability, - inside_unsafe_cell: false, } .visit_value(&mplace); // We deliberately *ignore* interpreter errors here. When there is a problem, the remaining From 22106c35e04f0949be51feffcbebd89dc58e58c8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 15 Oct 2023 16:50:34 +0200 Subject: [PATCH 3/3] const validation: ensure we don't have &mut to read-only memory or UnsafeCell in read-only memory --- compiler/rustc_const_eval/messages.ftl | 4 +- .../src/const_eval/eval_queries.rs | 15 ++- compiler/rustc_const_eval/src/errors.rs | 8 +- .../src/interpret/validity.rs | 124 +++++++++++++----- .../rustc_middle/src/mir/interpret/error.rs | 4 +- tests/ui/consts/invalid-union.32bit.stderr | 2 +- tests/ui/consts/invalid-union.64bit.stderr | 2 +- .../mutable_references_err.32bit.stderr | 50 +++++-- .../mutable_references_err.64bit.stderr | 50 +++++-- .../miri_unleashed/mutable_references_err.rs | 7 + .../static-no-inner-interior-mut.32bit.stderr | 87 ++++++++++++ .../static-no-inner-interior-mut.64bit.stderr | 87 ++++++++++++ .../static-no-inner-interior-mut.rs | 31 +++++ 13 files changed, 405 insertions(+), 66 deletions(-) create mode 100644 tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.32bit.stderr create mode 100644 tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.64bit.stderr create mode 100644 tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.rs diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index d23e2a9f3e4e0..d574a778c5d5d 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -438,7 +438,7 @@ 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_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 @@ -456,7 +456,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_to_read_only = writing to {$allocation} which is read-only 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 4adfbe336af33..b096c6eb358ef 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -336,10 +336,21 @@ pub fn eval_to_allocation_raw_provider<'tcx>( let mode = match tcx.static_mutability(cid.instance.def_id()) { Some(_) if cid.promoted.is_some() => { // Promoteds in statics are allowed to point to statics. - CtfeValidationMode::Const { inner, allow_static_ptrs: true } + CtfeValidationMode::Const { + allow_immutable_unsafe_cell: false, + allow_static_ptrs: true, + } } Some(_) => CtfeValidationMode::Regular, // a `static` - None => CtfeValidationMode::Const { inner, allow_static_ptrs: false }, + 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, + } + } }; ecx.const_validate_operand(&mplace.into(), path, &mut ref_tracking, mode)?; inner = true; diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index b1599dd689482..5c9c5cc959a14 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -625,13 +625,13 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { 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, @@ -778,10 +778,10 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { NullPtr { .. } | PtrToStatic { .. } | PtrToMut { .. } - | MutableRefInConst + | MutableRefToImmutable | NullFnPtr | NeverVal - | UnsafeCell + | UnsafeCellInImmutable | InvalidMetaSliceTooLarge { .. } | InvalidMetaTooLarge { .. } | DanglingPtrUseAfterFree { .. } diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 3e023a89648e5..8555b382f5331 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -9,11 +9,12 @@ 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, PointerKind, ValidationErrorInfo, + ExpectedKind, InterpError, InvalidMetaKind, PointerKind, Provenance, ValidationErrorInfo, ValidationErrorKind, ValidationErrorKind::*, }; use rustc_middle::ty; @@ -123,15 +124,34 @@ 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. + /// `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::Regular => false, + CtfeValidationMode::Const { allow_immutable_unsafe_cell, .. } => { + allow_immutable_unsafe_cell + } + } + } + + fn allow_static_ptrs(self) -> bool { + match self { + CtfeValidationMode::Regular => true, // statics can point to statics + CtfeValidationMode::Const { allow_static_ptrs, .. } => allow_static_ptrs, + } + } } /// State for tracking recursive validation of references @@ -412,6 +432,21 @@ 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` -- 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()) { @@ -422,16 +457,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. @@ -453,9 +501,20 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' // and we would catch that here. throw_validation_failure!(self.path, PtrToMut { ptr_kind }); } + 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; @@ -525,17 +584,7 @@ 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 { .. })) - && *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); - } - } + ty::Ref(..) => { self.check_safe_pointer(value, PointerKind::Ref)?; Ok(true) } @@ -636,6 +685,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> @@ -699,10 +761,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, .. })) { + // 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.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) { - throw_validation_failure!(self.path, UnsafeCell); + if !self.in_mutable_memory(op) { + throw_validation_failure!(self.path, UnsafeCellInImmutable); + } } } Ok(()) @@ -724,11 +788,11 @@ 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, .. })) - && def.is_unsafe_cell() - { - throw_validation_failure!(self.path, UnsafeCell); + if self.ctfe_mode.is_some_and(|c| !c.allow_immutable_unsafe_cell()) { + if let Some(def) = op.layout.ty.ty_adt_def() && def.is_unsafe_cell() { + if !self.in_mutable_memory(op) { + throw_validation_failure!(self.path, UnsafeCellInImmutable); + } } } diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 9de40b3f97459..8feb6eeab656f 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -414,13 +414,13 @@ pub enum ValidationErrorKind<'tcx> { PtrToUninhabited { ptr_kind: PointerKind, ty: Ty<'tcx> }, PtrToStatic { ptr_kind: PointerKind }, PtrToMut { ptr_kind: PointerKind }, - MutableRefInConst, + MutableRefToImmutable, + UnsafeCellInImmutable, NullFnPtr, NeverVal, NullablePtrOutOfRange { range: WrappingRange, max_value: u128 }, PtrOutOfRange { range: WrappingRange, max_value: u128 }, OutOfRange { value: String, range: WrappingRange, max_value: u128 }, - UnsafeCell, UninhabitedVal { ty: Ty<'tcx> }, InvalidEnumTag { value: String }, UninhabitedEnumVariant, diff --git a/tests/ui/consts/invalid-union.32bit.stderr b/tests/ui/consts/invalid-union.32bit.stderr index 0c57751cbfa4e..6ac9e8a9482df 100644 --- a/tests/ui/consts/invalid-union.32bit.stderr +++ b/tests/ui/consts/invalid-union.32bit.stderr @@ -2,7 +2,7 @@ error[E0080]: it is undefined behavior to use this value --> $DIR/invalid-union.rs:41:1 | LL | fn main() { - | ^^^^^^^^^ constructing invalid value at ..y..0: encountered `UnsafeCell` in a `const` + | ^^^^^^^^^ constructing invalid value at ..y..0: encountered `UnsafeCell` in read-only memory | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. = note: the raw bytes of the constant (size: 4, align: 4) { diff --git a/tests/ui/consts/invalid-union.64bit.stderr b/tests/ui/consts/invalid-union.64bit.stderr index 6c4d5882158d0..9852ba828f874 100644 --- a/tests/ui/consts/invalid-union.64bit.stderr +++ b/tests/ui/consts/invalid-union.64bit.stderr @@ -2,7 +2,7 @@ error[E0080]: it is undefined behavior to use this value --> $DIR/invalid-union.rs:41:1 | LL | fn main() { - | ^^^^^^^^^ constructing invalid value at ..y..0: encountered `UnsafeCell` in a `const` + | ^^^^^^^^^ constructing invalid value at ..y..0: encountered `UnsafeCell` in read-only memory | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. = note: the raw bytes of the constant (size: 8, align: 8) { diff --git a/tests/ui/consts/miri_unleashed/mutable_references_err.32bit.stderr b/tests/ui/consts/miri_unleashed/mutable_references_err.32bit.stderr index 0ea1792409b8b..358acc83bb82a 100644 --- a/tests/ui/consts/miri_unleashed/mutable_references_err.32bit.stderr +++ b/tests/ui/consts/miri_unleashed/mutable_references_err.32bit.stderr @@ -2,53 +2,79 @@ error[E0080]: it is undefined behavior to use this value --> $DIR/mutable_references_err.rs:15:1 | LL | const MUH: Meh = Meh { - | ^^^^^^^^^^^^^^ constructing invalid value at .x.: encountered `UnsafeCell` in a `const` + | ^^^^^^^^^^^^^^ constructing invalid value at .x.: encountered `UnsafeCell` in read-only memory | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. = note: the raw bytes of the constant (size: 4, align: 4) { - ╾─alloc3──╼ │ ╾──╼ + ╾─alloc4──╼ │ ╾──╼ } error[E0080]: it is undefined behavior to use this value - --> $DIR/mutable_references_err.rs:25:1 + --> $DIR/mutable_references_err.rs:26:1 | LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) }; - | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ...x: encountered `UnsafeCell` in a `const` + | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ...x: encountered `UnsafeCell` in read-only memory | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. = note: the raw bytes of the constant (size: 8, align: 4) { - ╾─alloc7──╼ ╾─alloc8──╼ │ ╾──╼╾──╼ + ╾─alloc8──╼ ╾─alloc9──╼ │ ╾──╼╾──╼ } error[E0080]: it is undefined behavior to use this value - --> $DIR/mutable_references_err.rs:29:1 + --> $DIR/mutable_references_err.rs:32:1 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a reference pointing to a static variable in a constant + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 4, align: 4) { + ╾─alloc1──╼ │ ╾──╼ + } + +error[E0080]: it is undefined behavior to use this value + --> $DIR/mutable_references_err.rs:35:1 | LL | const BLUNT: &mut i32 = &mut 42; - | ^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference in a `const` + | ^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference or box pointing to read-only memory | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. = note: the raw bytes of the constant (size: 4, align: 4) { - ╾─alloc10─╼ │ ╾──╼ + ╾─alloc13─╼ │ ╾──╼ } warning: skipping const checks | help: skipping check that does not even have a feature gate - --> $DIR/mutable_references_err.rs:16:8 + --> $DIR/mutable_references_err.rs:17:8 | LL | x: &UnsafeCell::new(42), | ^^^^^^^^^^^^^^^^^^^^ help: skipping check that does not even have a feature gate - --> $DIR/mutable_references_err.rs:25:27 + --> $DIR/mutable_references_err.rs:26:27 | LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: skipping check that does not even have a feature gate - --> $DIR/mutable_references_err.rs:29:25 + --> $DIR/mutable_references_err.rs:32:40 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:32:40 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:32:35 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:35:25 | LL | const BLUNT: &mut i32 = &mut 42; | ^^^^^^^ -error: aborting due to 3 previous errors; 1 warning emitted +error: aborting due to 4 previous errors; 1 warning emitted For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr b/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr index 67959d25634a3..f43de1c125d13 100644 --- a/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr +++ b/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr @@ -2,53 +2,79 @@ error[E0080]: it is undefined behavior to use this value --> $DIR/mutable_references_err.rs:15:1 | LL | const MUH: Meh = Meh { - | ^^^^^^^^^^^^^^ constructing invalid value at .x.: encountered `UnsafeCell` in a `const` + | ^^^^^^^^^^^^^^ constructing invalid value at .x.: encountered `UnsafeCell` in read-only memory | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. = note: the raw bytes of the constant (size: 8, align: 8) { - ╾───────alloc3────────╼ │ ╾──────╼ + ╾───────alloc4────────╼ │ ╾──────╼ } error[E0080]: it is undefined behavior to use this value - --> $DIR/mutable_references_err.rs:25:1 + --> $DIR/mutable_references_err.rs:26:1 | LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) }; - | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ...x: encountered `UnsafeCell` in a `const` + | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ...x: encountered `UnsafeCell` in read-only memory | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. = note: the raw bytes of the constant (size: 16, align: 8) { - ╾───────alloc7────────╼ ╾───────alloc8────────╼ │ ╾──────╼╾──────╼ + ╾───────alloc8────────╼ ╾───────alloc9────────╼ │ ╾──────╼╾──────╼ } error[E0080]: it is undefined behavior to use this value - --> $DIR/mutable_references_err.rs:29:1 + --> $DIR/mutable_references_err.rs:32:1 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a reference pointing to a static variable in a constant + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 8, align: 8) { + ╾───────alloc1────────╼ │ ╾──────╼ + } + +error[E0080]: it is undefined behavior to use this value + --> $DIR/mutable_references_err.rs:35:1 | LL | const BLUNT: &mut i32 = &mut 42; - | ^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference in a `const` + | ^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference or box pointing to read-only memory | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. = note: the raw bytes of the constant (size: 8, align: 8) { - ╾───────alloc10───────╼ │ ╾──────╼ + ╾───────alloc13───────╼ │ ╾──────╼ } warning: skipping const checks | help: skipping check that does not even have a feature gate - --> $DIR/mutable_references_err.rs:16:8 + --> $DIR/mutable_references_err.rs:17:8 | LL | x: &UnsafeCell::new(42), | ^^^^^^^^^^^^^^^^^^^^ help: skipping check that does not even have a feature gate - --> $DIR/mutable_references_err.rs:25:27 + --> $DIR/mutable_references_err.rs:26:27 | LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: skipping check that does not even have a feature gate - --> $DIR/mutable_references_err.rs:29:25 + --> $DIR/mutable_references_err.rs:32:40 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:32:40 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:32:35 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:35:25 | LL | const BLUNT: &mut i32 = &mut 42; | ^^^^^^^ -error: aborting due to 3 previous errors; 1 warning emitted +error: aborting due to 4 previous errors; 1 warning emitted For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/miri_unleashed/mutable_references_err.rs b/tests/ui/consts/miri_unleashed/mutable_references_err.rs index 6399b122bb1fb..f721bf28dd0cc 100644 --- a/tests/ui/consts/miri_unleashed/mutable_references_err.rs +++ b/tests/ui/consts/miri_unleashed/mutable_references_err.rs @@ -13,6 +13,7 @@ unsafe impl Sync for Meh {} // the following will never be ok! no interior mut behind consts, because // all allocs interned here will be marked immutable. const MUH: Meh = Meh { //~ ERROR: it is undefined behavior to use this value +//~| UnsafeCell x: &UnsafeCell::new(42), }; @@ -24,10 +25,16 @@ unsafe impl Sync for Synced {} // Make sure we also catch this behind a type-erased `dyn Trait` reference. const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) }; //~^ ERROR: it is undefined behavior to use this value +//~| UnsafeCell // Make sure we also catch mutable references. +static mut FOO: i32 = 0; +const SUBTLE: &mut i32 = unsafe { &mut FOO }; +//~^ ERROR: it is undefined behavior to use this value +//~| static const BLUNT: &mut i32 = &mut 42; //~^ ERROR: it is undefined behavior to use this value +//~| mutable reference fn main() { unsafe { diff --git a/tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.32bit.stderr b/tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.32bit.stderr new file mode 100644 index 0000000000000..aae668d0f8763 --- /dev/null +++ b/tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.32bit.stderr @@ -0,0 +1,87 @@ +error[E0080]: it is undefined behavior to use this value + --> $DIR/static-no-inner-interior-mut.rs:9:1 + | +LL | static REF: &AtomicI32 = &AtomicI32::new(42); + | ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ..v: encountered `UnsafeCell` in read-only memory + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 4, align: 4) { + ╾─alloc4──╼ │ ╾──╼ + } + +error[E0080]: it is undefined behavior to use this value + --> $DIR/static-no-inner-interior-mut.rs:11:1 + | +LL | static REFMUT: &mut i32 = &mut 0; + | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference or box pointing to read-only memory + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 4, align: 4) { + ╾─alloc6──╼ │ ╾──╼ + } + +error[E0080]: it is undefined behavior to use this value + --> $DIR/static-no-inner-interior-mut.rs:15:1 + | +LL | static REF2: &AtomicI32 = {let x = AtomicI32::new(42); &{x}}; + | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ..v: encountered `UnsafeCell` in read-only memory + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 4, align: 4) { + ╾─alloc11─╼ │ ╾──╼ + } + +error[E0080]: it is undefined behavior to use this value + --> $DIR/static-no-inner-interior-mut.rs:17:1 + | +LL | static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; + | ^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference or box pointing to read-only memory + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 4, align: 4) { + ╾─alloc13─╼ │ ╾──╼ + } + +error: encountered dangling pointer in final constant + --> $DIR/static-no-inner-interior-mut.rs:28:1 + | +LL | static RAW_SYNC: SyncPtr = SyncPtr(&AtomicI32::new(42)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered dangling pointer in final constant + --> $DIR/static-no-inner-interior-mut.rs:29:1 + | +LL | static RAW_MUT: SyncPtr = SyncPtr(&mut 42 as *mut _ as *const _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: skipping const checks + | +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-interior-mut.rs:9:26 + | +LL | static REF: &AtomicI32 = &AtomicI32::new(42); + | ^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-interior-mut.rs:11:27 + | +LL | static REFMUT: &mut i32 = &mut 0; + | ^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-interior-mut.rs:15:56 + | +LL | static REF2: &AtomicI32 = {let x = AtomicI32::new(42); &{x}}; + | ^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-interior-mut.rs:17:44 + | +LL | static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; + | ^^^^^^^^ +help: skipping check for `const_mut_refs` feature + --> $DIR/static-no-inner-interior-mut.rs:29:40 + | +LL | static RAW_MUT: SyncPtr = SyncPtr(&mut 42 as *mut _ as *const _); + | ^^^^^^^ + +error: aborting due to 6 previous errors; 1 warning emitted + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.64bit.stderr b/tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.64bit.stderr new file mode 100644 index 0000000000000..97b40de595049 --- /dev/null +++ b/tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.64bit.stderr @@ -0,0 +1,87 @@ +error[E0080]: it is undefined behavior to use this value + --> $DIR/static-no-inner-interior-mut.rs:9:1 + | +LL | static REF: &AtomicI32 = &AtomicI32::new(42); + | ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ..v: encountered `UnsafeCell` in read-only memory + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 8, align: 8) { + ╾───────alloc4────────╼ │ ╾──────╼ + } + +error[E0080]: it is undefined behavior to use this value + --> $DIR/static-no-inner-interior-mut.rs:11:1 + | +LL | static REFMUT: &mut i32 = &mut 0; + | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference or box pointing to read-only memory + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 8, align: 8) { + ╾───────alloc6────────╼ │ ╾──────╼ + } + +error[E0080]: it is undefined behavior to use this value + --> $DIR/static-no-inner-interior-mut.rs:15:1 + | +LL | static REF2: &AtomicI32 = {let x = AtomicI32::new(42); &{x}}; + | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ..v: encountered `UnsafeCell` in read-only memory + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 8, align: 8) { + ╾───────alloc11───────╼ │ ╾──────╼ + } + +error[E0080]: it is undefined behavior to use this value + --> $DIR/static-no-inner-interior-mut.rs:17:1 + | +LL | static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; + | ^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference or box pointing to read-only memory + | + = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: 8, align: 8) { + ╾───────alloc13───────╼ │ ╾──────╼ + } + +error: encountered dangling pointer in final constant + --> $DIR/static-no-inner-interior-mut.rs:28:1 + | +LL | static RAW_SYNC: SyncPtr = SyncPtr(&AtomicI32::new(42)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered dangling pointer in final constant + --> $DIR/static-no-inner-interior-mut.rs:29:1 + | +LL | static RAW_MUT: SyncPtr = SyncPtr(&mut 42 as *mut _ as *const _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: skipping const checks + | +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-interior-mut.rs:9:26 + | +LL | static REF: &AtomicI32 = &AtomicI32::new(42); + | ^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-interior-mut.rs:11:27 + | +LL | static REFMUT: &mut i32 = &mut 0; + | ^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-interior-mut.rs:15:56 + | +LL | static REF2: &AtomicI32 = {let x = AtomicI32::new(42); &{x}}; + | ^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-interior-mut.rs:17:44 + | +LL | static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; + | ^^^^^^^^ +help: skipping check for `const_mut_refs` feature + --> $DIR/static-no-inner-interior-mut.rs:29:40 + | +LL | static RAW_MUT: SyncPtr = SyncPtr(&mut 42 as *mut _ as *const _); + | ^^^^^^^ + +error: aborting due to 6 previous errors; 1 warning emitted + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.rs b/tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.rs new file mode 100644 index 0000000000000..4f0aa083ed140 --- /dev/null +++ b/tests/ui/consts/miri_unleashed/static-no-inner-interior-mut.rs @@ -0,0 +1,31 @@ +// stderr-per-bitwidth +// compile-flags: -Zunleash-the-miri-inside-of-you +#![feature(const_refs_to_cell)] +// All "inner" allocations that come with a `static` are interned immutably. This means it is +// crucial that we do not accept any form of (interior) mutability there. + +use std::sync::atomic::*; + +static REF: &AtomicI32 = &AtomicI32::new(42); //~ERROR undefined behavior +//~^ `UnsafeCell` in read-only memory +static REFMUT: &mut i32 = &mut 0; //~ERROR undefined behavior +//~^ pointing to read-only memory + +// Different way of writing this that avoids promotion. +static REF2: &AtomicI32 = {let x = AtomicI32::new(42); &{x}}; //~ERROR undefined behavior +//~^ `UnsafeCell` in read-only memory +static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; //~ERROR undefined behavior +//~^ pointing to read-only memory + +// This one is obvious, since it is non-Sync. (It also suppresses the other errors, so it is +// commented out.) +// static RAW: *const AtomicI32 = &AtomicI32::new(42); + +struct SyncPtr(*const T); +unsafe impl Sync for SyncPtr {} + +// This one does not get promoted, and then the reference is simply too short-lived. +static RAW_SYNC: SyncPtr = SyncPtr(&AtomicI32::new(42)); //~ERROR dangling pointer +static RAW_MUT: SyncPtr = SyncPtr(&mut 42 as *mut _ as *const _); //~ERROR dangling pointer + +fn main() {}