]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/interpret/intern.rs
Rollup merge of #66834 - infinity0:master, r=Mark-Simulacrum
[rust.git] / src / librustc_mir / interpret / intern.rs
1 //! This module specifies the type based interner for constants.
2 //!
3 //! After a const evaluation has computed a value, before we destroy the const evaluator's session
4 //! memory, we need to extract all memory allocations to the global memory pool so they stay around.
5
6 use super::validity::RefTracking;
7 use rustc::hir;
8 use rustc::mir::interpret::{ErrorHandled, InterpResult};
9 use rustc::ty::{self, Ty};
10 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
11
12 use syntax::ast::Mutability;
13
14 use super::{
15     AllocId, Allocation, InterpCx, Machine, MemoryKind, MPlaceTy, Scalar, ValueVisitor,
16 };
17
18 pub trait CompileTimeMachine<'mir, 'tcx> =
19     Machine<
20         'mir,
21         'tcx,
22         MemoryKinds = !,
23         PointerTag = (),
24         ExtraFnVal = !,
25         FrameExtra = (),
26         MemoryExtra = (),
27         AllocExtra = (),
28         MemoryMap = FxHashMap<AllocId, (MemoryKind<!>, Allocation)>,
29     >;
30
31 struct InternVisitor<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>> {
32     /// The ectx from which we intern.
33     ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
34     /// Previously encountered safe references.
35     ref_tracking: &'rt mut RefTracking<(MPlaceTy<'tcx>, Mutability, InternMode)>,
36     /// A list of all encountered allocations. After type-based interning, we traverse this list to
37     /// also intern allocations that are only referenced by a raw pointer or inside a union.
38     leftover_allocations: &'rt mut FxHashSet<AllocId>,
39     /// The root node of the value that we're looking at. This field is never mutated and only used
40     /// for sanity assertions that will ICE when `const_qualif` screws up.
41     mode: InternMode,
42     /// This field stores the mutability of the value *currently* being checked.
43     /// When encountering a mutable reference, we determine the pointee mutability
44     /// taking into account the mutability of the context: `& &mut i32` is entirely immutable,
45     /// despite the nested mutable reference!
46     /// The field gets updated when an `UnsafeCell` is encountered.
47     mutability: Mutability,
48 }
49
50 #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
51 enum InternMode {
52     /// Mutable references must in fact be immutable due to their surrounding immutability in a
53     /// `static`. In a `static mut` we start out as mutable and thus can also contain further `&mut`
54     /// that will actually be treated as mutable.
55     Static,
56     /// UnsafeCell is OK in the value of a constant: `const FOO = Cell::new(0)` creates
57     /// a new cell every time it is used.
58     ConstBase,
59     /// `UnsafeCell` ICEs.
60     Const,
61 }
62
63 /// Signalling data structure to ensure we don't recurse
64 /// into the memory of other constants or statics
65 struct IsStaticOrFn;
66
67 /// Intern an allocation without looking at its children.
68 /// `mode` is the mode of the environment where we found this pointer.
69 /// `mutablity` is the mutability of the place to be interned; even if that says
70 /// `immutable` things might become mutable if `ty` is not frozen.
71 /// `ty` can be `None` if there is no potential interior mutability
72 /// to account for (e.g. for vtables).
73 fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>>(
74     ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
75     leftover_allocations: &'rt mut FxHashSet<AllocId>,
76     mode: InternMode,
77     alloc_id: AllocId,
78     mutability: Mutability,
79     ty: Option<Ty<'tcx>>,
80 ) -> InterpResult<'tcx, Option<IsStaticOrFn>> {
81     trace!("InternVisitor::intern {:?} with {:?}", alloc_id, mutability,);
82     // remove allocation
83     let tcx = ecx.tcx;
84     let (kind, mut alloc) = match ecx.memory.alloc_map.remove(&alloc_id) {
85         Some(entry) => entry,
86         None => {
87             // Pointer not found in local memory map. It is either a pointer to the global
88             // map, or dangling.
89             // If the pointer is dangling (neither in local nor global memory), we leave it
90             // to validation to error. The `delay_span_bug` ensures that we don't forget such
91             // a check in validation.
92             if tcx.alloc_map.lock().get(alloc_id).is_none() {
93                 tcx.sess.delay_span_bug(ecx.tcx.span, "tried to intern dangling pointer");
94             }
95             // treat dangling pointers like other statics
96             // just to stop trying to recurse into them
97             return Ok(Some(IsStaticOrFn));
98         },
99     };
100     // This match is just a canary for future changes to `MemoryKind`, which most likely need
101     // changes in this function.
102     match kind {
103         MemoryKind::Stack | MemoryKind::Vtable | MemoryKind::CallerLocation => {},
104     }
105     // Set allocation mutability as appropriate. This is used by LLVM to put things into
106     // read-only memory, and also by Miri when evluating other constants/statics that
107     // access this one.
108     if mode == InternMode::Static {
109         // When `ty` is `None`, we assume no interior mutability.
110         let frozen = ty.map_or(true, |ty| ty.is_freeze(
111             ecx.tcx.tcx,
112             ecx.param_env,
113             ecx.tcx.span,
114         ));
115         // For statics, allocation mutability is the combination of the place mutability and
116         // the type mutability.
117         // The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere.
118         if mutability == Mutability::Immutable && frozen {
119             alloc.mutability = Mutability::Immutable;
120         } else {
121             // Just making sure we are not "upgrading" an immutable allocation to mutable.
122             assert_eq!(alloc.mutability, Mutability::Mutable);
123         }
124     } else {
125         // We *could* be non-frozen at `ConstBase`, for constants like `Cell::new(0)`.
126         // But we still intern that as immutable as the memory cannot be changed once the
127         // initial value was computed.
128         // Constants are never mutable.
129         assert_eq!(
130             mutability, Mutability::Immutable,
131             "Something went very wrong: mutability requested for a constant"
132         );
133         alloc.mutability = Mutability::Immutable;
134     };
135     // link the alloc id to the actual allocation
136     let alloc = tcx.intern_const_alloc(alloc);
137     leftover_allocations.extend(alloc.relocations().iter().map(|&(_, ((), reloc))| reloc));
138     tcx.alloc_map.lock().set_alloc_id_memory(alloc_id, alloc);
139     Ok(None)
140 }
141
142 impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>> InternVisitor<'rt, 'mir, 'tcx, M> {
143     fn intern_shallow(
144         &mut self,
145         alloc_id: AllocId,
146         mutability: Mutability,
147         ty: Option<Ty<'tcx>>,
148     ) -> InterpResult<'tcx, Option<IsStaticOrFn>> {
149         intern_shallow(
150             self.ecx,
151             self.leftover_allocations,
152             self.mode,
153             alloc_id,
154             mutability,
155             ty,
156         )
157     }
158 }
159
160 impl<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>>
161     ValueVisitor<'mir, 'tcx, M>
162 for
163     InternVisitor<'rt, 'mir, 'tcx, M>
164 {
165     type V = MPlaceTy<'tcx>;
166
167     #[inline(always)]
168     fn ecx(&self) -> &InterpCx<'mir, 'tcx, M> {
169         &self.ecx
170     }
171
172     fn visit_aggregate(
173         &mut self,
174         mplace: MPlaceTy<'tcx>,
175         fields: impl Iterator<Item=InterpResult<'tcx, Self::V>>,
176     ) -> InterpResult<'tcx> {
177         if let Some(def) = mplace.layout.ty.ty_adt_def() {
178             if Some(def.did) == self.ecx.tcx.lang_items().unsafe_cell_type() {
179                 // We are crossing over an `UnsafeCell`, we can mutate again. This means that
180                 // References we encounter inside here are interned as pointing to mutable
181                 // allocations.
182                 let old = std::mem::replace(&mut self.mutability, Mutability::Mutable);
183                 assert_ne!(
184                     self.mode, InternMode::Const,
185                     "UnsafeCells are not allowed behind references in constants. This should have \
186                     been prevented statically by const qualification. If this were allowed one \
187                     would be able to change a constant at one use site and other use sites could \
188                     observe that mutation.",
189                 );
190                 let walked = self.walk_aggregate(mplace, fields);
191                 self.mutability = old;
192                 return walked;
193             }
194         }
195         self.walk_aggregate(mplace, fields)
196     }
197
198     fn visit_primitive(&mut self, mplace: MPlaceTy<'tcx>) -> InterpResult<'tcx> {
199         // Handle Reference types, as these are the only relocations supported by const eval.
200         // Raw pointers (and boxes) are handled by the `leftover_relocations` logic.
201         let ty = mplace.layout.ty;
202         if let ty::Ref(_, referenced_ty, mutability) = ty.kind {
203             let value = self.ecx.read_immediate(mplace.into())?;
204             let mplace = self.ecx.ref_to_mplace(value)?;
205             // Handle trait object vtables
206             if let ty::Dynamic(..) =
207                 self.ecx.tcx.struct_tail_erasing_lifetimes(
208                     referenced_ty, self.ecx.param_env).kind
209             {
210                 if let Ok(vtable) = mplace.meta.unwrap().to_ptr() {
211                     // explitly choose `Immutable` here, since vtables are immutable, even
212                     // if the reference of the fat pointer is mutable
213                     self.intern_shallow(vtable.alloc_id, Mutability::Immutable, None)?;
214                 }
215             }
216             // Check if we have encountered this pointer+layout combination before.
217             // Only recurse for allocation-backed pointers.
218             if let Scalar::Ptr(ptr) = mplace.ptr {
219                 // We do not have any `frozen` logic here, because it's essentially equivalent to
220                 // the mutability except for the outermost item. Only `UnsafeCell` can "unfreeze",
221                 // and we check that in `visit_aggregate`.
222                 // This is not an inherent limitation, but one that we know to be true, because
223                 // const qualification enforces it. We can lift it in the future.
224                 match (self.mode, mutability) {
225                     // immutable references are fine everywhere
226                     (_, hir::Mutability::Immutable) => {},
227                     // all is "good and well" in the unsoundness of `static mut`
228
229                     // mutable references are ok in `static`. Either they are treated as immutable
230                     // because they are behind an immutable one, or they are behind an `UnsafeCell`
231                     // and thus ok.
232                     (InternMode::Static, hir::Mutability::Mutable) => {},
233                     // we statically prevent `&mut T` via `const_qualif` and double check this here
234                     (InternMode::ConstBase, hir::Mutability::Mutable) |
235                     (InternMode::Const, hir::Mutability::Mutable) => {
236                         match referenced_ty.kind {
237                             ty::Array(_, n)
238                                 if n.eval_usize(self.ecx.tcx.tcx, self.ecx.param_env) == 0 => {}
239                             ty::Slice(_)
240                                 if mplace.meta.unwrap().to_machine_usize(self.ecx)? == 0 => {}
241                             _ => bug!("const qualif failed to prevent mutable references"),
242                         }
243                     },
244                 }
245                 // Compute the mutability with which we'll start visiting the allocation. This is
246                 // what gets changed when we encounter an `UnsafeCell`
247                 let mutability = match (self.mutability, mutability) {
248                     // The only way a mutable reference actually works as a mutable reference is
249                     // by being in a `static mut` directly or behind another mutable reference.
250                     // If there's an immutable reference or we are inside a static, then our
251                     // mutable reference is equivalent to an immutable one. As an example:
252                     // `&&mut Foo` is semantically equivalent to `&&Foo`
253                     (Mutability::Mutable, hir::Mutability::Mutable) => Mutability::Mutable,
254                     _ => Mutability::Immutable,
255                 };
256                 // Recursing behind references changes the intern mode for constants in order to
257                 // cause assertions to trigger if we encounter any `UnsafeCell`s.
258                 let mode = match self.mode {
259                     InternMode::ConstBase => InternMode::Const,
260                     other => other,
261                 };
262                 match self.intern_shallow(ptr.alloc_id, mutability, Some(mplace.layout.ty))? {
263                     // No need to recurse, these are interned already and statics may have
264                     // cycles, so we don't want to recurse there
265                     Some(IsStaticOrFn) => {},
266                     // intern everything referenced by this value. The mutability is taken from the
267                     // reference. It is checked above that mutable references only happen in
268                     // `static mut`
269                     None => self.ref_tracking.track((mplace, mutability, mode), || ()),
270                 }
271             }
272         }
273         Ok(())
274     }
275 }
276
277 pub fn intern_const_alloc_recursive<M: CompileTimeMachine<'mir, 'tcx>>(
278     ecx: &mut InterpCx<'mir, 'tcx, M>,
279     // The `mutability` of the place, ignoring the type.
280     place_mut: Option<hir::Mutability>,
281     ret: MPlaceTy<'tcx>,
282 ) -> InterpResult<'tcx> {
283     let tcx = ecx.tcx;
284     let (base_mutability, base_intern_mode) = match place_mut {
285         Some(hir::Mutability::Immutable) => (Mutability::Immutable, InternMode::Static),
286         // `static mut` doesn't care about interior mutability, it's mutable anyway
287         Some(hir::Mutability::Mutable) => (Mutability::Mutable, InternMode::Static),
288         // consts, promoteds. FIXME: what about array lengths, array initializers?
289         None => (Mutability::Immutable, InternMode::ConstBase),
290     };
291
292     // Type based interning.
293     // `ref_tracking` tracks typed references we have seen and still need to crawl for
294     // more typed information inside them.
295     // `leftover_allocations` collects *all* allocations we see, because some might not
296     // be available in a typed way. They get interned at the end.
297     let mut ref_tracking = RefTracking::new((ret, base_mutability, base_intern_mode));
298     let leftover_allocations = &mut FxHashSet::default();
299
300     // start with the outermost allocation
301     intern_shallow(
302         ecx,
303         leftover_allocations,
304         base_intern_mode,
305         ret.ptr.to_ptr()?.alloc_id,
306         base_mutability,
307         Some(ret.layout.ty)
308     )?;
309
310     while let Some(((mplace, mutability, mode), _)) = ref_tracking.todo.pop() {
311         let interned = InternVisitor {
312             ref_tracking: &mut ref_tracking,
313             ecx,
314             mode,
315             leftover_allocations,
316             mutability,
317         }.visit_value(mplace);
318         if let Err(error) = interned {
319             // This can happen when e.g. the tag of an enum is not a valid discriminant. We do have
320             // to read enum discriminants in order to find references in enum variant fields.
321             if let err_unsup!(ValidationFailure(_)) = error.kind {
322                 let err = crate::const_eval::error_to_const_error(&ecx, error);
323                 match err.struct_error(ecx.tcx, "it is undefined behavior to use this value") {
324                     Ok(mut diag) => {
325                         diag.note(crate::const_eval::note_on_undefined_behavior_error());
326                         diag.emit();
327                     }
328                     Err(ErrorHandled::TooGeneric) |
329                     Err(ErrorHandled::Reported) => {},
330                 }
331             }
332         }
333     }
334
335     // Intern the rest of the allocations as mutable. These might be inside unions, padding, raw
336     // pointers, ... So we can't intern them according to their type rules
337
338     let mut todo: Vec<_> = leftover_allocations.iter().cloned().collect();
339     while let Some(alloc_id) = todo.pop() {
340         if let Some((_, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) {
341             // We can't call the `intern_shallow` method here, as its logic is tailored to safe
342             // references and a `leftover_allocations` set (where we only have a todo-list here).
343             // So we hand-roll the interning logic here again.
344             if base_intern_mode != InternMode::Static {
345                 // If it's not a static, it *must* be immutable.
346                 // We cannot have mutable memory inside a constant.
347                 // FIXME: ideally we would assert that they already are immutable, to double-
348                 // check our static checks.
349                 alloc.mutability = Mutability::Immutable;
350             }
351             let alloc = tcx.intern_const_alloc(alloc);
352             tcx.alloc_map.lock().set_alloc_id_memory(alloc_id, alloc);
353             for &(_, ((), reloc)) in alloc.relocations().iter() {
354                 if leftover_allocations.insert(reloc) {
355                     todo.push(reloc);
356                 }
357             }
358         } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) {
359             // dangling pointer
360             throw_unsup!(ValidationFailure("encountered dangling pointer in final constant".into()))
361         }
362     }
363     Ok(())
364 }