diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index 5eee0fba5fb46..4482d0a69c25c 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -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 { +pub struct Allocation { /// The actual bytes of the allocation. /// Note that the bytes of a pointer represent the offset of the pointer pub bytes: Vec, @@ -541,9 +541,11 @@ pub struct Allocation { /// 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 Allocation { +impl Allocation { /// 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 Allocation { undef_mask, align, mutability: Mutability::Immutable, + extra: Extra::default(), } } @@ -569,6 +572,7 @@ impl Allocation { undef_mask: UndefMask::new(size), align, mutability: Mutability::Mutable, + extra: Extra::default(), } } } diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index 2cfd058831f05..bc917140bbd67 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -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 interpret::AllocMap for FxHashMap { 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, Allocation<()>)>; + type MemoryMap = FxHashMap, 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, + _pointee_ty: Ty<'tcx>, + _pointee_size: Size, + _borrow_kind: Option, + ) -> EvalResult<'tcx, Self::PointerTag> { + Ok(()) + } + + #[inline(always)] + fn tag_dereference( + _ecx: &EvalContext<'a, 'mir, 'tcx, Self>, + _ptr: Pointer, + _ptr_ty: Ty<'tcx>, + ) -> EvalResult<'tcx, Self::PointerTag> { + Ok(()) + } } /// Project to a field of a (variant of a) const diff --git a/src/librustc_mir/interpret/cast.rs b/src/librustc_mir/interpret/cast.rs index f5e824b762888..ff07f44ac5fd3 100644 --- a/src/librustc_mir/interpret/cast.rs +++ b/src/librustc_mir/interpret/cast.rs @@ -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(), diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs index cf5358a989672..92cc09f4867b9 100644 --- a/src/librustc_mir/interpret/eval_context.rs +++ b/src/librustc_mir/interpret/eval_context.rs @@ -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> { - 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 { - assert!(local != mir::RETURN_PLACE, "Cannot make return place dead"); - trace!("{:?} is now dead", local); + #[inline(always)] + pub fn frame_mut(&mut self) -> &mut Frame<'mir, 'tcx, M::PointerTag> { + self.stack.last_mut().expect("no call frames exist") + } - mem::replace(&mut self.frame_mut().locals[local], LocalValue::Dead) + #[inline(always)] + pub(super) fn mir(&self) -> &'mir mir::Mir<'tcx> { + self.frame().mir } - pub fn str_to_value(&mut self, s: &str) -> EvalResult<'tcx, Value> { - let ptr = self.memory.allocate_static_bytes(s.as_bytes()); - Ok(Value::new_slice(Scalar::Ptr(ptr), s.len() as u64, self.tcx.tcx)) + 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> { + 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>, 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> { + 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 { + 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, @@ -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) { // Debug output if !log_enabled!(::log::Level::Trace) { diff --git a/src/librustc_mir/interpret/machine.rs b/src/librustc_mir/interpret/machine.rs index 560698f3f57a2..1318bbe1c2bf2 100644 --- a/src/librustc_mir/interpret/machine.rs +++ b/src/librustc_mir/interpret/machine.rs @@ -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::{ + Allocation, AllocId, EvalResult, Scalar, + EvalContext, PlaceTy, OpTy, Pointer, MemoryKind, +}; + +/// Classifying memory accesses +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MemoryAccess { + Read, + Write, +} -use super::{EvalContext, PlaceTy, OpTy, MemoryKind}; +/// 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 { @@ -62,29 +76,36 @@ pub trait AllocMap { /// 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" + /// . + 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, Allocation)> + + AllocMap< + AllocId, + (MemoryKind, Allocation) + > + Default + Clone; - /// Tag tracked alongside every pointer. This is inert for now, in preparation for - /// a future implementation of "Stacked Borrows" - /// . - 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; + /// 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>>; + ) -> EvalResult<'tcx, Cow<'tcx, Allocation>>; /// 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>; + ) -> Cow<'_, Allocation>; /// 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, 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, + _ptr: Pointer, + _size: Size, + _access: MemoryAccess, + ) -> EvalResult<'tcx> { + Ok(()) + } + + /// Hook for performing extra checks when memory gets deallocated. + #[inline] + fn memory_deallocated( + _alloc: &mut Allocation, + _ptr: Pointer, + ) -> 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, + pointee_ty: Ty<'tcx>, + pointee_size: Size, + borrow_kind: Option, + ) -> 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, + 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, diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs index 4b0c0c3ee6173..9febcceae068e 100644 --- a/src/librustc_mir/interpret/memory.rs +++ b/src/librustc_mir/interpret/memory.rs @@ -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 { @@ -44,12 +43,20 @@ pub enum MemoryKind { Machine(T), } +impl MayLeak for MemoryKind { + #[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, + alloc: Allocation, kind: MemoryKind, ) -> 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, ) -> 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>> { + ) -> EvalResult<'tcx, Cow<'tcx, Allocation>> { 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> { + pub fn get(&self, id: AllocId) -> EvalResult<'tcx, &Allocation> { // 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> { + ) -> EvalResult<'tcx, &mut Allocation> { 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( + fn dump_alloc_helper( &self, allocs_seen: &mut FxHashSet, allocs_to_print: &mut VecDeque, mut msg: String, - alloc: &Allocation, + alloc: &Allocation, 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, Allocation<()>)>, + M: Machine<'a, 'mir, 'tcx, PointerTag=(), AllocExtra=()>, + M::MemoryMap: AllocMap, Allocation)>, { /// mark an allocation as static and initialized, either mutable or not pub fn intern_static( diff --git a/src/librustc_mir/interpret/mod.rs b/src/librustc_mir/interpret/mod.rs index 39628598ef31c..55037a99e0124 100644 --- a/src/librustc_mir/interpret/mod.rs +++ b/src/librustc_mir/interpret/mod.rs @@ -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}; diff --git a/src/librustc_mir/interpret/place.rs b/src/librustc_mir/interpret/place.rs index e4055947b6421..af3d694862839 100644 --- a/src/librustc_mir/interpret/place.rs +++ b/src/librustc_mir/interpret/place.rs @@ -144,17 +144,6 @@ impl MemPlace { // 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 { - // 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, Allocation)>, + M::MemoryMap: AllocMap, Allocation)>, { /// 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, + ) -> EvalResult<'tcx, Value> { + 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. diff --git a/src/librustc_mir/interpret/snapshot.rs b/src/librustc_mir/interpret/snapshot.rs index 11d5785bc565d..047a0125f78af 100644 --- a/src/librustc_mir/interpret/snapshot.rs +++ b/src/librustc_mir/interpret/snapshot.rs @@ -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, diff --git a/src/librustc_mir/interpret/step.rs b/src/librustc_mir/interpret/step.rs index d15867eacddc6..1bab536e3e0f0 100644 --- a/src/librustc_mir/interpret/step.rs +++ b/src/librustc_mir/interpret/step.rs @@ -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)?; } diff --git a/src/librustc_mir/interpret/terminator.rs b/src/librustc_mir/interpret/terminator.rs index a339fa34ae1f6..c759727f546c8 100644 --- a/src/librustc_mir/interpret/terminator.rs +++ b/src/librustc_mir/interpret/terminator.rs @@ -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))?, }; diff --git a/src/librustc_mir/interpret/traits.rs b/src/librustc_mir/interpret/traits.rs index 2b0febc1ce717..a2d4eee2842c7 100644 --- a/src/librustc_mir/interpret/traits.rs +++ b/src/librustc_mir/interpret/traits.rs @@ -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> { - 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)); diff --git a/src/librustc_mir/interpret/validity.rs b/src/librustc_mir/interpret/validity.rs index c446980d04995..83b9705c1fe8e 100644 --- a/src/librustc_mir/interpret/validity.rs +++ b/src/librustc_mir/interpret/validity.rs @@ -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 diff --git a/src/tools/miri b/src/tools/miri index 8b14b03368429..bbb1d80703f27 160000 --- a/src/tools/miri +++ b/src/tools/miri @@ -1 +1 @@ -Subproject commit 8b14b03368429e6ee2a8ac0e0c876505606ab1f1 +Subproject commit bbb1d80703f272a5592ceeb3832a489776512251