rustc_const_eval/interpret/
intern.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
//! 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_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
use rustc_middle::mir::interpret::{ConstAllocation, CtfeProvenance, InterpResult};
use rustc_middle::query::TyCtxtAt;
use rustc_middle::span_bug;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_span::def_id::LocalDefId;
use rustc_span::sym;
use tracing::{instrument, trace};

use super::{
    AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy, err_ub, interp_ok,
};
use crate::const_eval;
use crate::errors::NestedStaticInThreadLocal;

pub trait CompileTimeMachine<'tcx, T> = Machine<
        'tcx,
        MemoryKind = T,
        Provenance = CtfeProvenance,
        ExtraFnVal = !,
        FrameExtra = (),
        AllocExtra = (),
        MemoryMap = FxIndexMap<AllocId, (MemoryKind<T>, 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)
    }
}

/// 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<'rt, 'tcx, T, M: CompileTimeMachine<'tcx, T>>(
    ecx: &'rt mut InterpCx<'tcx, M>,
    alloc_id: AllocId,
    mutability: Mutability,
) -> Result<impl Iterator<Item = CtfeProvenance> + 'tcx, ()> {
    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(());
    };
    // 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);
        }
    }
    // 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);
    } else {
        ecx.tcx.set_alloc_id_memory(alloc_id, alloc);
    }
    Ok(alloc.0.0.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>,
) {
    let feed = tcx.create_def(static_id, sym::nested, DefKind::Static {
        safety: hir::Safety::Safe,
        mutability: alloc.0.mutability,
        nested: true,
    });
    tcx.set_nested_alloc_id_static(alloc_id, feed.def_id());

    if tcx.is_thread_local_static(static_id.into()) {
        tcx.dcx().emit_err(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_predicates_of(tcx.explicit_predicates_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 InternResult {
    FoundBadMutablePointer,
    FoundDanglingPointer,
}

/// 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, const_eval::MemoryKind>>(
    ecx: &mut InterpCx<'tcx, M>,
    intern_kind: InternKind,
    ret: &MPlaceTy<'tcx>,
) -> Result<(), InternResult> {
    // 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`.
                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 {
        // 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 alloc = ecx.memory.alloc_map.get_mut(&base_alloc_id).unwrap();
        alloc.1.mutability = base_mutability;
        alloc.1.provenance().ptrs().iter().map(|&(_, prov)| prov).collect()
    } else {
        intern_shallow(ecx, base_alloc_id, base_mutability).unwrap().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 result = Ok(());

    // 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(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 a mutable reference inside a const where inner allocations should be
                // immutable.
                if !ecx.tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you {
                    span_bug!(
                        ecx.tcx.span,
                        "the static const safety checks accepted mutable references they should not have accepted"
                    );
                }
                // Prefer dangling pointer errors over mutable pointer errors
                if result.is_ok() {
                    result = Err(InternResult::FoundBadMutablePointer);
                }
            }
        }
        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);
        match intern_shallow(ecx, alloc_id, inner_mutability) {
            Ok(nested) => todo.extend(nested),
            Err(()) => {
                ecx.tcx.dcx().delayed_bug("found dangling pointer during const interning");
                result = Err(InternResult::FoundDanglingPointer);
            }
        }
    }
    result
}

/// Intern `ret`. This function assumes that `ret` references no other allocation.
#[instrument(level = "debug", skip(ecx))]
pub fn intern_const_alloc_for_constprop<'tcx, T, M: CompileTimeMachine<'tcx, T>>(
    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).map_err(|()| err_ub!(DeadLocal))?).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, M: super::intern::CompileTimeMachine<'tcx, !>> InterpCx<'tcx, M> {
    /// 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, M>, &PlaceTy<'tcx, M::Provenance>) -> 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).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)
    }
}