Skip to content

Commit 7f9b01a

Browse files
Add miri infinite loop detection
Use the approach suggested by @oli-obk, a table holding `EvalState` hashes and a table holding full `EvalState` objects. When a hash collision is observed, the state is cloned and put into the full table. If the collision was not spurious, it will be detected during the next iteration of the infinite loop.
1 parent 6c0f502 commit 7f9b01a

File tree

2 files changed

+71
-14
lines changed

2 files changed

+71
-14
lines changed

src/librustc_mir/interpret/eval_context.rs

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rustc::ty::layout::{self, Size, Align, HasDataLayout, IntegerExt, LayoutOf,
1010
use rustc::ty::subst::{Subst, Substs};
1111
use rustc::ty::{self, Ty, TyCtxt, TypeAndMut};
1212
use rustc::ty::query::TyCtxtAt;
13+
use rustc_data_structures::fx::{FxHashSet, FxHasher};
1314
use rustc_data_structures::indexed_vec::{IndexVec, Idx};
1415
use rustc::mir::interpret::{
1516
FrameInfo, GlobalId, Value, Scalar,
@@ -34,15 +35,16 @@ pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
3435
pub param_env: ty::ParamEnv<'tcx>,
3536

3637
/// Virtual memory and call stack.
37-
state: EvalState<'a, 'mir, 'tcx, M>,
38+
pub(crate) state: EvalState<'a, 'mir, 'tcx, M>,
3839

3940
/// The maximum number of stack frames allowed
4041
pub(crate) stack_limit: usize,
4142

42-
/// The maximum number of terminators that may be evaluated.
43-
/// This prevents infinite loops and huge computations from freezing up const eval.
44-
/// Remove once halting problem is solved.
45-
pub(crate) terminators_remaining: usize,
43+
/// The number of terminators to be evaluated before enabling the infinite
44+
/// loop detector.
45+
pub(crate) steps_until_detector_enabled: usize,
46+
47+
pub(crate) loop_detector: InfiniteLoopDetector<'a, 'mir, 'tcx, M>,
4648
}
4749

4850
pub(crate) struct EvalState<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
@@ -178,6 +180,56 @@ impl<'mir, 'tcx: 'mir> Hash for Frame<'mir, 'tcx> {
178180
}
179181
}
180182

183+
pub(crate) struct InfiniteLoopDetector<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
184+
/// The set of all `EvalState` *hashes* observed by this detector.
185+
///
186+
/// Not a proper bloom filter.
187+
bloom: FxHashSet<u64>,
188+
189+
/// The set of all `EvalState`s observed by this detector.
190+
///
191+
/// An `EvalState` will only be fully cloned once it has caused a collision
192+
/// in `bloom`. As a result, the detector must observe *two* full cycles of
193+
/// an infinite loop before it triggers.
194+
snapshots: FxHashSet<EvalState<'a, 'mir, 'tcx, M>>,
195+
}
196+
197+
impl<'a, 'mir, 'tcx, M> Default for InfiniteLoopDetector<'a, 'mir, 'tcx, M>
198+
where M: Machine<'mir, 'tcx>,
199+
'tcx: 'a + 'mir,
200+
{
201+
fn default() -> Self {
202+
InfiniteLoopDetector {
203+
bloom: FxHashSet::default(),
204+
snapshots: FxHashSet::default(),
205+
}
206+
}
207+
}
208+
209+
impl<'a, 'mir, 'tcx, M> InfiniteLoopDetector<'a, 'mir, 'tcx, M>
210+
where M: Machine<'mir, 'tcx>,
211+
'tcx: 'a + 'mir,
212+
{
213+
pub fn observe(&mut self, snapshot: &EvalState<'a, 'mir, 'tcx, M>) -> Result<(), (/*TODO*/)> {
214+
let mut fx = FxHasher::default();
215+
snapshot.hash(&mut fx);
216+
let hash = fx.finish();
217+
218+
if self.bloom.insert(hash) {
219+
// No collision
220+
return Ok(())
221+
}
222+
223+
if self.snapshots.insert(snapshot.clone()) {
224+
// Spurious collision or first cycle
225+
return Ok(())
226+
}
227+
228+
// Second cycle,
229+
Err(())
230+
}
231+
}
232+
181233
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
182234
pub enum StackPopCleanup {
183235
/// The stackframe existed to compute the initial value of a static/constant, make sure it
@@ -280,16 +332,17 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
280332
stack: Vec::new(),
281333
},
282334
stack_limit: tcx.sess.const_eval_stack_frame_limit,
283-
terminators_remaining: MAX_TERMINATORS,
335+
loop_detector: Default::default(),
336+
steps_until_detector_enabled: MAX_TERMINATORS,
284337
}
285338
}
286339

287340
pub(crate) fn with_fresh_body<F: FnOnce(&mut Self) -> R, R>(&mut self, f: F) -> R {
288341
let stack = mem::replace(self.stack_mut(), Vec::new());
289-
let terminators_remaining = mem::replace(&mut self.terminators_remaining, MAX_TERMINATORS);
342+
let steps = mem::replace(&mut self.steps_until_detector_enabled, MAX_TERMINATORS);
290343
let r = f(self);
291344
*self.stack_mut() = stack;
292-
self.terminators_remaining = terminators_remaining;
345+
self.steps_until_detector_enabled = steps;
293346
r
294347
}
295348

@@ -634,7 +687,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
634687
}
635688

636689
Aggregate(ref kind, ref operands) => {
637-
self.inc_step_counter_and_check_limit(operands.len());
690+
self.inc_step_counter_and_detect_loops(operands.len());
638691

639692
let (dest, active_field_index) = match **kind {
640693
mir::AggregateKind::Adt(adt_def, variant_index, _, active_field_index) => {

src/librustc_mir/interpret/step.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ use rustc::mir::interpret::EvalResult;
88
use super::{EvalContext, Machine};
99

1010
impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
11-
pub fn inc_step_counter_and_check_limit(&mut self, n: usize) {
12-
self.terminators_remaining = self.terminators_remaining.saturating_sub(n);
13-
if self.terminators_remaining == 0 {
11+
pub fn inc_step_counter_and_detect_loops(&mut self, n: usize) {
12+
self.steps_until_detector_enabled
13+
= self.steps_until_detector_enabled.saturating_sub(n);
14+
15+
if self.steps_until_detector_enabled == 0 {
16+
let _ = self.loop_detector.observe(&self.state); // TODO: Handle error
17+
1418
// FIXME(#49980): make this warning a lint
1519
self.tcx.sess.span_warn(self.frame().span, "Constant evaluating a complex constant, this might take some time");
16-
self.terminators_remaining = 1_000_000;
20+
self.steps_until_detector_enabled = 1_000_000;
1721
}
1822
}
1923

@@ -36,7 +40,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
3640
return Ok(true);
3741
}
3842

39-
self.inc_step_counter_and_check_limit(1);
43+
self.inc_step_counter_and_detect_loops(1);
4044

4145
let terminator = basic_block.terminator();
4246
assert_eq!(old_frames, self.cur_frame());

0 commit comments

Comments
 (0)