about summary refs log tree commit diff
path: root/compiler/rustc_const_eval/src/interpret/validity.rs
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2023-12-16 16:24:25 +0100
committerRalf Jung <post@ralfj.de>2024-01-22 09:28:00 +0100
commit2f1a8e2d7a641a398e9e02c8c99e80f6e44dce87 (patch)
treee8e044a80ab5b723153703589e31aa24308e2a6e /compiler/rustc_const_eval/src/interpret/validity.rs
parenta58ec8ff03b3269b20104eb7eae407be48ab95a7 (diff)
downloadrust-2f1a8e2d7a641a398e9e02c8c99e80f6e44dce87.tar.gz
rust-2f1a8e2d7a641a398e9e02c8c99e80f6e44dce87.zip
const-eval interner: from-scratch rewrite using mutability information from provenance rather than types
Diffstat (limited to 'compiler/rustc_const_eval/src/interpret/validity.rs')
-rw-r--r--compiler/rustc_const_eval/src/interpret/validity.rs145
1 files changed, 116 insertions, 29 deletions
diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs
index 8b44b87647d..f028d7802ae 100644
--- a/compiler/rustc_const_eval/src/interpret/validity.rs
+++ b/compiler/rustc_const_eval/src/interpret/validity.rs
@@ -9,12 +9,13 @@ use std::num::NonZeroUsize;
 
 use either::{Left, Right};
 
+use hir::def::DefKind;
 use rustc_ast::Mutability;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir as hir;
 use rustc_middle::mir::interpret::{
-    ExpectedKind, InterpError, InvalidMetaKind, Misalignment, PointerKind, ValidationErrorInfo,
-    ValidationErrorKind, ValidationErrorKind::*,
+    ExpectedKind, InterpError, InvalidMetaKind, Misalignment, PointerKind, Provenance,
+    ValidationErrorInfo, ValidationErrorKind, ValidationErrorKind::*,
 };
 use rustc_middle::ty;
 use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
@@ -123,15 +124,41 @@ pub enum PathElem {
 }
 
 /// Extra things to check for during validation of CTFE results.
+#[derive(Copy, Clone)]
 pub enum CtfeValidationMode {
-    /// Regular validation, nothing special happening.
-    Regular,
-    /// Validation of a `const`.
-    /// `inner` says if this is an inner, indirect allocation (as opposed to the top-level const
-    /// allocation). Being an inner allocation makes a difference because the top-level allocation
-    /// of a `const` is copied for each use, but the inner allocations are implicitly shared.
+    /// Validation of a `static`
+    Static { mutbl: Mutability },
+    /// Validation of a `const` (including promoteds).
+    /// `allow_immutable_unsafe_cell` says whether we allow `UnsafeCell` in immutable memory (which is the
+    /// case for the top-level allocation of a `const`, where this is fine because the allocation will be
+    /// copied at each use site).
     /// `allow_static_ptrs` says if pointers to statics are permitted (which is the case for promoteds in statics).
-    Const { inner: bool, allow_static_ptrs: bool },
+    Const { allow_immutable_unsafe_cell: bool, allow_static_ptrs: bool },
+}
+
+impl CtfeValidationMode {
+    fn allow_immutable_unsafe_cell(self) -> bool {
+        match self {
+            CtfeValidationMode::Static { .. } => false,
+            CtfeValidationMode::Const { allow_immutable_unsafe_cell, .. } => {
+                allow_immutable_unsafe_cell
+            }
+        }
+    }
+
+    fn allow_static_ptrs(self) -> bool {
+        match self {
+            CtfeValidationMode::Static { .. } => true, // statics can point to statics
+            CtfeValidationMode::Const { allow_static_ptrs, .. } => allow_static_ptrs,
+        }
+    }
+
+    fn may_contain_mutable_ref(self) -> bool {
+        match self {
+            CtfeValidationMode::Static { mutbl } => mutbl == Mutability::Mut,
+            CtfeValidationMode::Const { .. } => false,
+        }
+    }
 }
 
 /// State for tracking recursive validation of references
@@ -418,6 +445,22 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
         }
         // Recursive checking
         if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
+            // Determine whether this pointer expects to be pointing to something mutable.
+            let ptr_expected_mutbl = match ptr_kind {
+                PointerKind::Box => Mutability::Mut,
+                PointerKind::Ref => {
+                    let tam = value.layout.ty.builtin_deref(false).unwrap();
+                    // ZST never require mutability. We do not take into account interior mutability
+                    // here since we cannot know if there really is an `UnsafeCell` inside
+                    // `Option<UnsafeCell>` -- so we check that in the recursive descent behind this
+                    // reference.
+                    if size == Size::ZERO || tam.mutbl == Mutability::Not {
+                        Mutability::Not
+                    } else {
+                        Mutability::Mut
+                    }
+                }
+            };
             // Proceed recursively even for ZST, no reason to skip them!
             // `!` is a ZST and we want to validate it.
             if let Ok((alloc_id, _offset, _prov)) = self.ecx.ptr_try_get_alloc_id(place.ptr()) {
@@ -428,16 +471,29 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                         // Special handling for pointers to statics (irrespective of their type).
                         assert!(!self.ecx.tcx.is_thread_local_static(did));
                         assert!(self.ecx.tcx.is_static(did));
-                        if matches!(
-                            self.ctfe_mode,
-                            Some(CtfeValidationMode::Const { allow_static_ptrs: false, .. })
-                        ) {
+                        if self.ctfe_mode.is_some_and(|c| !c.allow_static_ptrs()) {
                             // See const_eval::machine::MemoryExtra::can_access_statics for why
                             // this check is so important.
                             // This check is reachable when the const just referenced the static,
                             // but never read it (so we never entered `before_access_global`).
                             throw_validation_failure!(self.path, PtrToStatic { ptr_kind });
                         }
+                        // Mutability check.
+                        if ptr_expected_mutbl == Mutability::Mut {
+                            if matches!(
+                                self.ecx.tcx.def_kind(did),
+                                DefKind::Static(Mutability::Not)
+                            ) && self
+                                .ecx
+                                .tcx
+                                .type_of(did)
+                                .no_bound_vars()
+                                .expect("statics should not have generic parameters")
+                                .is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all())
+                            {
+                                throw_validation_failure!(self.path, MutableRefToImmutable);
+                            }
+                        }
                         // We skip recursively checking other statics. These statics must be sound by
                         // themselves, and the only way to get broken statics here is by using
                         // unsafe code.
@@ -454,14 +510,29 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                         if alloc.inner().mutability == Mutability::Mut
                             && matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. }))
                         {
-                            // This should be unreachable, but if someone manages to copy a pointer
-                            // out of a `static`, then that pointer might point to mutable memory,
-                            // and we would catch that here.
-                            throw_validation_failure!(self.path, PtrToMut { ptr_kind });
+                            // This is impossible: this can only be some inner allocation of a
+                            // `static mut` (everything else either hits the `GlobalAlloc::Static`
+                            // case or is interned immutably). To get such a pointer we'd have to
+                            // load it from a static, but such loads lead to a CTFE error.
+                            span_bug!(
+                                self.ecx.tcx.span,
+                                "encountered reference to mutable memory inside a `const`"
+                            );
+                        }
+                        if ptr_expected_mutbl == Mutability::Mut
+                            && alloc.inner().mutability == Mutability::Not
+                        {
+                            throw_validation_failure!(self.path, MutableRefToImmutable);
                         }
                     }
-                    // Nothing to check for these.
-                    None | Some(GlobalAlloc::Function(..) | GlobalAlloc::VTable(..)) => {}
+                    Some(GlobalAlloc::Function(..) | GlobalAlloc::VTable(..)) => {
+                        // These are immutable, we better don't allow mutable pointers here.
+                        if ptr_expected_mutbl == Mutability::Mut {
+                            throw_validation_failure!(self.path, MutableRefToImmutable);
+                        }
+                    }
+                    // Dangling, already handled.
+                    None => bug!(),
                 }
             }
             let path = &self.path;
@@ -532,11 +603,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 Ok(true)
             }
             ty::Ref(_, ty, mutbl) => {
-                if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. }))
+                if self.ctfe_mode.is_some_and(|c| !c.may_contain_mutable_ref())
                     && *mutbl == Mutability::Mut
                 {
-                    // A mutable reference inside a const? That does not seem right (except if it is
-                    // a ZST).
                     let layout = self.ecx.layout_of(*ty)?;
                     if !layout.is_zst() {
                         throw_validation_failure!(self.path, MutableRefInConst);
@@ -642,6 +711,19 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
             )
         }
     }
+
+    fn in_mutable_memory(&self, op: &OpTy<'tcx, M::Provenance>) -> bool {
+        if let Some(mplace) = op.as_mplace_or_imm().left() {
+            if let Some(alloc_id) = mplace.ptr().provenance.and_then(|p| p.get_alloc_id()) {
+                if self.ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner().mutability
+                    == Mutability::Mut
+                {
+                    return true;
+                }
+            }
+        }
+        false
+    }
 }
 
 impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
@@ -705,10 +787,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
         op: &OpTy<'tcx, M::Provenance>,
         _fields: NonZeroUsize,
     ) -> InterpResult<'tcx> {
-        // Special check preventing `UnsafeCell` inside unions in the inner part of constants.
-        if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) {
-            if !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) {
-                throw_validation_failure!(self.path, UnsafeCell);
+        // Special check for CTFE validation, preventing `UnsafeCell` inside unions in immutable memory.
+        if self.ctfe_mode.is_some_and(|c| !c.allow_immutable_unsafe_cell()) {
+            if !op.layout.is_zst() && !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) {
+                if !self.in_mutable_memory(op) {
+                    throw_validation_failure!(self.path, UnsafeCellInImmutable);
+                }
             }
         }
         Ok(())
@@ -730,11 +814,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
         }
 
         // Special check preventing `UnsafeCell` in the inner part of constants
-        if let Some(def) = op.layout.ty.ty_adt_def() {
-            if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. }))
+        if self.ctfe_mode.is_some_and(|c| !c.allow_immutable_unsafe_cell()) {
+            if !op.layout.is_zst()
+                && let Some(def) = op.layout.ty.ty_adt_def()
                 && def.is_unsafe_cell()
             {
-                throw_validation_failure!(self.path, UnsafeCell);
+                if !self.in_mutable_memory(op) {
+                    throw_validation_failure!(self.path, UnsafeCellInImmutable);
+                }
             }
         }