diff --git a/src/librustc_mir/const_eval/eval_queries.rs b/src/librustc_mir/const_eval/eval_queries.rs index b6c635fb22ab5..0637ebf959e5a 100644 --- a/src/librustc_mir/const_eval/eval_queries.rs +++ b/src/librustc_mir/const_eval/eval_queries.rs @@ -66,7 +66,7 @@ fn eval_body_using_ecx<'mir, 'tcx>( intern_kind, ret, body.ignore_interior_mut_in_const_validation, - )?; + ); debug!("eval_body_using_ecx done: {:?}", *ret); Ok(ret) diff --git a/src/librustc_mir/const_eval/mod.rs b/src/librustc_mir/const_eval/mod.rs index e1146ef30d131..7f557e340bbc8 100644 --- a/src/librustc_mir/const_eval/mod.rs +++ b/src/librustc_mir/const_eval/mod.rs @@ -53,7 +53,7 @@ pub(crate) fn const_caller_location( let mut ecx = mk_eval_cx(tcx, DUMMY_SP, ty::ParamEnv::reveal_all(), false); let loc_place = ecx.alloc_caller_location(file, line, col); - intern_const_alloc_recursive(&mut ecx, InternKind::Constant, loc_place, false).unwrap(); + intern_const_alloc_recursive(&mut ecx, InternKind::Constant, loc_place, false); ConstValue::Scalar(loc_place.ptr) } diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs index 9bb7c879505f6..eba4dd336ade2 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 1c44101595d4f..02a7f24a1e351 100644 --- a/src/librustc_mir/interpret/intern.rs +++ b/src/librustc_mir/interpret/intern.rs @@ -5,10 +5,9 @@ use super::validity::RefTracking; 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::mir::interpret::InterpResult; +use rustc_middle::ty::{self, query::TyCtxtAt, Ty}; use rustc_ast::ast::Mutability; @@ -29,43 +28,45 @@ 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, - /// 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`), + /// 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) { + // FIXME: show this in validation instead so we can point at where in the value the error is? + 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, - mode: InternMode, alloc_id: AllocId, - mutability: Mutability, + mode: InternMode, ty: Option>, -) -> InterpResult<'tcx, Option> { - trace!("InternVisitor::intern {:?} with {:?}", alloc_id, mutability,); +) -> Option { + 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,14 +89,15 @@ 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"); } // treat dangling pointers like other statics // just to stop trying to recurse into them - return Ok(Some(IsStaticOrFn)); + return Some(IsStaticOrFn); } }; // This match is just a canary for future changes to `MemoryKind`, which most likely need @@ -107,45 +108,45 @@ 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 let alloc = tcx.intern_const_alloc(alloc); leftover_allocations.extend(alloc.relocations().iter().map(|&(_, ((), reloc))| reloc)); tcx.set_alloc_id_memory(alloc_id, alloc); - Ok(None) + None } impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>> InternVisitor<'rt, 'mir, 'tcx, M> { fn intern_shallow( &mut self, alloc_id: AllocId, - mutability: Mutability, + mode: InternMode, ty: Option>, - ) -> InterpResult<'tcx, Option> { - intern_shallow(self.ecx, self.leftover_allocations, self.mode, alloc_id, mutability, ty) + ) -> Option { + intern_shallow(self.ecx, self.leftover_allocations, alloc_id, mode, ty) } } @@ -166,22 +167,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,78 +192,92 @@ 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, - "vtables pointers cannot be integer pointers", - ); + // 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"); } } // 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 +288,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), @@ -281,71 +297,78 @@ pub enum InternKind { ConstProp, } +/// Intern `ret` and everything it references. +/// +/// This *cannot raise an interpreter error*. Doing so is left to validation, which +/// tracks where in the value we are and thus can show much better error messages. +/// Any errors here would anyway be turned into `const_err` lints, whereas validation failures +/// are hard errors. pub fn intern_const_alloc_recursive>( ecx: &mut InterpCx<'mir, 'tcx, M>, intern_kind: InternKind, ret: MPlaceTy<'tcx>, - ignore_interior_mut_in_const_validation: bool, -) -> InterpResult<'tcx> -where + ignore_interior_mut_in_const: bool, +) 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() { - let interned = InternVisitor { + ref_tracking.track((ret, base_intern_mode), || ()); + + while let Some(((mplace, mode), _)) = ref_tracking.todo.pop() { + let res = 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 { - // This can happen when e.g. the tag of an enum is not a valid discriminant. We do have - // to read enum discriminants in order to find references in enum variant fields. - if let err_ub!(ValidationFailure(_)) = error.kind { - let err = crate::const_eval::error_to_const_error(&ecx, error); - match err.struct_error( - ecx.tcx, - "it is undefined behavior to use this value", - |mut diag| { - diag.note(crate::const_eval::note_on_undefined_behavior_error()); - diag.emit(); - }, - ) { - ErrorHandled::TooGeneric - | ErrorHandled::Reported(ErrorReported) - | ErrorHandled::Linted => {} - } + // We deliberately *ignore* interpreter errors here. When there is a problem, the remaining + // references are "leftover"-interned, and later validation will show a proper error + // and point at the right part of the value causing the problem. + match res { + Ok(()) => {} + Err(error) => { + ecx.tcx.sess.delay_span_bug( + ecx.tcx.span, + "error during interning should later cause validation failure", + ); + // Some errors shouldn't come up because creating them causes + // an allocation, which we should avoid. When that happens, + // dedicated error variants should be introduced instead. + assert!( + !error.kind.allocates(), + "interning encountered allocating error: {}", + error + ); } } } @@ -366,26 +389,27 @@ 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 = &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); @@ -396,13 +420,13 @@ where } } } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) { - // dangling pointer - throw_ub_format!("encountered dangling pointer in final constant") + // Codegen does not like dangling pointers, and generally `tcx` assumes that + // all allocations referenced anywhere actually exist. So, make sure we error here. + ecx.tcx.sess.span_err(ecx.tcx.span, "encountered dangling pointer in final constant"); } else if ecx.tcx.get_global_alloc(alloc_id).is_none() { - // We have hit an `AllocId` that is neither in local or global memory and isn't marked - // as dangling by local memory. + // We have hit an `AllocId` that is neither in local or global memory and isn't + // marked as dangling by local memory. That should be impossible. span_bug!(ecx.tcx.span, "encountered unknown alloc id {:?}", alloc_id); } } - Ok(()) } diff --git a/src/librustc_mir/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs index 4be95b69850df..81de8dcece683 100644 --- a/src/librustc_mir/transform/const_prop.rs +++ b/src/librustc_mir/transform/const_prop.rs @@ -702,8 +702,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { )) => l.is_bits() && r.is_bits(), interpret::Operand::Indirect(_) if mir_opt_level >= 2 => { let mplace = op.assert_mem_place(&self.ecx); - intern_const_alloc_recursive(&mut self.ecx, InternKind::ConstProp, mplace, false) - .expect("failed to intern alloc"); + intern_const_alloc_recursive(&mut self.ecx, InternKind::ConstProp, mplace, false); true } _ => false, diff --git a/src/test/ui/consts/dangling-alloc-id-ice.rs b/src/test/ui/consts/dangling-alloc-id-ice.rs index dbc50f1fbd4b4..3b7f1de5b9bea 100644 --- a/src/test/ui/consts/dangling-alloc-id-ice.rs +++ b/src/test/ui/consts/dangling-alloc-id-ice.rs @@ -1,11 +1,13 @@ // https://github.com/rust-lang/rust/issues/55223 +#![allow(const_err)] union Foo<'a> { y: &'a (), long_live_the_unit: &'static (), } -const FOO: &() = { //~ ERROR any use of this value will cause an error +const FOO: &() = { //~ ERROR it is undefined behavior to use this value +//~^ ERROR encountered dangling pointer in final constant let y = (); unsafe { Foo { y: &y }.long_live_the_unit } }; diff --git a/src/test/ui/consts/dangling-alloc-id-ice.stderr b/src/test/ui/consts/dangling-alloc-id-ice.stderr index 0e213555052c8..14a49810b9de5 100644 --- a/src/test/ui/consts/dangling-alloc-id-ice.stderr +++ b/src/test/ui/consts/dangling-alloc-id-ice.stderr @@ -1,13 +1,25 @@ -error: any use of this value will cause an error - --> $DIR/dangling-alloc-id-ice.rs:8:1 +error: encountered dangling pointer in final constant + --> $DIR/dangling-alloc-id-ice.rs:9:1 | LL | / const FOO: &() = { +LL | | LL | | let y = (); LL | | unsafe { Foo { y: &y }.long_live_the_unit } LL | | }; - | |__^ encountered dangling pointer in final constant + | |__^ + +error[E0080]: it is undefined behavior to use this value + --> $DIR/dangling-alloc-id-ice.rs:9:1 + | +LL | / const FOO: &() = { +LL | | +LL | | let y = (); +LL | | unsafe { Foo { y: &y }.long_live_the_unit } +LL | | }; + | |__^ type validation failed: encountered a dangling reference (use-after-free) | - = note: `#[deny(const_err)]` on by default + = 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. -error: aborting due to previous error +error: aborting due to 2 previous errors +For more information about this error, try `rustc --explain E0080`. diff --git a/src/test/ui/consts/dangling_raw_ptr.rs b/src/test/ui/consts/dangling_raw_ptr.rs index c2d8e6d421a28..ddd1fb1ba76e1 100644 --- a/src/test/ui/consts/dangling_raw_ptr.rs +++ b/src/test/ui/consts/dangling_raw_ptr.rs @@ -1,4 +1,4 @@ -const FOO: *const u32 = { //~ ERROR any use of this value will cause an error +const FOO: *const u32 = { //~ ERROR encountered dangling pointer in final constant let x = 42; &x }; diff --git a/src/test/ui/consts/dangling_raw_ptr.stderr b/src/test/ui/consts/dangling_raw_ptr.stderr index 4d4c2876c4598..a79ac62d5cdbd 100644 --- a/src/test/ui/consts/dangling_raw_ptr.stderr +++ b/src/test/ui/consts/dangling_raw_ptr.stderr @@ -1,13 +1,11 @@ -error: any use of this value will cause an error +error: encountered dangling pointer in final constant --> $DIR/dangling_raw_ptr.rs:1:1 | LL | / const FOO: *const u32 = { LL | | let x = 42; LL | | &x LL | | }; - | |__^ encountered dangling pointer in final constant - | - = note: `#[deny(const_err)]` on by default + | |__^ error: aborting due to previous error 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 f8aa652827381..0000000000000 --- 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 4772baf9d9a01..0000000000000 --- 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 867091af7ba76..0000000000000 --- 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 98a1c8bdd8967..0000000000000 --- 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 ed2ca86ba2c6b..ca927ef4a518b 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); // this is fine for the same reason as `BAR`. static BOO: &mut Foo<()> = &mut Foo(()); +// interior mutability is fine struct Meh { x: &'static UnsafeCell, } - 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 83c4e0ceba0de..7109ffd8b61d7 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 0000000000000..06fb27bcff866 --- /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 ensures that our mutability story is sound + +struct Meh { + x: &'static UnsafeCell, +} +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, +} +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 0000000000000..45e7d5a2cc3b3 --- /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 7388aad2a9e53..0000000000000 --- 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, -} - -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 7ddf77af4d3af..0000000000000 --- 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 0000000000000..cabd754e01ac3 --- /dev/null +++ b/src/test/ui/consts/miri_unleashed/raw_mutable_const.rs @@ -0,0 +1,10 @@ +// compile-flags: -Zunleash-the-miri-inside-of-you + +#![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 0000000000000..b5b5a965295a7 --- /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:7: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:7: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 0000000000000..00fad046b557d --- /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 = &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 0000000000000..974b1c3ff45b5 --- /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 = &Vec::new() as *const _; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error +