diff --git a/src/librustc/ich/hcx.rs b/src/librustc/ich/hcx.rs index 371f631737c9871001536c5f4e964f2f0a012736..1acc58948f413b71ecf72bf47a71884bb8b3a48e 100644 --- a/src/librustc/ich/hcx.rs +++ b/src/librustc/ich/hcx.rs @@ -15,7 +15,6 @@ use ich::{self, CachingSourceMapView, Fingerprint}; use middle::cstore::CrateStore; use ty::{TyCtxt, fast_reject}; -use mir::interpret::AllocId; use session::Session; use std::cmp::Ord; @@ -60,8 +59,6 @@ pub struct StableHashingContext<'a> { // CachingSourceMapView, so we initialize it lazily. raw_source_map: &'a SourceMap, caching_source_map: Option>, - - pub(super) alloc_id_recursion_tracker: FxHashSet, } #[derive(PartialEq, Eq, Clone, Copy)] @@ -105,7 +102,6 @@ pub fn new(sess: &'a Session, hash_spans: hash_spans_initial, hash_bodies: true, node_id_hashing_mode: NodeIdHashingMode::HashDefPath, - alloc_id_recursion_tracker: Default::default(), } } diff --git a/src/librustc/ich/impls_ty.rs b/src/librustc/ich/impls_ty.rs index bb8498cb94d67d7177814bc4b055a941047f1548..68320dfddefbc04934d7a24581d4eb2e4b419f80 100644 --- a/src/librustc/ich/impls_ty.rs +++ b/src/librustc/ich/impls_ty.rs @@ -412,7 +412,7 @@ fn hash_stable( ty::tls::with_opt(|tcx| { trace!("hashing {:?}", *self); let tcx = tcx.expect("can't hash AllocIds during hir lowering"); - let alloc_kind = tcx.alloc_map.lock().get(*self).expect("no value for AllocId"); + let alloc_kind = tcx.alloc_map.lock().get(*self); alloc_kind.hash_stable(hcx, hasher); }); } diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index d40dbae09d2cb60f8b27420e38b9453aeb39ec21..ccc5bba1ad61170e592665f3857073852191861e 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -133,9 +133,14 @@ fn wrapping_signed_offset(self, val: u64, i: i64) -> u64 { impl PointerArithmetic for T {} +/// Pointer is generic over the type that represents a reference to Allocations, +/// thus making it possible for the most convenient representation to be used in +/// each context. +/// +/// Defaults to the index based and loosely coupled AllocId. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] -pub struct Pointer { - pub alloc_id: AllocId, +pub struct Pointer { + pub alloc_id: Id, pub offset: Size, } @@ -543,16 +548,16 @@ pub fn undef(size: Size, align: Align) -> Self { impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {} #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, RustcEncodable, RustcDecodable)] -pub struct Relocations(SortedMap); +pub struct Relocations(SortedMap); -impl Relocations { - pub fn new() -> Relocations { +impl Relocations { + pub fn new() -> Self { Relocations(SortedMap::new()) } // The caller must guarantee that the given relocations are already sorted // by address and contain no duplicates. - pub fn from_presorted(r: Vec<(Size, AllocId)>) -> Relocations { + pub fn from_presorted(r: Vec<(Size, Id)>) -> Self { Relocations(SortedMap::from_presorted_elements(r)) } } diff --git a/src/librustc/mir/interpret/value.rs b/src/librustc/mir/interpret/value.rs index 11a4f8b884e7f1b46d3fa910e801f3e807563053..9982da483ce09968cd18a3b601312946d93125ec 100644 --- a/src/librustc/mir/interpret/value.rs +++ b/src/librustc/mir/interpret/value.rs @@ -326,7 +326,7 @@ fn from(ptr: Pointer) -> Self { /// size. Like a range of bytes in an `Allocation`, a `Scalar` can either represent the raw bytes /// of a simple value or a pointer into another `Allocation` #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] -pub enum Scalar { +pub enum Scalar { /// The raw bytes of a simple value. Bits { /// The first `size` bytes are the value. @@ -338,12 +338,12 @@ pub enum Scalar { /// A pointer into an `Allocation`. An `Allocation` in the `memory` module has a list of /// relocations, but a `Scalar` is only large enough to contain one, so we just represent the /// relocation and its associated offset together as a `Pointer` here. - Ptr(Pointer), + Ptr(Pointer), } #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] -pub enum ScalarMaybeUndef { - Scalar(Scalar), +pub enum ScalarMaybeUndef { + Scalar(Scalar), Undef, } diff --git a/src/librustc_data_structures/stable_hasher.rs b/src/librustc_data_structures/stable_hasher.rs index 1024e69cc2b0e889b67d74b35ad75fbfba58240e..c70a0abe8c7e437e5ed2f92aac748d6dca74b1b3 100644 --- a/src/librustc_data_structures/stable_hasher.rs +++ b/src/librustc_data_structures/stable_hasher.rs @@ -281,6 +281,23 @@ fn hash_stable(&self, } } +impl HashStable for (T1, T2, T3, T4) + where T1: HashStable, + T2: HashStable, + T3: HashStable, + T4: HashStable, +{ + fn hash_stable(&self, + ctx: &mut CTX, + hasher: &mut StableHasher) { + let (ref _0, ref _1, ref _2, ref _3) = *self; + _0.hash_stable(ctx, hasher); + _1.hash_stable(ctx, hasher); + _2.hash_stable(ctx, hasher); + _3.hash_stable(ctx, hasher); + } +} + impl, CTX> HashStable for [T] { default fn hash_stable(&self, ctx: &mut CTX, diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index 70addf2c907e65ebeabff542e3a6c7b1735b013c..92ddd8777f733eb0398566b1c45fe9f1fccd6867 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -196,6 +196,8 @@ fn into(self) -> EvalError<'tcx> { } } +impl_stable_hash_for!(struct CompileTimeEvaluator {}); + #[derive(Clone, Debug)] enum ConstEvalError { NeedsRfc(String), diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs index 6e144ba7ed2ee7627e74d0b9fd87f5518127f916..f7277f8d27610b3fb5ac780e62caf498210da9d1 100644 --- a/src/librustc_mir/interpret/eval_context.rs +++ b/src/librustc_mir/interpret/eval_context.rs @@ -9,12 +9,12 @@ // except according to those terms. use std::fmt::Write; -use std::hash::{Hash, Hasher}; use std::mem; use rustc::hir::def_id::DefId; use rustc::hir::def::Def; use rustc::hir::map::definitions::DefPathData; +use rustc::ich::StableHashingContext; use rustc::mir; use rustc::ty::layout::{ self, Size, Align, HasDataLayout, LayoutOf, TyLayout @@ -22,10 +22,10 @@ use rustc::ty::subst::{Subst, Substs}; use rustc::ty::{self, Ty, TyCtxt, TypeFoldable}; use rustc::ty::query::TyCtxtAt; -use rustc_data_structures::fx::{FxHashSet, FxHasher}; use rustc_data_structures::indexed_vec::IndexVec; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableHasherResult}; use rustc::mir::interpret::{ - GlobalId, Scalar, FrameInfo, + GlobalId, Scalar, FrameInfo, AllocId, EvalResult, EvalErrorKind, ScalarMaybeUndef, truncate, sign_extend, @@ -38,6 +38,8 @@ Memory, Machine }; +use super::snapshot::InfiniteLoopDetector; + pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> { /// Stores the `Machine` instance. pub machine: M, @@ -95,7 +97,7 @@ pub struct Frame<'mir, 'tcx: 'mir> { /// The locals are stored as `Option`s. /// `None` represents a local that is currently dead, while a live local /// can either directly contain `Scalar` or refer to some part of an `Allocation`. - pub locals: IndexVec, + pub locals: IndexVec>, //////////////////////////////////////////////////////////////////////////////// // Current position within the function @@ -108,38 +110,16 @@ pub struct Frame<'mir, 'tcx: 'mir> { 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<'a, 'mir, 'tcx: 'mir> HashStable> for Frame<'mir, 'tcx> { + fn hash_stable( + &self, + hcx: &mut StableHashingContext<'a>, + hasher: &mut StableHasher) { -impl<'mir, 'tcx: 'mir> Hash for Frame<'mir, 'tcx> { - fn hash(&self, state: &mut H) { let Frame { - mir: _, + mir, instance, - span: _, + span, return_to_block, return_place, locals, @@ -147,12 +127,8 @@ fn hash(&self, state: &mut H) { stmt, } = self; - instance.hash(state); - return_to_block.hash(state); - return_place.hash(state); - locals.hash(state); - block.hash(state); - stmt.hash(state); + (mir, instance, span, return_to_block).hash_stable(hcx, hasher); + (return_place, locals, block, stmt).hash_stable(hcx, hasher); } } @@ -168,15 +144,27 @@ pub enum StackPopCleanup { None { cleanup: bool }, } +impl<'a> HashStable> for StackPopCleanup { + fn hash_stable( + &self, + hcx: &mut StableHashingContext<'a>, + hasher: &mut StableHasher) { + match self { + StackPopCleanup::Goto(ref block) => block.hash_stable(hcx, hasher), + StackPopCleanup::None { cleanup } => cleanup.hash_stable(hcx, hasher), + } + } +} + // State of a local variable #[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub enum LocalValue { +pub enum LocalValue { Dead, // Mostly for convenience, we re-use the `Operand` type here. // This is an optimization over just always having a pointer here; // we can thus avoid doing an allocation when the local just stores // immediate values *and* never has its address taken. - Live(Operand), + Live(Operand), } impl<'tcx> LocalValue { @@ -195,72 +183,10 @@ pub fn access_mut(&mut self) -> EvalResult<'tcx, &mut Operand> { } } -/// The virtual machine state during const-evaluation at a given point in time. -type EvalSnapshot<'a, 'mir, 'tcx, M> - = (M, Vec>, Memory<'a, 'mir, 'tcx, M>); - -pub(super) 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, - - /// 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>, -} - -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>, - 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()) - } -} +impl_stable_hash_for!(enum self::LocalValue { + Dead, + Live(x), +}); impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for &'a EvalContext<'a, 'mir, 'tcx, M> { #[inline] diff --git a/src/librustc_mir/interpret/machine.rs b/src/librustc_mir/interpret/machine.rs index 57af63d63d9c47d074d9572b4c642b588c97345d..61963f6d3d354560828405c7cbdd9f4fa758b49f 100644 --- a/src/librustc_mir/interpret/machine.rs +++ b/src/librustc_mir/interpret/machine.rs @@ -15,17 +15,19 @@ use std::hash::Hash; use rustc::hir::def_id::DefId; +use rustc::ich::StableHashingContext; use rustc::mir::interpret::{Allocation, EvalResult, Scalar}; use rustc::mir; use rustc::ty::{self, layout::TyLayout, query::TyCtxtAt}; +use rustc_data_structures::stable_hasher::HashStable; use super::{EvalContext, PlaceTy, OpTy}; /// 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>: Clone + Eq + Hash { +pub trait Machine<'mir, 'tcx>: Clone + Eq + Hash + for<'a> HashStable> { /// Additional data that can be accessed via the Memory - type MemoryData: Clone + Eq + Hash; + type MemoryData: Clone + Eq + Hash + for<'a> HashStable>; /// Additional memory kinds a machine wishes to distinguish from the builtin ones type MemoryKinds: ::std::fmt::Debug + Copy + Clone + Eq + Hash; diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs index 59bebbb87a7755289ffd46041ffdce433251a61d..9e61de92936bdb7cf2fdb84670e9b69b3c15d0f7 100644 --- a/src/librustc_mir/interpret/memory.rs +++ b/src/librustc_mir/interpret/memory.rs @@ -17,7 +17,6 @@ //! short-circuiting the empty case! use std::collections::VecDeque; -use std::hash::{Hash, Hasher}; use std::ptr; use rustc::ty::{self, Instance, query::TyCtxtAt}; @@ -26,7 +25,7 @@ EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic, truncate}; pub use rustc::mir::interpret::{write_target_uint, read_target_uint}; -use rustc_data_structures::fx::{FxHashSet, FxHashMap, FxHasher}; +use rustc_data_structures::fx::{FxHashSet, FxHashMap}; use syntax::ast::Mutability; @@ -70,58 +69,6 @@ fn data_layout(&self) -> &TargetDataLayout { } } -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_map, - tcx: _, - } = self; - - *data == other.data - && *alloc_map == other.alloc_map - } -} - -impl<'a, 'mir, 'tcx, M> Hash for Memory<'a, 'mir, 'tcx, M> - where M: Machine<'mir, 'tcx>, - 'tcx: 'a + 'mir, -{ - fn hash(&self, state: &mut H) { - let Memory { - data, - alloc_map: _, - tcx: _, - } = self; - - data.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.alloc_map.iter() - .map(|(&id, alloc)| { - let mut h = FxHasher::default(); - id.hash(&mut h); - alloc.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 { diff --git a/src/librustc_mir/interpret/mod.rs b/src/librustc_mir/interpret/mod.rs index 462c4b8889dd1977dc06c8154a388ac92fe56c6e..1e8de02923240652af9a56a30e1b601b82a9f1c3 100644 --- a/src/librustc_mir/interpret/mod.rs +++ b/src/librustc_mir/interpret/mod.rs @@ -17,6 +17,7 @@ mod machine; mod memory; mod operator; +mod snapshot; mod step; mod terminator; mod traits; diff --git a/src/librustc_mir/interpret/operand.rs b/src/librustc_mir/interpret/operand.rs index 6f9e0cf3e37b60f8d4e0ac636121d23c5c554ed2..4093a6304b360d19e2d849a26c5da07db1a0165a 100644 --- a/src/librustc_mir/interpret/operand.rs +++ b/src/librustc_mir/interpret/operand.rs @@ -17,9 +17,10 @@ use rustc::{mir, ty}; use rustc::ty::layout::{self, Size, LayoutOf, TyLayout, HasDataLayout, IntegerExt}; use rustc_data_structures::indexed_vec::Idx; - use rustc::mir::interpret::{ - GlobalId, ConstValue, Scalar, EvalResult, Pointer, ScalarMaybeUndef, EvalErrorKind + GlobalId, AllocId, + ConstValue, Pointer, Scalar, ScalarMaybeUndef, + EvalResult, EvalErrorKind }; use super::{EvalContext, Machine, MemPlace, MPlaceTy, MemoryKind}; @@ -31,9 +32,9 @@ /// In particular, thanks to `ScalarPair`, arithmetic operations and casts can be entirely /// defined on `Value`, and do not have to work with a `Place`. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -pub enum Value { - Scalar(ScalarMaybeUndef), - ScalarPair(ScalarMaybeUndef, ScalarMaybeUndef), +pub enum Value { + Scalar(ScalarMaybeUndef), + ScalarPair(ScalarMaybeUndef, ScalarMaybeUndef), } impl<'tcx> Value { @@ -81,6 +82,11 @@ pub fn to_scalar_ptr(self) -> EvalResult<'tcx, Scalar> { } } +impl_stable_hash_for!(enum ::interpret::Value { + Scalar(x), + ScalarPair(x, y), +}); + // ScalarPair needs a type to interpret, so we often have a value and a type together // as input for binary and cast operations. #[derive(Copy, Clone, Debug)] @@ -101,9 +107,9 @@ fn deref(&self) -> &Value { /// or still in memory. The latter is an optimization, to delay reading that chunk of /// memory and to avoid having to store arbitrary-sized data here. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -pub enum Operand { - Immediate(Value), - Indirect(MemPlace), +pub enum Operand { + Immediate(Value), + Indirect(MemPlace), } impl Operand { @@ -126,6 +132,11 @@ pub fn to_immediate(self) -> Value { } } +impl_stable_hash_for!(enum ::interpret::Operand { + Immediate(x), + Indirect(x), +}); + #[derive(Copy, Clone, Debug)] pub struct OpTy<'tcx> { crate op: Operand, // ideally we'd make this private, but const_prop needs this diff --git a/src/librustc_mir/interpret/place.rs b/src/librustc_mir/interpret/place.rs index 6ee768fde5fa376a36f8b191b46b7328a6e0ee79..51a4294452719656c0e14d0a56b48c7b56184ea7 100644 --- a/src/librustc_mir/interpret/place.rs +++ b/src/librustc_mir/interpret/place.rs @@ -14,33 +14,41 @@ use std::convert::TryFrom; +use rustc::ich::StableHashingContext; use rustc::mir; use rustc::ty::{self, Ty}; use rustc::ty::layout::{self, Size, Align, LayoutOf, TyLayout, HasDataLayout}; use rustc_data_structures::indexed_vec::Idx; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableHasherResult}; use rustc::mir::interpret::{ - GlobalId, Scalar, EvalResult, Pointer, ScalarMaybeUndef, PointerArithmetic + GlobalId, AllocId, Scalar, EvalResult, Pointer, ScalarMaybeUndef, PointerArithmetic }; use super::{EvalContext, Machine, Value, ValTy, Operand, OpTy, MemoryKind}; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -pub struct MemPlace { +pub struct MemPlace { /// A place may have an integral pointer for ZSTs, and since it might /// be turned back into a reference before ever being dereferenced. /// However, it may never be undef. - pub ptr: Scalar, + pub ptr: Scalar, pub align: Align, /// Metadata for unsized places. Interpretation is up to the type. /// Must not be present for sized types, but can be missing for unsized types /// (e.g. `extern type`). - pub extra: Option, + pub extra: Option>, } +impl_stable_hash_for!(struct ::interpret::MemPlace { + ptr, + align, + extra, +}); + #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -pub enum Place { +pub enum Place { /// A place referring to a value allocated in the `Memory` system. - Ptr(MemPlace), + Ptr(MemPlace), /// To support alloc-free locals, we are able to write directly to a local. /// (Without that optimization, we'd just always be a `MemPlace`.) @@ -50,6 +58,21 @@ pub enum Place { }, } +impl<'a> HashStable> for Place { + fn hash_stable( + &self, hcx: &mut StableHashingContext<'a>, + hasher: &mut StableHasher) { + + match self { + Place::Ptr(mem_place) => mem_place.hash_stable(hcx, hasher), + + Place::Local { frame, local } => { + frame.hash_stable(hcx, hasher); + local.hash_stable(hcx, hasher); + }, + } + } +} #[derive(Copy, Clone, Debug)] pub struct PlaceTy<'tcx> { place: Place, diff --git a/src/librustc_mir/interpret/snapshot.rs b/src/librustc_mir/interpret/snapshot.rs new file mode 100644 index 0000000000000000000000000000000000000000..8aa053baae9f017afd022673b04bf5c5fe92d0c2 --- /dev/null +++ b/src/librustc_mir/interpret/snapshot.rs @@ -0,0 +1,411 @@ +//! This module contains the machinery necessary to detect infinite loops +//! during const-evaluation by taking snapshots of the state of the interpreter +//! at regular intervals. + +use std::hash::{Hash, Hasher}; + +use rustc::ich::{StableHashingContext, StableHashingContextProvider}; +use rustc::mir; +use rustc::mir::interpret::{ + AllocId, Pointer, Scalar, ScalarMaybeUndef, + Relocations, Allocation, UndefMask, + EvalResult, EvalErrorKind, +}; + +use rustc::ty::{self, TyCtxt}; +use rustc::ty::layout::Align; +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::indexed_vec::IndexVec; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableHasherResult}; +use syntax::ast::Mutability; +use syntax::source_map::Span; + +use super::eval_context::{LocalValue, StackPopCleanup}; +use super::{Frame, Memory, Machine, Operand, MemPlace, Place, Value}; + +pub(super) 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, + + /// 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>, +} + +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, + tcx: &TyCtxt<'b, 'tcx, 'tcx>, + machine: &M, + memory: &Memory<'a, 'mir, 'tcx, M>, + stack: &[Frame<'mir, 'tcx>], + ) -> EvalResult<'tcx, ()> { + + let mut hcx = tcx.get_stable_hashing_context(); + let mut hasher = StableHasher::::new(); + (machine, stack).hash_stable(&mut hcx, &mut hasher); + let hash = hasher.finish(); + + if self.hashes.insert(hash) { + // No collision + return Ok(()) + } + + info!("snapshotting the state of the interpreter"); + + if self.snapshots.insert(EvalSnapshot::new(machine, memory, stack)) { + // Spurious collision or first cycle + return Ok(()) + } + + // Second cycle + Err(EvalErrorKind::InfiniteLoop.into()) + } +} + +trait SnapshotContext<'a> { + fn resolve(&'a self, id: &AllocId) -> Option<&'a Allocation>; +} + +/// Taking a snapshot of the evaluation context produces a view of +/// the state of the interpreter that is invariant to `AllocId`s. +trait Snapshot<'a, Ctx: SnapshotContext<'a>> { + type Item; + fn snapshot(&self, ctx: &'a Ctx) -> Self::Item; +} + +macro_rules! __impl_snapshot_field { + ($field:ident, $ctx:expr) => ($field.snapshot($ctx)); + ($field:ident, $ctx:expr, $delegate:expr) => ($delegate); +} + +macro_rules! impl_snapshot_for { + // FIXME(mark-i-m): Some of these should be `?` rather than `*`. + (enum $enum_name:ident { + $( $variant:ident $( ( $($field:ident $(-> $delegate:expr)*),* ) )* ),* $(,)* + }) => { + + impl<'a, Ctx> self::Snapshot<'a, Ctx> for $enum_name + where Ctx: self::SnapshotContext<'a>, + { + type Item = $enum_name>; + + #[inline] + fn snapshot(&self, __ctx: &'a Ctx) -> Self::Item { + match *self { + $( + $enum_name::$variant $( ( $(ref $field),* ) )* => + $enum_name::$variant $( + ( $( __impl_snapshot_field!($field, __ctx $(, $delegate)*) ),* ), + )* + )* + } + } + } + }; + + // FIXME(mark-i-m): same here. + (struct $struct_name:ident { $($field:ident $(-> $delegate:expr)*),* $(,)* }) => { + impl<'a, Ctx> self::Snapshot<'a, Ctx> for $struct_name + where Ctx: self::SnapshotContext<'a>, + { + type Item = $struct_name>; + + #[inline] + fn snapshot(&self, __ctx: &'a Ctx) -> Self::Item { + let $struct_name { + $(ref $field),* + } = *self; + + $struct_name { + $( $field: __impl_snapshot_field!($field, __ctx $(, $delegate)*) ),* + } + } + } + }; +} + +impl<'a, Ctx, T> Snapshot<'a, Ctx> for Option + where Ctx: SnapshotContext<'a>, + T: Snapshot<'a, Ctx> +{ + type Item = Option<>::Item>; + + fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { + match self { + Some(x) => Some(x.snapshot(ctx)), + None => None, + } + } +} + +#[derive(Eq, PartialEq)] +struct AllocIdSnapshot<'a>(Option>); + +impl<'a, Ctx> Snapshot<'a, Ctx> for AllocId + where Ctx: SnapshotContext<'a>, +{ + type Item = AllocIdSnapshot<'a>; + + fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { + AllocIdSnapshot(ctx.resolve(self).map(|alloc| alloc.snapshot(ctx))) + } +} + +impl_snapshot_for!(struct Pointer { + alloc_id, + offset -> *offset, +}); + +impl<'a, Ctx> Snapshot<'a, Ctx> for Scalar + where Ctx: SnapshotContext<'a>, +{ + type Item = Scalar>; + + fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { + match self { + Scalar::Ptr(p) => Scalar::Ptr(p.snapshot(ctx)), + Scalar::Bits{ size, bits } => Scalar::Bits { + size: *size, + bits: *bits, + }, + } + } +} + +impl_snapshot_for!(enum ScalarMaybeUndef { + Scalar(s), + Undef, +}); + +impl_snapshot_for!(struct MemPlace { + ptr, + extra, + align -> *align, +}); + +impl<'a, Ctx> Snapshot<'a, Ctx> for Place + where Ctx: SnapshotContext<'a>, +{ + type Item = Place>; + + fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { + match self { + Place::Ptr(p) => Place::Ptr(p.snapshot(ctx)), + + Place::Local{ frame, local } => Place::Local{ + frame: *frame, + local: *local, + }, + } + } +} + +impl_snapshot_for!(enum Value { + Scalar(s), + ScalarPair(s, t), +}); + +impl_snapshot_for!(enum Operand { + Immediate(v), + Indirect(m), +}); + +impl_snapshot_for!(enum LocalValue { + Live(v), + Dead, +}); + +impl<'a, Ctx> Snapshot<'a, Ctx> for Relocations + where Ctx: SnapshotContext<'a>, +{ + type Item = Relocations>; + + fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { + Relocations::from_presorted(self.iter() + .map(|(size, id)| (*size, id.snapshot(ctx))) + .collect()) + } +} + +#[derive(Eq, PartialEq)] +struct AllocationSnapshot<'a> { + bytes: &'a [u8], + relocations: Relocations>, + undef_mask: &'a UndefMask, + align: &'a Align, + mutability: &'a Mutability, +} + +impl<'a, Ctx> Snapshot<'a, Ctx> for &'a Allocation + where Ctx: SnapshotContext<'a>, +{ + type Item = AllocationSnapshot<'a>; + + fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { + let Allocation { bytes, relocations, undef_mask, align, mutability } = self; + + AllocationSnapshot { + bytes, + undef_mask, + align, + mutability, + relocations: relocations.snapshot(ctx), + } + } +} + +#[derive(Eq, PartialEq)] +struct FrameSnapshot<'a, 'tcx: 'a> { + instance: &'a ty::Instance<'tcx>, + span: &'a Span, + return_to_block: &'a StackPopCleanup, + return_place: Place>, + locals: IndexVec>>, + block: &'a mir::BasicBlock, + stmt: usize, +} + +impl<'a, 'mir, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a Frame<'mir, 'tcx> + where Ctx: SnapshotContext<'a>, +{ + type Item = FrameSnapshot<'a, 'tcx>; + + fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { + let Frame { + mir: _, + instance, + span, + return_to_block, + return_place, + locals, + block, + stmt, + } = self; + + FrameSnapshot { + instance, + span, + return_to_block, + block, + stmt: *stmt, + return_place: return_place.snapshot(ctx), + locals: locals.iter().map(|local| local.snapshot(ctx)).collect(), + } + } +} + +#[derive(Eq, PartialEq)] +struct MemorySnapshot<'a, 'mir: 'a, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx> + 'a> { + data: &'a M::MemoryData, +} + +impl<'a, 'mir, 'tcx, M> Memory<'a, 'mir, 'tcx, M> + where M: Machine<'mir, 'tcx>, +{ + fn snapshot<'b: 'a>(&'b self) -> MemorySnapshot<'b, 'mir, 'tcx, M> { + let Memory { data, .. } = self; + MemorySnapshot { data } + } +} + +impl<'a, 'b, 'mir, 'tcx, M> SnapshotContext<'b> for Memory<'a, 'mir, 'tcx, M> + where M: Machine<'mir, 'tcx>, +{ + fn resolve(&'b self, id: &AllocId) -> Option<&'b Allocation> { + self.get(*id).ok() + } +} + +/// The virtual machine state during const-evaluation at a given point in time. +struct EvalSnapshot<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> { + machine: M, + memory: Memory<'a, 'mir, 'tcx, M>, + stack: Vec>, +} + +impl<'a, 'mir, 'tcx, M> EvalSnapshot<'a, 'mir, 'tcx, M> + where M: Machine<'mir, 'tcx>, +{ + fn new( + machine: &M, + memory: &Memory<'a, 'mir, 'tcx, M>, + stack: &[Frame<'mir, 'tcx>]) -> Self { + + EvalSnapshot { + machine: machine.clone(), + memory: memory.clone(), + stack: stack.into(), + } + } + + fn snapshot<'b: 'a>(&'b self) + -> (&'b M, MemorySnapshot<'b, 'mir, 'tcx, M>, Vec>) { + let EvalSnapshot{ machine, memory, stack } = self; + (&machine, memory.snapshot(), stack.iter().map(|frame| frame.snapshot(memory)).collect()) + } +} + +impl<'a, 'mir, 'tcx, M> Hash for EvalSnapshot<'a, 'mir, 'tcx, M> + where M: Machine<'mir, 'tcx>, +{ + fn hash(&self, state: &mut H) { + // Implement in terms of hash stable, so that k1 == k2 -> hash(k1) == hash(k2) + let mut hcx = self.memory.tcx.get_stable_hashing_context(); + let mut hasher = StableHasher::::new(); + self.hash_stable(&mut hcx, &mut hasher); + hasher.finish().hash(state) + } +} + +impl<'a, 'b, 'mir, 'tcx, M> HashStable> + for EvalSnapshot<'a, 'mir, 'tcx, M> + where M: Machine<'mir, 'tcx>, +{ + fn hash_stable( + &self, + hcx: &mut StableHashingContext<'b>, + hasher: &mut StableHasher) { + + let EvalSnapshot{ machine, memory, stack } = self; + (machine, &memory.data, stack).hash_stable(hcx, hasher); + } +} + +impl<'a, 'mir, 'tcx, M> Eq for EvalSnapshot<'a, 'mir, 'tcx, M> + where M: Machine<'mir, 'tcx>, +{} + +impl<'a, 'mir, 'tcx, M> PartialEq for EvalSnapshot<'a, 'mir, 'tcx, M> + where M: Machine<'mir, 'tcx>, +{ + fn eq(&self, other: &Self) -> bool { + self.snapshot() == other.snapshot() + } +} diff --git a/src/librustc_mir/interpret/step.rs b/src/librustc_mir/interpret/step.rs index 114ef093ec2fddd90327a2cfcbfe9326b36ad91c..545333e8791760fd29306e59154c4e51b972d271 100644 --- a/src/librustc_mir/interpret/step.rs +++ b/src/librustc_mir/interpret/step.rs @@ -73,7 +73,12 @@ pub fn inc_step_counter_and_detect_loops(&mut self) -> EvalResult<'tcx, ()> { "Constant evaluating a complex constant, this might take some time"); } - self.loop_detector.observe_and_analyze(&self.machine, &self.stack, &self.memory) + self.loop_detector.observe_and_analyze( + &self.tcx, + &self.machine, + &self.memory, + &self.stack[..], + ) } pub fn run(&mut self) -> EvalResult<'tcx> { diff --git a/src/test/ui/consts/const-eval/issue-52475.rs b/src/test/ui/consts/const-eval/issue-52475.rs new file mode 100644 index 0000000000000000000000000000000000000000..1c1e6535e69dcfe61fda296bc129b0758b6f86f4 --- /dev/null +++ b/src/test/ui/consts/const-eval/issue-52475.rs @@ -0,0 +1,25 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(const_let)] + +fn main() { + let _ = [(); { + //~^ WARNING Constant evaluating a complex constant, this might take some time + //~| ERROR could not evaluate repeat length + let mut x = &0; + let mut n = 0; + while n < 5 { //~ ERROR constant contains unimplemented expression type + n = (n + 1) % 5; + x = &0; // Materialize a new AllocId + } + 0 + }]; +} diff --git a/src/test/ui/consts/const-eval/issue-52475.stderr b/src/test/ui/consts/const-eval/issue-52475.stderr new file mode 100644 index 0000000000000000000000000000000000000000..f45587f3f7580140a64083a797d45d2544bec1d9 --- /dev/null +++ b/src/test/ui/consts/const-eval/issue-52475.stderr @@ -0,0 +1,42 @@ +error[E0019]: constant contains unimplemented expression type + --> $DIR/issue-52475.rs:19:9 + | +LL | / while n < 5 { //~ ERROR constant contains unimplemented expression type +LL | | n = (n + 1) % 5; +LL | | x = &0; // Materialize a new AllocId +LL | | } + | |_________^ + +warning: Constant evaluating a complex constant, this might take some time + --> $DIR/issue-52475.rs:14: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 x = &0; +... | +LL | | 0 +LL | | }]; + | |_____^ + +error[E0080]: could not evaluate repeat length + --> $DIR/issue-52475.rs:14: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 x = &0; +... | +LL | | n = (n + 1) % 5; + | | ----------- duplicate interpreter state observed here, const evaluation will never terminate +... | +LL | | 0 +LL | | }]; + | |_____^ + +error: aborting due to 2 previous errors + +Some errors occurred: E0019, E0080. +For more information about an error, try `rustc --explain E0019`.