| //! This module specifies the type based interner for constants. | |
| //! | |
| //! After a const evaluation has computed a value, before we destroy the const evaluator's session | |
| //! memory, we need to extract all memory allocations to the global memory pool so they stay around. | |
| //! | |
| //! 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: 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 hir::def::DefKind; | |
| use rustc_ast::Mutability; | |
| use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; | |
| use rustc_hir as hir; | |
| use rustc_hir::definitions::{DefPathData, PerParentDisambiguatorState}; | |
| use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; | |
| use rustc_middle::mir::interpret::{ | |
| AllocBytes, ConstAllocation, CtfeProvenance, InterpResult, Provenance, | |
| }; | |
| use rustc_middle::query::TyCtxtAt; | |
| use rustc_middle::span_bug; | |
| use rustc_middle::ty::TyCtxt; | |
| use rustc_middle::ty::layout::TyAndLayout; | |
| use rustc_span::def_id::LocalDefId; | |
| use tracing::{instrument, trace}; | |
| use super::{AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy, interp_ok}; | |
| use crate::const_eval::DummyMachine; | |
| use crate::{const_eval, diagnostics}; | |
| pub trait CompileTimeMachine<'tcx> = Machine< | |
| 'tcx, | |
| MemoryKind = const_eval::MemoryKind, | |
| Provenance = CtfeProvenance, | |
| ExtraFnVal = !, | |
| FrameExtra = (), | |
| AllocExtra = (), | |
| MemoryMap = FxIndexMap<AllocId, (MemoryKind<const_eval::MemoryKind>, Allocation)>, | |
| > + HasStaticRootDefId; | |
| pub trait HasStaticRootDefId { | |
| /// Returns the `DefId` of the static item that is currently being evaluated. | |
| /// Used for interning to be able to handle nested allocations. | |
| fn static_def_id(&self) -> Option<LocalDefId>; | |
| } | |
| impl HasStaticRootDefId for const_eval::CompileTimeMachine<'_> { | |
| fn static_def_id(&self) -> Option<LocalDefId> { | |
| Some(self.static_root_ids?.1) | |
| } | |
| } | |
| fn prepare_alloc<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( | |
| tcx: TyCtxt<'tcx>, | |
| kind: MemoryKind<const_eval::MemoryKind>, | |
| alloc: &mut Allocation<Prov, Extra, Bytes>, | |
| mutability: Mutability, | |
| ) -> Result<(), InternError> { | |
| match kind { | |
| MemoryKind::Machine(const_eval::MemoryKind::Heap { was_made_global }) => { | |
| if !was_made_global { | |
| // Attempting to intern a `const_allocate`d pointer that was not made global via | |
| // `const_make_global`. | |
| tcx.dcx().delayed_bug("non-global heap allocation in const value"); | |
| return Err(InternError::ConstAllocNotGlobal); | |
| } | |
| } | |
| MemoryKind::Stack | MemoryKind::CallerLocation => {} | |
| } | |
| if !alloc.provenance_merge_bytes(&tcx) { | |
| // Per-byte provenance is not supported by backends, so we cannot accept it here. | |
| tcx.dcx().delayed_bug("partial pointer in const value"); | |
| return Err(InternError::PartialPointer); | |
| } | |
| // 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. | |
| match mutability { | |
| Mutability::Not => { | |
| alloc.mutability = Mutability::Not; | |
| } | |
| Mutability::Mut => { | |
| // This must be already mutable, we won't "un-freeze" allocations ever. | |
| assert_eq!(alloc.mutability, Mutability::Mut); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| /// 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). | |
| /// | |
| /// Returns an iterator over all relocations referred to by this allocation. | |
| fn intern_shallow<'tcx, M: CompileTimeMachine<'tcx>>( | |
| ecx: &mut InterpCx<'tcx, M>, | |
| alloc_id: AllocId, | |
| mutability: Mutability, | |
| disambiguator: Option<&mut PerParentDisambiguatorState>, | |
| ) -> Result<impl Iterator<Item = CtfeProvenance> + 'tcx, InternError> { | |
| trace!("intern_shallow {:?}", alloc_id); | |
| // remove allocation | |
| // FIXME(#120456) - is `swap_remove` correct? | |
| let Some((kind, mut alloc)) = ecx.memory.alloc_map.swap_remove(&alloc_id) else { | |
| return Err(InternError::DanglingPointer); | |
| }; | |
| if let Err(err) = prepare_alloc(*ecx.tcx, kind, &mut alloc, mutability) { | |
| // We want to error here, but we have to first put the | |
| // allocation back into the `alloc_map` to keep things in a consistent state. | |
| ecx.memory.alloc_map.insert(alloc_id, (kind, alloc)); | |
| return Err(err); | |
| } | |
| // link the alloc id to the actual allocation | |
| let alloc = ecx.tcx.mk_const_alloc(alloc); | |
| if let Some(static_id) = ecx.machine.static_def_id() { | |
| intern_as_new_static( | |
| ecx.tcx, | |
| static_id, | |
| alloc_id, | |
| alloc, | |
| disambiguator.expect("disambiguator needed"), | |
| ); | |
| } else { | |
| ecx.tcx.set_alloc_id_memory(alloc_id, alloc); | |
| } | |
| Ok(alloc.inner().provenance().ptrs().iter().map(|&(_, prov)| prov)) | |
| } | |
| /// Creates a new `DefId` and feeds all the right queries to make this `DefId` | |
| /// appear as if it were a user-written `static` (though it has no HIR). | |
| fn intern_as_new_static<'tcx>( | |
| tcx: TyCtxtAt<'tcx>, | |
| static_id: LocalDefId, | |
| alloc_id: AllocId, | |
| alloc: ConstAllocation<'tcx>, | |
| disambiguator: &mut PerParentDisambiguatorState, | |
| ) { | |
| // `intern_const_alloc_recursive` is called once per static and it contains the `PerParentDisambiguatorState`. | |
| // The `<static_id>::{{nested}}` path is thus unique to `intern_const_alloc_recursive` and the | |
| // `PerParentDisambiguatorState` ensures the generated path is unique for this call as we generate | |
| // `<static_id>::{{nested#n}}` where `n` is the `n`th `intern_as_new_static` call. | |
| let feed = tcx.create_def( | |
| static_id, | |
| None, | |
| DefKind::Static { safety: hir::Safety::Safe, mutability: alloc.0.mutability, nested: true }, | |
| Some(DefPathData::NestedStatic), | |
| disambiguator, | |
| ); | |
| tcx.set_nested_alloc_id_static(alloc_id, feed.def_id()); | |
| if tcx.is_thread_local_static(static_id.into()) { | |
| tcx.dcx() | |
| .emit_err(diagnostics::NestedStaticInThreadLocal { span: tcx.def_span(static_id) }); | |
| } | |
| // These do not inherit the codegen attrs of the parent static allocation, since | |
| // it doesn't make sense for them to inherit their `#[no_mangle]` and `#[link_name = ..]` | |
| // and the like. | |
| feed.codegen_fn_attrs(CodegenFnAttrs::new()); | |
| feed.eval_static_initializer(Ok(alloc)); | |
| feed.generics_of(tcx.generics_of(static_id).clone()); | |
| feed.def_ident_span(tcx.def_ident_span(static_id)); | |
| feed.explicit_clauses_of(tcx.explicit_clauses_of(static_id)); | |
| feed.feed_hir(); | |
| } | |
| /// How a constant value should be interned. | |
| #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)] | |
| pub enum InternKind { | |
| /// The `mutability` of the static, ignoring the type which may have interior mutability. | |
| Static(hir::Mutability), | |
| /// A `const` item | |
| Constant, | |
| Promoted, | |
| } | |
| #[derive(Debug)] | |
| pub enum InternError { | |
| BadMutablePointer, | |
| DanglingPointer, | |
| ConstAllocNotGlobal, | |
| PartialPointer, | |
| } | |
| /// Intern `ret` and everything it references. | |
| /// | |
| /// This *cannot raise an interpreter error*. Doing so is left to validation, which | |
| /// tracks where in the value we are and thus can show much better error messages. | |
| /// | |
| /// For `InternKind::Static` the root allocation will not be interned, but must be handled by the caller. | |
| #[instrument(level = "debug", skip(ecx))] | |
| pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx>>( | |
| ecx: &mut InterpCx<'tcx, M>, | |
| intern_kind: InternKind, | |
| ret: &MPlaceTy<'tcx>, | |
| ) -> Result<(), InternError> { | |
| let mut disambiguator = | |
| ecx.machine.static_def_id().map(|id| PerParentDisambiguatorState::new(id)); | |
| let mut disambiguator = disambiguator.as_mut(); | |
| // 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, is_static) = 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, false) | |
| } | |
| InternKind::Static(Mutability::Not) => { | |
| ( | |
| // Outermost allocation is mutable if `!Freeze` i.e. contains interior mutable types. | |
| if ret.layout.ty.is_freeze(*ecx.tcx, ecx.typing_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, | |
| true, | |
| ) | |
| } | |
| 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, true) | |
| } | |
| }; | |
| // Intern the base allocation, and initialize todo list for recursive interning. | |
| let base_alloc_id = ret.ptr().provenance.unwrap().alloc_id(); | |
| trace!(?base_alloc_id, ?base_mutability); | |
| // First we intern the base allocation, as it requires a different mutability. | |
| // This gives us the initial set of nested allocations, which will then all be processed | |
| // recursively in the loop below. | |
| let mut todo: Vec<_> = if is_static { | |
| assert!(disambiguator.is_some()); | |
| // Do not steal the root allocation, we need it later to create the return value of `eval_static_initializer`. | |
| // But still change its mutability to match the requested one. | |
| let (kind, alloc) = ecx.memory.alloc_map.get_mut(&base_alloc_id).unwrap(); | |
| prepare_alloc(*ecx.tcx, *kind, alloc, base_mutability)?; | |
| alloc.provenance().ptrs().iter().map(|&(_, prov)| prov).collect() | |
| } else { | |
| assert!(disambiguator.is_none()); | |
| intern_shallow(ecx, base_alloc_id, base_mutability, None)?.collect() | |
| }; | |
| // 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<_> = std::iter::once(base_alloc_id).collect(); | |
| // 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_ptr = 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`). We only return these errors as a `Result` | |
| // so that the caller can run validation, and subsequently only report interning errors | |
| // if validation fails. Validation has the better error messages so we prefer those, but | |
| // interning has better coverage since it "sees" *all* pointers, including raw pointers and | |
| // references stored in unions. | |
| while let Some(prov) = todo.pop() { | |
| trace!(?prov); | |
| let alloc_id = prov.alloc_id(); | |
| if base_alloc_id == alloc_id && is_static { | |
| // This is a pointer to the static itself. It's ok for a static to refer to itself, | |
| // even mutably. Whether that mutable pointer is legal at all is checked in validation. | |
| // See tests/ui/statics/recursive_interior_mut.rs for how such a situation can occur. | |
| // We also already collected all the nested allocations, so there's no need to do that again. | |
| continue; | |
| } | |
| // Ensure that this is derived from a shared reference. Crucially, we check this *before* | |
| // checking whether the `alloc_id` has already been interned. The point of this check is to | |
| // ensure that when there are multiple pointers to the same allocation, they are *all* | |
| // derived from a shared reference. Therefore it would be bad if we only checked the first | |
| // pointer to any given allocation. | |
| // (It is likely not possible to actually have multiple pointers to the same allocation, | |
| // so alternatively we could also check that and ICE if there are multiple such pointers.) | |
| // See <https://github.com/rust-lang/rust/pull/128543> for why we are checking for "shared | |
| // reference" and not "immutable", i.e., for why we are allowing interior-mutable shared | |
| // references: they can actually be created in safe code while pointing to apparently | |
| // "immutable" values, via promotion or tail expression lifetime extension of | |
| // `&None::<Cell<T>>`. | |
| // We also exclude promoteds from this as `&mut []` can be promoted, which is a mutable | |
| // reference pointing to an immutable (zero-sized) allocation. We rely on the promotion | |
| // analysis not screwing up to ensure that it is sound to intern promoteds as immutable. | |
| if intern_kind != InternKind::Promoted | |
| && inner_mutability == Mutability::Not | |
| && !prov.shared_ref() | |
| { | |
| let is_already_global = ecx.tcx.try_get_global_alloc(alloc_id).is_some(); | |
| if is_already_global && !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 can also skip the rest of this loop iteration, since after all it is already | |
| // interned. | |
| continue; | |
| } | |
| // If this is a dangling pointer, that's actually fine -- the problematic case is | |
| // when there is memory there that someone might expect to be mutable, but we make it immutable. | |
| let dangling = !is_already_global && !ecx.memory.alloc_map.contains_key(&alloc_id); | |
| if !dangling { | |
| found_bad_mutable_ptr = true; | |
| } | |
| } | |
| if ecx.tcx.try_get_global_alloc(alloc_id).is_some() { | |
| // Already interned. | |
| debug_assert!(!ecx.memory.alloc_map.contains_key(&alloc_id)); | |
| continue; | |
| } | |
| // 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`. | |
| just_interned.insert(alloc_id); | |
| let next = intern_shallow(ecx, alloc_id, inner_mutability, disambiguator.as_deref_mut())?; | |
| todo.extend(next); | |
| } | |
| if found_bad_mutable_ptr { | |
| // We found a mutable pointer inside a const where inner allocations should be immutable, | |
| // and there was no other error. This should usually never happen! However, this can happen | |
| // in unleash-miri mode, so report it as a normal error then. | |
| if ecx.tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you { | |
| return Err(InternError::BadMutablePointer); | |
| } else { | |
| span_bug!( | |
| ecx.tcx.span, | |
| "the static const safety checks accepted a mutable pointer they should not have accepted" | |
| ); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| /// Intern `ret`. This function assumes that `ret` references no other allocation. | |
| #[instrument(level = "debug", skip(ecx))] | |
| pub fn intern_const_alloc_for_constprop<'tcx, M: CompileTimeMachine<'tcx>>( | |
| ecx: &mut InterpCx<'tcx, M>, | |
| alloc_id: AllocId, | |
| ) -> InterpResult<'tcx, ()> { | |
| if ecx.tcx.try_get_global_alloc(alloc_id).is_some() { | |
| // The constant is already in global memory. Do nothing. | |
| return interp_ok(()); | |
| } | |
| // Move allocation to `tcx`. | |
| if let Some(_) = intern_shallow(ecx, alloc_id, Mutability::Not, None).unwrap().next() { | |
| // 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") | |
| } | |
| interp_ok(()) | |
| } | |
| impl<'tcx> InterpCx<'tcx, DummyMachine> { | |
| /// A helper function that allocates memory for the layout given and gives you access to mutate | |
| /// it. Once your own mutation code is done, the backing `Allocation` is removed from the | |
| /// current `Memory` and interned as read-only into the global memory. | |
| pub fn intern_with_temp_alloc( | |
| &mut self, | |
| layout: TyAndLayout<'tcx>, | |
| f: impl FnOnce( | |
| &mut InterpCx<'tcx, DummyMachine>, | |
| &PlaceTy<'tcx, CtfeProvenance>, | |
| ) -> InterpResult<'tcx, ()>, | |
| ) -> InterpResult<'tcx, AllocId> { | |
| // `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 alloc_id = dest.ptr().provenance.unwrap().alloc_id(); // this was just allocated, it must have provenance | |
| for prov in intern_shallow(self, alloc_id, Mutability::Not, None).unwrap() { | |
| // 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 self.tcx.try_get_global_alloc(prov.alloc_id()).is_none() { | |
| panic!("`intern_with_temp_alloc` with nested allocations"); | |
| } | |
| } | |
| interp_ok(alloc_id) | |
| } | |
| } |