453 lines
13 KiB
Rust
453 lines
13 KiB
Rust
//! 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.
|
|
|
|
// This lives in `interpret` because it needs access to all sots of private state. However,
|
|
// it is not used by the general miri engine, just by CTFE.
|
|
|
|
use std::hash::{Hash, Hasher};
|
|
|
|
use rustc::ich::StableHashingContextProvider;
|
|
use rustc::mir;
|
|
use rustc::mir::interpret::{
|
|
AllocId, Pointer, Scalar,
|
|
Relocations, Allocation, UndefMask, InterpResult,
|
|
};
|
|
|
|
use rustc::ty::{self, TyCtxt};
|
|
use rustc::ty::layout::{Align, Size};
|
|
use rustc_data_structures::fx::FxHashSet;
|
|
use rustc_index::vec::IndexVec;
|
|
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
|
|
use syntax::ast::Mutability;
|
|
use syntax::source_map::Span;
|
|
|
|
use super::eval_context::{LocalState, StackPopCleanup};
|
|
use super::{Frame, Memory, Operand, MemPlace, Place, Immediate, ScalarMaybeUndef, LocalValue};
|
|
use crate::const_eval::CompileTimeInterpreter;
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct InfiniteLoopDetector<'mir, 'tcx> {
|
|
/// The set of all `InterpSnapshot` *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 `InterpSnapshot`s observed by this detector.
|
|
///
|
|
/// An `InterpSnapshot` 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<InterpSnapshot<'mir, 'tcx>>,
|
|
}
|
|
|
|
impl<'mir, 'tcx> InfiniteLoopDetector<'mir, 'tcx> {
|
|
pub fn observe_and_analyze(
|
|
&mut self,
|
|
tcx: TyCtxt<'tcx>,
|
|
span: Span,
|
|
memory: &Memory<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
|
|
stack: &[Frame<'mir, 'tcx>],
|
|
) -> InterpResult<'tcx, ()> {
|
|
// Compute stack's hash before copying anything
|
|
let mut hcx = tcx.get_stable_hashing_context();
|
|
let mut hasher = StableHasher::new();
|
|
stack.hash_stable(&mut hcx, &mut hasher);
|
|
let hash = hasher.finish::<u64>();
|
|
|
|
// Check if we know that hash already
|
|
if self.hashes.is_empty() {
|
|
// FIXME(#49980): make this warning a lint
|
|
tcx.sess.span_warn(span,
|
|
"Constant evaluating a complex constant, this might take some time");
|
|
}
|
|
if self.hashes.insert(hash) {
|
|
// No collision
|
|
return Ok(())
|
|
}
|
|
|
|
// We need to make a full copy. NOW things that to get really expensive.
|
|
info!("snapshotting the state of the interpreter");
|
|
|
|
if self.snapshots.insert(InterpSnapshot::new(memory, stack)) {
|
|
// Spurious collision or first cycle
|
|
return Ok(())
|
|
}
|
|
|
|
// Second cycle
|
|
throw_exhaust!(InfiniteLoop)
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// This assumes the type has two type parameters, first for the tag (set to `()`),
|
|
// then for the id
|
|
macro_rules! impl_snapshot_for {
|
|
(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<(), AllocIdSnapshot<'a>>;
|
|
|
|
#[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)?) ),* )
|
|
)?
|
|
}
|
|
)*
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
(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<(), AllocIdSnapshot<'a>>;
|
|
|
|
#[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<T>
|
|
where Ctx: SnapshotContext<'a>,
|
|
T: Snapshot<'a, Ctx>
|
|
{
|
|
type Item = Option<<T as Snapshot<'a, Ctx>>::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<AllocationSnapshot<'a>>);
|
|
|
|
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, // just copy offset verbatim
|
|
tag -> *tag, // just copy tag
|
|
});
|
|
|
|
impl<'a, Ctx> Snapshot<'a, Ctx> for Scalar
|
|
where Ctx: SnapshotContext<'a>,
|
|
{
|
|
type Item = Scalar<(), AllocIdSnapshot<'a>>;
|
|
|
|
fn snapshot(&self, ctx: &'a Ctx) -> Self::Item {
|
|
match self {
|
|
Scalar::Ptr(p) => Scalar::Ptr(p.snapshot(ctx)),
|
|
Scalar::Raw{ size, data } => Scalar::Raw {
|
|
data: *data,
|
|
size: *size,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_snapshot_for!(enum ScalarMaybeUndef {
|
|
Scalar(s),
|
|
Undef,
|
|
});
|
|
|
|
impl_stable_hash_for!(struct crate::interpret::MemPlace {
|
|
ptr,
|
|
align,
|
|
meta,
|
|
});
|
|
impl_snapshot_for!(struct MemPlace {
|
|
ptr,
|
|
meta,
|
|
align -> *align, // just copy alignment verbatim
|
|
});
|
|
|
|
impl_stable_hash_for!(enum crate::interpret::Place {
|
|
Ptr(mem_place),
|
|
Local { frame, local },
|
|
});
|
|
impl<'a, Ctx> Snapshot<'a, Ctx> for Place
|
|
where Ctx: SnapshotContext<'a>,
|
|
{
|
|
type Item = Place<(), AllocIdSnapshot<'a>>;
|
|
|
|
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_stable_hash_for!(enum crate::interpret::Immediate {
|
|
Scalar(x),
|
|
ScalarPair(x, y),
|
|
});
|
|
impl_snapshot_for!(enum Immediate {
|
|
Scalar(s),
|
|
ScalarPair(s, t),
|
|
});
|
|
|
|
impl_stable_hash_for!(enum crate::interpret::Operand {
|
|
Immediate(x),
|
|
Indirect(x),
|
|
});
|
|
impl_snapshot_for!(enum Operand {
|
|
Immediate(v),
|
|
Indirect(m),
|
|
});
|
|
|
|
impl_stable_hash_for!(enum crate::interpret::LocalValue {
|
|
Dead,
|
|
Uninitialized,
|
|
Live(x),
|
|
});
|
|
impl_snapshot_for!(enum LocalValue {
|
|
Dead,
|
|
Uninitialized,
|
|
Live(v),
|
|
});
|
|
|
|
impl<'a, Ctx> Snapshot<'a, Ctx> for Relocations
|
|
where Ctx: SnapshotContext<'a>,
|
|
{
|
|
type Item = Relocations<(), AllocIdSnapshot<'a>>;
|
|
|
|
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<(), AllocIdSnapshot<'a>>,
|
|
undef_mask: &'a UndefMask,
|
|
align: &'a Align,
|
|
size: &'a Size,
|
|
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 {
|
|
size,
|
|
align,
|
|
mutability,
|
|
extra: (),
|
|
..
|
|
} = self;
|
|
|
|
let all_bytes = 0..self.len();
|
|
// This 'inspect' is okay since following access respects undef and relocations. This does
|
|
// influence interpreter exeuction, but only to detect the error of cycles in evalution
|
|
// dependencies.
|
|
let bytes = self.inspect_with_undef_and_ptr_outside_interpreter(all_bytes);
|
|
|
|
let undef_mask = self.undef_mask();
|
|
let relocations = self.relocations();
|
|
|
|
AllocationSnapshot {
|
|
bytes,
|
|
undef_mask,
|
|
align,
|
|
size,
|
|
mutability,
|
|
relocations: relocations.snapshot(ctx),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_stable_hash_for!(enum crate::interpret::eval_context::StackPopCleanup {
|
|
Goto(block),
|
|
None { cleanup },
|
|
});
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
struct FrameSnapshot<'a, 'tcx> {
|
|
instance: ty::Instance<'tcx>,
|
|
span: Span,
|
|
return_to_block: &'a StackPopCleanup,
|
|
return_place: Option<Place<(), AllocIdSnapshot<'a>>>,
|
|
locals: IndexVec<mir::Local, LocalValue<(), AllocIdSnapshot<'a>>>,
|
|
block: &'a mir::BasicBlock,
|
|
stmt: usize,
|
|
}
|
|
|
|
impl_stable_hash_for!(impl<> for struct Frame<'mir, 'tcx> {
|
|
body,
|
|
instance,
|
|
span,
|
|
return_to_block,
|
|
return_place -> (return_place.as_ref().map(|r| &**r)),
|
|
locals,
|
|
block,
|
|
stmt,
|
|
extra,
|
|
});
|
|
|
|
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 {
|
|
body: _,
|
|
instance,
|
|
span,
|
|
return_to_block,
|
|
return_place,
|
|
locals,
|
|
block,
|
|
stmt,
|
|
extra: _,
|
|
} = self;
|
|
|
|
FrameSnapshot {
|
|
instance: *instance,
|
|
span: *span,
|
|
return_to_block,
|
|
block,
|
|
stmt: *stmt,
|
|
return_place: return_place.map(|r| r.snapshot(ctx)),
|
|
locals: locals.iter().map(|local| local.snapshot(ctx)).collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a LocalState<'tcx>
|
|
where Ctx: SnapshotContext<'a>,
|
|
{
|
|
type Item = LocalValue<(), AllocIdSnapshot<'a>>;
|
|
|
|
fn snapshot(&self, ctx: &'a Ctx) -> Self::Item {
|
|
let LocalState { value, layout: _ } = self;
|
|
value.snapshot(ctx)
|
|
}
|
|
}
|
|
|
|
impl_stable_hash_for!(struct LocalState<'tcx> {
|
|
value,
|
|
layout -> _,
|
|
});
|
|
|
|
impl<'b, 'mir, 'tcx> SnapshotContext<'b>
|
|
for Memory<'mir, 'tcx, CompileTimeInterpreter<'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.
|
|
/// We assume the `CompileTimeInterpreter` has no interesting extra state that
|
|
/// is worth considering here.
|
|
struct InterpSnapshot<'mir, 'tcx> {
|
|
memory: Memory<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
|
|
stack: Vec<Frame<'mir, 'tcx>>,
|
|
}
|
|
|
|
impl InterpSnapshot<'mir, 'tcx> {
|
|
fn new(
|
|
memory: &Memory<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
|
|
stack: &[Frame<'mir, 'tcx>],
|
|
) -> Self {
|
|
InterpSnapshot {
|
|
memory: memory.clone(),
|
|
stack: stack.into(),
|
|
}
|
|
}
|
|
|
|
// Used to compare two snapshots
|
|
fn snapshot(&'b self)
|
|
-> Vec<FrameSnapshot<'b, 'tcx>>
|
|
{
|
|
// Start with the stack, iterate and recursively snapshot
|
|
self.stack.iter().map(|frame| frame.snapshot(&self.memory)).collect()
|
|
}
|
|
}
|
|
|
|
impl<'mir, 'tcx> Hash for InterpSnapshot<'mir, 'tcx> {
|
|
fn hash<H: Hasher>(&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::<u64>().hash(state)
|
|
}
|
|
}
|
|
|
|
impl_stable_hash_for!(impl<> for struct InterpSnapshot<'mir, 'tcx> {
|
|
// Not hashing memory: Avoid hashing memory all the time during execution
|
|
memory -> _,
|
|
stack,
|
|
});
|
|
|
|
impl<'mir, 'tcx> Eq for InterpSnapshot<'mir, 'tcx> {}
|
|
|
|
impl<'mir, 'tcx> PartialEq for InterpSnapshot<'mir, 'tcx> {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
// FIXME: This looks to be a *ridiculously expensive* comparison operation.
|
|
// Doesn't this make tons of copies? Either `snapshot` is very badly named,
|
|
// or it does!
|
|
self.snapshot() == other.snapshot()
|
|
}
|
|
}
|