RemainderByZero |
DivisionByZero |
GeneratorResumedAfterReturn |
- GeneratorResumedAfterPanic => {}
+ GeneratorResumedAfterPanic |
+ InfiniteLoop => {}
ReferencedConstant(ref err) => err.hash_stable(hcx, hasher),
MachineError(ref err) => err.hash_stable(hcx, hasher),
FunctionPointerTyMismatch(a, b) => {
ReferencedConstant(Lrc<ConstEvalErr<'tcx>>),
GeneratorResumedAfterReturn,
GeneratorResumedAfterPanic,
+ InfiniteLoop,
}
pub type EvalResult<'tcx, T = ()> = Result<T, EvalError<'tcx>>;
RemainderByZero => "attempt to calculate the remainder with a divisor of zero",
GeneratorResumedAfterReturn => "generator resumed after completion",
GeneratorResumedAfterPanic => "generator resumed after panicking",
+ InfiniteLoop =>
+ "duplicate interpreter state observed here, const evaluation will never terminate",
}
}
}
use std::sync::atomic::{AtomicU32, Ordering};
use std::num::NonZeroU32;
-#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
pub enum Lock {
NoLock,
WriteLock(DynamicLifetime),
}
// This is generic so that it can be reused by miri
-#[derive(Clone, RustcEncodable, RustcDecodable)]
+#[derive(Clone, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
pub struct ValidationOperand<'tcx, T> {
pub place: T,
pub ty: Ty<'tcx>,
RemainderByZero => RemainderByZero,
GeneratorResumedAfterReturn => GeneratorResumedAfterReturn,
GeneratorResumedAfterPanic => GeneratorResumedAfterPanic,
+ InfiniteLoop => InfiniteLoop,
})
}
}
Ok((value, ptr, layout.ty))
}
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct CompileTimeEvaluator;
impl<'tcx> Into<EvalError<'tcx>> for ConstEvalError {
use std::fmt::Write;
+use std::hash::{Hash, Hasher};
use std::mem;
use rustc::hir::def_id::DefId;
use rustc::ty::subst::{Subst, Substs};
use rustc::ty::{self, Ty, TyCtxt, TypeAndMut};
use rustc::ty::query::TyCtxtAt;
+use rustc_data_structures::fx::{FxHashSet, FxHasher};
use rustc_data_structures::indexed_vec::{IndexVec, Idx};
use rustc::mir::interpret::{
FrameInfo, GlobalId, Value, Scalar,
/// The maximum number of stack frames allowed
pub(crate) stack_limit: usize,
- /// The maximum number of terminators that may be evaluated.
- /// This prevents infinite loops and huge computations from freezing up const eval.
- /// Remove once halting problem is solved.
- pub(crate) terminators_remaining: usize,
+ /// When this value is negative, it indicates the number of interpreter
+ /// steps *until* the loop detector is enabled. When it is positive, it is
+ /// the number of steps after the detector has been enabled modulo the loop
+ /// detector period.
+ pub(crate) steps_since_detector_enabled: isize,
+
+ pub(crate) loop_detector: InfiniteLoopDetector<'a, 'mir, 'tcx, M>,
}
/// A stack frame.
+#[derive(Clone)]
pub struct Frame<'mir, 'tcx: 'mir> {
////////////////////////////////////////////////////////////////////////////////
// Function and callsite information
pub stmt: usize,
}
+impl<'mir, 'tcx: 'mir> Eq for Frame<'mir, 'tcx> {}
+
+impl<'mir, 'tcx: 'mir> PartialEq for Frame<'mir, 'tcx> {
+ fn eq(&self, other: &Self) -> bool {
+ let Frame {
+ mir: _,
+ instance,
+ span: _,
+ return_to_block,
+ return_place,
+ locals,
+ block,
+ stmt,
+ } = self;
+
+ // Some of these are constant during evaluation, but are included
+ // anyways for correctness.
+ *instance == other.instance
+ && *return_to_block == other.return_to_block
+ && *return_place == other.return_place
+ && *locals == other.locals
+ && *block == other.block
+ && *stmt == other.stmt
+ }
+}
+
+impl<'mir, 'tcx: 'mir> Hash for Frame<'mir, 'tcx> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ let Frame {
+ mir: _,
+ instance,
+ span: _,
+ return_to_block,
+ return_place,
+ locals,
+ block,
+ stmt,
+ } = self;
+
+ instance.hash(state);
+ return_to_block.hash(state);
+ return_place.hash(state);
+ locals.hash(state);
+ block.hash(state);
+ stmt.hash(state);
+ }
+}
+
+/// The virtual machine state during const-evaluation at a given point in time.
+type EvalSnapshot<'a, 'mir, 'tcx, M>
+ = (M, Vec<Frame<'mir, 'tcx>>, Memory<'a, 'mir, 'tcx, M>);
+
+pub(crate) struct InfiniteLoopDetector<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
+ /// The set of all `EvalSnapshot` *hashes* observed by this detector.
+ ///
+ /// When a collision occurs in this table, we store the full snapshot in
+ /// `snapshots`.
+ hashes: FxHashSet<u64>,
+
+ /// The set of all `EvalSnapshot`s observed by this detector.
+ ///
+ /// An `EvalSnapshot` will only be fully cloned once it has caused a
+ /// collision in `hashes`. As a result, the detector must observe at least
+ /// *two* full cycles of an infinite loop before it triggers.
+ snapshots: FxHashSet<EvalSnapshot<'a, 'mir, 'tcx, M>>,
+}
+
+impl<'a, 'mir, 'tcx, M> Default for InfiniteLoopDetector<'a, 'mir, 'tcx, M>
+ where M: Machine<'mir, 'tcx>,
+ 'tcx: 'a + 'mir,
+{
+ fn default() -> Self {
+ InfiniteLoopDetector {
+ hashes: FxHashSet::default(),
+ snapshots: FxHashSet::default(),
+ }
+ }
+}
+
+impl<'a, 'mir, 'tcx, M> InfiniteLoopDetector<'a, 'mir, 'tcx, M>
+ where M: Machine<'mir, 'tcx>,
+ 'tcx: 'a + 'mir,
+{
+ /// Returns `true` if the loop detector has not yet observed a snapshot.
+ pub fn is_empty(&self) -> bool {
+ self.hashes.is_empty()
+ }
+
+ pub fn observe_and_analyze(
+ &mut self,
+ machine: &M,
+ stack: &Vec<Frame<'mir, 'tcx>>,
+ memory: &Memory<'a, 'mir, 'tcx, M>,
+ ) -> EvalResult<'tcx, ()> {
+ let snapshot = (machine, stack, memory);
+
+ let mut fx = FxHasher::default();
+ snapshot.hash(&mut fx);
+ let hash = fx.finish();
+
+ if self.hashes.insert(hash) {
+ // No collision
+ return Ok(())
+ }
+
+ if self.snapshots.insert((machine.clone(), stack.clone(), memory.clone())) {
+ // Spurious collision or first cycle
+ return Ok(())
+ }
+
+ // Second cycle
+ Err(EvalErrorKind::InfiniteLoop.into())
+ }
+}
+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum StackPopCleanup {
/// The stackframe existed to compute the initial value of a static/constant, make sure it
}
}
-const MAX_TERMINATORS: usize = 1_000_000;
+const STEPS_UNTIL_DETECTOR_ENABLED: isize = 1_000_000;
impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
pub fn new(
memory: Memory::new(tcx, memory_data),
stack: Vec::new(),
stack_limit: tcx.sess.const_eval_stack_frame_limit,
- terminators_remaining: MAX_TERMINATORS,
+ loop_detector: Default::default(),
+ steps_since_detector_enabled: -STEPS_UNTIL_DETECTOR_ENABLED,
}
}
pub(crate) fn with_fresh_body<F: FnOnce(&mut Self) -> R, R>(&mut self, f: F) -> R {
let stack = mem::replace(&mut self.stack, Vec::new());
- let terminators_remaining = mem::replace(&mut self.terminators_remaining, MAX_TERMINATORS);
+ let steps = mem::replace(&mut self.steps_since_detector_enabled, -STEPS_UNTIL_DETECTOR_ENABLED);
let r = f(self);
self.stack = stack;
- self.terminators_remaining = terminators_remaining;
+ self.steps_since_detector_enabled = steps;
r
}
}
Aggregate(ref kind, ref operands) => {
- self.inc_step_counter_and_check_limit(operands.len());
-
let (dest, active_field_index) = match **kind {
mir::AggregateKind::Adt(adt_def, variant_index, _, active_field_index) => {
self.write_discriminant_value(dest_ty, dest, variant_index)?;
//! This separation exists to ensure that no fancy miri features like
//! interpreting common C functions leak into CTFE.
+use std::hash::Hash;
+
use rustc::mir::interpret::{AllocId, EvalResult, Scalar, Pointer, AccessKind, GlobalId};
use super::{EvalContext, Place, ValTy, Memory};
/// Methods of this trait signifies a point where CTFE evaluation would fail
/// and some use case dependent behaviour can instead be applied
-pub trait Machine<'mir, 'tcx>: Sized {
+pub trait Machine<'mir, 'tcx>: Clone + Eq + Hash {
/// Additional data that can be accessed via the Memory
- type MemoryData;
+ type MemoryData: Clone + Eq + Hash;
/// Additional memory kinds a machine wishes to distinguish from the builtin ones
type MemoryKinds: ::std::fmt::Debug + PartialEq + Copy + Clone;
use std::collections::VecDeque;
+use std::hash::{Hash, Hasher};
use std::ptr;
use rustc::hir::def_id::DefId;
use rustc::mir::interpret::{Pointer, AllocId, Allocation, AccessKind, Value,
EvalResult, Scalar, EvalErrorKind, GlobalId, AllocType};
pub use rustc::mir::interpret::{write_target_uint, write_target_int, read_target_uint};
-use rustc_data_structures::fx::{FxHashSet, FxHashMap};
+use rustc_data_structures::fx::{FxHashSet, FxHashMap, FxHasher};
use syntax::ast::Mutability;
// Allocations and pointers
////////////////////////////////////////////////////////////////////////////////
-#[derive(Debug, PartialEq, Copy, Clone)]
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum MemoryKind<T> {
/// Error if deallocated except during a stack pop
Stack,
// Top-level interpreter memory
////////////////////////////////////////////////////////////////////////////////
+#[derive(Clone)]
pub struct Memory<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
/// Additional data required by the Machine
pub data: M::MemoryData,
pub tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
}
+impl<'a, 'mir, 'tcx, M> Eq for Memory<'a, 'mir, 'tcx, M>
+ where M: Machine<'mir, 'tcx>,
+ 'tcx: 'a + 'mir,
+{}
+
+impl<'a, 'mir, 'tcx, M> PartialEq for Memory<'a, 'mir, 'tcx, M>
+ where M: Machine<'mir, 'tcx>,
+ 'tcx: 'a + 'mir,
+{
+ fn eq(&self, other: &Self) -> bool {
+ let Memory {
+ data,
+ alloc_kind,
+ alloc_map,
+ cur_frame,
+ tcx: _,
+ } = self;
+
+ *data == other.data
+ && *alloc_kind == other.alloc_kind
+ && *alloc_map == other.alloc_map
+ && *cur_frame == other.cur_frame
+ }
+}
+
+impl<'a, 'mir, 'tcx, M> Hash for Memory<'a, 'mir, 'tcx, M>
+ where M: Machine<'mir, 'tcx>,
+ 'tcx: 'a + 'mir,
+{
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ let Memory {
+ data,
+ alloc_kind: _,
+ alloc_map: _,
+ cur_frame,
+ tcx: _,
+ } = self;
+
+ data.hash(state);
+ cur_frame.hash(state);
+
+ // We ignore some fields which don't change between evaluation steps.
+
+ // Since HashMaps which contain the same items may have different
+ // iteration orders, we use a commutative operation (in this case
+ // addition, but XOR would also work), to combine the hash of each
+ // `Allocation`.
+ self.allocations()
+ .map(|allocs| {
+ let mut h = FxHasher::default();
+ allocs.hash(&mut h);
+ h.finish()
+ })
+ .fold(0u64, |hash, x| hash.wrapping_add(x))
+ .hash(state);
+ }
+}
+
impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
pub fn new(tcx: TyCtxtAt<'a, 'tcx, 'tcx>, data: M::MemoryData) -> Self {
Memory {
for i in 0..size.bytes() {
let defined = undef_mask.get(src.offset + Size::from_bytes(i));
-
+
for j in 0..repeat {
dest_allocation.undef_mask.set(
dest.offset + Size::from_bytes(i + (size.bytes() * j)),
use super::{EvalContext, Machine, ValTy};
use interpret::memory::HasMemory;
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum Place {
/// A place referring to a value allocated in the `Memory` system.
Ptr {
Local { frame: usize, local: mir::Local },
}
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum PlaceExtra {
None,
Length(u64),
use super::{EvalContext, Machine};
impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
- pub fn inc_step_counter_and_check_limit(&mut self, n: usize) {
- self.terminators_remaining = self.terminators_remaining.saturating_sub(n);
- if self.terminators_remaining == 0 {
+ pub fn inc_step_counter_and_detect_loops(&mut self) -> EvalResult<'tcx, ()> {
+ /// The number of steps between loop detector snapshots.
+ /// Should be a power of two for performance reasons.
+ const DETECTOR_SNAPSHOT_PERIOD: isize = 256;
+
+ {
+ let steps = &mut self.steps_since_detector_enabled;
+
+ *steps += 1;
+ if *steps < 0 {
+ return Ok(());
+ }
+
+ *steps %= DETECTOR_SNAPSHOT_PERIOD;
+ if *steps != 0 {
+ return Ok(());
+ }
+ }
+
+ if self.loop_detector.is_empty() {
+ // First run of the loop detector
+
// FIXME(#49980): make this warning a lint
- self.tcx.sess.span_warn(self.frame().span, "Constant evaluating a complex constant, this might take some time");
- self.terminators_remaining = 1_000_000;
+ self.tcx.sess.span_warn(self.frame().span,
+ "Constant evaluating a complex constant, this might take some time");
}
+
+ self.loop_detector.observe_and_analyze(&self.machine, &self.stack, &self.memory)
}
/// Returns true as long as there are more things to do.
return Ok(true);
}
- self.inc_step_counter_and_check_limit(1);
+ self.inc_step_counter_and_detect_loops()?;
let terminator = basic_block.terminator();
assert_eq!(old_frames, self.cur_frame());
--- /dev/null
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(const_let)]
+
+fn main() {
+ // Tests the Collatz conjecture with an incorrect base case (0 instead of 1).
+ // The value of `n` will loop indefinitely (4 - 2 - 1 - 4).
+ let _ = [(); {
+ //~^ WARNING Constant evaluating a complex constant, this might take some time
+ //~| ERROR could not evaluate repeat length
+ let mut n = 113383; // #20 in https://oeis.org/A006884
+ while n != 0 { //~ ERROR constant contains unimplemented expression type
+ n = if n % 2 == 0 { n/2 } else { 3*n + 1 };
+ }
+ n
+ }];
+}
--- /dev/null
+error[E0019]: constant contains unimplemented expression type
+ --> $DIR/infinite_loop.rs:20:9
+ |
+LL | / while n != 0 { //~ ERROR constant contains unimplemented expression type
+LL | | n = if n % 2 == 0 { n/2 } else { 3*n + 1 };
+LL | | }
+ | |_________^
+
+warning: Constant evaluating a complex constant, this might take some time
+ --> $DIR/infinite_loop.rs:16:18
+ |
+LL | let _ = [(); {
+ | __________________^
+LL | | //~^ WARNING Constant evaluating a complex constant, this might take some time
+LL | | //~| ERROR could not evaluate repeat length
+LL | | let mut n = 113383; // #20 in https://oeis.org/A006884
+... |
+LL | | n
+LL | | }];
+ | |_____^
+
+error[E0080]: could not evaluate repeat length
+ --> $DIR/infinite_loop.rs:16:18
+ |
+LL | let _ = [(); {
+ | __________________^
+LL | | //~^ WARNING Constant evaluating a complex constant, this might take some time
+LL | | //~| ERROR could not evaluate repeat length
+LL | | let mut n = 113383; // #20 in https://oeis.org/A006884
+LL | | while n != 0 { //~ ERROR constant contains unimplemented expression type
+LL | | n = if n % 2 == 0 { n/2 } else { 3*n + 1 };
+ | | ---------- duplicate interpreter state observed here, const evaluation will never terminate
+LL | | }
+LL | | n
+LL | | }];
+ | |_____^
+
+error: aborting due to 2 previous errors
+
+Some errors occurred: E0019, E0080.
+For more information about an error, try `rustc --explain E0019`.