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
commit 66910ba686
14 changed files with 313 additions and 149 deletions

View file

@ -524,7 +524,7 @@ impl<'tcx, M: fmt::Debug + Eq + Hash + Clone> AllocMap<'tcx, 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 @@ impl<Tag> Allocation<Tag> {
undef_mask,
align,
mutability: Mutability::Immutable,
extra: Extra::default(),
}
}
@ -569,6 +572,7 @@ impl<Tag> Allocation<Tag> {
undef_mask: UndefMask::new(size),
align,
mutability: Mutability::Mutable,
extra: Extra::default(),
}
}
}

View file

@ -19,8 +19,8 @@ use std::collections::hash_map::Entry;
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 rustc_data_structures::fx::FxHashMap;
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 @@ impl<K: Hash + Eq, V> interpret::AllocMap<K, V> for FxHashMap<K, V> {
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 @@ impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx>
&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

View file

@ -37,8 +37,6 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
}
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
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(),

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::mir::interpret::{
};
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 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
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 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
).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 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
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 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
// 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 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
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 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
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 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
}
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 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
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 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
.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) {

View file

@ -16,11 +16,25 @@ use std::borrow::{Borrow, Cow};
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 @@ pub trait AllocMap<K: Hash + Eq, V> {
/// 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 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
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 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
/// 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 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
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,

View file

@ -22,17 +22,16 @@ use std::borrow::Cow;
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 @@ impl<'a, 'b, 'c, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> HasDataLayout
// 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 @@ impl<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'a, 'mir, 'tcx>>
}
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
}
}
// 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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
})
}
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
}
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
/// 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(

View file

@ -24,6 +24,8 @@ mod traits;
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::place::{Place, PlaceTy, MemPlace, MPlaceTy};
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};

View file

@ -144,17 +144,6 @@ impl<Tag> MemPlace<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.

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,

View file

@ -248,9 +248,10 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
)?;
}
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)?;
}

View file

@ -446,7 +446,10 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
};
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))?,
};

View file

@ -26,7 +26,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
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));

View file

@ -163,6 +163,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
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 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
// 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

@ -1 +1 @@
Subproject commit 8b14b03368429e6ee2a8ac0e0c876505606ab1f1
Subproject commit bbb1d80703f272a5592ceeb3832a489776512251