about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2020-04-29 10:00:22 +0200
committerRalf Jung <post@ralfj.de>2020-05-10 11:45:43 +0200
commitc400f758e5cda9c2617f6d8dec87a6f24ddb291e (patch)
tree6add93098e1176bd551ceac2d73193d83b639000
parentb3269536d0a883caa0904cf6589aa1310ff70b5e (diff)
downloadrust-c400f758e5cda9c2617f6d8dec87a6f24ddb291e.tar.gz
rust-c400f758e5cda9c2617f6d8dec87a6f24ddb291e.zip
Miri interning: replace ICEs by proper errors, make intern_shallow type signature more precise
-rw-r--r--src/librustc_mir/interpret/eval_context.rs3
-rw-r--r--src/librustc_mir/interpret/intern.rs263
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_const.rs25
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_const.stderr39
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_const2.rs16
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_const2.stderr29
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references.rs3
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references.stderr6
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references_err.rs37
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references_err.stderr40
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references_ice.rs29
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr25
-rw-r--r--src/test/ui/consts/miri_unleashed/raw_mutable_const.rs12
-rw-r--r--src/test/ui/consts/miri_unleashed/raw_mutable_const.stderr16
-rw-r--r--src/test/ui/consts/raw-ptr-const.rs10
-rw-r--r--src/test/ui/consts/raw-ptr-const.stderr8
16 files changed, 274 insertions, 287 deletions
diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs
index 9bb7c879505..eba4dd336ad 100644
--- a/src/librustc_mir/interpret/eval_context.rs
+++ b/src/librustc_mir/interpret/eval_context.rs
@@ -871,6 +871,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         // Our result will later be validated anyway, and there seems no good reason
         // to have to fail early here.  This is also more consistent with
         // `Memory::get_static_alloc` which has to use `const_eval_raw` to avoid cycles.
+        // FIXME: We can hit delay_span_bug if this is an invalid const, interning finds
+        // that problem, but we never run validation to show an error. Can we ensure
+        // this does not happen?
         let val = self.tcx.const_eval_raw(param_env.and(gid))?;
         self.raw_const_to_mplace(val)
     }
diff --git a/src/librustc_mir/interpret/intern.rs b/src/librustc_mir/interpret/intern.rs
index 1c44101595d..c9661c92d2e 100644
--- a/src/librustc_mir/interpret/intern.rs
+++ b/src/librustc_mir/interpret/intern.rs
@@ -8,7 +8,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_errors::ErrorReported;
 use rustc_hir as hir;
 use rustc_middle::mir::interpret::{ErrorHandled, InterpResult};
-use rustc_middle::ty::{self, Ty};
+use rustc_middle::ty::{self, query::TyCtxtAt, Ty};
 
 use rustc_ast::ast::Mutability;
 
@@ -29,43 +29,44 @@ struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>> {
     /// The ectx from which we intern.
     ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
     /// Previously encountered safe references.
-    ref_tracking: &'rt mut RefTracking<(MPlaceTy<'tcx>, Mutability, InternMode)>,
+    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 FxHashSet<AllocId>,
-    /// The root node of the value that we're looking at. This field is never mutated and only used
+    /// The root kind of the value that we're looking at. This field is never mutated and only used
     /// for sanity assertions that will ICE when `const_qualif` screws up.
     mode: InternMode,
-    /// This field stores the mutability of the value *currently* being checked.
-    /// When encountering a mutable reference, we determine the pointee mutability
-    /// taking into account the mutability of the context: `& &mut i32` is entirely immutable,
-    /// despite the nested mutable reference!
-    /// The field gets updated when an `UnsafeCell` is encountered.
-    mutability: Mutability,
+    /// This field stores whether we are *currently* inside an `UnsafeCell`. This can affect
+    /// the intern mode of references we encounter.
+    inside_unsafe_cell: bool,
 
     /// This flag is to avoid triggering UnsafeCells are not allowed behind references in constants
     /// for promoteds.
     /// It's a copy of `mir::Body`'s ignore_interior_mut_in_const_validation field
-    ignore_interior_mut_in_const_validation: bool,
+    ignore_interior_mut_in_const: bool,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
 enum InternMode {
-    /// Mutable references must in fact be immutable due to their surrounding immutability in a
-    /// `static`. In a `static mut` we start out as mutable and thus can also contain further `&mut`
-    /// that will actually be treated as mutable.
-    Static,
-    /// UnsafeCell is OK in the value of a constant: `const FOO = Cell::new(0)` creates
-    /// a new cell every time it is used.
+    /// 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),
+    /// The "base value" of a const, which can have `UnsafeCell` (as in `const FOO: Cell<i32>`),
+    /// but that interior mutability is simply ignored.
     ConstBase,
-    /// `UnsafeCell` ICEs.
-    Const,
+    /// The "inner values" of a const with references, where `UnsafeCell` is an error.
+    ConstInner,
 }
 
 /// Signalling data structure to ensure we don't recurse
 /// into the memory of other constants or statics
 struct IsStaticOrFn;
 
+fn mutable_memory_in_const(tcx: TyCtxtAt<'_>, kind: &str) {
+    tcx.sess.span_err(tcx.span, &format!("mutable memory ({}) is not allowed in constant", kind));
+}
+
 /// Intern an allocation without looking at its children.
 /// `mode` is the mode of the environment where we found this pointer.
 /// `mutablity` is the mutability of the place to be interned; even if that says
@@ -75,12 +76,11 @@ struct IsStaticOrFn;
 fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>>(
     ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
     leftover_allocations: &'rt mut FxHashSet<AllocId>,
-    mode: InternMode,
     alloc_id: AllocId,
-    mutability: Mutability,
+    mode: InternMode,
     ty: Option<Ty<'tcx>>,
 ) -> InterpResult<'tcx, Option<IsStaticOrFn>> {
-    trace!("InternVisitor::intern {:?} with {:?}", alloc_id, mutability,);
+    trace!("intern_shallow {:?} with {:?}", alloc_id, mode);
     // remove allocation
     let tcx = ecx.tcx;
     let (kind, mut alloc) = match ecx.memory.alloc_map.remove(&alloc_id) {
@@ -89,8 +89,9 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>>(
             // 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. The `delay_span_bug` ensures that we don't forget such
-            // a check in validation.
+            // to validation to error -- it has the much better error messages, pointing out where
+            // in the value the dangling reference lies.
+            // The `delay_span_bug` ensures that we don't forget such a check in validation.
             if tcx.get_global_alloc(alloc_id).is_none() {
                 tcx.sess.delay_span_bug(ecx.tcx.span, "tried to intern dangling pointer");
             }
@@ -107,28 +108,28 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>>(
     // 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 mode == InternMode::Static {
-        // When `ty` is `None`, we assume no interior mutability.
+    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.tcx, ecx.param_env, ecx.tcx.span));
         // For statics, allocation mutability is the combination of the place mutability and
         // the type mutability.
         // The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere.
-        if mutability == Mutability::Not && frozen {
+        let immutable = mutability == Mutability::Not && frozen;
+        if immutable {
             alloc.mutability = Mutability::Not;
         } else {
             // Just making sure we are not "upgrading" an immutable allocation to mutable.
             assert_eq!(alloc.mutability, Mutability::Mut);
         }
     } else {
-        // We *could* be non-frozen at `ConstBase`, for constants like `Cell::new(0)`.
-        // But we still intern that as immutable as the memory cannot be changed once the
-        // initial value was computed.
-        // Constants are never mutable.
-        assert_eq!(
-            mutability,
-            Mutability::Not,
-            "Something went very wrong: mutability requested for a constant"
-        );
+        // 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.
+
+        // There are no sensible checks we can do here; grep for `mutable_memory_in_const` to
+        // find the checks we are doing elsewhere to avoid even getting here for memory
+        // that "wants" to be mutable.
         alloc.mutability = Mutability::Not;
     };
     // link the alloc id to the actual allocation
@@ -142,10 +143,16 @@ impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>> InternVisitor<'rt, 'mir
     fn intern_shallow(
         &mut self,
         alloc_id: AllocId,
-        mutability: Mutability,
+        mode: InternMode,
         ty: Option<Ty<'tcx>>,
     ) -> InterpResult<'tcx, Option<IsStaticOrFn>> {
-        intern_shallow(self.ecx, self.leftover_allocations, self.mode, alloc_id, mutability, ty)
+        intern_shallow(
+            self.ecx,
+            self.leftover_allocations,
+            alloc_id,
+            mode,
+            ty,
+        )
     }
 }
 
@@ -166,22 +173,22 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx>> ValueVisitor<'mir
     ) -> InterpResult<'tcx> {
         if let Some(def) = mplace.layout.ty.ty_adt_def() {
             if Some(def.did) == self.ecx.tcx.lang_items().unsafe_cell_type() {
+                if self.mode == InternMode::ConstInner && !self.ignore_interior_mut_in_const {
+                    // We do not actually make this memory mutable.  But in case the user
+                    // *expected* it to be mutable, make sure we error.  This is just a
+                    // sanity check to prevent users from accidentally exploiting the UB
+                    // they caused.  It also helps us to find cases where const-checking
+                    // failed to prevent an `UnsafeCell` (but as `ignore_interior_mut_in_const`
+                    // shows that part is not airtight).
+                    mutable_memory_in_const(self.ecx.tcx, "`UnsafeCell`");
+                }
                 // 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.
-                let old = std::mem::replace(&mut self.mutability, Mutability::Mut);
-                if !self.ignore_interior_mut_in_const_validation {
-                    assert_ne!(
-                        self.mode,
-                        InternMode::Const,
-                        "UnsafeCells are not allowed behind references in constants. This should \
-                        have been prevented statically by const qualification. If this were \
-                        allowed one would be able to change a constant at one use site and other \
-                        use sites could observe that mutation.",
-                    );
-                }
+                // Remember the `old` value to handle nested `UnsafeCell`.
+                let old = std::mem::replace(&mut self.inside_unsafe_cell, true);
                 let walked = self.walk_aggregate(mplace, fields);
-                self.mutability = old;
+                self.inside_unsafe_cell = old;
                 return walked;
             }
         }
@@ -191,23 +198,26 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx>> ValueVisitor<'mir
     fn visit_value(&mut self, mplace: MPlaceTy<'tcx>) -> InterpResult<'tcx> {
         // Handle Reference types, as these are the only relocations supported by const eval.
         // Raw pointers (and boxes) are handled by the `leftover_relocations` logic.
+        let tcx = self.ecx.tcx;
         let ty = mplace.layout.ty;
-        if let ty::Ref(_, referenced_ty, mutability) = ty.kind {
+        if let ty::Ref(_, referenced_ty, ref_mutability) = ty.kind {
             let value = self.ecx.read_immediate(mplace.into())?;
             let mplace = self.ecx.ref_to_mplace(value)?;
+            assert_eq!(mplace.layout.ty, referenced_ty);
             // Handle trait object vtables.
             if let ty::Dynamic(..) =
-                self.ecx.tcx.struct_tail_erasing_lifetimes(referenced_ty, self.ecx.param_env).kind
+                tcx.struct_tail_erasing_lifetimes(referenced_ty, self.ecx.param_env).kind
             {
-                // Validation has already errored on an invalid vtable pointer so we can safely not
-                // do anything if this is not a real pointer.
+                // Validation will error (with a better message) on an invalid vtable pointer
+                // so we can safely not do anything if this is not a real pointer.
                 if let Scalar::Ptr(vtable) = mplace.meta.unwrap_meta() {
-                    // Explicitly choose `Immutable` here, since vtables are immutable, even
+                    // Explicitly choose const mode here, since vtables are immutable, even
                     // if the reference of the fat pointer is mutable.
-                    self.intern_shallow(vtable.alloc_id, Mutability::Not, None)?;
+                    self.intern_shallow(vtable.alloc_id, InternMode::ConstInner, None)?;
                 } else {
-                    self.ecx().tcx.sess.delay_span_bug(
-                        rustc_span::DUMMY_SP,
+                    // Let validation show the error message, but make sure it *does* error.
+                    tcx.sess.delay_span_bug(
+                        tcx.span,
                         "vtables pointers cannot be integer pointers",
                     );
                 }
@@ -215,54 +225,66 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx>> ValueVisitor<'mir
             // Check if we have encountered this pointer+layout combination before.
             // Only recurse for allocation-backed pointers.
             if let Scalar::Ptr(ptr) = mplace.ptr {
-                // We do not have any `frozen` logic here, because it's essentially equivalent to
-                // the mutability except for the outermost item. Only `UnsafeCell` can "unfreeze",
-                // and we check that in `visit_aggregate`.
-                // This is not an inherent limitation, but one that we know to be true, because
-                // const qualification enforces it. We can lift it in the future.
-                match (self.mode, mutability) {
-                    // immutable references are fine everywhere
-                    (_, hir::Mutability::Not) => {}
-                    // all is "good and well" in the unsoundness of `static mut`
+                // Compute the mode with which we intern this.
+                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`.
 
-                    // mutable references are ok in `static`. Either they are treated as immutable
-                    // because they are behind an immutable one, or they are behind an `UnsafeCell`
-                    // and thus ok.
-                    (InternMode::Static, hir::Mutability::Mut) => {}
-                    // we statically prevent `&mut T` via `const_qualif` and double check this here
-                    (InternMode::ConstBase | InternMode::Const, hir::Mutability::Mut) => {
-                        match referenced_ty.kind {
-                            ty::Array(_, n)
-                                if n.eval_usize(self.ecx.tcx.tcx, self.ecx.param_env) == 0 => {}
-                            ty::Slice(_)
-                                if mplace.meta.unwrap_meta().to_machine_usize(self.ecx)? == 0 => {}
-                            _ => bug!("const qualif failed to prevent mutable references"),
+                        // 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* consier `freeze` here -- that is done more precisely
+                                // when traversing the referenced data (by tracking `UnsafeCell`).
+                                InternMode::Static(Mutability::Not)
+                            }
+                            Mutability::Mut => {
+                                // Mutable reference.
+                                InternMode::Static(mutbl)
+                            }
                         }
                     }
-                }
-                // Compute the mutability with which we'll start visiting the allocation. This is
-                // what gets changed when we encounter 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`
-                let mutability = self.mutability.and(mutability);
-                // Recursing behind references changes the intern mode for constants in order to
-                // cause assertions to trigger if we encounter any `UnsafeCell`s.
-                let mode = match self.mode {
-                    InternMode::ConstBase => InternMode::Const,
-                    other => other,
+                    InternMode::ConstBase | InternMode::ConstInner => {
+                        // Ignore `UnsafeCell`, everything is immutable.  Do some sanity checking
+                        // for mutable references that we encounter -- they must all be ZST.
+                        // This helps to prevent users from accidentally exploiting UB that they
+                        // caused (by somehow getting a mutable reference in a `const`).
+                        if ref_mutability == Mutability::Mut {
+                            match referenced_ty.kind {
+                                ty::Array(_, n)
+                                    if n.eval_usize(tcx.tcx, self.ecx.param_env) == 0 => {}
+                                ty::Slice(_)
+                                    if mplace.meta.unwrap_meta().to_machine_usize(self.ecx)? == 0 => {}
+                                _ => mutable_memory_in_const(tcx, "`&mut`"),
+                            }
+                        } else {
+                            // A shared reference. We cannot check `freeze` here due to references
+                            // like `&dyn Trait` that are actually immutable.  We do check for
+                            // concrete `UnsafeCell` when traversing the pointee though (if it is
+                            // a new allocation, not yet interned).
+                        }
+                        // Go on with the "inner" rules.
+                        InternMode::ConstInner
+                    }
                 };
-                match self.intern_shallow(ptr.alloc_id, mutability, Some(mplace.layout.ty))? {
+                match self.intern_shallow(ptr.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, mutability, mode), || ()),
+                    None => self.ref_tracking.track((mplace, ref_mode), || ()),
                 }
             }
             Ok(())
@@ -273,6 +295,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx>> ValueVisitor<'mir
     }
 }
 
+#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
 pub enum InternKind {
     /// The `mutability` of the static, ignoring the type which may have interior mutability.
     Static(hir::Mutability),
@@ -285,48 +308,48 @@ pub fn intern_const_alloc_recursive<M: CompileTimeMachine<'mir, 'tcx>>(
     ecx: &mut InterpCx<'mir, 'tcx, M>,
     intern_kind: InternKind,
     ret: MPlaceTy<'tcx>,
-    ignore_interior_mut_in_const_validation: bool,
+    ignore_interior_mut_in_const: bool,
 ) -> InterpResult<'tcx>
 where
     'tcx: 'mir,
 {
     let tcx = ecx.tcx;
-    let (base_mutability, base_intern_mode) = match intern_kind {
-        // `static mut` doesn't care about interior mutability, it's mutable anyway
-        InternKind::Static(mutbl) => (mutbl, InternMode::Static),
+    let base_intern_mode = match intern_kind {
+        InternKind::Static(mutbl) => InternMode::Static(mutbl),
         // FIXME: what about array lengths, array initializers?
-        InternKind::Constant | InternKind::ConstProp => (Mutability::Not, InternMode::ConstBase),
-        InternKind::Promoted => (Mutability::Not, InternMode::ConstBase),
+        InternKind::Constant | InternKind::ConstProp | InternKind::Promoted =>
+            InternMode::ConstBase,
     };
 
     // Type based interning.
-    // `ref_tracking` tracks typed references we have seen and still need to crawl for
+    // `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::new((ret, base_mutability, base_intern_mode));
+    let mut ref_tracking = RefTracking::empty();
     let leftover_allocations = &mut FxHashSet::default();
 
     // start with the outermost allocation
     intern_shallow(
         ecx,
         leftover_allocations,
-        base_intern_mode,
         // The outermost allocation must exist, because we allocated it with
         // `Memory::allocate`.
         ret.ptr.assert_ptr().alloc_id,
-        base_mutability,
+        base_intern_mode,
         Some(ret.layout.ty),
     )?;
 
-    while let Some(((mplace, mutability, mode), _)) = ref_tracking.todo.pop() {
+    ref_tracking.track((ret, base_intern_mode), || ());
+
+    while let Some(((mplace, mode), _)) = ref_tracking.todo.pop() {
         let interned = InternVisitor {
             ref_tracking: &mut ref_tracking,
             ecx,
             mode,
             leftover_allocations,
-            mutability,
-            ignore_interior_mut_in_const_validation,
+            ignore_interior_mut_in_const,
+            inside_unsafe_cell: false,
         }
         .visit_value(mplace);
         if let Err(error) = interned {
@@ -366,26 +389,28 @@ where
                 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.
+                // 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;
                 }
                 InternKind::Constant | InternKind::ConstProp => {
-                    // If it's a constant, it *must* be immutable.
-                    // We cannot have mutable memory inside a constant.
-                    // We use `delay_span_bug` here, because this can be reached in the presence
-                    // of fancy transmutes.
-                    if alloc.mutability == Mutability::Mut {
-                        // For better errors later, mark the allocation as immutable
-                        // (on top of the delayed ICE).
-                        alloc.mutability = Mutability::Not;
-                        ecx.tcx.sess.delay_span_bug(ecx.tcx.span, "mutable allocation in constant");
-                    }
+                    // If it's a constant, we should not have any "leftovers" as everything
+                    // is tracked by const-checking.
+                    // FIXME: downgrade this to a warning? It rejects some legitimate consts,
+                    // such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
+                    ecx.tcx.sess.span_err(
+                        ecx.tcx.span,
+                        "untyped pointers are not allowed in constant",
+                    );
+                    // For better errors later, mark the allocation as immutable.
+                    alloc.mutability = Mutability::Not;
                 }
             }
             let alloc = tcx.intern_const_alloc(alloc);
diff --git a/src/test/ui/consts/miri_unleashed/mutable_const.rs b/src/test/ui/consts/miri_unleashed/mutable_const.rs
deleted file mode 100644
index f8aa6528273..00000000000
--- a/src/test/ui/consts/miri_unleashed/mutable_const.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-// compile-flags: -Zunleash-the-miri-inside-of-you
-// normalize-stderr-test "alloc[0-9]+" -> "allocN"
-
-#![deny(const_err)] // The `allow` variant is tested by `mutable_const2`.
-//~^ NOTE lint level
-// Here we check that even though `MUTABLE_BEHIND_RAW` is created from a mutable
-// allocation, we intern that allocation as *immutable* and reject writes to it.
-// We avoid the `delay_span_bug` ICE by having compilation fail via the `deny` above.
-
-use std::cell::UnsafeCell;
-
-// make sure we do not just intern this as mutable
-const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
-
-const MUTATING_BEHIND_RAW: () = { //~ NOTE
-    // Test that `MUTABLE_BEHIND_RAW` is actually immutable, by doing this at const time.
-    unsafe {
-        *MUTABLE_BEHIND_RAW = 99 //~ ERROR any use of this value will cause an error
-        //~^ NOTE: which is read-only
-        // FIXME would be good to match more of the error message here, but looks like we
-        // normalize *after* checking the annoations here.
-    }
-};
-
-fn main() {}
diff --git a/src/test/ui/consts/miri_unleashed/mutable_const.stderr b/src/test/ui/consts/miri_unleashed/mutable_const.stderr
deleted file mode 100644
index 4772baf9d9a..00000000000
--- a/src/test/ui/consts/miri_unleashed/mutable_const.stderr
+++ /dev/null
@@ -1,39 +0,0 @@
-error: any use of this value will cause an error
-  --> $DIR/mutable_const.rs:18:9
-   |
-LL | / const MUTATING_BEHIND_RAW: () = {
-LL | |     // Test that `MUTABLE_BEHIND_RAW` is actually immutable, by doing this at const time.
-LL | |     unsafe {
-LL | |         *MUTABLE_BEHIND_RAW = 99
-   | |         ^^^^^^^^^^^^^^^^^^^^^^^^ writing to allocN which is read-only
-...  |
-LL | |     }
-LL | | };
-   | |__-
-   |
-note: the lint level is defined here
-  --> $DIR/mutable_const.rs:4:9
-   |
-LL | #![deny(const_err)] // The `allow` variant is tested by `mutable_const2`.
-   |         ^^^^^^^^^
-
-warning: skipping const checks
-   |
-help: skipping check that does not even have a feature gate
-  --> $DIR/mutable_const.rs:13:38
-   |
-LL | const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
-   |                                      ^^^^^^^^^^^^^^^^^^^^
-help: skipping check for `const_raw_ptr_deref` feature
-  --> $DIR/mutable_const.rs:18:9
-   |
-LL |         *MUTABLE_BEHIND_RAW = 99
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^
-help: skipping check for `const_mut_refs` feature
-  --> $DIR/mutable_const.rs:18:9
-   |
-LL |         *MUTABLE_BEHIND_RAW = 99
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: aborting due to previous error; 1 warning emitted
-
diff --git a/src/test/ui/consts/miri_unleashed/mutable_const2.rs b/src/test/ui/consts/miri_unleashed/mutable_const2.rs
deleted file mode 100644
index 867091af7ba..00000000000
--- a/src/test/ui/consts/miri_unleashed/mutable_const2.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-// compile-flags: -Zunleash-the-miri-inside-of-you
-// failure-status: 101
-// rustc-env:RUST_BACKTRACE=0
-// normalize-stderr-test "note: rustc 1.* running on .*" -> "note: rustc VERSION running on TARGET"
-// normalize-stderr-test "note: compiler flags: .*" -> "note: compiler flags: FLAGS"
-// normalize-stderr-test "interpret/intern.rs:[0-9]+:[0-9]+" -> "interpret/intern.rs:LL:CC"
-
-#![allow(const_err)]
-
-use std::cell::UnsafeCell;
-
-// make sure we do not just intern this as mutable
-const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
-//~^ ERROR: mutable allocation in constant
-
-fn main() {}
diff --git a/src/test/ui/consts/miri_unleashed/mutable_const2.stderr b/src/test/ui/consts/miri_unleashed/mutable_const2.stderr
deleted file mode 100644
index 98a1c8bdd89..00000000000
--- a/src/test/ui/consts/miri_unleashed/mutable_const2.stderr
+++ /dev/null
@@ -1,29 +0,0 @@
-warning: skipping const checks
-   |
-help: skipping check that does not even have a feature gate
-  --> $DIR/mutable_const2.rs:13:38
-   |
-LL | const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
-   |                                      ^^^^^^^^^^^^^^^^^^^^
-
-warning: 1 warning emitted
-
-error: internal compiler error: mutable allocation in constant
-  --> $DIR/mutable_const2.rs:13:1
-   |
-LL | const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-thread 'rustc' panicked at 'no errors encountered even though `delay_span_bug` issued', src/librustc_errors/lib.rs:366:17
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-error: internal compiler error: unexpected panic
-
-note: the compiler unexpectedly panicked. this is a bug.
-
-note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports
-
-note: rustc VERSION running on TARGET
-
-note: compiler flags: FLAGS
-
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references.rs b/src/test/ui/consts/miri_unleashed/mutable_references.rs
index ed2ca86ba2c..ca927ef4a51 100644
--- a/src/test/ui/consts/miri_unleashed/mutable_references.rs
+++ b/src/test/ui/consts/miri_unleashed/mutable_references.rs
@@ -17,12 +17,11 @@ struct Foo<T>(T);
 // this is fine for the same reason as `BAR`.
 static BOO: &mut Foo<()> = &mut Foo(());
 
+// interior mutability is fine
 struct Meh {
     x: &'static UnsafeCell<i32>,
 }
-
 unsafe impl Sync for Meh {}
-
 static MEH: Meh = Meh {
     x: &UnsafeCell::new(42),
 };
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references.stderr b/src/test/ui/consts/miri_unleashed/mutable_references.stderr
index 83c4e0ceba0..7109ffd8b61 100644
--- a/src/test/ui/consts/miri_unleashed/mutable_references.stderr
+++ b/src/test/ui/consts/miri_unleashed/mutable_references.stderr
@@ -1,5 +1,5 @@
 error[E0594]: cannot assign to `*OH_YES`, as `OH_YES` is an immutable static item
-  --> $DIR/mutable_references.rs:37:5
+  --> $DIR/mutable_references.rs:36:5
    |
 LL |     *OH_YES = 99;
    |     ^^^^^^^^^^^^ cannot assign
@@ -22,12 +22,12 @@ help: skipping check for `const_mut_refs` feature
 LL | static BOO: &mut Foo<()> = &mut Foo(());
    |                            ^^^^^^^^^^^^
 help: skipping check that does not even have a feature gate
-  --> $DIR/mutable_references.rs:27:8
+  --> $DIR/mutable_references.rs:26:8
    |
 LL |     x: &UnsafeCell::new(42),
    |        ^^^^^^^^^^^^^^^^^^^^
 help: skipping check for `const_mut_refs` feature
-  --> $DIR/mutable_references.rs:31:27
+  --> $DIR/mutable_references.rs:30:27
    |
 LL | static OH_YES: &mut i32 = &mut 42;
    |                           ^^^^^^^
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references_err.rs b/src/test/ui/consts/miri_unleashed/mutable_references_err.rs
new file mode 100644
index 00000000000..5014a601390
--- /dev/null
+++ b/src/test/ui/consts/miri_unleashed/mutable_references_err.rs
@@ -0,0 +1,37 @@
+// compile-flags: -Zunleash-the-miri-inside-of-you
+
+#![allow(const_err)]
+
+use std::cell::UnsafeCell;
+
+// this test ICEs to ensure that our mutability story is sound
+
+struct Meh {
+    x: &'static UnsafeCell<i32>,
+}
+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: mutable memory (`UnsafeCell`) is not allowed in constant
+    x: &UnsafeCell::new(42),
+};
+
+struct Synced {
+    x: UnsafeCell<i32>,
+}
+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: mutable memory (`UnsafeCell`) is not allowed in constant
+
+// Make sure we also catch mutable references.
+const BLUNT: &mut i32 = &mut 42;
+//~^ ERROR: mutable memory (`&mut`) is not allowed in constant
+
+fn main() {
+    unsafe {
+        *MUH.x.get() = 99;
+    }
+}
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references_err.stderr b/src/test/ui/consts/miri_unleashed/mutable_references_err.stderr
new file mode 100644
index 00000000000..45e7d5a2cc3
--- /dev/null
+++ b/src/test/ui/consts/miri_unleashed/mutable_references_err.stderr
@@ -0,0 +1,40 @@
+error: mutable memory (`UnsafeCell`) is not allowed in constant
+  --> $DIR/mutable_references_err.rs:16:1
+   |
+LL | / const MUH: Meh = Meh {
+LL | |     x: &UnsafeCell::new(42),
+LL | | };
+   | |__^
+
+error: mutable memory (`UnsafeCell`) is not allowed in constant
+  --> $DIR/mutable_references_err.rs:26:1
+   |
+LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) };
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: mutable memory (`&mut`) is not allowed in constant
+  --> $DIR/mutable_references_err.rs:30:1
+   |
+LL | const BLUNT: &mut i32 = &mut 42;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: skipping const checks
+   |
+help: skipping check that does not even have a feature gate
+  --> $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:26:27
+   |
+LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) };
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: skipping check for `const_mut_refs` feature
+  --> $DIR/mutable_references_err.rs:30:25
+   |
+LL | const BLUNT: &mut i32 = &mut 42;
+   |                         ^^^^^^^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references_ice.rs b/src/test/ui/consts/miri_unleashed/mutable_references_ice.rs
deleted file mode 100644
index 7388aad2a9e..00000000000
--- a/src/test/ui/consts/miri_unleashed/mutable_references_ice.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-// compile-flags: -Zunleash-the-miri-inside-of-you
-// failure-status: 101
-// rustc-env:RUST_BACKTRACE=0
-// normalize-stderr-test "note: rustc 1.* running on .*" -> "note: rustc VERSION running on TARGET"
-// normalize-stderr-test "note: compiler flags: .*" -> "note: compiler flags: FLAGS"
-// normalize-stderr-test "interpret/intern.rs:[0-9]+:[0-9]+" -> "interpret/intern.rs:LL:CC"
-
-#![allow(const_err)]
-
-use std::cell::UnsafeCell;
-
-// this test ICEs to ensure that our mutability story is sound
-
-struct Meh {
-    x: &'static UnsafeCell<i32>,
-}
-
-unsafe impl Sync for Meh {}
-
-// the following will never be ok!
-const MUH: Meh = Meh {
-    x: &UnsafeCell::new(42),
-};
-
-fn main() {
-    unsafe {
-        *MUH.x.get() = 99;
-    }
-}
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr b/src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr
deleted file mode 100644
index 7ddf77af4d3..00000000000
--- a/src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr
+++ /dev/null
@@ -1,25 +0,0 @@
-thread 'rustc' panicked at 'assertion failed: `(left != right)`
-  left: `Const`,
- right: `Const`: UnsafeCells are not allowed behind references in constants. This should have been prevented statically by const qualification. If this were allowed one would be able to change a constant at one use site and other use sites could observe that mutation.', src/librustc_mir/interpret/intern.rs:LL:CC
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-error: internal compiler error: unexpected panic
-
-note: the compiler unexpectedly panicked. this is a bug.
-
-note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports
-
-note: rustc VERSION running on TARGET
-
-note: compiler flags: FLAGS
-
-warning: skipping const checks
-   |
-help: skipping check that does not even have a feature gate
-  --> $DIR/mutable_references_ice.rs:22:8
-   |
-LL |     x: &UnsafeCell::new(42),
-   |        ^^^^^^^^^^^^^^^^^^^^
-
-warning: 1 warning emitted
-
diff --git a/src/test/ui/consts/miri_unleashed/raw_mutable_const.rs b/src/test/ui/consts/miri_unleashed/raw_mutable_const.rs
new file mode 100644
index 00000000000..3d200e23173
--- /dev/null
+++ b/src/test/ui/consts/miri_unleashed/raw_mutable_const.rs
@@ -0,0 +1,12 @@
+// compile-flags: -Zunleash-the-miri-inside-of-you
+
+#![feature(const_raw_ptr_deref)]
+#![feature(const_mut_refs)]
+#![allow(const_err)]
+
+use std::cell::UnsafeCell;
+
+const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
+//~^ ERROR: untyped pointers are not allowed in constant
+
+fn main() {}
diff --git a/src/test/ui/consts/miri_unleashed/raw_mutable_const.stderr b/src/test/ui/consts/miri_unleashed/raw_mutable_const.stderr
new file mode 100644
index 00000000000..c6dbf086ec0
--- /dev/null
+++ b/src/test/ui/consts/miri_unleashed/raw_mutable_const.stderr
@@ -0,0 +1,16 @@
+error: untyped pointers are not allowed in constant
+  --> $DIR/raw_mutable_const.rs:9:1
+   |
+LL | const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: skipping const checks
+   |
+help: skipping check that does not even have a feature gate
+  --> $DIR/raw_mutable_const.rs:9:38
+   |
+LL | const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
+   |                                      ^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to previous error; 1 warning emitted
+
diff --git a/src/test/ui/consts/raw-ptr-const.rs b/src/test/ui/consts/raw-ptr-const.rs
new file mode 100644
index 00000000000..00fad046b55
--- /dev/null
+++ b/src/test/ui/consts/raw-ptr-const.rs
@@ -0,0 +1,10 @@
+#![allow(const_err)] // make sure we hit the `delay_span_bug`
+
+// This is a regression test for a `delay_span_bug` during interning when a constant
+// evaluates to a (non-dangling) raw pointer.  For now this errors; potentially it
+// could also be allowed.
+
+const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;
+//~^ ERROR untyped pointers are not allowed in constant
+
+fn main() {}
diff --git a/src/test/ui/consts/raw-ptr-const.stderr b/src/test/ui/consts/raw-ptr-const.stderr
new file mode 100644
index 00000000000..974b1c3ff45
--- /dev/null
+++ b/src/test/ui/consts/raw-ptr-const.stderr
@@ -0,0 +1,8 @@
+error: untyped pointers are not allowed in constant
+  --> $DIR/raw-ptr-const.rs:7:1
+   |
+LL | const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+