@@ -3,11 +3,14 @@ use std::hash::{Hash, Hasher};
33use rustc::ich::{StableHashingContext, StableHashingContextProvider};
44use rustc::mir;
55use rustc::mir::interpret::{
6- AllocId, Pointer, Scalar, ScalarMaybeUndef, Relocations, Allocation, UndefMask
6+ AllocId, Pointer, Scalar, ScalarMaybeUndef,
7+ Relocations, Allocation, UndefMask,
8+ EvalResult, EvalErrorKind,
79};
810
9- use rustc::ty;
11+ use rustc::ty::{self, TyCtxt} ;
1012use rustc::ty::layout::Align;
13+ use rustc_data_structures::fx::FxHashSet;
1114use rustc_data_structures::indexed_vec::IndexVec;
1215use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableHasherResult};
1316use syntax::ast::Mutability;
@@ -16,6 +19,72 @@ use syntax::source_map::Span;
1619use super::eval_context::{LocalValue, StackPopCleanup};
1720use super::{Frame, Memory, Machine, Operand, MemPlace, Place, Value};
1821
22+ pub(super) struct InfiniteLoopDetector<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
23+ /// The set of all `EvalSnapshot` *hashes* observed by this detector.
24+ ///
25+ /// When a collision occurs in this table, we store the full snapshot in
26+ /// `snapshots`.
27+ hashes: FxHashSet<u64>,
28+
29+ /// The set of all `EvalSnapshot`s observed by this detector.
30+ ///
31+ /// An `EvalSnapshot` will only be fully cloned once it has caused a
32+ /// collision in `hashes`. As a result, the detector must observe at least
33+ /// *two* full cycles of an infinite loop before it triggers.
34+ snapshots: FxHashSet<EvalSnapshot<'a, 'mir, 'tcx, M>>,
35+ }
36+
37+ impl<'a, 'mir, 'tcx, M> Default for InfiniteLoopDetector<'a, 'mir, 'tcx, M>
38+ where M: Machine<'mir, 'tcx>,
39+ 'tcx: 'a + 'mir,
40+ {
41+ fn default() -> Self {
42+ InfiniteLoopDetector {
43+ hashes: FxHashSet::default(),
44+ snapshots: FxHashSet::default(),
45+ }
46+ }
47+ }
48+
49+ impl<'a, 'mir, 'tcx, M> InfiniteLoopDetector<'a, 'mir, 'tcx, M>
50+ where M: Machine<'mir, 'tcx>,
51+ 'tcx: 'a + 'mir,
52+ {
53+ /// Returns `true` if the loop detector has not yet observed a snapshot.
54+ pub fn is_empty(&self) -> bool {
55+ self.hashes.is_empty()
56+ }
57+
58+ pub fn observe_and_analyze(
59+ &mut self,
60+ tcx: &TyCtxt<'b, 'tcx, 'tcx>,
61+ machine: &M,
62+ memory: &Memory<'a, 'mir, 'tcx, M>,
63+ stack: &[Frame<'mir, 'tcx>],
64+ ) -> EvalResult<'tcx, ()> {
65+
66+ let mut hcx = tcx.get_stable_hashing_context();
67+ let mut hasher = StableHasher::<u64>::new();
68+ (machine, stack).hash_stable(&mut hcx, &mut hasher);
69+ let hash = hasher.finish();
70+
71+ if self.hashes.insert(hash) {
72+ // No collision
73+ return Ok(())
74+ }
75+
76+ info!("snapshotting the state of the interpreter");
77+
78+ if self.snapshots.insert(EvalSnapshot::new(machine, memory, stack)) {
79+ // Spurious collision or first cycle
80+ return Ok(())
81+ }
82+
83+ // Second cycle
84+ Err(EvalErrorKind::InfiniteLoop.into())
85+ }
86+ }
87+
1988trait SnapshotContext<'a> {
2089 fn resolve(&'a self, id: &AllocId) -> Option<&'a Allocation>;
2190}
@@ -269,7 +338,7 @@ impl<'a, 'b, 'mir, 'tcx, M> SnapshotContext<'b> for Memory<'a, 'mir, 'tcx, M>
269338}
270339
271340/// The virtual machine state during const-evaluation at a given point in time.
272- pub struct EvalSnapshot<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
341+ struct EvalSnapshot<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
273342 machine: M,
274343 memory: Memory<'a, 'mir, 'tcx, M>,
275344 stack: Vec<Frame<'mir, 'tcx>>,
@@ -278,7 +347,7 @@ pub struct EvalSnapshot<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
278347impl<'a, 'mir, 'tcx, M> EvalSnapshot<'a, 'mir, 'tcx, M>
279348 where M: Machine<'mir, 'tcx>,
280349{
281- pub fn new(
350+ fn new(
282351 machine: &M,
283352 memory: &Memory<'a, 'mir, 'tcx, M>,
284353 stack: &[Frame<'mir, 'tcx>]) -> Self {
0 commit comments