From 2f1a8e2d7a641a398e9e02c8c99e80f6e44dce87 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 16 Dec 2023 16:24:25 +0100 Subject: [PATCH 1/4] const-eval interner: from-scratch rewrite using mutability information from provenance rather than types --- compiler/rustc_const_eval/messages.ftl | 22 +- .../src/const_eval/eval_queries.rs | 32 +- .../src/const_eval/machine.rs | 2 +- compiler/rustc_const_eval/src/errors.rs | 43 +- .../rustc_const_eval/src/interpret/intern.rs | 557 +++++------------- .../rustc_const_eval/src/interpret/place.rs | 6 +- .../src/interpret/validity.rs | 145 ++++- .../src/util/caller_location.rs | 1 + .../rustc_middle/src/mir/interpret/error.rs | 4 +- .../rustc_middle/src/mir/interpret/pointer.rs | 6 + .../src/borrow_tracker/stacked_borrows/mod.rs | 4 +- .../src/borrow_tracker/tree_borrows/mod.rs | 4 +- .../heap/alloc_intrinsic_nontransient.rs | 4 +- .../heap/alloc_intrinsic_nontransient_fail.rs | 18 - .../alloc_intrinsic_nontransient_fail.stderr | 10 - .../heap/alloc_intrinsic_untyped.rs | 2 +- .../heap/alloc_intrinsic_untyped.stderr | 4 +- .../heap/dealloc_intrinsic_dangling.rs | 2 +- .../heap/dealloc_intrinsic_dangling.stderr | 2 +- .../consts/const-mut-refs/mut_ref_in_final.rs | 15 + .../const-mut-refs/mut_ref_in_final.stderr | 26 +- ...ut_ref_in_final_dynamic_check.32bit.stderr | 8 +- ...ut_ref_in_final_dynamic_check.64bit.stderr | 8 +- .../mut_ref_in_final_dynamic_check.rs | 4 +- .../const-points-to-static.32bit.stderr | 22 - .../const-points-to-static.64bit.stderr | 22 - tests/ui/consts/const-points-to-static.rs | 13 - tests/ui/consts/dangling-alloc-id-ice.rs | 2 +- tests/ui/consts/dangling-alloc-id-ice.stderr | 2 +- tests/ui/consts/dangling_raw_ptr.rs | 2 +- tests/ui/consts/dangling_raw_ptr.stderr | 2 +- ...interior-mut-const-via-union.32bit.stderr} | 8 +- ...interior-mut-const-via-union.64bit.stderr} | 8 +- ...ion.rs => interior-mut-const-via-union.rs} | 0 .../const_refers_to_static.32bit.stderr | 18 +- .../const_refers_to_static.64bit.stderr | 18 +- .../miri_unleashed/const_refers_to_static.rs | 5 + .../mutable_references_err.32bit.stderr | 177 +++++- .../mutable_references_err.64bit.stderr | 177 +++++- .../miri_unleashed/mutable_references_err.rs | 50 +- .../miri_unleashed/raw_mutable_const.rs | 8 - .../miri_unleashed/raw_mutable_const.stderr | 18 - .../static-no-inner-mut.32bit.stderr | 82 +++ .../static-no-inner-mut.64bit.stderr | 82 +++ .../miri_unleashed/static-no-inner-mut.rs | 36 ++ tests/ui/consts/raw-ptr-const.rs | 6 +- tests/ui/consts/raw-ptr-const.stderr | 10 - tests/ui/consts/raw-ptr-temp-const.rs | 6 + tests/ui/consts/raw-ptr-temp-const.stderr | 9 + tests/ui/consts/refs-to-cell-in-final.rs | 18 + tests/ui/consts/refs-to-cell-in-final.stderr | 17 + tests/ui/consts/std/cell.stderr | 8 +- 52 files changed, 1080 insertions(+), 675 deletions(-) delete mode 100644 tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient_fail.rs delete mode 100644 tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient_fail.stderr delete mode 100644 tests/ui/consts/const-points-to-static.32bit.stderr delete mode 100644 tests/ui/consts/const-points-to-static.64bit.stderr delete mode 100644 tests/ui/consts/const-points-to-static.rs rename tests/ui/consts/{invalid-union.32bit.stderr => interior-mut-const-via-union.32bit.stderr} (82%) rename tests/ui/consts/{invalid-union.64bit.stderr => interior-mut-const-via-union.64bit.stderr} (82%) rename tests/ui/consts/{invalid-union.rs => interior-mut-const-via-union.rs} (100%) delete mode 100644 tests/ui/consts/miri_unleashed/raw_mutable_const.rs delete mode 100644 tests/ui/consts/miri_unleashed/raw_mutable_const.stderr create mode 100644 tests/ui/consts/miri_unleashed/static-no-inner-mut.32bit.stderr create mode 100644 tests/ui/consts/miri_unleashed/static-no-inner-mut.64bit.stderr create mode 100644 tests/ui/consts/miri_unleashed/static-no-inner-mut.rs delete mode 100644 tests/ui/consts/raw-ptr-const.stderr create mode 100644 tests/ui/consts/raw-ptr-temp-const.rs create mode 100644 tests/ui/consts/raw-ptr-temp-const.stderr create mode 100644 tests/ui/consts/refs-to-cell-in-final.rs create mode 100644 tests/ui/consts/refs-to-cell-in-final.stderr diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 4a426ed16e541..2728d2ca463df 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -46,8 +46,8 @@ const_eval_dangling_int_pointer = {$bad_pointer_message}: {$pointer} is a dangling pointer (it has no provenance) const_eval_dangling_null_pointer = {$bad_pointer_message}: null pointer is a dangling pointer (it has no provenance) -const_eval_dangling_ptr_in_final = encountered dangling pointer in final constant +const_eval_dangling_ptr_in_final = encountered dangling pointer in final value of {const_eval_intern_kind} const_eval_dead_local = accessing a dead local variable const_eval_dealloc_immutable = @@ -134,6 +134,14 @@ const_eval_interior_mutable_data_refer = This would make multiple uses of a constant to be able to see different values and allow circumventing the `Send` and `Sync` requirements for shared mutable data, which is unsound. +const_eval_intern_kind = {$kind -> + [static] static + [static_mut] mutable static + [const] constant + [promoted] promoted + *[other] {""} +} + const_eval_invalid_align = align has to be a power of 2 @@ -205,6 +213,8 @@ const_eval_modified_global = const_eval_mut_deref = mutation through a reference is not allowed in {const_eval_const_context}s +const_eval_mutable_ptr_in_final = encountered mutable pointer in final value of {const_eval_intern_kind} + const_eval_non_const_fmt_macro_call = cannot call non-const formatting macro in {const_eval_const_context}s @@ -392,9 +402,6 @@ const_eval_unstable_in_stable = .unstable_sugg = if it is not part of the public API, make this function unstably const .bypass_sugg = otherwise `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks -const_eval_unsupported_untyped_pointer = unsupported untyped pointer in constant - .note = memory only reachable via raw pointers is not supported - const_eval_unterminated_c_string = reading a null-terminated string starting at {$pointer} with no null found before end of allocation @@ -406,7 +413,6 @@ const_eval_upcast_mismatch = ## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`. ## (We'd love to sort this differently to make that more clear but tidy won't let us...) -const_eval_validation_box_to_mut = {$front_matter}: encountered a box pointing to mutable memory in a constant const_eval_validation_box_to_static = {$front_matter}: encountered a box pointing to a static variable in a constant const_eval_validation_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty} const_eval_validation_dangling_box_no_provenance = {$front_matter}: encountered a dangling box ({$pointer} has no provenance) @@ -441,7 +447,8 @@ const_eval_validation_invalid_fn_ptr = {$front_matter}: encountered {$value}, bu const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer -const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in a `const` +const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in a `const` or `static` +const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!` const_eval_validation_null_box = {$front_matter}: encountered a null box const_eval_validation_null_fn_ptr = {$front_matter}: encountered a null function pointer @@ -451,7 +458,6 @@ const_eval_validation_out_of_range = {$front_matter}: encountered {$value}, but const_eval_validation_partial_pointer = {$front_matter}: encountered a partial pointer or a mix of pointers const_eval_validation_pointer_as_int = {$front_matter}: encountered a pointer, but {$expected} const_eval_validation_ptr_out_of_range = {$front_matter}: encountered a pointer, but expected something that cannot possibly fail to be {$in_range} -const_eval_validation_ref_to_mut = {$front_matter}: encountered a reference pointing to mutable memory in a constant const_eval_validation_ref_to_static = {$front_matter}: encountered a reference pointing to a static variable in a constant const_eval_validation_ref_to_uninhabited = {$front_matter}: encountered a reference pointing to uninhabited type {$ty} const_eval_validation_unaligned_box = {$front_matter}: encountered an unaligned box (required {$required_bytes} byte alignment but found {$found_bytes}) @@ -459,7 +465,7 @@ const_eval_validation_unaligned_ref = {$front_matter}: encountered an unaligned const_eval_validation_uninhabited_enum_variant = {$front_matter}: encountered an uninhabited enum variant const_eval_validation_uninhabited_val = {$front_matter}: encountered a value of uninhabited type `{$ty}` const_eval_validation_uninit = {$front_matter}: encountered uninitialized memory, but {$expected} -const_eval_validation_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in a `const` +const_eval_validation_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in read-only memory const_eval_write_through_immutable_pointer = writing through a pointer that was derived from a shared (immutable) reference diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index 4236117d75b91..6a92ed9717de5 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -293,6 +293,9 @@ pub fn eval_in_interpreter<'mir, 'tcx>( cid: GlobalId<'tcx>, is_static: bool, ) -> ::rustc_middle::mir::interpret::EvalToAllocationRawResult<'tcx> { + // `is_static` just means "in static", it could still be a promoted! + debug_assert_eq!(is_static, ecx.tcx.static_mutability(cid.instance.def_id()).is_some()); + let res = ecx.load_mir(cid.instance.def, cid.promoted); match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body)) { Err(error) => { @@ -330,8 +333,7 @@ pub fn eval_in_interpreter<'mir, 'tcx>( Ok(mplace) => { // Since evaluation had no errors, validate the resulting constant. // This is a separate `try` block to provide more targeted error reporting. - let validation = - const_validate_mplace(&ecx, &mplace, is_static, cid.promoted.is_some()); + let validation = const_validate_mplace(&ecx, &mplace, cid); let alloc_id = mplace.ptr().provenance.unwrap().alloc_id(); @@ -350,22 +352,26 @@ pub fn eval_in_interpreter<'mir, 'tcx>( pub fn const_validate_mplace<'mir, 'tcx>( ecx: &InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>, mplace: &MPlaceTy<'tcx>, - is_static: bool, - is_promoted: bool, + cid: GlobalId<'tcx>, ) -> InterpResult<'tcx> { let mut ref_tracking = RefTracking::new(mplace.clone()); let mut inner = false; while let Some((mplace, path)) = ref_tracking.todo.pop() { - let mode = if is_static { - if is_promoted { - // Promoteds in statics are allowed to point to statics. - CtfeValidationMode::Const { inner, allow_static_ptrs: true } - } else { - // a `static` - CtfeValidationMode::Regular + let mode = match ecx.tcx.static_mutability(cid.instance.def_id()) { + Some(_) if cid.promoted.is_some() => { + // Promoteds in statics are consts that re allowed to point to statics. + CtfeValidationMode::Const { + allow_immutable_unsafe_cell: false, + allow_static_ptrs: true, + } + } + Some(mutbl) => CtfeValidationMode::Static { mutbl }, // a `static` + None => { + // In normal `const` (not promoted), the outermost allocation is always only copied, + // so having `UnsafeCell` in there is okay despite them being in immutable memory. + let allow_immutable_unsafe_cell = cid.promoted.is_none() && !inner; + CtfeValidationMode::Const { allow_immutable_unsafe_cell, allow_static_ptrs: false } } - } else { - CtfeValidationMode::Const { inner, allow_static_ptrs: false } }; ecx.const_validate_operand(&mplace.into(), path, &mut ref_tracking, mode)?; inner = true; diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 6947ace17c5e6..6ee290633494e 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -723,7 +723,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, && ty.is_freeze(*ecx.tcx, ecx.param_env) { let place = ecx.ref_to_mplace(val)?; - let new_place = place.map_provenance(|p| p.map(CtfeProvenance::as_immutable)); + let new_place = place.map_provenance(CtfeProvenance::as_immutable); Ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout)) } else { Ok(val.clone()) diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 171cc89d6adb8..c07db2773c1f0 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use rustc_errors::{ DiagCtxt, DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, IntoDiagnostic, Level, @@ -13,12 +15,24 @@ use rustc_middle::ty::{self, Ty}; use rustc_span::Span; use rustc_target::abi::call::AdjustForForeignAbiError; use rustc_target::abi::{Size, WrappingRange}; +use rustc_type_ir::Mutability; + +use crate::interpret::InternKind; #[derive(Diagnostic)] #[diag(const_eval_dangling_ptr_in_final)] pub(crate) struct DanglingPtrInFinal { #[primary_span] pub span: Span, + pub kind: InternKind, +} + +#[derive(Diagnostic)] +#[diag(const_eval_mutable_ptr_in_final)] +pub(crate) struct MutablePtrInFinal { + #[primary_span] + pub span: Span, + pub kind: InternKind, } #[derive(Diagnostic)] @@ -194,14 +208,6 @@ pub(crate) struct UnallowedInlineAsm { pub kind: ConstContext, } -#[derive(Diagnostic)] -#[diag(const_eval_unsupported_untyped_pointer)] -#[note] -pub(crate) struct UnsupportedUntypedPointer { - #[primary_span] - pub span: Span, -} - #[derive(Diagnostic)] #[diag(const_eval_interior_mutable_data_refer, code = "E0492")] pub(crate) struct InteriorMutableDataRefer { @@ -615,18 +621,16 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_static, PtrToStatic { ptr_kind: PointerKind::Ref } => const_eval_validation_ref_to_static, - PtrToMut { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_mut, - PtrToMut { ptr_kind: PointerKind::Ref } => const_eval_validation_ref_to_mut, - PointerAsInt { .. } => const_eval_validation_pointer_as_int, PartialPointer => const_eval_validation_partial_pointer, MutableRefInConst => const_eval_validation_mutable_ref_in_const, + MutableRefToImmutable => const_eval_validation_mutable_ref_to_immutable, NullFnPtr => const_eval_validation_null_fn_ptr, NeverVal => const_eval_validation_never_val, NullablePtrOutOfRange { .. } => const_eval_validation_nullable_ptr_out_of_range, PtrOutOfRange { .. } => const_eval_validation_ptr_out_of_range, OutOfRange { .. } => const_eval_validation_out_of_range, - UnsafeCell => const_eval_validation_unsafe_cell, + UnsafeCellInImmutable => const_eval_validation_unsafe_cell, UninhabitedVal { .. } => const_eval_validation_uninhabited_val, InvalidEnumTag { .. } => const_eval_validation_invalid_enum_tag, UninhabitedEnumVariant => const_eval_validation_uninhabited_enum_variant, @@ -772,11 +776,11 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { } NullPtr { .. } | PtrToStatic { .. } - | PtrToMut { .. } | MutableRefInConst + | MutableRefToImmutable | NullFnPtr | NeverVal - | UnsafeCell + | UnsafeCellInImmutable | InvalidMetaSliceTooLarge { .. } | InvalidMetaTooLarge { .. } | DanglingPtrUseAfterFree { .. } @@ -905,3 +909,14 @@ impl ReportErrorExt for ResourceExhaustionInfo { } fn add_args(self, _: &DiagCtxt, _: &mut DiagnosticBuilder<'_, G>) {} } + +impl rustc_errors::IntoDiagnosticArg for InternKind { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Borrowed(match self { + InternKind::Static(Mutability::Not) => "static", + InternKind::Static(Mutability::Mut) => "static_mut", + InternKind::Constant => "const", + InternKind::Promoted => "promoted", + })) + } +} diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs index 202819ee6338f..a5a94e80b6606 100644 --- a/compiler/rustc_const_eval/src/interpret/intern.rs +++ b/compiler/rustc_const_eval/src/interpret/intern.rs @@ -5,30 +5,24 @@ //! //! In principle, this is not very complicated: we recursively walk the final value, follow all the //! pointers, and move all reachable allocations to the global `tcx` memory. The only complication -//! is picking the right mutability for the allocations in a `static` initializer: we want to make -//! as many allocations as possible immutable so LLVM can put them into read-only memory. At the -//! same time, we need to make memory that could be mutated by the program mutable to avoid -//! incorrect compilations. To achieve this, we do a type-based traversal of the final value, -//! tracking mutable and shared references and `UnsafeCell` to determine the current mutability. -//! (In principle, we could skip this type-based part for `const` and promoteds, as they need to be -//! always immutable. At least for `const` however we use this opportunity to reject any `const` -//! that contains allocations whose mutability we cannot identify.) +//! is picking the right mutability: the outermost allocation generally has a clear mutability, but +//! what about the other allocations it points to that have also been created with this value? We +//! don't want to do guesswork here. The rules are: `static`, `const`, and promoted can only create +//! immutable allocations that way. `static mut` can be initialized with expressions like `&mut 42`, +//! so all inner allocations are marked mutable. Some of them could potentially be made immutable, +//! but that would require relying on type information, and given how many ways Rust has to lie +//! about type information, we want to avoid doing that. -use super::validity::RefTracking; -use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; +use rustc_ast::Mutability; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_middle::mir::interpret::{CtfeProvenance, InterpResult}; -use rustc_middle::ty::{self, layout::TyAndLayout, Ty}; - -use rustc_ast::Mutability; +use rustc_middle::ty::layout::TyAndLayout; -use super::{ - AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy, Projectable, - ValueVisitor, -}; +use super::{AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy}; use crate::const_eval; -use crate::errors::{DanglingPtrInFinal, UnsupportedUntypedPointer}; +use crate::errors::{DanglingPtrInFinal, MutablePtrInFinal}; pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine< 'mir, @@ -41,271 +35,44 @@ pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine< MemoryMap = FxIndexMap, Allocation)>, >; -struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>> { - /// The ectx from which we intern. - ecx: &'rt mut InterpCx<'mir, 'tcx, M>, - /// Previously encountered safe references. - ref_tracking: &'rt mut RefTracking<(MPlaceTy<'tcx>, InternMode)>, - /// A list of all encountered allocations. After type-based interning, we traverse this list to - /// also intern allocations that are only referenced by a raw pointer or inside a union. - leftover_allocations: &'rt mut FxIndexSet, - /// The root kind of the value that we're looking at. This field is never mutated for a - /// particular allocation. It is primarily used to make as many allocations as possible - /// read-only so LLVM can place them in const memory. - mode: InternMode, - /// This field stores whether we are *currently* inside an `UnsafeCell`. This can affect - /// the intern mode of references we encounter. - inside_unsafe_cell: bool, -} - -#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)] -enum InternMode { - /// A static and its current mutability. Below shared references inside a `static mut`, - /// this is *immutable*, and below mutable references inside an `UnsafeCell`, this - /// is *mutable*. - Static(hir::Mutability), - /// A `const`. - Const, -} - -/// Signalling data structure to ensure we don't recurse -/// into the memory of other constants or statics -struct IsStaticOrFn; - -/// Intern an allocation without looking at its children. -/// `mode` is the mode of the environment where we found this pointer. -/// `mutability` is the mutability of the place to be interned; even if that says -/// `immutable` things might become mutable if `ty` is not frozen. -/// `ty` can be `None` if there is no potential interior mutability -/// to account for (e.g. for vtables). -fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>>( +/// Intern an allocation. Returns `Err` if the allocation does not exist in the local memory. +/// +/// `mutability` can be used to force immutable interning: if it is `Mutability::Not`, the +/// allocation is interned immutably; if it is `Mutability::Mut`, then the allocation *must be* +/// already mutable (as a sanity check). +/// +/// `recursive_alloc` is called for all recursively encountered allocations. +fn intern_shallow<'rt, 'mir, 'tcx, T, M: CompileTimeMachine<'mir, 'tcx, T>>( ecx: &'rt mut InterpCx<'mir, 'tcx, M>, - leftover_allocations: &'rt mut FxIndexSet, alloc_id: AllocId, - mode: InternMode, - ty: Option>, -) -> Option { - trace!("intern_shallow {:?} with {:?}", alloc_id, mode); + mutability: Mutability, + mut recursive_alloc: impl FnMut(&InterpCx<'mir, 'tcx, M>, CtfeProvenance), +) -> Result<(), ()> { + trace!("intern_shallow {:?}", alloc_id); // remove allocation - let tcx = ecx.tcx; - let Some((kind, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else { - // Pointer not found in local memory map. It is either a pointer to the global - // map, or dangling. - // If the pointer is dangling (neither in local nor global memory), we leave it - // to validation to error -- it has the much better error messages, pointing out where - // in the value the dangling reference lies. - // The `span_delayed_bug` ensures that we don't forget such a check in validation. - if tcx.try_get_global_alloc(alloc_id).is_none() { - tcx.dcx().span_delayed_bug(ecx.tcx.span, "tried to intern dangling pointer"); - } - // treat dangling pointers like other statics - // just to stop trying to recurse into them - return Some(IsStaticOrFn); + let Some((_kind, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else { + return Err(()); }; - // This match is just a canary for future changes to `MemoryKind`, which most likely need - // changes in this function. - match kind { - MemoryKind::Stack - | MemoryKind::Machine(const_eval::MemoryKind::Heap) - | MemoryKind::CallerLocation => {} - } // Set allocation mutability as appropriate. This is used by LLVM to put things into // read-only memory, and also by Miri when evaluating other globals that // access this one. - if let InternMode::Static(mutability) = mode { - // For this, we need to take into account `UnsafeCell`. When `ty` is `None`, we assume - // no interior mutability. - let frozen = ty.map_or(true, |ty| ty.is_freeze(*ecx.tcx, ecx.param_env)); - // For statics, allocation mutability is the combination of place mutability and - // type mutability. - // The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere. - let immutable = mutability == Mutability::Not && frozen; - if immutable { + match mutability { + Mutability::Not => { alloc.mutability = Mutability::Not; - } else { - // Just making sure we are not "upgrading" an immutable allocation to mutable. + } + Mutability::Mut => { + // This must be already mutable, we won't "un-freeze" allocations ever. assert_eq!(alloc.mutability, Mutability::Mut); } - } else { - // No matter what, *constants are never mutable*. Mutating them is UB. - // See const_eval::machine::MemoryExtra::can_access_statics for why - // immutability is so important. - - // Validation will ensure that there is no `UnsafeCell` on an immutable allocation. - alloc.mutability = Mutability::Not; - }; - // link the alloc id to the actual allocation - leftover_allocations.extend(alloc.provenance().ptrs().iter().map(|&(_, prov)| prov.alloc_id())); - let alloc = tcx.mk_const_alloc(alloc); - tcx.set_alloc_id_memory(alloc_id, alloc); - None -} - -impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>> - InternVisitor<'rt, 'mir, 'tcx, M> -{ - fn intern_shallow( - &mut self, - alloc_id: AllocId, - mode: InternMode, - ty: Option>, - ) -> Option { - intern_shallow(self.ecx, self.leftover_allocations, alloc_id, mode, ty) } -} - -impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>> - ValueVisitor<'mir, 'tcx, M> for InternVisitor<'rt, 'mir, 'tcx, M> -{ - type V = MPlaceTy<'tcx>; - - #[inline(always)] - fn ecx(&self) -> &InterpCx<'mir, 'tcx, M> { - self.ecx - } - - fn visit_value(&mut self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx> { - // Handle Reference types, as these are the only types with provenance supported by const eval. - // Raw pointers (and boxes) are handled by the `leftover_allocations` logic. - let tcx = self.ecx.tcx; - let ty = mplace.layout.ty; - if let ty::Ref(_, referenced_ty, ref_mutability) = *ty.kind() { - let value = self.ecx.read_immediate(mplace)?; - let mplace = self.ecx.ref_to_mplace(&value)?; - assert_eq!(mplace.layout.ty, referenced_ty); - // Handle trait object vtables. - if let ty::Dynamic(_, _, ty::Dyn) = - tcx.struct_tail_erasing_lifetimes(referenced_ty, self.ecx.param_env).kind() - { - let ptr = mplace.meta().unwrap_meta().to_pointer(&tcx)?; - if let Some(prov) = ptr.provenance { - // Explicitly choose const mode here, since vtables are immutable, even - // if the reference of the fat pointer is mutable. - self.intern_shallow(prov.alloc_id(), InternMode::Const, None); - } else { - // Validation will error (with a better message) on an invalid vtable pointer. - // Let validation show the error message, but make sure it *does* error. - tcx.dcx() - .span_delayed_bug(tcx.span, "vtables pointers cannot be integer pointers"); - } - } - // Check if we have encountered this pointer+layout combination before. - // Only recurse for allocation-backed pointers. - if let Some(prov) = mplace.ptr().provenance { - // Compute the mode with which we intern this. Our goal here is to make as many - // statics as we can immutable so they can be placed in read-only memory by LLVM. - let ref_mode = match self.mode { - InternMode::Static(mutbl) => { - // In statics, merge outer mutability with reference mutability and - // take into account whether we are in an `UnsafeCell`. - - // The only way a mutable reference actually works as a mutable reference is - // by being in a `static mut` directly or behind another mutable reference. - // If there's an immutable reference or we are inside a `static`, then our - // mutable reference is equivalent to an immutable one. As an example: - // `&&mut Foo` is semantically equivalent to `&&Foo` - match ref_mutability { - _ if self.inside_unsafe_cell => { - // Inside an `UnsafeCell` is like inside a `static mut`, the "outer" - // mutability does not matter. - InternMode::Static(ref_mutability) - } - Mutability::Not => { - // A shared reference, things become immutable. - // We do *not* consider `freeze` here: `intern_shallow` considers - // `freeze` for the actual mutability of this allocation; the intern - // mode for references contained in this allocation is tracked more - // precisely when traversing the referenced data (by tracking - // `UnsafeCell`). This makes sure that `&(&i32, &Cell)` still - // has the left inner reference interned into a read-only - // allocation. - InternMode::Static(Mutability::Not) - } - Mutability::Mut => { - // Mutable reference. - InternMode::Static(mutbl) - } - } - } - InternMode::Const => { - // Ignore `UnsafeCell`, everything is immutable. Validity does some sanity - // checking for mutable references that we encounter -- they must all be - // ZST. - InternMode::Const - } - }; - match self.intern_shallow(prov.alloc_id(), ref_mode, Some(referenced_ty)) { - // No need to recurse, these are interned already and statics may have - // cycles, so we don't want to recurse there - Some(IsStaticOrFn) => {} - // intern everything referenced by this value. The mutability is taken from the - // reference. It is checked above that mutable references only happen in - // `static mut` - None => self.ref_tracking.track((mplace, ref_mode), || ()), - } - } - Ok(()) - } else { - // Not a reference. Check if we want to recurse. - let is_walk_needed = |mplace: &MPlaceTy<'tcx>| -> InterpResult<'tcx, bool> { - // ZSTs cannot contain pointers, we can avoid the interning walk. - if mplace.layout.is_zst() { - return Ok(false); - } - - // Now, check whether this allocation could contain references. - // - // Note, this check may sometimes not be cheap, so we only do it when the walk we'd like - // to avoid could be expensive: on the potentially larger types, arrays and slices, - // rather than on all aggregates unconditionally. - if matches!(mplace.layout.ty.kind(), ty::Array(..) | ty::Slice(..)) { - let Some((size, _align)) = self.ecx.size_and_align_of_mplace(mplace)? else { - // We do the walk if we can't determine the size of the mplace: we may be - // dealing with extern types here in the future. - return Ok(true); - }; - - // If there is no provenance in this allocation, it does not contain references - // that point to another allocation, and we can avoid the interning walk. - if let Some(alloc) = self.ecx.get_ptr_alloc(mplace.ptr(), size)? { - if !alloc.has_provenance() { - return Ok(false); - } - } else { - // We're encountering a ZST here, and can avoid the walk as well. - return Ok(false); - } - } - - // In the general case, we do the walk. - Ok(true) - }; - - // If this allocation contains no references to intern, we avoid the potentially costly - // walk. - // - // We can do this before the checks for interior mutability below, because only references - // are relevant in that situation, and we're checking if there are any here. - if !is_walk_needed(mplace)? { - return Ok(()); - } - - if let Some(def) = mplace.layout.ty.ty_adt_def() { - if def.is_unsafe_cell() { - // We are crossing over an `UnsafeCell`, we can mutate again. This means that - // References we encounter inside here are interned as pointing to mutable - // allocations. - // Remember the `old` value to handle nested `UnsafeCell`. - let old = std::mem::replace(&mut self.inside_unsafe_cell, true); - let walked = self.walk_value(mplace); - self.inside_unsafe_cell = old; - return walked; - } - } - - self.walk_value(mplace) - } + // record child allocations + for &(_, prov) in alloc.provenance().ptrs().iter() { + recursive_alloc(ecx, prov); } + // link the alloc id to the actual allocation + let alloc = ecx.tcx.mk_const_alloc(alloc); + ecx.tcx.set_alloc_id_memory(alloc_id, alloc); + Ok(()) } /// How a constant value should be interned. @@ -332,122 +99,105 @@ pub fn intern_const_alloc_recursive< intern_kind: InternKind, ret: &MPlaceTy<'tcx>, ) -> Result<(), ErrorGuaranteed> { - let tcx = ecx.tcx; - let base_intern_mode = match intern_kind { - InternKind::Static(mutbl) => InternMode::Static(mutbl), - // `Constant` includes array lengths. - InternKind::Constant | InternKind::Promoted => InternMode::Const, - }; - - // Type based interning. - // `ref_tracking` tracks typed references we have already interned and still need to crawl for - // more typed information inside them. - // `leftover_allocations` collects *all* allocations we see, because some might not - // be available in a typed way. They get interned at the end. - let mut ref_tracking = RefTracking::empty(); - let leftover_allocations = &mut FxIndexSet::default(); - - // start with the outermost allocation - intern_shallow( - ecx, - leftover_allocations, - // The outermost allocation must exist, because we allocated it with - // `Memory::allocate`. - ret.ptr().provenance.unwrap().alloc_id(), - base_intern_mode, - Some(ret.layout.ty), - ); - - ref_tracking.track((ret.clone(), base_intern_mode), || ()); - - while let Some(((mplace, mode), _)) = ref_tracking.todo.pop() { - let res = InternVisitor { - ref_tracking: &mut ref_tracking, - ecx, - mode, - leftover_allocations, - inside_unsafe_cell: false, + // We are interning recursively, and for mutability we are distinguishing the "root" allocation + // that we are starting in, and all other allocations that we are encountering recursively. + let (base_mutability, inner_mutability) = match intern_kind { + InternKind::Constant | InternKind::Promoted => { + // Completely immutable. Interning anything mutably here can only lead to unsoundness, + // since all consts are conceptually independent values but share the same underlying + // memory. + (Mutability::Not, Mutability::Not) } - .visit_value(&mplace); - // We deliberately *ignore* interpreter errors here. When there is a problem, the remaining - // references are "leftover"-interned, and later validation will show a proper error - // and point at the right part of the value causing the problem. - match res { - Ok(()) => {} - Err(error) => { - ecx.tcx.dcx().span_delayed_bug( - ecx.tcx.span, - format!( - "error during interning should later cause validation failure: {}", - ecx.format_error(error), - ), - ); - } + InternKind::Static(Mutability::Not) => { + ( + // Outermost allocation is mutable if `!Freeze`. + if ret.layout.ty.is_freeze(*ecx.tcx, ecx.param_env) { + Mutability::Not + } else { + Mutability::Mut + }, + // Inner allocations are never mutable. They can only arise via the "tail + // expression" / "outer scope" rule, and we treat them consistently with `const`. + Mutability::Not, + ) } - } - - // Intern the rest of the allocations as mutable. These might be inside unions, padding, raw - // pointers, ... So we can't intern them according to their type rules + InternKind::Static(Mutability::Mut) => { + // Just make everything mutable. We accept code like + // `static mut X = &mut [42]`, so even inner allocations need to be mutable. + (Mutability::Mut, Mutability::Mut) + } + }; - let mut todo: Vec<_> = leftover_allocations.iter().cloned().collect(); - debug!(?todo); - debug!("dead_alloc_map: {:#?}", ecx.memory.dead_alloc_map); - while let Some(alloc_id) = todo.pop() { - if let Some((_, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) { - // We can't call the `intern_shallow` method here, as its logic is tailored to safe - // references and a `leftover_allocations` set (where we only have a todo-list here). - // So we hand-roll the interning logic here again. - match intern_kind { - // Statics may point to mutable allocations. - // Even for immutable statics it would be ok to have mutable allocations behind - // raw pointers, e.g. for `static FOO: *const AtomicUsize = &AtomicUsize::new(42)`. - InternKind::Static(_) => {} - // Raw pointers in promoteds may only point to immutable things so we mark - // everything as immutable. - // It is UB to mutate through a raw pointer obtained via an immutable reference: - // Since all references and pointers inside a promoted must by their very definition - // be created from an immutable reference (and promotion also excludes interior - // mutability), mutating through them would be UB. - // There's no way we can check whether the user is using raw pointers correctly, - // so all we can do is mark this as immutable here. - InternKind::Promoted => { - // See const_eval::machine::MemoryExtra::can_access_statics for why - // immutability is so important. - alloc.mutability = Mutability::Not; - } - // If it's a constant, we should not have any "leftovers" as everything - // is tracked by const-checking. - // FIXME: downgrade this to a warning? It rejects some legitimate consts, - // such as `const CONST_RAW: *const Vec = &Vec::new() as *const _;`. - // - // NOTE: it looks likes this code path is only reachable when we try to intern - // something that cannot be promoted, which in constants means values that have - // drop glue, such as the example above. - InternKind::Constant => { - ecx.tcx.dcx().emit_err(UnsupportedUntypedPointer { span: ecx.tcx.span }); - // For better errors later, mark the allocation as immutable. - alloc.mutability = Mutability::Not; - } - } - let alloc = tcx.mk_const_alloc(alloc); - tcx.set_alloc_id_memory(alloc_id, alloc); - for &(_, prov) in alloc.inner().provenance().ptrs().iter() { - let alloc_id = prov.alloc_id(); - if leftover_allocations.insert(alloc_id) { - todo.push(alloc_id); + // Initialize recursive interning. + let base_alloc_id = ret.ptr().provenance.unwrap().alloc_id(); + let mut todo = vec![(base_alloc_id, base_mutability)]; + // We need to distinguish "has just been interned" from "was already in `tcx`", + // so we track this in a separate set. + let mut just_interned = FxHashSet::default(); + // Whether we encountered a bad mutable pointer. + // We want to first report "dangling" and then "mutable", so we need to delay reporting these + // errors. + let mut found_bad_mutable_pointer = false; + + // Keep interning as long as there are things to intern. + // We show errors if there are dangling pointers, or mutable pointers in immutable contexts + // (i.e., everything except for `static mut`). When these errors affect references, it is + // unfortunate that we show these errors here and not during validation, since validation can + // show much nicer errors. However, we do need these checks to be run on all pointers, including + // raw pointers, so we cannot rely on validation to catch them -- and since interning runs + // before validation, and interning doesn't know the type of anything, this means we can't show + // better errors. Maybe we should consider doing validation before interning in the future. + while let Some((alloc_id, mutability)) = todo.pop() { + if ecx.tcx.try_get_global_alloc(alloc_id).is_some() { + // Already interned. + debug_assert!(!ecx.memory.alloc_map.contains_key(&alloc_id)); + continue; + } + just_interned.insert(alloc_id); + intern_shallow(ecx, alloc_id, mutability, |ecx, prov| { + let alloc_id = prov.alloc_id(); + if intern_kind != InternKind::Promoted + && inner_mutability == Mutability::Not + && !prov.immutable() + { + if ecx.tcx.try_get_global_alloc(alloc_id).is_some() + && !just_interned.contains(&alloc_id) + { + // This is a pointer to some memory from another constant. We encounter mutable + // pointers to such memory since we do not always track immutability through + // these "global" pointers. Allowing them is harmless; the point of these checks + // during interning is to justify why we intern the *new* allocations immutably, + // so we can completely ignore existing allocations. We also don't need to add + // this to the todo list, since after all it is already interned. + return; } + // Found a mutable pointer inside a const where inner allocations should be + // immutable. We exclude promoteds from this, since things like `&mut []` and + // `&None::>` lead to promotion that can produce mutable pointers. We rely + // on the promotion analysis not screwing up to ensure that it is sound to intern + // promoteds as immutable. + found_bad_mutable_pointer = true; } - } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) { - // Codegen does not like dangling pointers, and generally `tcx` assumes that - // all allocations referenced anywhere actually exist. So, make sure we error here. - let reported = ecx.tcx.dcx().emit_err(DanglingPtrInFinal { span: ecx.tcx.span }); - return Err(reported); - } else if ecx.tcx.try_get_global_alloc(alloc_id).is_none() { - // We have hit an `AllocId` that is neither in local or global memory and isn't - // marked as dangling by local memory. That should be impossible. - span_bug!(ecx.tcx.span, "encountered unknown alloc id {:?}", alloc_id); - } + // It is tempting to intern as immutable if `prov.immutable()`. However, there + // might be multiple pointers to the same allocation, and if *at least one* of + // them is mutable, the allocation must be interned mutably. We will intern the + // allocation when we encounter the first pointer. Therefore we always intern + // with `inner_mutability`, and furthermore we ensured above that if that is + // "immutable", then there are *no* mutable pointers anywhere in the newly + // interned memory. + todo.push((alloc_id, inner_mutability)); + }) + .map_err(|()| { + ecx.tcx.dcx().emit_err(DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind }) + })?; } + if found_bad_mutable_pointer { + return Err(ecx + .tcx + .dcx() + .emit_err(MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind })); + } + Ok(()) } @@ -462,29 +212,18 @@ pub fn intern_const_alloc_for_constprop< ecx: &mut InterpCx<'mir, 'tcx, M>, alloc_id: AllocId, ) -> InterpResult<'tcx, ()> { - // Move allocation to `tcx`. - let Some((_, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else { - // Pointer not found in local memory map. It is either a pointer to the global - // map, or dangling. - if ecx.tcx.try_get_global_alloc(alloc_id).is_none() { - throw_ub!(DeadLocal) - } + if ecx.tcx.try_get_global_alloc(alloc_id).is_some() { // The constant is already in global memory. Do nothing. return Ok(()); - }; - - alloc.mutability = Mutability::Not; - - // We are not doing recursive interning, so we don't currently support provenance. - // (If this assertion ever triggers, we should just implement a - // proper recursive interning loop.) - assert!(alloc.provenance().ptrs().is_empty()); - - // Link the alloc id to the actual allocation - let alloc = ecx.tcx.mk_const_alloc(alloc); - ecx.tcx.set_alloc_id_memory(alloc_id, alloc); - - Ok(()) + } + // Move allocation to `tcx`. + intern_shallow(ecx, alloc_id, Mutability::Not, |_ecx, _| { + // We are not doing recursive interning, so we don't currently support provenance. + // (If this assertion ever triggers, we should just implement a + // proper recursive interning loop -- or just call `intern_const_alloc_recursive`. + panic!("`intern_const_alloc_for_constprop` called on allocation with nested provenance") + }) + .map_err(|()| err_ub!(DeadLocal).into()) } impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>> @@ -504,12 +243,16 @@ impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>> // `allocate` picks a fresh AllocId that we will associate with its data below. let dest = self.allocate(layout, MemoryKind::Stack)?; f(self, &dest.clone().into())?; - let mut alloc = - self.memory.alloc_map.remove(&dest.ptr().provenance.unwrap().alloc_id()).unwrap().1; - alloc.mutability = Mutability::Not; - let alloc = self.tcx.mk_const_alloc(alloc); let alloc_id = dest.ptr().provenance.unwrap().alloc_id(); // this was just allocated, it must have provenance - self.tcx.set_alloc_id_memory(alloc_id, alloc); + intern_shallow(self, alloc_id, Mutability::Not, |ecx, prov| { + // We are not doing recursive interning, so we don't currently support provenance. + // (If this assertion ever triggers, we should just implement a + // proper recursive interning loop -- or just call `intern_const_alloc_recursive`. + if !ecx.tcx.try_get_global_alloc(prov.alloc_id()).is_some() { + panic!("`intern_with_temp_alloc` with nested allocations"); + } + }) + .unwrap(); Ok(alloc_id) } } diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index 639b269ac257a..b39efad61bb3c 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -62,8 +62,8 @@ pub(super) struct MemPlace { impl MemPlace { /// Adjust the provenance of the main pointer (metadata is unaffected). - pub fn map_provenance(self, f: impl FnOnce(Option) -> Option) -> Self { - MemPlace { ptr: self.ptr.map_provenance(f), ..self } + pub fn map_provenance(self, f: impl FnOnce(Prov) -> Prov) -> Self { + MemPlace { ptr: self.ptr.map_provenance(|p| p.map(f)), ..self } } /// Turn a mplace into a (thin or wide) pointer, as a reference, pointing to the same space. @@ -128,7 +128,7 @@ impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> { } /// Adjust the provenance of the main pointer (metadata is unaffected). - pub fn map_provenance(self, f: impl FnOnce(Option) -> Option) -> Self { + pub fn map_provenance(self, f: impl FnOnce(Prov) -> Prov) -> Self { MPlaceTy { mplace: self.mplace.map_provenance(f), ..self } } diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 8b44b87647dab..f028d7802ae6b 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -9,12 +9,13 @@ use std::num::NonZeroUsize; use either::{Left, Right}; +use hir::def::DefKind; use rustc_ast::Mutability; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_middle::mir::interpret::{ - ExpectedKind, InterpError, InvalidMetaKind, Misalignment, PointerKind, ValidationErrorInfo, - ValidationErrorKind, ValidationErrorKind::*, + ExpectedKind, InterpError, InvalidMetaKind, Misalignment, PointerKind, Provenance, + ValidationErrorInfo, ValidationErrorKind, ValidationErrorKind::*, }; use rustc_middle::ty; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; @@ -123,15 +124,41 @@ pub enum PathElem { } /// Extra things to check for during validation of CTFE results. +#[derive(Copy, Clone)] pub enum CtfeValidationMode { - /// Regular validation, nothing special happening. - Regular, - /// Validation of a `const`. - /// `inner` says if this is an inner, indirect allocation (as opposed to the top-level const - /// allocation). Being an inner allocation makes a difference because the top-level allocation - /// of a `const` is copied for each use, but the inner allocations are implicitly shared. + /// Validation of a `static` + Static { mutbl: Mutability }, + /// Validation of a `const` (including promoteds). + /// `allow_immutable_unsafe_cell` says whether we allow `UnsafeCell` in immutable memory (which is the + /// case for the top-level allocation of a `const`, where this is fine because the allocation will be + /// copied at each use site). /// `allow_static_ptrs` says if pointers to statics are permitted (which is the case for promoteds in statics). - Const { inner: bool, allow_static_ptrs: bool }, + Const { allow_immutable_unsafe_cell: bool, allow_static_ptrs: bool }, +} + +impl CtfeValidationMode { + fn allow_immutable_unsafe_cell(self) -> bool { + match self { + CtfeValidationMode::Static { .. } => false, + CtfeValidationMode::Const { allow_immutable_unsafe_cell, .. } => { + allow_immutable_unsafe_cell + } + } + } + + fn allow_static_ptrs(self) -> bool { + match self { + CtfeValidationMode::Static { .. } => true, // statics can point to statics + CtfeValidationMode::Const { allow_static_ptrs, .. } => allow_static_ptrs, + } + } + + fn may_contain_mutable_ref(self) -> bool { + match self { + CtfeValidationMode::Static { mutbl } => mutbl == Mutability::Mut, + CtfeValidationMode::Const { .. } => false, + } + } } /// State for tracking recursive validation of references @@ -418,6 +445,22 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' } // Recursive checking if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() { + // Determine whether this pointer expects to be pointing to something mutable. + let ptr_expected_mutbl = match ptr_kind { + PointerKind::Box => Mutability::Mut, + PointerKind::Ref => { + let tam = value.layout.ty.builtin_deref(false).unwrap(); + // ZST never require mutability. We do not take into account interior mutability + // here since we cannot know if there really is an `UnsafeCell` inside + // `Option` -- so we check that in the recursive descent behind this + // reference. + if size == Size::ZERO || tam.mutbl == Mutability::Not { + Mutability::Not + } else { + Mutability::Mut + } + } + }; // Proceed recursively even for ZST, no reason to skip them! // `!` is a ZST and we want to validate it. if let Ok((alloc_id, _offset, _prov)) = self.ecx.ptr_try_get_alloc_id(place.ptr()) { @@ -428,16 +471,29 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' // Special handling for pointers to statics (irrespective of their type). assert!(!self.ecx.tcx.is_thread_local_static(did)); assert!(self.ecx.tcx.is_static(did)); - if matches!( - self.ctfe_mode, - Some(CtfeValidationMode::Const { allow_static_ptrs: false, .. }) - ) { + if self.ctfe_mode.is_some_and(|c| !c.allow_static_ptrs()) { // See const_eval::machine::MemoryExtra::can_access_statics for why // this check is so important. // This check is reachable when the const just referenced the static, // but never read it (so we never entered `before_access_global`). throw_validation_failure!(self.path, PtrToStatic { ptr_kind }); } + // Mutability check. + if ptr_expected_mutbl == Mutability::Mut { + if matches!( + self.ecx.tcx.def_kind(did), + DefKind::Static(Mutability::Not) + ) && self + .ecx + .tcx + .type_of(did) + .no_bound_vars() + .expect("statics should not have generic parameters") + .is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all()) + { + throw_validation_failure!(self.path, MutableRefToImmutable); + } + } // We skip recursively checking other statics. These statics must be sound by // themselves, and the only way to get broken statics here is by using // unsafe code. @@ -454,14 +510,29 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' if alloc.inner().mutability == Mutability::Mut && matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) { - // This should be unreachable, but if someone manages to copy a pointer - // out of a `static`, then that pointer might point to mutable memory, - // and we would catch that here. - throw_validation_failure!(self.path, PtrToMut { ptr_kind }); + // This is impossible: this can only be some inner allocation of a + // `static mut` (everything else either hits the `GlobalAlloc::Static` + // case or is interned immutably). To get such a pointer we'd have to + // load it from a static, but such loads lead to a CTFE error. + span_bug!( + self.ecx.tcx.span, + "encountered reference to mutable memory inside a `const`" + ); + } + if ptr_expected_mutbl == Mutability::Mut + && alloc.inner().mutability == Mutability::Not + { + throw_validation_failure!(self.path, MutableRefToImmutable); } } - // Nothing to check for these. - None | Some(GlobalAlloc::Function(..) | GlobalAlloc::VTable(..)) => {} + Some(GlobalAlloc::Function(..) | GlobalAlloc::VTable(..)) => { + // These are immutable, we better don't allow mutable pointers here. + if ptr_expected_mutbl == Mutability::Mut { + throw_validation_failure!(self.path, MutableRefToImmutable); + } + } + // Dangling, already handled. + None => bug!(), } } let path = &self.path; @@ -532,11 +603,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' Ok(true) } ty::Ref(_, ty, mutbl) => { - if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) + if self.ctfe_mode.is_some_and(|c| !c.may_contain_mutable_ref()) && *mutbl == Mutability::Mut { - // A mutable reference inside a const? That does not seem right (except if it is - // a ZST). let layout = self.ecx.layout_of(*ty)?; if !layout.is_zst() { throw_validation_failure!(self.path, MutableRefInConst); @@ -642,6 +711,19 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' ) } } + + fn in_mutable_memory(&self, op: &OpTy<'tcx, M::Provenance>) -> bool { + if let Some(mplace) = op.as_mplace_or_imm().left() { + if let Some(alloc_id) = mplace.ptr().provenance.and_then(|p| p.get_alloc_id()) { + if self.ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner().mutability + == Mutability::Mut + { + return true; + } + } + } + false + } } impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> @@ -705,10 +787,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> op: &OpTy<'tcx, M::Provenance>, _fields: NonZeroUsize, ) -> InterpResult<'tcx> { - // Special check preventing `UnsafeCell` inside unions in the inner part of constants. - if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) { - if !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) { - throw_validation_failure!(self.path, UnsafeCell); + // Special check for CTFE validation, preventing `UnsafeCell` inside unions in immutable memory. + if self.ctfe_mode.is_some_and(|c| !c.allow_immutable_unsafe_cell()) { + if !op.layout.is_zst() && !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) { + if !self.in_mutable_memory(op) { + throw_validation_failure!(self.path, UnsafeCellInImmutable); + } } } Ok(()) @@ -730,11 +814,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> } // Special check preventing `UnsafeCell` in the inner part of constants - if let Some(def) = op.layout.ty.ty_adt_def() { - if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) + if self.ctfe_mode.is_some_and(|c| !c.allow_immutable_unsafe_cell()) { + if !op.layout.is_zst() + && let Some(def) = op.layout.ty.ty_adt_def() && def.is_unsafe_cell() { - throw_validation_failure!(self.path, UnsafeCell); + if !self.in_mutable_memory(op) { + throw_validation_failure!(self.path, UnsafeCellInImmutable); + } } } diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index 4a3cfd50b4414..36a315b8f3078 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -27,6 +27,7 @@ fn alloc_caller_location<'mir, 'tcx>( // See https://github.com/rust-lang/rust/pull/89920#discussion_r730012398 ecx.allocate_str("", MemoryKind::CallerLocation, Mutability::Not).unwrap() }; + let file = file.map_provenance(CtfeProvenance::as_immutable); let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) }; let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) }; diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 1b4e9c286351a..1c9b4e558f0aa 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -416,14 +416,14 @@ pub enum ValidationErrorKind<'tcx> { PartialPointer, 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/compiler/rustc_middle/src/mir/interpret/pointer.rs b/compiler/rustc_middle/src/mir/interpret/pointer.rs index 6893387736a5a..dabf6297aa9c8 100644 --- a/compiler/rustc_middle/src/mir/interpret/pointer.rs +++ b/compiler/rustc_middle/src/mir/interpret/pointer.rs @@ -277,6 +277,12 @@ impl From for Pointer { Pointer::new(alloc_id.into(), Size::ZERO) } } +impl From for Pointer { + #[inline(always)] + fn from(prov: CtfeProvenance) -> Self { + Pointer::new(prov, Size::ZERO) + } +} impl From> for Pointer> { #[inline(always)] diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index eac315d04368d..7740d383ee3f2 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -830,7 +830,9 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' let new_prov = this.sb_reborrow(place, size, new_perm, new_tag, info)?; // Adjust place. - Ok(place.clone().map_provenance(|_| new_prov)) + // (If the closure gets called, that means the old provenance was `Some`, and hence the new + // one must also be `Some`.) + Ok(place.clone().map_provenance(|_| new_prov.unwrap())) } /// Retags an individual pointer, returning the retagged version. diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 573d13bf4a5d9..0945a5292bb92 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -351,7 +351,9 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' let new_prov = this.tb_reborrow(place, reborrow_size, new_perm, new_tag)?; // Adjust place. - Ok(place.clone().map_provenance(|_| new_prov)) + // (If the closure gets called, that means the old provenance was `Some`, and hence the new + // one must also be `Some`.) + Ok(place.clone().map_provenance(|_| new_prov.unwrap())) } /// Retags an individual pointer, returning the retagged version. diff --git a/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient.rs b/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient.rs index 0a8fc7bcaac55..6bc5faac532cc 100644 --- a/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient.rs +++ b/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient.rs @@ -5,6 +5,7 @@ use std::intrinsics; const FOO: &i32 = foo(); +const FOO_RAW: *const i32 = foo(); const fn foo() -> &'static i32 { let t = unsafe { @@ -15,5 +16,6 @@ const fn foo() -> &'static i32 { unsafe { &*t } } fn main() { - assert_eq!(*FOO, 20) + assert_eq!(*FOO, 20); + assert_eq!(unsafe { *FOO_RAW }, 20); } diff --git a/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient_fail.rs b/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient_fail.rs deleted file mode 100644 index 9ae906bbb7397..0000000000000 --- a/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient_fail.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![feature(core_intrinsics)] -#![feature(const_heap)] -#![feature(const_mut_refs)] -use std::intrinsics; - -const FOO: *const i32 = foo(); -//~^ ERROR unsupported untyped pointer in constant - -const fn foo() -> &'static i32 { - let t = unsafe { - let i = intrinsics::const_allocate(4, 4) as *mut i32; - *i = 20; - i - }; - unsafe { &*t } -} -fn main() { -} diff --git a/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient_fail.stderr b/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient_fail.stderr deleted file mode 100644 index dfaecfbcd8f1e..0000000000000 --- a/tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient_fail.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error: unsupported untyped pointer in constant - --> $DIR/alloc_intrinsic_nontransient_fail.rs:6:1 - | -LL | const FOO: *const i32 = foo(); - | ^^^^^^^^^^^^^^^^^^^^^ - | - = note: memory only reachable via raw pointers is not supported - -error: aborting due to 1 previous error - diff --git a/tests/ui/consts/const-eval/heap/alloc_intrinsic_untyped.rs b/tests/ui/consts/const-eval/heap/alloc_intrinsic_untyped.rs index 1354b3c33b31d..105e8e38d84eb 100644 --- a/tests/ui/consts/const-eval/heap/alloc_intrinsic_untyped.rs +++ b/tests/ui/consts/const-eval/heap/alloc_intrinsic_untyped.rs @@ -4,6 +4,6 @@ use std::intrinsics; const BAR: *mut i32 = unsafe { intrinsics::const_allocate(4, 4) as *mut i32 }; -//~^ error: unsupported untyped pointer in constant +//~^ error: mutable pointer in final value of constant fn main() {} diff --git a/tests/ui/consts/const-eval/heap/alloc_intrinsic_untyped.stderr b/tests/ui/consts/const-eval/heap/alloc_intrinsic_untyped.stderr index 3903ccd6ade7b..bd82e6781be1e 100644 --- a/tests/ui/consts/const-eval/heap/alloc_intrinsic_untyped.stderr +++ b/tests/ui/consts/const-eval/heap/alloc_intrinsic_untyped.stderr @@ -1,10 +1,8 @@ -error: unsupported untyped pointer in constant +error: encountered mutable pointer in final value of constant --> $DIR/alloc_intrinsic_untyped.rs:6:1 | LL | const BAR: *mut i32 = unsafe { intrinsics::const_allocate(4, 4) as *mut i32 }; | ^^^^^^^^^^^^^^^^^^^ - | - = note: memory only reachable via raw pointers is not supported error: aborting due to 1 previous error diff --git a/tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.rs b/tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.rs index a717a5f829217..6ede6f5339ca4 100644 --- a/tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.rs +++ b/tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.rs @@ -5,7 +5,7 @@ use std::intrinsics; const _X: &'static u8 = unsafe { - //~^ error: dangling pointer in final constant + //~^ error: dangling pointer in final value of constant let ptr = intrinsics::const_allocate(4, 4); intrinsics::const_deallocate(ptr, 4, 4); &*ptr diff --git a/tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.stderr b/tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.stderr index 5f4630f6f4bc8..59f5f79a95f38 100644 --- a/tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.stderr +++ b/tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.stderr @@ -1,4 +1,4 @@ -error: encountered dangling pointer in final constant +error: encountered dangling pointer in final value of constant --> $DIR/dealloc_intrinsic_dangling.rs:7:1 | LL | const _X: &'static u8 = unsafe { diff --git a/tests/ui/consts/const-mut-refs/mut_ref_in_final.rs b/tests/ui/consts/const-mut-refs/mut_ref_in_final.rs index a7d329f125be4..93197d5bce494 100644 --- a/tests/ui/consts/const-mut-refs/mut_ref_in_final.rs +++ b/tests/ui/consts/const-mut-refs/mut_ref_in_final.rs @@ -44,6 +44,21 @@ static mut FOO3: NotAMutex<&mut i32> = NotAMutex(UnsafeCell::new(&mut 42)); // the enclosing scope rule. const BAR: NotAMutex<&i32> = NotAMutex(UnsafeCell::new(&42)); +struct SyncPtr { x : *const T } +unsafe impl Sync for SyncPtr {} + +// These pass the lifetime checks because of the "tail expression" / "outer scope" rule. +// (This relies on `SyncPtr` being a curly brace struct.) +// However, we intern the inner memory as read-only, so this must be rejected. +static RAW_MUT_CAST_S: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; +//~^ ERROR mutable references are not allowed +static RAW_MUT_COERCE_S: SyncPtr = SyncPtr { x: &mut 0 }; +//~^ ERROR mutable references are not allowed +const RAW_MUT_CAST_C: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; +//~^ ERROR mutable references are not allowed +const RAW_MUT_COERCE_C: SyncPtr = SyncPtr { x: &mut 0 }; +//~^ ERROR mutable references are not allowed + fn main() { println!("{}", unsafe { *A }); unsafe { *B = 4 } // Bad news diff --git a/tests/ui/consts/const-mut-refs/mut_ref_in_final.stderr b/tests/ui/consts/const-mut-refs/mut_ref_in_final.stderr index 78c58b5ab092b..59e6aa4011c23 100644 --- a/tests/ui/consts/const-mut-refs/mut_ref_in_final.stderr +++ b/tests/ui/consts/const-mut-refs/mut_ref_in_final.stderr @@ -54,7 +54,31 @@ LL | static mut FOO3: NotAMutex<&mut i32> = NotAMutex(UnsafeCell::new(&mut 42)); | | creates a temporary value which is freed while still in use | using this value as a static requires that borrow lasts for `'static` -error: aborting due to 6 previous errors +error[E0764]: mutable references are not allowed in the final value of statics + --> $DIR/mut_ref_in_final.rs:53:53 + | +LL | static RAW_MUT_CAST_S: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^ + +error[E0764]: mutable references are not allowed in the final value of statics + --> $DIR/mut_ref_in_final.rs:55:54 + | +LL | static RAW_MUT_COERCE_S: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^ + +error[E0764]: mutable references are not allowed in the final value of constants + --> $DIR/mut_ref_in_final.rs:57:52 + | +LL | const RAW_MUT_CAST_C: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^ + +error[E0764]: mutable references are not allowed in the final value of constants + --> $DIR/mut_ref_in_final.rs:59:53 + | +LL | const RAW_MUT_COERCE_C: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^ + +error: aborting due to 10 previous errors Some errors have detailed explanations: E0716, E0764. For more information about an error, try `rustc --explain E0716`. diff --git a/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.32bit.stderr b/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.32bit.stderr index 33d4fec70166c..87420a0375147 100644 --- a/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.32bit.stderr +++ b/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.32bit.stderr @@ -1,16 +1,16 @@ error[E0080]: it is undefined behavior to use this value - --> $DIR/mut_ref_in_final_dynamic_check.rs:17:1 + --> $DIR/mut_ref_in_final_dynamic_check.rs:15:1 | LL | const A: Option<&mut i32> = helper(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ..0: encountered mutable reference in a `const` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ..0: encountered mutable reference in a `const` or `static` | = 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) { 2a 00 00 00 │ *... } -error: encountered dangling pointer in final constant - --> $DIR/mut_ref_in_final_dynamic_check.rs:24:1 +error: encountered dangling pointer in final value of constant + --> $DIR/mut_ref_in_final_dynamic_check.rs:22:1 | LL | const B: Option<&mut i32> = helper2(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.64bit.stderr b/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.64bit.stderr index 9eb2675856f3d..fc68207512c09 100644 --- a/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.64bit.stderr +++ b/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.64bit.stderr @@ -1,16 +1,16 @@ error[E0080]: it is undefined behavior to use this value - --> $DIR/mut_ref_in_final_dynamic_check.rs:17:1 + --> $DIR/mut_ref_in_final_dynamic_check.rs:15:1 | LL | const A: Option<&mut i32> = helper(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ..0: encountered mutable reference in a `const` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ..0: encountered mutable reference in a `const` or `static` | = 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) { 2a 00 00 00 00 00 00 00 │ *....... } -error: encountered dangling pointer in final constant - --> $DIR/mut_ref_in_final_dynamic_check.rs:24:1 +error: encountered dangling pointer in final value of constant + --> $DIR/mut_ref_in_final_dynamic_check.rs:22:1 | LL | const B: Option<&mut i32> = helper2(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.rs b/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.rs index 22e7a74e5fbab..455b557b97c4e 100644 --- a/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.rs +++ b/tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.rs @@ -12,8 +12,6 @@ const fn helper() -> Option<&'static mut i32> { unsafe { // Undefined behaviour (integer as pointer), who doesn't love tests like this. Some(&mut *(42 as *mut i32)) } } -// The error is an evaluation error and not a validation error, so the error is reported -// directly at the site where it occurs. const A: Option<&mut i32> = helper(); //~ ERROR it is undefined behavior to use this value //~^ encountered mutable reference in a `const` @@ -21,6 +19,6 @@ const fn helper2() -> Option<&'static mut i32> { unsafe { // Undefined behaviour (dangling pointer), who doesn't love tests like this. Some(&mut *(&mut 42 as *mut i32)) } } -const B: Option<&mut i32> = helper2(); //~ ERROR encountered dangling pointer in final constant +const B: Option<&mut i32> = helper2(); //~ ERROR encountered dangling pointer in final value of constant fn main() {} diff --git a/tests/ui/consts/const-points-to-static.32bit.stderr b/tests/ui/consts/const-points-to-static.32bit.stderr deleted file mode 100644 index 73fbf5e1837e2..0000000000000 --- a/tests/ui/consts/const-points-to-static.32bit.stderr +++ /dev/null @@ -1,22 +0,0 @@ -error[E0080]: it is undefined behavior to use this value - --> $DIR/const-points-to-static.rs:6:1 - | -LL | const TEST: &u8 = &MY_STATIC; - | ^^^^^^^^^^^^^^^ 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) { - ╾ALLOC0╼ │ ╾──╼ - } - -warning: skipping const checks - | -help: skipping check that does not even have a feature gate - --> $DIR/const-points-to-static.rs:6:20 - | -LL | const TEST: &u8 = &MY_STATIC; - | ^^^^^^^^^ - -error: aborting due to 1 previous error; 1 warning emitted - -For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/const-points-to-static.64bit.stderr b/tests/ui/consts/const-points-to-static.64bit.stderr deleted file mode 100644 index 42088bbb2c4a9..0000000000000 --- a/tests/ui/consts/const-points-to-static.64bit.stderr +++ /dev/null @@ -1,22 +0,0 @@ -error[E0080]: it is undefined behavior to use this value - --> $DIR/const-points-to-static.rs:6:1 - | -LL | const TEST: &u8 = &MY_STATIC; - | ^^^^^^^^^^^^^^^ 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) { - ╾ALLOC0╼ │ ╾──────╼ - } - -warning: skipping const checks - | -help: skipping check that does not even have a feature gate - --> $DIR/const-points-to-static.rs:6:20 - | -LL | const TEST: &u8 = &MY_STATIC; - | ^^^^^^^^^ - -error: aborting due to 1 previous error; 1 warning emitted - -For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/const-points-to-static.rs b/tests/ui/consts/const-points-to-static.rs deleted file mode 100644 index ca825a1f5ed65..0000000000000 --- a/tests/ui/consts/const-points-to-static.rs +++ /dev/null @@ -1,13 +0,0 @@ -// compile-flags: -Zunleash-the-miri-inside-of-you -// stderr-per-bitwidth - -#![allow(dead_code)] - -const TEST: &u8 = &MY_STATIC; -//~^ ERROR it is undefined behavior to use this value -//~| encountered a reference pointing to a static variable - -static MY_STATIC: u8 = 4; - -fn main() { -} diff --git a/tests/ui/consts/dangling-alloc-id-ice.rs b/tests/ui/consts/dangling-alloc-id-ice.rs index d591bfc731ce4..d9f458f3770a8 100644 --- a/tests/ui/consts/dangling-alloc-id-ice.rs +++ b/tests/ui/consts/dangling-alloc-id-ice.rs @@ -6,7 +6,7 @@ union Foo<'a> { } const FOO: &() = { -//~^ ERROR encountered dangling pointer in final constant +//~^ ERROR encountered dangling pointer in final value of constant let y = (); unsafe { Foo { y: &y }.long_live_the_unit } }; diff --git a/tests/ui/consts/dangling-alloc-id-ice.stderr b/tests/ui/consts/dangling-alloc-id-ice.stderr index e14204c09e54b..8322b18d692dc 100644 --- a/tests/ui/consts/dangling-alloc-id-ice.stderr +++ b/tests/ui/consts/dangling-alloc-id-ice.stderr @@ -1,4 +1,4 @@ -error: encountered dangling pointer in final constant +error: encountered dangling pointer in final value of constant --> $DIR/dangling-alloc-id-ice.rs:8:1 | LL | const FOO: &() = { diff --git a/tests/ui/consts/dangling_raw_ptr.rs b/tests/ui/consts/dangling_raw_ptr.rs index ddd1fb1ba76e1..0ab3643f1216b 100644 --- a/tests/ui/consts/dangling_raw_ptr.rs +++ b/tests/ui/consts/dangling_raw_ptr.rs @@ -1,4 +1,4 @@ -const FOO: *const u32 = { //~ ERROR encountered dangling pointer in final constant +const FOO: *const u32 = { //~ ERROR encountered dangling pointer in final value of constant let x = 42; &x }; diff --git a/tests/ui/consts/dangling_raw_ptr.stderr b/tests/ui/consts/dangling_raw_ptr.stderr index 5a428355034aa..28a58ae7be292 100644 --- a/tests/ui/consts/dangling_raw_ptr.stderr +++ b/tests/ui/consts/dangling_raw_ptr.stderr @@ -1,4 +1,4 @@ -error: encountered dangling pointer in final constant +error: encountered dangling pointer in final value of constant --> $DIR/dangling_raw_ptr.rs:1:1 | LL | const FOO: *const u32 = { diff --git a/tests/ui/consts/invalid-union.32bit.stderr b/tests/ui/consts/interior-mut-const-via-union.32bit.stderr similarity index 82% rename from tests/ui/consts/invalid-union.32bit.stderr rename to tests/ui/consts/interior-mut-const-via-union.32bit.stderr index 177e4f03e837c..e9e9f1a4545d9 100644 --- a/tests/ui/consts/invalid-union.32bit.stderr +++ b/tests/ui/consts/interior-mut-const-via-union.32bit.stderr @@ -1,8 +1,8 @@ error[E0080]: it is undefined behavior to use this value - --> $DIR/invalid-union.rs:35:1 + --> $DIR/interior-mut-const-via-union.rs:35: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) { @@ -10,13 +10,13 @@ LL | fn main() { } note: erroneous constant encountered - --> $DIR/invalid-union.rs:37:25 + --> $DIR/interior-mut-const-via-union.rs:37:25 | LL | let _: &'static _ = &C; | ^^ note: erroneous constant encountered - --> $DIR/invalid-union.rs:37:25 + --> $DIR/interior-mut-const-via-union.rs:37:25 | LL | let _: &'static _ = &C; | ^^ diff --git a/tests/ui/consts/invalid-union.64bit.stderr b/tests/ui/consts/interior-mut-const-via-union.64bit.stderr similarity index 82% rename from tests/ui/consts/invalid-union.64bit.stderr rename to tests/ui/consts/interior-mut-const-via-union.64bit.stderr index 09c648c3cda36..9cc98975ca9b5 100644 --- a/tests/ui/consts/invalid-union.64bit.stderr +++ b/tests/ui/consts/interior-mut-const-via-union.64bit.stderr @@ -1,8 +1,8 @@ error[E0080]: it is undefined behavior to use this value - --> $DIR/invalid-union.rs:35:1 + --> $DIR/interior-mut-const-via-union.rs:35: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) { @@ -10,13 +10,13 @@ LL | fn main() { } note: erroneous constant encountered - --> $DIR/invalid-union.rs:37:25 + --> $DIR/interior-mut-const-via-union.rs:37:25 | LL | let _: &'static _ = &C; | ^^ note: erroneous constant encountered - --> $DIR/invalid-union.rs:37:25 + --> $DIR/interior-mut-const-via-union.rs:37:25 | LL | let _: &'static _ = &C; | ^^ diff --git a/tests/ui/consts/invalid-union.rs b/tests/ui/consts/interior-mut-const-via-union.rs similarity index 100% rename from tests/ui/consts/invalid-union.rs rename to tests/ui/consts/interior-mut-const-via-union.rs diff --git a/tests/ui/consts/miri_unleashed/const_refers_to_static.32bit.stderr b/tests/ui/consts/miri_unleashed/const_refers_to_static.32bit.stderr index ab33b0c00c907..5fe8e250df912 100644 --- a/tests/ui/consts/miri_unleashed/const_refers_to_static.32bit.stderr +++ b/tests/ui/consts/miri_unleashed/const_refers_to_static.32bit.stderr @@ -38,6 +38,17 @@ LL | const READ_IMMUT: &usize = { ╾ALLOC1╼ │ ╾──╼ } +error[E0080]: it is undefined behavior to use this value + --> $DIR/const_refers_to_static.rs:34:1 + | +LL | const REF_IMMUT: &u8 = &MY_STATIC; + | ^^^^^^^^^^^^^^^^^^^^ 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) { + ╾ALLOC2╼ │ ╾──╼ + } + warning: skipping const checks | help: skipping check that does not even have a feature gate @@ -75,7 +86,12 @@ help: skipping check that does not even have a feature gate | LL | &FOO | ^^^ +help: skipping check that does not even have a feature gate + --> $DIR/const_refers_to_static.rs:34:25 + | +LL | const REF_IMMUT: &u8 = &MY_STATIC; + | ^^^^^^^^^ -error: aborting due to 5 previous errors; 1 warning emitted +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/const_refers_to_static.64bit.stderr b/tests/ui/consts/miri_unleashed/const_refers_to_static.64bit.stderr index f1f58d9ca6d6f..a80b07056a30f 100644 --- a/tests/ui/consts/miri_unleashed/const_refers_to_static.64bit.stderr +++ b/tests/ui/consts/miri_unleashed/const_refers_to_static.64bit.stderr @@ -38,6 +38,17 @@ LL | const READ_IMMUT: &usize = { ╾ALLOC1╼ │ ╾──────╼ } +error[E0080]: it is undefined behavior to use this value + --> $DIR/const_refers_to_static.rs:34:1 + | +LL | const REF_IMMUT: &u8 = &MY_STATIC; + | ^^^^^^^^^^^^^^^^^^^^ 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) { + ╾ALLOC2╼ │ ╾──────╼ + } + warning: skipping const checks | help: skipping check that does not even have a feature gate @@ -75,7 +86,12 @@ help: skipping check that does not even have a feature gate | LL | &FOO | ^^^ +help: skipping check that does not even have a feature gate + --> $DIR/const_refers_to_static.rs:34:25 + | +LL | const REF_IMMUT: &u8 = &MY_STATIC; + | ^^^^^^^^^ -error: aborting due to 5 previous errors; 1 warning emitted +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/const_refers_to_static.rs b/tests/ui/consts/miri_unleashed/const_refers_to_static.rs index 7ed5a48d996c6..df2563d8d7f2d 100644 --- a/tests/ui/consts/miri_unleashed/const_refers_to_static.rs +++ b/tests/ui/consts/miri_unleashed/const_refers_to_static.rs @@ -30,4 +30,9 @@ const READ_IMMUT: &usize = { //~ ERROR it is undefined behavior to use this valu &FOO }; +static MY_STATIC: u8 = 4; +const REF_IMMUT: &u8 = &MY_STATIC; +//~^ ERROR it is undefined behavior to use this value +//~| encountered a reference pointing to a static variable + fn main() {} 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 c9da91a959775..2df80020fdc91 100644 --- a/tests/ui/consts/miri_unleashed/mutable_references_err.32bit.stderr +++ b/tests/ui/consts/miri_unleashed/mutable_references_err.32bit.stderr @@ -1,54 +1,199 @@ -error[E0080]: it is undefined behavior to use this value - --> $DIR/mutable_references_err.rs:15:1 +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:17:1 | LL | const MUH: Meh = Meh { - | ^^^^^^^^^^^^^^ constructing invalid value at .x.: encountered `UnsafeCell` in a `const` + | ^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:27:1 + | +LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0080]: it is undefined behavior to use this value + --> $DIR/mutable_references_err.rs:32:1 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference in a `const` or `static` | = 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) { ╾ALLOC0╼ │ ╾──╼ } +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:35:1 + | +LL | const BLUNT: &mut i32 = &mut 42; + | ^^^^^^^^^^^^^^^^^^^^^ + error[E0080]: it is undefined behavior to use this value - --> $DIR/mutable_references_err.rs:25:1 + --> $DIR/mutable_references_err.rs:40:1 | -LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) }; - | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ...x: encountered `UnsafeCell` in a `const` +LL | static mut MUT_TO_READONLY: &mut i32 = unsafe { &mut *(&READONLY as *const _ as *mut _) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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: 4) { - ╾ALLOC1╼ ╾ALLOC2╼ │ ╾──╼╾──╼ + = 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:29:1 + --> $DIR/mutable_references_err.rs:47:1 | -LL | const BLUNT: &mut i32 = &mut 42; - | ^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference in a `const` +LL | const POINTS_TO_MUTABLE1: &i32 = unsafe { &MUTABLE }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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) { - ╾ALLOC3╼ │ ╾──╼ + ╾ALLOC2╼ │ ╾──╼ } +error[E0080]: evaluation of constant value failed + --> $DIR/mutable_references_err.rs:51:43 + | +LL | const POINTS_TO_MUTABLE2: &i32 = unsafe { &*MUTABLE_REF }; + | ^^^^^^^^^^^^^ constant accesses static + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:55:1 + | +LL | const POINTS_TO_MUTABLE_INNER: *const i32 = &mut 42 as *mut _ as *const _; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:57:1 + | +LL | const POINTS_TO_MUTABLE_INNER2: *const i32 = &mut 42 as *const _; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:59:1 + | +LL | const INTERIOR_MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:69:1 + | +LL | const RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:71:1 + | +LL | const RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:73:1 + | +LL | const RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 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:18: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:27: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; | ^^^^^^^ +help: skipping check for `const_mut_refs` feature + --> $DIR/mutable_references_err.rs:40:49 + | +LL | static mut MUT_TO_READONLY: &mut i32 = unsafe { &mut *(&READONLY as *const _ as *mut _) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: skipping check for `const_mut_refs` feature + --> $DIR/mutable_references_err.rs:40:49 + | +LL | static mut MUT_TO_READONLY: &mut i32 = unsafe { &mut *(&READONLY as *const _ as *mut _) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:40:49 + | +LL | static mut MUT_TO_READONLY: &mut i32 = unsafe { &mut *(&READONLY as *const _ as *mut _) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:47:44 + | +LL | const POINTS_TO_MUTABLE1: &i32 = unsafe { &MUTABLE }; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:47:44 + | +LL | const POINTS_TO_MUTABLE1: &i32 = unsafe { &MUTABLE }; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:50:36 + | +LL | static mut MUTABLE_REF: &mut i32 = &mut 42; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:51:45 + | +LL | const POINTS_TO_MUTABLE2: &i32 = unsafe { &*MUTABLE_REF }; + | ^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:51:45 + | +LL | const POINTS_TO_MUTABLE2: &i32 = unsafe { &*MUTABLE_REF }; + | ^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:55:45 + | +LL | const POINTS_TO_MUTABLE_INNER: *const i32 = &mut 42 as *mut _ as *const _; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:57:46 + | +LL | const POINTS_TO_MUTABLE_INNER2: *const i32 = &mut 42 as *const _; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:59:47 + | +LL | const INTERIOR_MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _; + | ^^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:69:51 + | +LL | const RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; + | ^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:71:50 + | +LL | const RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:73:51 + | +LL | const RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^ -error: aborting due to 3 previous errors; 1 warning emitted +error: aborting due to 13 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 71be616b7ed73..3ff6811ea61fb 100644 --- a/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr +++ b/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr @@ -1,54 +1,199 @@ -error[E0080]: it is undefined behavior to use this value - --> $DIR/mutable_references_err.rs:15:1 +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:17:1 | LL | const MUH: Meh = Meh { - | ^^^^^^^^^^^^^^ constructing invalid value at .x.: encountered `UnsafeCell` in a `const` + | ^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:27:1 + | +LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0080]: it is undefined behavior to use this value + --> $DIR/mutable_references_err.rs:32:1 + | +LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; + | ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference in a `const` or `static` | = 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) { ╾ALLOC0╼ │ ╾──────╼ } +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:35:1 + | +LL | const BLUNT: &mut i32 = &mut 42; + | ^^^^^^^^^^^^^^^^^^^^^ + error[E0080]: it is undefined behavior to use this value - --> $DIR/mutable_references_err.rs:25:1 + --> $DIR/mutable_references_err.rs:40:1 | -LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) }; - | ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at ...x: encountered `UnsafeCell` in a `const` +LL | static mut MUT_TO_READONLY: &mut i32 = unsafe { &mut *(&READONLY as *const _ as *mut _) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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: 16, align: 8) { - ╾ALLOC1╼ ╾ALLOC2╼ │ ╾──────╼╾──────╼ + = 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:29:1 + --> $DIR/mutable_references_err.rs:47:1 | -LL | const BLUNT: &mut i32 = &mut 42; - | ^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered mutable reference in a `const` +LL | const POINTS_TO_MUTABLE1: &i32 = unsafe { &MUTABLE }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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) { - ╾ALLOC3╼ │ ╾──────╼ + ╾ALLOC2╼ │ ╾──────╼ } +error[E0080]: evaluation of constant value failed + --> $DIR/mutable_references_err.rs:51:43 + | +LL | const POINTS_TO_MUTABLE2: &i32 = unsafe { &*MUTABLE_REF }; + | ^^^^^^^^^^^^^ constant accesses static + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:55:1 + | +LL | const POINTS_TO_MUTABLE_INNER: *const i32 = &mut 42 as *mut _ as *const _; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:57:1 + | +LL | const POINTS_TO_MUTABLE_INNER2: *const i32 = &mut 42 as *const _; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:59:1 + | +LL | const INTERIOR_MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:69:1 + | +LL | const RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:71:1 + | +LL | const RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of constant + --> $DIR/mutable_references_err.rs:73:1 + | +LL | const RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 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:18: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:27: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; | ^^^^^^^ +help: skipping check for `const_mut_refs` feature + --> $DIR/mutable_references_err.rs:40:49 + | +LL | static mut MUT_TO_READONLY: &mut i32 = unsafe { &mut *(&READONLY as *const _ as *mut _) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: skipping check for `const_mut_refs` feature + --> $DIR/mutable_references_err.rs:40:49 + | +LL | static mut MUT_TO_READONLY: &mut i32 = unsafe { &mut *(&READONLY as *const _ as *mut _) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:40:49 + | +LL | static mut MUT_TO_READONLY: &mut i32 = unsafe { &mut *(&READONLY as *const _ as *mut _) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:47:44 + | +LL | const POINTS_TO_MUTABLE1: &i32 = unsafe { &MUTABLE }; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:47:44 + | +LL | const POINTS_TO_MUTABLE1: &i32 = unsafe { &MUTABLE }; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:50:36 + | +LL | static mut MUTABLE_REF: &mut i32 = &mut 42; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:51:45 + | +LL | const POINTS_TO_MUTABLE2: &i32 = unsafe { &*MUTABLE_REF }; + | ^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:51:45 + | +LL | const POINTS_TO_MUTABLE2: &i32 = unsafe { &*MUTABLE_REF }; + | ^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:55:45 + | +LL | const POINTS_TO_MUTABLE_INNER: *const i32 = &mut 42 as *mut _ as *const _; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:57:46 + | +LL | const POINTS_TO_MUTABLE_INNER2: *const i32 = &mut 42 as *const _; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:59:47 + | +LL | const INTERIOR_MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _; + | ^^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:69:51 + | +LL | const RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; + | ^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:71:50 + | +LL | const RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/mutable_references_err.rs:73:51 + | +LL | const RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^ -error: aborting due to 3 previous errors; 1 warning emitted +error: aborting due to 13 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..83a460dadd0ef 100644 --- a/tests/ui/consts/miri_unleashed/mutable_references_err.rs +++ b/tests/ui/consts/miri_unleashed/mutable_references_err.rs @@ -1,6 +1,8 @@ // stderr-per-bitwidth // compile-flags: -Zunleash-the-miri-inside-of-you +#![allow(invalid_reference_casting, static_mut_ref)] +use std::sync::atomic::*; use std::cell::UnsafeCell; // this test ensures that our mutability story is sound @@ -12,7 +14,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 +const MUH: Meh = Meh { //~ ERROR: mutable pointer in final value x: &UnsafeCell::new(42), }; @@ -23,11 +25,53 @@ 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 +//~^ ERROR: mutable pointer in final value -// Make sure we also catch mutable references. +// Make sure we also catch mutable references in values that shouldn't have them. +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: mutable pointer in final value + +// Check for mutable references to read-only memory. +static READONLY: i32 = 0; +static mut MUT_TO_READONLY: &mut i32 = unsafe { &mut *(&READONLY as *const _ as *mut _) }; //~^ ERROR: it is undefined behavior to use this value +//~| pointing to read-only memory + +// Check for consts pointing to mutable memory. +// Currently it's not even possible to create such a const. +static mut MUTABLE: i32 = 42; +const POINTS_TO_MUTABLE1: &i32 = unsafe { &MUTABLE }; +//~^ ERROR: undefined behavior to use this value +//~| pointing to a static +static mut MUTABLE_REF: &mut i32 = &mut 42; +const POINTS_TO_MUTABLE2: &i32 = unsafe { &*MUTABLE_REF }; +//~^ ERROR: evaluation of constant value failed +//~| accesses static + +const POINTS_TO_MUTABLE_INNER: *const i32 = &mut 42 as *mut _ as *const _; +//~^ ERROR: mutable pointer in final value +const POINTS_TO_MUTABLE_INNER2: *const i32 = &mut 42 as *const _; +//~^ ERROR: mutable pointer in final value +const INTERIOR_MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _; +//~^ ERROR: mutable pointer in final value + +struct SyncPtr { x : *const T } +unsafe impl Sync for SyncPtr {} + +// These pass the lifetime checks because of the "tail expression" / "outer scope" rule. +// (This relies on `SyncPtr` being a curly brace struct.) +// However, we intern the inner memory as read-only, so this must be rejected. +// (Also see `static-no-inner-mut` for similar tests on `static`.) +const RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; +//~^ ERROR mutable pointer in final value +const RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; +//~^ ERROR mutable pointer in final value +const RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; +//~^ ERROR mutable pointer in final value fn main() { unsafe { diff --git a/tests/ui/consts/miri_unleashed/raw_mutable_const.rs b/tests/ui/consts/miri_unleashed/raw_mutable_const.rs deleted file mode 100644 index adb1f8bf3ec55..0000000000000 --- a/tests/ui/consts/miri_unleashed/raw_mutable_const.rs +++ /dev/null @@ -1,8 +0,0 @@ -// compile-flags: -Zunleash-the-miri-inside-of-you - -use std::cell::UnsafeCell; - -const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _; -//~^ ERROR: unsupported untyped pointer in constant - -fn main() {} diff --git a/tests/ui/consts/miri_unleashed/raw_mutable_const.stderr b/tests/ui/consts/miri_unleashed/raw_mutable_const.stderr deleted file mode 100644 index f4abaecac5e15..0000000000000 --- a/tests/ui/consts/miri_unleashed/raw_mutable_const.stderr +++ /dev/null @@ -1,18 +0,0 @@ -error: unsupported untyped pointer in constant - --> $DIR/raw_mutable_const.rs:5:1 - | -LL | const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: memory only reachable via raw pointers is not supported - -warning: skipping const checks - | -help: skipping check that does not even have a feature gate - --> $DIR/raw_mutable_const.rs:5:38 - | -LL | const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _; - | ^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 1 previous error; 1 warning emitted - diff --git a/tests/ui/consts/miri_unleashed/static-no-inner-mut.32bit.stderr b/tests/ui/consts/miri_unleashed/static-no-inner-mut.32bit.stderr new file mode 100644 index 0000000000000..e8ed6742fab3a --- /dev/null +++ b/tests/ui/consts/miri_unleashed/static-no-inner-mut.32bit.stderr @@ -0,0 +1,82 @@ +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:9:1 + | +LL | static REF: &AtomicI32 = &AtomicI32::new(42); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:10:1 + | +LL | static REFMUT: &mut i32 = &mut 0; + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:13:1 + | +LL | static REF2: &AtomicI32 = {let x = AtomicI32::new(42); &{x}}; + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:14:1 + | +LL | static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:29:1 + | +LL | static RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:31:1 + | +LL | static RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:33:1 + | +LL | static RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: skipping const checks + | +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-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-mut.rs:10:27 + | +LL | static REFMUT: &mut i32 = &mut 0; + | ^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-mut.rs:13: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-mut.rs:14:44 + | +LL | static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; + | ^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-mut.rs:29:52 + | +LL | static RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; + | ^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-mut.rs:31:51 + | +LL | static RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-mut.rs:33:52 + | +LL | static RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^ + +error: aborting due to 7 previous errors; 1 warning emitted + diff --git a/tests/ui/consts/miri_unleashed/static-no-inner-mut.64bit.stderr b/tests/ui/consts/miri_unleashed/static-no-inner-mut.64bit.stderr new file mode 100644 index 0000000000000..e8ed6742fab3a --- /dev/null +++ b/tests/ui/consts/miri_unleashed/static-no-inner-mut.64bit.stderr @@ -0,0 +1,82 @@ +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:9:1 + | +LL | static REF: &AtomicI32 = &AtomicI32::new(42); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:10:1 + | +LL | static REFMUT: &mut i32 = &mut 0; + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:13:1 + | +LL | static REF2: &AtomicI32 = {let x = AtomicI32::new(42); &{x}}; + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:14:1 + | +LL | static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:29:1 + | +LL | static RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:31:1 + | +LL | static RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: encountered mutable pointer in final value of static + --> $DIR/static-no-inner-mut.rs:33:1 + | +LL | static RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: skipping const checks + | +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-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-mut.rs:10:27 + | +LL | static REFMUT: &mut i32 = &mut 0; + | ^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-mut.rs:13: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-mut.rs:14:44 + | +LL | static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; + | ^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-mut.rs:29:52 + | +LL | static RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; + | ^^^^^^^^^^^^^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-mut.rs:31:51 + | +LL | static RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; + | ^^^^^^^ +help: skipping check that does not even have a feature gate + --> $DIR/static-no-inner-mut.rs:33:52 + | +LL | static RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; + | ^^^^^^ + +error: aborting due to 7 previous errors; 1 warning emitted + diff --git a/tests/ui/consts/miri_unleashed/static-no-inner-mut.rs b/tests/ui/consts/miri_unleashed/static-no-inner-mut.rs new file mode 100644 index 0000000000000..a4033eb56834e --- /dev/null +++ b/tests/ui/consts/miri_unleashed/static-no-inner-mut.rs @@ -0,0 +1,36 @@ +// stderr-per-bitwidth +// compile-flags: -Zunleash-the-miri-inside-of-you +#![feature(const_refs_to_cell, const_mut_refs)] +// 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 mutable pointer in final value +static REFMUT: &mut i32 = &mut 0; //~ERROR mutable pointer in final value + +// Different way of writing this that avoids promotion. +static REF2: &AtomicI32 = {let x = AtomicI32::new(42); &{x}}; //~ERROR mutable pointer in final value +static REFMUT2: &mut i32 = {let mut x = 0; &mut {x}}; //~ERROR mutable pointer in final value + +// 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 { x : *const T } +unsafe impl Sync for SyncPtr {} + +// All of these pass the lifetime checks because of the "tail expression" / "outer scope" rule. +// (This relies on `SyncPtr` being a curly brace struct.) +// Then they get interned immutably, which is not great. +// `mut_ref_in_final.rs` and `std/cell.rs` ensure that we don't accept this even with the feature +// fate, but for unleashed Miri there's not really any way we can reject them: it's just +// non-dangling raw pointers. +static RAW_SYNC: SyncPtr = SyncPtr { x: &AtomicI32::new(42) }; +//~^ ERROR mutable pointer in final value +static RAW_MUT_CAST: SyncPtr = SyncPtr { x : &mut 42 as *mut _ as *const _ }; +//~^ ERROR mutable pointer in final value +static RAW_MUT_COERCE: SyncPtr = SyncPtr { x: &mut 0 }; +//~^ ERROR mutable pointer in final value + +fn main() {} diff --git a/tests/ui/consts/raw-ptr-const.rs b/tests/ui/consts/raw-ptr-const.rs index 541c5fd1ab134..24a77db9ffc5d 100644 --- a/tests/ui/consts/raw-ptr-const.rs +++ b/tests/ui/consts/raw-ptr-const.rs @@ -1,8 +1,8 @@ +// check-pass + // This is a regression test for a `span_delayed_bug` during interning when a constant -// evaluates to a (non-dangling) raw pointer. For now this errors; potentially it -// could also be allowed. +// evaluates to a (non-dangling) raw pointer. const CONST_RAW: *const Vec = &Vec::new() as *const _; -//~^ ERROR unsupported untyped pointer in constant fn main() {} diff --git a/tests/ui/consts/raw-ptr-const.stderr b/tests/ui/consts/raw-ptr-const.stderr deleted file mode 100644 index 9aef78505c087..0000000000000 --- a/tests/ui/consts/raw-ptr-const.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error: unsupported untyped pointer in constant - --> $DIR/raw-ptr-const.rs:5:1 - | -LL | const CONST_RAW: *const Vec = &Vec::new() as *const _; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: memory only reachable via raw pointers is not supported - -error: aborting due to 1 previous error - diff --git a/tests/ui/consts/raw-ptr-temp-const.rs b/tests/ui/consts/raw-ptr-temp-const.rs new file mode 100644 index 0000000000000..deb51cd505c7a --- /dev/null +++ b/tests/ui/consts/raw-ptr-temp-const.rs @@ -0,0 +1,6 @@ +// A variant of raw-ptr-const that directly constructs a raw pointer. + +const CONST_RAW: *const Vec = std::ptr::addr_of!(Vec::new()); +//~^ ERROR cannot take address of a temporary + +fn main() {} diff --git a/tests/ui/consts/raw-ptr-temp-const.stderr b/tests/ui/consts/raw-ptr-temp-const.stderr new file mode 100644 index 0000000000000..601671f71aec7 --- /dev/null +++ b/tests/ui/consts/raw-ptr-temp-const.stderr @@ -0,0 +1,9 @@ +error[E0745]: cannot take address of a temporary + --> $DIR/raw-ptr-temp-const.rs:3:55 + | +LL | const CONST_RAW: *const Vec = std::ptr::addr_of!(Vec::new()); + | ^^^^^^^^^^ temporary value + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0745`. diff --git a/tests/ui/consts/refs-to-cell-in-final.rs b/tests/ui/consts/refs-to-cell-in-final.rs new file mode 100644 index 0000000000000..6a849ff0df929 --- /dev/null +++ b/tests/ui/consts/refs-to-cell-in-final.rs @@ -0,0 +1,18 @@ +#![feature(const_refs_to_cell)] + +use std::cell::*; + +struct SyncPtr { x : *const T } +unsafe impl Sync for SyncPtr {} + +// These pass the lifetime checks because of the "tail expression" / "outer scope" rule. +// (This relies on `SyncPtr` being a curly brace struct.) +// However, we intern the inner memory as read-only. +// The resulting constant would pass all validation checks, so it is crucial that this gets rejected +// by static const checks! +static RAW_SYNC_S: SyncPtr> = SyncPtr { x: &Cell::new(42) }; +//~^ ERROR: cannot refer to interior mutable data +const RAW_SYNC_C: SyncPtr> = SyncPtr { x: &Cell::new(42) }; +//~^ ERROR: cannot refer to interior mutable data + +fn main() {} diff --git a/tests/ui/consts/refs-to-cell-in-final.stderr b/tests/ui/consts/refs-to-cell-in-final.stderr new file mode 100644 index 0000000000000..fae16fa01251c --- /dev/null +++ b/tests/ui/consts/refs-to-cell-in-final.stderr @@ -0,0 +1,17 @@ +error[E0492]: statics cannot refer to interior mutable data + --> $DIR/refs-to-cell-in-final.rs:13:54 + | +LL | static RAW_SYNC_S: SyncPtr> = SyncPtr { x: &Cell::new(42) }; + | ^^^^^^^^^^^^^^ this borrow of an interior mutable value may end up in the final value + | + = help: to fix this, the value can be extracted to a separate `static` item and then referenced + +error[E0492]: constants cannot refer to interior mutable data + --> $DIR/refs-to-cell-in-final.rs:15:53 + | +LL | const RAW_SYNC_C: SyncPtr> = SyncPtr { x: &Cell::new(42) }; + | ^^^^^^^^^^^^^^ this borrow of an interior mutable value may end up in the final value + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0492`. diff --git a/tests/ui/consts/std/cell.stderr b/tests/ui/consts/std/cell.stderr index 937fa7db0c8c0..873b797a466db 100644 --- a/tests/ui/consts/std/cell.stderr +++ b/tests/ui/consts/std/cell.stderr @@ -1,22 +1,22 @@ -error: encountered dangling pointer in final constant +error: encountered dangling pointer in final value of static --> $DIR/cell.rs:6:1 | LL | static FOO: Wrap<*mut u32> = Wrap(Cell::new(42).as_ptr()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: encountered dangling pointer in final constant +error: encountered dangling pointer in final value of constant --> $DIR/cell.rs:8:1 | LL | const FOO_CONST: Wrap<*mut u32> = Wrap(Cell::new(42).as_ptr()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: encountered dangling pointer in final constant +error: encountered dangling pointer in final value of constant --> $DIR/cell.rs:22:1 | LL | const FOO4_CONST: Wrap<*mut u32> = Wrap(FOO3_CONST.0.as_ptr()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: encountered dangling pointer in final constant +error: encountered dangling pointer in final value of constant --> $DIR/cell.rs:27:1 | LL | const FOO2: *mut u32 = Cell::new(42).as_ptr(); From 0288a0bfa0f038350aaae224b6cdd7e440ea8408 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 17 Dec 2023 11:24:34 +0100 Subject: [PATCH 2/4] raw pointers are not references --- compiler/rustc_const_eval/messages.ftl | 16 ++++++++-------- compiler/rustc_const_eval/src/errors.rs | 8 ++++---- .../src/transform/check_consts/ops.rs | 10 +++++----- tests/ui/consts/const-address-of-mut.rs | 8 ++++---- tests/ui/consts/const-address-of-mut.stderr | 8 ++++---- tests/ui/consts/min_const_fn/address_of.rs | 4 ++-- tests/ui/consts/min_const_fn/address_of.stderr | 4 ++-- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 2728d2ca463df..e7e8b2b36006d 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -337,7 +337,7 @@ const_eval_too_many_caller_args = const_eval_transient_mut_borrow = mutable references are not allowed in {const_eval_const_context}s -const_eval_transient_mut_borrow_raw = raw mutable references are not allowed in {const_eval_const_context}s +const_eval_transient_mut_raw = raw mutable pointers are not allowed in {const_eval_const_context}s const_eval_try_block_from_output_non_const = `try` block cannot convert `{$ty}` to the result in {const_eval_const_context}s @@ -351,21 +351,21 @@ const_eval_unallowed_heap_allocations = const_eval_unallowed_inline_asm = inline assembly is not allowed in {const_eval_const_context}s -const_eval_unallowed_mutable_refs = - mutable references are not allowed in the final value of {const_eval_const_context}s +const_eval_unallowed_mutable_raw = + raw mutable pointers are not allowed in the final value of {const_eval_const_context}s .teach_note = + References in statics and constants may only refer to immutable values. + + Statics are shared everywhere, and if they refer to mutable data one might violate memory safety since holding multiple mutable references to shared data is not allowed. If you really want global mutable state, try using static mut or a global UnsafeCell. -const_eval_unallowed_mutable_refs_raw = - raw mutable references are not allowed in the final value of {const_eval_const_context}s +const_eval_unallowed_mutable_refs = + mutable references are not allowed in the final value of {const_eval_const_context}s .teach_note = - References in statics and constants may only refer to immutable values. - - Statics are shared everywhere, and if they refer to mutable data one might violate memory safety since holding multiple mutable references to shared data is not allowed. diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index c07db2773c1f0..4a654480ef5a8 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -114,8 +114,8 @@ pub(crate) struct TransientMutBorrowErr { } #[derive(Diagnostic)] -#[diag(const_eval_transient_mut_borrow_raw, code = "E0658")] -pub(crate) struct TransientMutBorrowErrRaw { +#[diag(const_eval_transient_mut_raw, code = "E0658")] +pub(crate) struct TransientMutRawErr { #[primary_span] pub span: Span, pub kind: ConstContext, @@ -156,8 +156,8 @@ pub(crate) struct UnallowedMutableRefs { } #[derive(Diagnostic)] -#[diag(const_eval_unallowed_mutable_refs_raw, code = "E0764")] -pub(crate) struct UnallowedMutableRefsRaw { +#[diag(const_eval_unallowed_mutable_raw, code = "E0764")] +pub(crate) struct UnallowedMutableRaw { #[primary_span] pub span: Span, pub kind: ConstContext, diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs index 327c91731bf58..39bc2b960e9f5 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs @@ -466,7 +466,7 @@ impl<'tcx> NonConstOp<'tcx> for MutBorrow { fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> { match self.0 { - hir::BorrowKind::Raw => ccx.dcx().create_err(errors::UnallowedMutableRefsRaw { + hir::BorrowKind::Raw => ccx.tcx.dcx().create_err(errors::UnallowedMutableRaw { span, kind: ccx.const_kind(), teach: ccx.tcx.sess.teach(&error_code!(E0764)).then_some(()), @@ -491,10 +491,10 @@ impl<'tcx> NonConstOp<'tcx> for TransientMutBorrow { fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> { let kind = ccx.const_kind(); match self.0 { - hir::BorrowKind::Raw => ccx.tcx.sess.create_feature_err( - errors::TransientMutBorrowErrRaw { span, kind }, - sym::const_mut_refs, - ), + hir::BorrowKind::Raw => ccx + .tcx + .sess + .create_feature_err(errors::TransientMutRawErr { span, kind }, sym::const_mut_refs), hir::BorrowKind::Ref => ccx.tcx.sess.create_feature_err( errors::TransientMutBorrowErr { span, kind }, sym::const_mut_refs, diff --git a/tests/ui/consts/const-address-of-mut.rs b/tests/ui/consts/const-address-of-mut.rs index 3788088b810b9..5f0c76d628555 100644 --- a/tests/ui/consts/const-address-of-mut.rs +++ b/tests/ui/consts/const-address-of-mut.rs @@ -1,14 +1,14 @@ #![feature(raw_ref_op)] -const A: () = { let mut x = 2; &raw mut x; }; //~ mutable reference +const A: () = { let mut x = 2; &raw mut x; }; //~ mutable pointer -static B: () = { let mut x = 2; &raw mut x; }; //~ mutable reference +static B: () = { let mut x = 2; &raw mut x; }; //~ mutable pointer -static mut C: () = { let mut x = 2; &raw mut x; }; //~ mutable reference +static mut C: () = { let mut x = 2; &raw mut x; }; //~ mutable pointer const fn foo() { let mut x = 0; - let y = &raw mut x; //~ mutable reference + let y = &raw mut x; //~ mutable pointer } fn main() {} diff --git a/tests/ui/consts/const-address-of-mut.stderr b/tests/ui/consts/const-address-of-mut.stderr index 2a69bb8be97d4..1b371fcee984b 100644 --- a/tests/ui/consts/const-address-of-mut.stderr +++ b/tests/ui/consts/const-address-of-mut.stderr @@ -1,4 +1,4 @@ -error[E0658]: raw mutable references are not allowed in constants +error[E0658]: raw mutable pointers are not allowed in constants --> $DIR/const-address-of-mut.rs:3:32 | LL | const A: () = { let mut x = 2; &raw mut x; }; @@ -8,7 +8,7 @@ LL | const A: () = { let mut x = 2; &raw mut x; }; = help: add `#![feature(const_mut_refs)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: raw mutable references are not allowed in statics +error[E0658]: raw mutable pointers are not allowed in statics --> $DIR/const-address-of-mut.rs:5:33 | LL | static B: () = { let mut x = 2; &raw mut x; }; @@ -18,7 +18,7 @@ LL | static B: () = { let mut x = 2; &raw mut x; }; = help: add `#![feature(const_mut_refs)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: raw mutable references are not allowed in statics +error[E0658]: raw mutable pointers are not allowed in statics --> $DIR/const-address-of-mut.rs:7:37 | LL | static mut C: () = { let mut x = 2; &raw mut x; }; @@ -28,7 +28,7 @@ LL | static mut C: () = { let mut x = 2; &raw mut x; }; = help: add `#![feature(const_mut_refs)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: raw mutable references are not allowed in constant functions +error[E0658]: raw mutable pointers are not allowed in constant functions --> $DIR/const-address-of-mut.rs:11:13 | LL | let y = &raw mut x; diff --git a/tests/ui/consts/min_const_fn/address_of.rs b/tests/ui/consts/min_const_fn/address_of.rs index 40d1882d7d2ad..aa75423ca4d64 100644 --- a/tests/ui/consts/min_const_fn/address_of.rs +++ b/tests/ui/consts/min_const_fn/address_of.rs @@ -2,7 +2,7 @@ const fn mutable_address_of_in_const() { let mut a = 0; - let b = &raw mut a; //~ ERROR mutable reference + let b = &raw mut a; //~ ERROR mutable pointer } struct X; @@ -10,7 +10,7 @@ struct X; impl X { const fn inherent_mutable_address_of_in_const() { let mut a = 0; - let b = &raw mut a; //~ ERROR mutable reference + let b = &raw mut a; //~ ERROR mutable pointer } } diff --git a/tests/ui/consts/min_const_fn/address_of.stderr b/tests/ui/consts/min_const_fn/address_of.stderr index 4c23ba6cd5190..143760c0943d2 100644 --- a/tests/ui/consts/min_const_fn/address_of.stderr +++ b/tests/ui/consts/min_const_fn/address_of.stderr @@ -1,4 +1,4 @@ -error[E0658]: raw mutable references are not allowed in constant functions +error[E0658]: raw mutable pointers are not allowed in constant functions --> $DIR/address_of.rs:5:13 | LL | let b = &raw mut a; @@ -8,7 +8,7 @@ LL | let b = &raw mut a; = help: add `#![feature(const_mut_refs)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: raw mutable references are not allowed in constant functions +error[E0658]: raw mutable pointers are not allowed in constant functions --> $DIR/address_of.rs:13:17 | LL | let b = &raw mut a; From 73ce868c7e1fa2d6a4d717de59ca326a0f566ea1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 3 Jan 2024 18:14:42 +0100 Subject: [PATCH 3/4] more clear code Co-authored-by: Oli Scherer --- .../rustc_const_eval/src/interpret/validity.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index f028d7802ae6b..b5cd32595201f 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -454,20 +454,17 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' // here since we cannot know if there really is an `UnsafeCell` inside // `Option` -- so we check that in the recursive descent behind this // reference. - if size == Size::ZERO || tam.mutbl == Mutability::Not { - Mutability::Not - } else { - Mutability::Mut - } + if size == Size::ZERO { Mutability::Not } else { tam.mutbl } } }; // 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()) { // Let's see what kind of memory this points to. - let alloc_kind = self.ecx.tcx.try_get_global_alloc(alloc_id); + // `unwrap` since dangling pointers have already been handled. + let alloc_kind = self.ecx.tcx.try_get_global_alloc(alloc_id).unwrap(); match alloc_kind { - Some(GlobalAlloc::Static(did)) => { + GlobalAlloc::Static(did) => { // 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)); @@ -506,7 +503,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' // referring to statics). return Ok(()); } - Some(GlobalAlloc::Memory(alloc)) => { + GlobalAlloc::Memory(alloc) => { if alloc.inner().mutability == Mutability::Mut && matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) { @@ -525,14 +522,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' throw_validation_failure!(self.path, MutableRefToImmutable); } } - Some(GlobalAlloc::Function(..) | GlobalAlloc::VTable(..)) => { + 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; From 2ab85e41789e3996458f1a0578f225669a8e1d84 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 3 Jan 2024 18:26:26 +0100 Subject: [PATCH 4/4] reword comment --- .../rustc_const_eval/src/interpret/intern.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs index a5a94e80b6606..751fbfacaad00 100644 --- a/compiler/rustc_const_eval/src/interpret/intern.rs +++ b/compiler/rustc_const_eval/src/interpret/intern.rs @@ -178,13 +178,16 @@ pub fn intern_const_alloc_recursive< // promoteds as immutable. found_bad_mutable_pointer = true; } - // It is tempting to intern as immutable if `prov.immutable()`. However, there - // might be multiple pointers to the same allocation, and if *at least one* of - // them is mutable, the allocation must be interned mutably. We will intern the - // allocation when we encounter the first pointer. Therefore we always intern - // with `inner_mutability`, and furthermore we ensured above that if that is - // "immutable", then there are *no* mutable pointers anywhere in the newly - // interned memory. + // We always intern with `inner_mutability`, and furthermore we ensured above that if + // that is "immutable", then there are *no* mutable pointers anywhere in the newly + // interned memory -- justifying that we can indeed intern immutably. However this also + // means we can *not* easily intern immutably here if `prov.immutable()` is true and + // `inner_mutability` is `Mut`: there might be other pointers to that allocation, and + // we'd have to somehow check that they are *all* immutable before deciding that this + // allocation can be made immutable. In the future we could consider analyzing all + // pointers before deciding which allocations can be made immutable; but for now we are + // okay with losing some potential for immutability here. This can anyway only affect + // `static mut`. todo.push((alloc_id, inner_mutability)); }) .map_err(|()| {