Auto merge of #55125 - RalfJung:stacked-borrows, r=oli-obk

miri engine: Hooks for basic stacked borrows

r? @oli-obk
This commit is contained in:
bors
2018-10-21 09:32:25 +00:00
14 changed files with 313 additions and 149 deletions
+6 -2
View File
@@ -524,7 +524,7 @@ pub fn set_id_same_memory(&mut self, id: AllocId, mem: M) {
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
pub struct Allocation<Tag=()> {
pub struct Allocation<Tag=(),Extra=()> {
/// The actual bytes of the allocation.
/// Note that the bytes of a pointer represent the offset of the pointer
pub bytes: Vec<u8>,
@@ -541,9 +541,11 @@ pub struct Allocation<Tag=()> {
/// Also used by codegen to determine if a static should be put into mutable memory,
/// which happens for `static mut` and `static` with interior mutability.
pub mutability: Mutability,
/// Extra state for the machine.
pub extra: Extra,
}
impl<Tag> Allocation<Tag> {
impl<Tag, Extra: Default> Allocation<Tag, Extra> {
/// Creates a read-only allocation initialized by the given bytes
pub fn from_bytes(slice: &[u8], align: Align) -> Self {
let mut undef_mask = UndefMask::new(Size::ZERO);
@@ -554,6 +556,7 @@ pub fn from_bytes(slice: &[u8], align: Align) -> Self {
undef_mask,
align,
mutability: Mutability::Immutable,
extra: Extra::default(),
}
}
@@ -569,6 +572,7 @@ pub fn undef(size: Size, align: Align) -> Self {
undef_mask: UndefMask::new(size),
align,
mutability: Mutability::Mutable,
extra: Extra::default(),
}
}
}
+39 -13
View File
@@ -19,8 +19,8 @@
use rustc::hir::{self, def_id::DefId};
use rustc::mir::interpret::ConstEvalErr;
use rustc::mir;
use rustc::ty::{self, TyCtxt, Instance, query::TyCtxtAt};
use rustc::ty::layout::{self, LayoutOf, TyLayout};
use rustc::ty::{self, Ty, TyCtxt, Instance, query::TyCtxtAt};
use rustc::ty::layout::{self, Size, LayoutOf, TyLayout};
use rustc::ty::subst::Subst;
use rustc_data_structures::indexed_vec::IndexVec;
use rustc_data_structures::fx::FxHashMap;
@@ -28,13 +28,10 @@
use syntax::ast::Mutability;
use syntax::source_map::{Span, DUMMY_SP};
use rustc::mir::interpret::{
EvalResult, EvalError, EvalErrorKind, GlobalId,
Scalar, Allocation, AllocId, ConstValue,
};
use interpret::{self,
PlaceTy, MemPlace, OpTy, Operand, Value,
EvalContext, StackPopCleanup, MemoryKind,
PlaceTy, MemPlace, OpTy, Operand, Value, Pointer, Scalar, ConstValue,
EvalResult, EvalError, EvalErrorKind, GlobalId, EvalContext, StackPopCleanup,
Allocation, AllocId, MemoryKind,
snapshot,
};
@@ -53,7 +50,7 @@ pub fn mk_borrowck_eval_cx<'a, 'mir, 'tcx>(
) -> EvalResult<'tcx, CompileTimeEvalContext<'a, 'mir, 'tcx>> {
debug!("mk_borrowck_eval_cx: {:?}", instance);
let param_env = tcx.param_env(instance.def_id());
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new(), ());
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new());
// insert a stack frame so any queries have the correct substs
// cannot use `push_stack_frame`; if we do `const_prop` explodes
ecx.stack.push(interpret::Frame {
@@ -76,7 +73,7 @@ pub fn mk_eval_cx<'a, 'tcx>(
) -> EvalResult<'tcx, CompileTimeEvalContext<'a, 'tcx, 'tcx>> {
debug!("mk_eval_cx: {:?}, {:?}", instance, param_env);
let span = tcx.def_span(instance.def_id());
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new(), ());
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new());
let mir = ecx.load_mir(instance.def)?;
// insert a stack frame so any queries have the correct substs
ecx.push_stack_frame(
@@ -155,7 +152,7 @@ fn eval_body_and_ecx<'a, 'mir, 'tcx>(
// and try improving it down the road when more information is available
let span = tcx.def_span(cid.instance.def_id());
let span = mir.map(|mir| mir.span).unwrap_or(span);
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new(), ());
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new());
let r = eval_body_using_ecx(&mut ecx, cid, mir, param_env);
(r, ecx)
}
@@ -333,16 +330,25 @@ fn get_mut_or<E>(
type CompileTimeEvalContext<'a, 'mir, 'tcx> =
EvalContext<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>;
impl interpret::MayLeak for ! {
#[inline(always)]
fn may_leak(self) -> bool {
// `self` is uninhabited
self
}
}
impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx>
for CompileTimeInterpreter<'a, 'mir, 'tcx>
{
type MemoryData = ();
type MemoryKinds = !;
type AllocExtra = ();
type PointerTag = ();
type MemoryMap = FxHashMap<AllocId, (MemoryKind<!>, Allocation<()>)>;
type MemoryMap = FxHashMap<AllocId, (MemoryKind<!>, Allocation)>;
const STATIC_KIND: Option<!> = None; // no copying of statics allowed
const ENABLE_PTR_TRACKING_HOOKS: bool = false; // we don't have no provenance
#[inline(always)]
fn enforce_validity(_ecx: &EvalContext<'a, 'mir, 'tcx, Self>) -> bool {
@@ -456,6 +462,26 @@ fn before_terminator(ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>) -> EvalResult<
&ecx.stack[..],
)
}
#[inline(always)]
fn tag_reference(
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
_ptr: Pointer<Self::PointerTag>,
_pointee_ty: Ty<'tcx>,
_pointee_size: Size,
_borrow_kind: Option<mir::BorrowKind>,
) -> EvalResult<'tcx, Self::PointerTag> {
Ok(())
}
#[inline(always)]
fn tag_dereference(
_ecx: &EvalContext<'a, 'mir, 'tcx, Self>,
_ptr: Pointer<Self::PointerTag>,
_ptr_ty: Ty<'tcx>,
) -> EvalResult<'tcx, Self::PointerTag> {
Ok(())
}
}
/// Project to a field of a (variant of a) const
+20 -7
View File
@@ -37,8 +37,6 @@ pub fn cast(
kind: CastKind,
dest: PlaceTy<'tcx, M::PointerTag>,
) -> EvalResult<'tcx> {
let src_layout = src.layout;
let dst_layout = dest.layout;
use rustc::mir::CastKind::*;
match kind {
Unsize => {
@@ -46,15 +44,28 @@ pub fn cast(
}
Misc => {
let src_layout = src.layout;
let src = self.read_value(src)?;
let src = if M::ENABLE_PTR_TRACKING_HOOKS && src_layout.ty.is_region_ptr() {
// The only `Misc` casts on references are those creating raw pointers.
assert!(dest.layout.ty.is_unsafe_ptr());
// For the purpose of the "ptr tag hooks", treat this as creating
// a new, raw reference.
let place = self.ref_to_mplace(src)?;
self.create_ref(place, None)?
} else {
*src
};
if self.type_is_fat_ptr(src_layout.ty) {
match (*src, self.type_is_fat_ptr(dest.layout.ty)) {
match (src, self.type_is_fat_ptr(dest.layout.ty)) {
// pointers to extern types
(Value::Scalar(_),_) |
// slices and trait objects to other slices/trait objects
(Value::ScalarPair(..), true) => {
// No change to value
self.write_value(*src, dest)?;
self.write_value(src, dest)?;
}
// slices and trait objects to thin pointers (dropping the metadata)
(Value::ScalarPair(data, _), false) => {
@@ -65,11 +76,13 @@ pub fn cast(
match src_layout.variants {
layout::Variants::Single { index } => {
if let Some(def) = src_layout.ty.ty_adt_def() {
// Cast from a univariant enum
assert!(src_layout.is_zst());
let discr_val = def
.discriminant_for_variant(*self.tcx, index)
.val;
return self.write_scalar(
Scalar::from_uint(discr_val, dst_layout.size),
Scalar::from_uint(discr_val, dest.layout.size),
dest);
}
}
@@ -85,7 +98,7 @@ pub fn cast(
ReifyFnPointer => {
// The src operand does not matter, just its type
match src_layout.ty.sty {
match src.layout.ty.sty {
ty::FnDef(def_id, substs) => {
if self.tcx.has_attr(def_id, "rustc_args_required_const") {
bug!("reifying a fn ptr that requires \
@@ -117,7 +130,7 @@ pub fn cast(
ClosureFnPointer => {
// The src operand does not matter, just its type
match src_layout.ty.sty {
match src.layout.ty.sty {
ty::Closure(def_id, substs) => {
let substs = self.tcx.subst_and_normalize_erasing_regions(
self.substs(),
+73 -53
View File
@@ -11,6 +11,7 @@
use std::fmt::Write;
use std::mem;
use syntax::source_map::{self, Span, DUMMY_SP};
use rustc::hir::def_id::DefId;
use rustc::hir::def::Def;
use rustc::hir::map::definitions::DefPathData;
@@ -29,8 +30,6 @@
};
use rustc_data_structures::fx::FxHashMap;
use syntax::source_map::{self, Span};
use super::{
Value, Operand, MemPlace, MPlaceTy, Place, PlaceTy, ScalarMaybeUndef,
Memory, Machine
@@ -205,63 +204,59 @@ pub fn new(
tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
machine: M,
memory_data: M::MemoryData,
) -> Self {
EvalContext {
machine,
tcx,
param_env,
memory: Memory::new(tcx, memory_data),
memory: Memory::new(tcx),
stack: Vec::new(),
vtables: FxHashMap::default(),
}
}
#[inline(always)]
pub fn memory(&self) -> &Memory<'a, 'mir, 'tcx, M> {
&self.memory
}
#[inline(always)]
pub fn memory_mut(&mut self) -> &mut Memory<'a, 'mir, 'tcx, M> {
&mut self.memory
}
#[inline(always)]
pub fn stack(&self) -> &[Frame<'mir, 'tcx, M::PointerTag>] {
&self.stack
}
#[inline]
#[inline(always)]
pub fn cur_frame(&self) -> usize {
assert!(self.stack.len() > 0);
self.stack.len() - 1
}
/// Mark a storage as live, killing the previous content and returning it.
/// Remember to deallocate that!
pub fn storage_live(
&mut self,
local: mir::Local
) -> EvalResult<'tcx, LocalValue<M::PointerTag>> {
assert!(local != mir::RETURN_PLACE, "Cannot make return place live");
trace!("{:?} is now live", local);
let layout = self.layout_of_local(self.cur_frame(), local)?;
let init = LocalValue::Live(self.uninit_operand(layout)?);
// StorageLive *always* kills the value that's currently stored
Ok(mem::replace(&mut self.frame_mut().locals[local], init))
#[inline(always)]
pub fn frame(&self) -> &Frame<'mir, 'tcx, M::PointerTag> {
self.stack.last().expect("no call frames exist")
}
/// Returns the old value of the local.
/// Remember to deallocate that!
pub fn storage_dead(&mut self, local: mir::Local) -> LocalValue<M::PointerTag> {
assert!(local != mir::RETURN_PLACE, "Cannot make return place dead");
trace!("{:?} is now dead", local);
mem::replace(&mut self.frame_mut().locals[local], LocalValue::Dead)
#[inline(always)]
pub fn frame_mut(&mut self) -> &mut Frame<'mir, 'tcx, M::PointerTag> {
self.stack.last_mut().expect("no call frames exist")
}
pub fn str_to_value(&mut self, s: &str) -> EvalResult<'tcx, Value<M::PointerTag>> {
let ptr = self.memory.allocate_static_bytes(s.as_bytes());
Ok(Value::new_slice(Scalar::Ptr(ptr), s.len() as u64, self.tcx.tcx))
#[inline(always)]
pub(super) fn mir(&self) -> &'mir mir::Mir<'tcx> {
self.frame().mir
}
pub fn substs(&self) -> &'tcx Substs<'tcx> {
if let Some(frame) = self.stack.last() {
frame.instance.substs
} else {
Substs::empty()
}
}
pub(super) fn resolve(
@@ -285,10 +280,14 @@ pub(super) fn resolve(
).ok_or_else(|| EvalErrorKind::TooGeneric.into())
}
pub(super) fn type_is_sized(&self, ty: Ty<'tcx>) -> bool {
pub fn type_is_sized(&self, ty: Ty<'tcx>) -> bool {
ty.is_sized(self.tcx, self.param_env)
}
pub fn type_is_freeze(&self, ty: Ty<'tcx>) -> bool {
ty.is_freeze(*self.tcx, self.param_env, DUMMY_SP)
}
pub fn load_mir(
&self,
instance: ty::InstanceDef<'tcx>,
@@ -336,6 +335,11 @@ pub fn layout_of_local(
self.layout_of(local_ty)
}
pub fn str_to_value(&mut self, s: &str) -> EvalResult<'tcx, Value<M::PointerTag>> {
let ptr = self.memory.allocate_static_bytes(s.as_bytes());
Ok(Value::new_slice(Scalar::Ptr(ptr), s.len() as u64, self.tcx.tcx))
}
/// Return the actual dynamic size and alignment of the place at the given type.
/// Only the "meta" (metadata) part of the place matters.
/// This can fail to provide an answer for extern types.
@@ -354,11 +358,11 @@ pub(super) fn size_and_align_of(
// and it also rounds up to alignment, which we want to avoid,
// as the unsized field's alignment could be smaller.
assert!(!layout.ty.is_simd());
debug!("DST layout: {:?}", layout);
trace!("DST layout: {:?}", layout);
let sized_size = layout.fields.offset(layout.fields.count() - 1);
let sized_align = layout.align;
debug!(
trace!(
"DST {} statically sized prefix size: {:?} align: {:?}",
layout.ty,
sized_size,
@@ -434,6 +438,9 @@ pub fn push_stack_frame(
return_place: Option<PlaceTy<'tcx, M::PointerTag>>,
return_to_block: StackPopCleanup,
) -> EvalResult<'tcx> {
if self.stack.len() > 1 { // FIXME should be "> 0", printing topmost frame crashes rustc...
debug!("PAUSING({}) {}", self.cur_frame(), self.frame().instance);
}
::log_settings::settings().indentation += 1;
// first push a stack frame so we have access to the local substs
@@ -498,6 +505,10 @@ pub fn push_stack_frame(
self.frame_mut().locals = locals;
}
if self.stack.len() > 1 { // FIXME no check should be needed, but some instances ICE
debug!("ENTERING({}) {}", self.cur_frame(), self.frame().instance);
}
if self.stack.len() > self.tcx.sess.const_eval_stack_frame_limit {
err!(StackFrameLimitReached)
} else {
@@ -506,6 +517,9 @@ pub fn push_stack_frame(
}
pub(super) fn pop_stack_frame(&mut self) -> EvalResult<'tcx> {
if self.stack.len() > 1 { // FIXME no check should be needed, but some instances ICE
debug!("LEAVING({}) {}", self.cur_frame(), self.frame().instance);
}
::log_settings::settings().indentation -= 1;
let frame = self.stack.pop().expect(
"tried to pop a stack frame, but there were none",
@@ -549,9 +563,37 @@ pub(super) fn pop_stack_frame(&mut self) -> EvalResult<'tcx> {
return err!(Unreachable);
}
if self.stack.len() > 1 { // FIXME should be "> 0", printing topmost frame crashes rustc...
debug!("CONTINUING({}) {}", self.cur_frame(), self.frame().instance);
}
Ok(())
}
/// Mark a storage as live, killing the previous content and returning it.
/// Remember to deallocate that!
pub fn storage_live(
&mut self,
local: mir::Local
) -> EvalResult<'tcx, LocalValue<M::PointerTag>> {
assert!(local != mir::RETURN_PLACE, "Cannot make return place live");
trace!("{:?} is now live", local);
let layout = self.layout_of_local(self.cur_frame(), local)?;
let init = LocalValue::Live(self.uninit_operand(layout)?);
// StorageLive *always* kills the value that's currently stored
Ok(mem::replace(&mut self.frame_mut().locals[local], init))
}
/// Returns the old value of the local.
/// Remember to deallocate that!
pub fn storage_dead(&mut self, local: mir::Local) -> LocalValue<M::PointerTag> {
assert!(local != mir::RETURN_PLACE, "Cannot make return place dead");
trace!("{:?} is now dead", local);
mem::replace(&mut self.frame_mut().locals[local], LocalValue::Dead)
}
pub(super) fn deallocate_local(
&mut self,
local: LocalValue<M::PointerTag>,
@@ -576,28 +618,6 @@ pub fn const_eval(&self, gid: GlobalId<'tcx>) -> EvalResult<'tcx, &'tcx ty::Cons
.map_err(|err| EvalErrorKind::ReferencedConstant(err).into())
}
#[inline(always)]
pub fn frame(&self) -> &Frame<'mir, 'tcx, M::PointerTag> {
self.stack.last().expect("no call frames exist")
}
#[inline(always)]
pub fn frame_mut(&mut self) -> &mut Frame<'mir, 'tcx, M::PointerTag> {
self.stack.last_mut().expect("no call frames exist")
}
pub(super) fn mir(&self) -> &'mir mir::Mir<'tcx> {
self.frame().mir
}
pub fn substs(&self) -> &'tcx Substs<'tcx> {
if let Some(frame) = self.stack.last() {
frame.instance.substs
} else {
Substs::empty()
}
}
pub fn dump_place(&self, place: Place<M::PointerTag>) {
// Debug output
if !log_enabled!(::log::Level::Trace) {
+81 -18
View File
@@ -16,11 +16,25 @@
use std::hash::Hash;
use rustc::hir::def_id::DefId;
use rustc::mir::interpret::{Allocation, AllocId, EvalResult, Scalar};
use rustc::mir;
use rustc::ty::{self, layout::TyLayout, query::TyCtxtAt};
use rustc::ty::{self, Ty, layout::{Size, TyLayout}, query::TyCtxtAt};
use super::{EvalContext, PlaceTy, OpTy, MemoryKind};
use super::{
Allocation, AllocId, EvalResult, Scalar,
EvalContext, PlaceTy, OpTy, Pointer, MemoryKind,
};
/// Classifying memory accesses
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MemoryAccess {
Read,
Write,
}
/// Whether this kind of memory is allowed to leak
pub trait MayLeak: Copy {
fn may_leak(self) -> bool;
}
/// The functionality needed by memory to manage its allocations
pub trait AllocMap<K: Hash + Eq, V> {
@@ -62,29 +76,36 @@ fn get_mut_or<E>(
/// 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<'a, 'mir, 'tcx>: Sized {
/// Additional data that can be accessed via the Memory
type MemoryData;
/// Additional memory kinds a machine wishes to distinguish from the builtin ones
type MemoryKinds: ::std::fmt::Debug + Copy + Eq;
type MemoryKinds: ::std::fmt::Debug + MayLeak + Eq + 'static;
/// Tag tracked alongside every pointer. This is used to implement "Stacked Borrows"
/// <https://www.ralfj.de/blog/2018/08/07/stacked-borrows.html>.
type PointerTag: ::std::fmt::Debug + Default + Copy + Eq + Hash + 'static;
/// Extra data stored in every allocation.
type AllocExtra: ::std::fmt::Debug + Default + Clone;
/// Memory's allocation map
type MemoryMap:
AllocMap<AllocId, (MemoryKind<Self::MemoryKinds>, Allocation<Self::PointerTag>)> +
AllocMap<
AllocId,
(MemoryKind<Self::MemoryKinds>, Allocation<Self::PointerTag, Self::AllocExtra>)
> +
Default +
Clone;
/// Tag tracked alongside every pointer. This is inert for now, in preparation for
/// a future implementation of "Stacked Borrows"
/// <https://www.ralfj.de/blog/2018/08/07/stacked-borrows.html>.
type PointerTag: ::std::fmt::Debug + Default + Copy + Eq + Hash + 'static;
/// The memory kind to use for copied statics -- or None if those are not supported.
/// Statics are copied under two circumstances: When they are mutated, and when
/// `static_with_default_tag` or `find_foreign_static` (see below) returns an owned allocation
/// that is added to the memory so that the work is not done twice.
const STATIC_KIND: Option<Self::MemoryKinds>;
/// As an optimization, you can prevent the pointer tracking hooks from ever being
/// called. You should only do this if you do not care about provenance tracking.
/// This controls the `tag_reference` and `tag_dereference` hooks.
const ENABLE_PTR_TRACKING_HOOKS: bool;
/// Whether to enforce the validity invariant
fn enforce_validity(ecx: &EvalContext<'a, 'mir, 'tcx, Self>) -> bool;
@@ -127,7 +148,7 @@ fn call_intrinsic(
fn find_foreign_static(
tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
def_id: DefId,
) -> EvalResult<'tcx, Cow<'tcx, Allocation<Self::PointerTag>>>;
) -> EvalResult<'tcx, Cow<'tcx, Allocation<Self::PointerTag, Self::AllocExtra>>>;
/// Called to turn an allocation obtained from the `tcx` into one that has
/// the appropriate tags on each pointer.
@@ -138,7 +159,7 @@ fn find_foreign_static(
/// owned allocation to the map even when the map is shared.)
fn static_with_default_tag(
alloc: &'_ Allocation
) -> Cow<'_, Allocation<Self::PointerTag>>;
) -> Cow<'_, Allocation<Self::PointerTag, Self::AllocExtra>>;
/// Called for all binary operations on integer(-like) types when one operand is a pointer
/// value, and for the `Offset` operation that is inherently about pointers.
@@ -153,15 +174,57 @@ fn ptr_op(
right_layout: TyLayout<'tcx>,
) -> EvalResult<'tcx, (Scalar<Self::PointerTag>, bool)>;
/// Heap allocations via the `box` keyword
///
/// Returns a pointer to the allocated memory
/// Heap allocations via the `box` keyword.
fn box_alloc(
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
dest: PlaceTy<'tcx, Self::PointerTag>,
) -> EvalResult<'tcx>;
/// Hook for performing extra checks on a memory access.
///
/// Takes read-only access to the allocation so we can keep all the memory read
/// operations take `&self`. Use a `RefCell` in `AllocExtra` if you
/// need to mutate.
#[inline]
fn memory_accessed(
_alloc: &Allocation<Self::PointerTag, Self::AllocExtra>,
_ptr: Pointer<Self::PointerTag>,
_size: Size,
_access: MemoryAccess,
) -> EvalResult<'tcx> {
Ok(())
}
/// Hook for performing extra checks when memory gets deallocated.
#[inline]
fn memory_deallocated(
_alloc: &mut Allocation<Self::PointerTag, Self::AllocExtra>,
_ptr: Pointer<Self::PointerTag>,
) -> EvalResult<'tcx> {
Ok(())
}
/// Executed when evaluating the `&` operator: Creating a new reference.
/// This has the chance to adjust the tag.
/// `borrow_kind` can be `None` in case a raw ptr is being created.
fn tag_reference(
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
ptr: Pointer<Self::PointerTag>,
pointee_ty: Ty<'tcx>,
pointee_size: Size,
borrow_kind: Option<mir::BorrowKind>,
) -> EvalResult<'tcx, Self::PointerTag>;
/// Executed when evaluating the `*` operator: Following a reference.
/// This has the change to adjust the tag.
fn tag_dereference(
ecx: &EvalContext<'a, 'mir, 'tcx, Self>,
ptr: Pointer<Self::PointerTag>,
ptr_ty: Ty<'tcx>,
) -> EvalResult<'tcx, Self::PointerTag>;
/// Execute a validation operation
#[inline]
fn validation_op(
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
_op: ::rustc::mir::ValidationOp,
+36 -31
View File
@@ -22,17 +22,16 @@
use rustc::ty::{self, Instance, ParamEnv, query::TyCtxtAt};
use rustc::ty::layout::{self, Align, TargetDataLayout, Size, HasDataLayout};
use rustc::mir::interpret::{
Pointer, AllocId, Allocation, ConstValue, GlobalId,
EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic,
truncate
};
pub use rustc::mir::interpret::{write_target_uint, read_target_uint};
pub use rustc::mir::interpret::{truncate, write_target_uint, read_target_uint};
use rustc_data_structures::fx::{FxHashSet, FxHashMap};
use syntax::ast::Mutability;
use super::{Machine, AllocMap, ScalarMaybeUndef};
use super::{
Pointer, AllocId, Allocation, ConstValue, GlobalId,
EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic,
Machine, MemoryAccess, AllocMap, MayLeak, ScalarMaybeUndef,
};
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub enum MemoryKind<T> {
@@ -44,12 +43,20 @@ pub enum MemoryKind<T> {
Machine(T),
}
impl<T: MayLeak> MayLeak for MemoryKind<T> {
#[inline]
fn may_leak(self) -> bool {
match self {
MemoryKind::Stack => false,
MemoryKind::Vtable => true,
MemoryKind::Machine(k) => k.may_leak()
}
}
}
// `Memory` has to depend on the `Machine` because some of its operations
// (e.g. `get`) call a `Machine` hook.
pub struct Memory<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'a, 'mir, 'tcx>> {
/// Additional data required by the Machine
pub data: M::MemoryData,
/// Allocations local to this instance of the miri engine. The kind
/// helps ensure that the same mechanism is used for allocation and
/// deallocation. When an allocation is not found here, it is a
@@ -91,11 +98,9 @@ fn data_layout(&self) -> &TargetDataLayout {
// carefully copy only the reachable parts.
impl<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'a, 'mir, 'tcx>>
Clone for Memory<'a, 'mir, 'tcx, M>
where M::MemoryData: Clone
{
fn clone(&self) -> Self {
Memory {
data: self.data.clone(),
alloc_map: self.alloc_map.clone(),
dead_alloc_map: self.dead_alloc_map.clone(),
tcx: self.tcx,
@@ -104,9 +109,8 @@ fn clone(&self) -> Self {
}
impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
pub fn new(tcx: TyCtxtAt<'a, 'tcx, 'tcx>, data: M::MemoryData) -> Self {
pub fn new(tcx: TyCtxtAt<'a, 'tcx, 'tcx>) -> Self {
Memory {
data,
alloc_map: Default::default(),
dead_alloc_map: FxHashMap::default(),
tcx,
@@ -123,7 +127,7 @@ pub fn allocate_static_bytes(&mut self, bytes: &[u8]) -> Pointer<M::PointerTag>
pub fn allocate_with(
&mut self,
alloc: Allocation<M::PointerTag>,
alloc: Allocation<M::PointerTag, M::AllocExtra>,
kind: MemoryKind<M::MemoryKinds>,
) -> EvalResult<'tcx, AllocId> {
let id = self.tcx.alloc_map.lock().reserve();
@@ -186,13 +190,13 @@ pub fn deallocate(
size_and_align: Option<(Size, Align)>,
kind: MemoryKind<M::MemoryKinds>,
) -> EvalResult<'tcx> {
debug!("deallocating: {}", ptr.alloc_id);
trace!("deallocating: {}", ptr.alloc_id);
if ptr.offset.bytes() != 0 {
return err!(DeallocateNonBasePtr);
}
let (alloc_kind, alloc) = match self.alloc_map.remove(&ptr.alloc_id) {
let (alloc_kind, mut alloc) = match self.alloc_map.remove(&ptr.alloc_id) {
Some(alloc) => alloc,
None => {
// Deallocating static memory -- always an error
@@ -227,6 +231,9 @@ pub fn deallocate(
}
}
// Let the machine take some extra action
M::memory_deallocated(&mut alloc, ptr)?;
// Don't forget to remember size and align of this now-dead allocation
let old = self.dead_alloc_map.insert(
ptr.alloc_id,
@@ -334,7 +341,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
fn get_static_alloc(
tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
id: AllocId,
) -> EvalResult<'tcx, Cow<'tcx, Allocation<M::PointerTag>>> {
) -> EvalResult<'tcx, Cow<'tcx, Allocation<M::PointerTag, M::AllocExtra>>> {
let alloc = tcx.alloc_map.lock().get(id);
let def_id = match alloc {
Some(AllocType::Memory(mem)) => {
@@ -376,7 +383,7 @@ fn get_static_alloc(
})
}
pub fn get(&self, id: AllocId) -> EvalResult<'tcx, &Allocation<M::PointerTag>> {
pub fn get(&self, id: AllocId) -> EvalResult<'tcx, &Allocation<M::PointerTag, M::AllocExtra>> {
// The error type of the inner closure here is somewhat funny. We have two
// ways of "erroring": An actual error, or because we got a reference from
// `get_static_alloc` that we can actually use directly without inserting anything anywhere.
@@ -409,7 +416,7 @@ pub fn get(&self, id: AllocId) -> EvalResult<'tcx, &Allocation<M::PointerTag>> {
pub fn get_mut(
&mut self,
id: AllocId,
) -> EvalResult<'tcx, &mut Allocation<M::PointerTag>> {
) -> EvalResult<'tcx, &mut Allocation<M::PointerTag, M::AllocExtra>> {
let tcx = self.tcx;
let a = self.alloc_map.get_mut_or(id, || {
// Need to make a copy, even if `get_static_alloc` is able
@@ -482,12 +489,12 @@ pub fn dump_alloc(&self, id: AllocId) {
self.dump_allocs(vec![id]);
}
fn dump_alloc_helper<Tag>(
fn dump_alloc_helper<Tag, Extra>(
&self,
allocs_seen: &mut FxHashSet<AllocId>,
allocs_to_print: &mut VecDeque<AllocId>,
mut msg: String,
alloc: &Allocation<Tag>,
alloc: &Allocation<Tag, Extra>,
extra: String,
) {
use std::fmt::Write;
@@ -590,13 +597,7 @@ pub fn dump_allocs(&self, mut allocs: Vec<AllocId>) {
pub fn leak_report(&self) -> usize {
trace!("### LEAK REPORT ###");
let leaks: Vec<_> = self.alloc_map.filter_map_collect(|&id, &(kind, _)| {
// exclude statics and vtables
let exclude = match kind {
MemoryKind::Stack => false,
MemoryKind::Vtable => true,
MemoryKind::Machine(k) => Some(k) == M::STATIC_KIND,
};
if exclude { None } else { Some(id) }
if kind.may_leak() { None } else { Some(id) }
});
let n = leaks.len();
self.dump_allocs(leaks);
@@ -633,6 +634,8 @@ fn get_bytes_internal(
}
let alloc = self.get(ptr.alloc_id)?;
M::memory_accessed(alloc, ptr, size, MemoryAccess::Read)?;
assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes());
assert_eq!(size.bytes() as usize as u64, size.bytes());
let offset = ptr.offset.bytes() as usize;
@@ -677,6 +680,8 @@ fn get_bytes_mut(
self.clear_relocations(ptr, size)?;
let alloc = self.get_mut(ptr.alloc_id)?;
M::memory_accessed(alloc, ptr, size, MemoryAccess::Write)?;
assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes());
assert_eq!(size.bytes() as usize as u64, size.bytes());
let offset = ptr.offset.bytes() as usize;
@@ -687,8 +692,8 @@ fn get_bytes_mut(
/// Interning (for CTFE)
impl<'a, 'mir, 'tcx, M> Memory<'a, 'mir, 'tcx, M>
where
M: Machine<'a, 'mir, 'tcx, PointerTag=()>,
M::MemoryMap: AllocMap<AllocId, (MemoryKind<M::MemoryKinds>, Allocation<()>)>,
M: Machine<'a, 'mir, 'tcx, PointerTag=(), AllocExtra=()>,
M::MemoryMap: AllocMap<AllocId, (MemoryKind<M::MemoryKinds>, Allocation)>,
{
/// mark an allocation as static and initialized, either mutable or not
pub fn intern_static(
+3 -1
View File
@@ -24,6 +24,8 @@
mod validity;
mod intrinsics;
pub use rustc::mir::interpret::*; // have all the `interpret` symbols in one place: here
pub use self::eval_context::{
EvalContext, Frame, StackPopCleanup, LocalValue,
};
@@ -32,7 +34,7 @@
pub use self::memory::{Memory, MemoryKind};
pub use self::machine::{Machine, AllocMap};
pub use self::machine::{Machine, AllocMap, MemoryAccess, MayLeak};
pub use self::operand::{ScalarMaybeUndef, Value, ValTy, Operand, OpTy};
+41 -18
View File
@@ -144,17 +144,6 @@ pub fn to_ptr(self) -> EvalResult<'tcx, Pointer<Tag>> {
// it now must be aligned.
self.to_scalar_ptr_align().0.to_ptr()
}
/// Turn a mplace into a (thin or fat) pointer, as a reference, pointing to the same space.
/// This is the inverse of `ref_to_mplace`.
pub fn to_ref(self) -> Value<Tag> {
// We ignore the alignment of the place here -- special handling for packed structs ends
// at the `&` operator.
match self.meta {
None => Value::Scalar(self.ptr.into()),
Some(meta) => Value::ScalarPair(self.ptr.into(), meta.into()),
}
}
}
impl<'tcx, Tag> MPlaceTy<'tcx, Tag> {
@@ -267,25 +256,59 @@ impl<'a, 'mir, 'tcx, Tag, M> EvalContext<'a, 'mir, 'tcx, M>
where
Tag: ::std::fmt::Debug+Default+Copy+Eq+Hash+'static,
M: Machine<'a, 'mir, 'tcx, PointerTag=Tag>,
M::MemoryMap: AllocMap<AllocId, (MemoryKind<M::MemoryKinds>, Allocation<Tag>)>,
M::MemoryMap: AllocMap<AllocId, (MemoryKind<M::MemoryKinds>, Allocation<Tag, M::AllocExtra>)>,
{
/// Take a value, which represents a (thin or fat) reference, and make it a place.
/// Alignment is just based on the type. This is the inverse of `MemPlace::to_ref`.
/// Alignment is just based on the type. This is the inverse of `create_ref`.
pub fn ref_to_mplace(
&self, val: ValTy<'tcx, M::PointerTag>
&self,
val: ValTy<'tcx, M::PointerTag>,
) -> EvalResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
let ptr = match val.to_scalar_ptr()? {
Scalar::Ptr(ptr) if M::ENABLE_PTR_TRACKING_HOOKS => {
// Machine might want to track the `*` operator
let tag = M::tag_dereference(self, ptr, val.layout.ty)?;
Scalar::Ptr(Pointer::new_with_tag(ptr.alloc_id, ptr.offset, tag))
}
other => other,
};
let pointee_type = val.layout.ty.builtin_deref(true).unwrap().ty;
let layout = self.layout_of(pointee_type)?;
let align = layout.align;
let mplace = match *val {
Value::Scalar(ptr) =>
MemPlace { ptr: ptr.not_undef()?, align, meta: None },
Value::ScalarPair(ptr, meta) =>
MemPlace { ptr: ptr.not_undef()?, align, meta: Some(meta.not_undef()?) },
Value::Scalar(_) =>
MemPlace { ptr, align, meta: None },
Value::ScalarPair(_, meta) =>
MemPlace { ptr, align, meta: Some(meta.not_undef()?) },
};
Ok(MPlaceTy { mplace, layout })
}
/// Turn a mplace into a (thin or fat) pointer, as a reference, pointing to the same space.
/// This is the inverse of `ref_to_mplace`.
pub fn create_ref(
&mut self,
place: MPlaceTy<'tcx, M::PointerTag>,
borrow_kind: Option<mir::BorrowKind>,
) -> EvalResult<'tcx, Value<M::PointerTag>> {
let ptr = match place.ptr {
Scalar::Ptr(ptr) if M::ENABLE_PTR_TRACKING_HOOKS => {
// Machine might want to track the `&` operator
let (size, _) = self.size_and_align_of_mplace(place)?
.expect("create_ref cannot determine size");
let tag = M::tag_reference(self, ptr, place.layout.ty, size, borrow_kind)?;
Scalar::Ptr(Pointer::new_with_tag(ptr.alloc_id, ptr.offset, tag))
},
other => other,
};
Ok(match place.meta {
None => Value::Scalar(ptr.into()),
Some(meta) => Value::ScalarPair(ptr.into(), meta.into()),
})
}
/// Offset a pointer to project to a field. Unlike place_field, this is always
/// possible without allocating, so it can take &self. Also return the field's layout.
/// This supports both struct and array fields.
+1 -1
View File
@@ -305,7 +305,7 @@ impl<'a, Ctx> Snapshot<'a, Ctx> for &'a Allocation
type Item = AllocationSnapshot<'a>;
fn snapshot(&self, ctx: &'a Ctx) -> Self::Item {
let Allocation { bytes, relocations, undef_mask, align, mutability } = self;
let Allocation { bytes, relocations, undef_mask, align, mutability, extra: () } = self;
AllocationSnapshot {
bytes,
+3 -2
View File
@@ -248,9 +248,10 @@ fn eval_rvalue_into_place(
)?;
}
Ref(_, _, ref place) => {
Ref(_, borrow_kind, ref place) => {
let src = self.eval_place(place)?;
let val = self.force_allocation(src)?.to_ref();
let val = self.force_allocation(src)?;
let val = self.create_ref(val, Some(borrow_kind))?;
self.write_value(val, dest)?;
}
+4 -1
View File
@@ -446,7 +446,10 @@ fn drop_in_place(
};
let arg = OpTy {
op: Operand::Immediate(place.to_ref()),
op: Operand::Immediate(self.create_ref(
place,
None // this is a "raw reference"
)?),
layout: self.layout_of(self.tcx.mk_mut_ptr(place.layout.ty))?,
};
+1 -1
View File
@@ -26,7 +26,7 @@ pub fn get_vtable(
ty: Ty<'tcx>,
poly_trait_ref: ty::PolyExistentialTraitRef<'tcx>,
) -> EvalResult<'tcx, Pointer<M::PointerTag>> {
debug!("get_vtable(trait_ref={:?})", poly_trait_ref);
trace!("get_vtable(trait_ref={:?})", poly_trait_ref);
let (ty, poly_trait_ref) = self.tcx.erase_regions(&(ty, poly_trait_ref));
+4
View File
@@ -163,6 +163,8 @@ fn validate_primitive_type(
scalar_format(value), path, "a valid unicode codepoint");
},
ty::Float(_) | ty::Int(_) | ty::Uint(_) => {
// NOTE: Keep this in sync with the array optimization for int/float
// types below!
let size = value.layout.size;
let value = value.to_scalar_or_undef();
if const_mode {
@@ -511,6 +513,8 @@ pub fn validate_operand(
// This is the size in bytes of the whole array.
let size = Size::from_bytes(ty_size * len);
// NOTE: Keep this in sync with the handling of integer and float
// types above, in `validate_primitive_type`.
// In run-time mode, we accept pointers in here. This is actually more
// permissive than a per-element check would be, e.g. we accept
// an &[u8] that contains a pointer even though bytewise checking would