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