From 1223a112d4249d74332d00a3276dc59d4ee88c92 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 23 Oct 2018 17:54:20 +0200 Subject: [PATCH 01/20] Duplicate mod.rs for better diff tracking --- src/librustc/mir/interpret/allocation.rs | 752 +++++++++++++++++++++++ 1 file changed, 752 insertions(+) create mode 100644 src/librustc/mir/interpret/allocation.rs diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs new file mode 100644 index 0000000000000..4c2b2b2d41d1b --- /dev/null +++ b/src/librustc/mir/interpret/allocation.rs @@ -0,0 +1,752 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! An interpreter for MIR used in CTFE and by miri + +#[macro_export] +macro_rules! err { + ($($tt:tt)*) => { Err($crate::mir::interpret::EvalErrorKind::$($tt)*.into()) }; +} + +mod error; +mod value; + +pub use self::error::{ + EvalError, EvalResult, EvalErrorKind, AssertMessage, ConstEvalErr, struct_error, + FrameInfo, ConstEvalResult, +}; + +pub use self::value::{Scalar, ConstValue}; + +use std::fmt; +use mir; +use hir::def_id::DefId; +use ty::{self, TyCtxt, Instance}; +use ty::layout::{self, Align, HasDataLayout, Size}; +use middle::region; +use std::iter; +use std::io; +use std::ops::{Deref, DerefMut}; +use std::hash::Hash; +use syntax::ast::Mutability; +use rustc_serialize::{Encoder, Decodable, Encodable}; +use rustc_data_structures::sorted_map::SortedMap; +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::sync::{Lock as Mutex, HashMapExt}; +use rustc_data_structures::tiny_list::TinyList; +use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian, BigEndian}; +use ty::codec::TyDecoder; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::num::NonZeroU32; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)] +pub enum Lock { + NoLock, + WriteLock(DynamicLifetime), + /// This should never be empty -- that would be a read lock held and nobody + /// there to release it... + ReadLock(Vec), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)] +pub struct DynamicLifetime { + pub frame: usize, + pub region: Option, // "None" indicates "until the function ends" +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)] +pub enum AccessKind { + Read, + Write, +} + +/// Uniquely identifies a specific constant or static. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, RustcEncodable, RustcDecodable)] +pub struct GlobalId<'tcx> { + /// For a constant or static, the `Instance` of the item itself. + /// For a promoted global, the `Instance` of the function they belong to. + pub instance: ty::Instance<'tcx>, + + /// The index for promoted globals within their function's `Mir`. + pub promoted: Option, +} + +//////////////////////////////////////////////////////////////////////////////// +// Pointer arithmetic +//////////////////////////////////////////////////////////////////////////////// + +pub trait PointerArithmetic: layout::HasDataLayout { + // These are not supposed to be overridden. + + #[inline(always)] + fn pointer_size(self) -> Size { + self.data_layout().pointer_size + } + + //// Trunace the given value to the pointer size; also return whether there was an overflow + fn truncate_to_ptr(self, val: u128) -> (u64, bool) { + let max_ptr_plus_1 = 1u128 << self.pointer_size().bits(); + ((val % max_ptr_plus_1) as u64, val >= max_ptr_plus_1) + } + + // Overflow checking only works properly on the range from -u64 to +u64. + fn overflowing_signed_offset(self, val: u64, i: i128) -> (u64, bool) { + // FIXME: is it possible to over/underflow here? + if i < 0 { + // trickery to ensure that i64::min_value() works fine + // this formula only works for true negative values, it panics for zero! + let n = u64::max_value() - (i as u64) + 1; + val.overflowing_sub(n) + } else { + self.overflowing_offset(val, i as u64) + } + } + + fn overflowing_offset(self, val: u64, i: u64) -> (u64, bool) { + let (res, over1) = val.overflowing_add(i); + let (res, over2) = self.truncate_to_ptr(res as u128); + (res, over1 || over2) + } + + fn signed_offset<'tcx>(self, val: u64, i: i64) -> EvalResult<'tcx, u64> { + let (res, over) = self.overflowing_signed_offset(val, i as i128); + if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) } + } + + fn offset<'tcx>(self, val: u64, i: u64) -> EvalResult<'tcx, u64> { + let (res, over) = self.overflowing_offset(val, i); + if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) } + } + + fn wrapping_signed_offset(self, val: u64, i: i64) -> u64 { + self.overflowing_signed_offset(val, i as i128).0 + } +} + +impl PointerArithmetic for T {} + + +/// Pointer is generic over the type that represents a reference to Allocations, +/// thus making it possible for the most convenient representation to be used in +/// each context. +/// +/// Defaults to the index based and loosely coupled AllocId. +/// +/// Pointer is also generic over the `Tag` associated with each pointer, +/// which is used to do provenance tracking during execution. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] +pub struct Pointer { + pub alloc_id: Id, + pub offset: Size, + pub tag: Tag, +} + +/// Produces a `Pointer` which points to the beginning of the Allocation +impl From for Pointer { + #[inline(always)] + fn from(alloc_id: AllocId) -> Self { + Pointer::new(alloc_id, Size::ZERO) + } +} + +impl<'tcx> Pointer<()> { + #[inline(always)] + pub fn new(alloc_id: AllocId, offset: Size) -> Self { + Pointer { alloc_id, offset, tag: () } + } + + #[inline(always)] + pub fn with_default_tag(self) -> Pointer + where Tag: Default + { + Pointer::new_with_tag(self.alloc_id, self.offset, Default::default()) + } +} + +impl<'tcx, Tag> Pointer { + #[inline(always)] + pub fn new_with_tag(alloc_id: AllocId, offset: Size, tag: Tag) -> Self { + Pointer { alloc_id, offset, tag } + } + + pub fn wrapping_signed_offset(self, i: i64, cx: C) -> Self { + Pointer::new_with_tag( + self.alloc_id, + Size::from_bytes(cx.data_layout().wrapping_signed_offset(self.offset.bytes(), i)), + self.tag, + ) + } + + pub fn overflowing_signed_offset(self, i: i128, cx: C) -> (Self, bool) { + let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset.bytes(), i); + (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over) + } + + pub fn signed_offset(self, i: i64, cx: C) -> EvalResult<'tcx, Self> { + Ok(Pointer::new_with_tag( + self.alloc_id, + Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?), + self.tag, + )) + } + + pub fn overflowing_offset(self, i: Size, cx: C) -> (Self, bool) { + let (res, over) = cx.data_layout().overflowing_offset(self.offset.bytes(), i.bytes()); + (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over) + } + + pub fn offset(self, i: Size, cx: C) -> EvalResult<'tcx, Self> { + Ok(Pointer::new_with_tag( + self.alloc_id, + Size::from_bytes(cx.data_layout().offset(self.offset.bytes(), i.bytes())?), + self.tag + )) + } + + #[inline] + pub fn erase_tag(self) -> Pointer { + Pointer { alloc_id: self.alloc_id, offset: self.offset, tag: () } + } +} + + +#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd, Debug)] +pub struct AllocId(pub u64); + +impl ::rustc_serialize::UseSpecializedEncodable for AllocId {} +impl ::rustc_serialize::UseSpecializedDecodable for AllocId {} + +#[derive(RustcDecodable, RustcEncodable)] +enum AllocKind { + Alloc, + Fn, + Static, +} + +pub fn specialized_encode_alloc_id< + 'a, 'tcx, + E: Encoder, +>( + encoder: &mut E, + tcx: TyCtxt<'a, 'tcx, 'tcx>, + alloc_id: AllocId, +) -> Result<(), E::Error> { + let alloc_type: AllocType<'tcx, &'tcx Allocation> = + tcx.alloc_map.lock().get(alloc_id).expect("no value for AllocId"); + match alloc_type { + AllocType::Memory(alloc) => { + trace!("encoding {:?} with {:#?}", alloc_id, alloc); + AllocKind::Alloc.encode(encoder)?; + alloc.encode(encoder)?; + } + AllocType::Function(fn_instance) => { + trace!("encoding {:?} with {:#?}", alloc_id, fn_instance); + AllocKind::Fn.encode(encoder)?; + fn_instance.encode(encoder)?; + } + AllocType::Static(did) => { + // referring to statics doesn't need to know about their allocations, + // just about its DefId + AllocKind::Static.encode(encoder)?; + did.encode(encoder)?; + } + } + Ok(()) +} + +// Used to avoid infinite recursion when decoding cyclic allocations. +type DecodingSessionId = NonZeroU32; + +#[derive(Clone)] +enum State { + Empty, + InProgressNonAlloc(TinyList), + InProgress(TinyList, AllocId), + Done(AllocId), +} + +pub struct AllocDecodingState { + // For each AllocId we keep track of which decoding state it's currently in. + decoding_state: Vec>, + // The offsets of each allocation in the data stream. + data_offsets: Vec, +} + +impl AllocDecodingState { + + pub fn new_decoding_session(&self) -> AllocDecodingSession<'_> { + static DECODER_SESSION_ID: AtomicU32 = AtomicU32::new(0); + let counter = DECODER_SESSION_ID.fetch_add(1, Ordering::SeqCst); + + // Make sure this is never zero + let session_id = DecodingSessionId::new((counter & 0x7FFFFFFF) + 1).unwrap(); + + AllocDecodingSession { + state: self, + session_id, + } + } + + pub fn new(data_offsets: Vec) -> AllocDecodingState { + let decoding_state: Vec<_> = ::std::iter::repeat(Mutex::new(State::Empty)) + .take(data_offsets.len()) + .collect(); + + AllocDecodingState { + decoding_state: decoding_state, + data_offsets, + } + } +} + +#[derive(Copy, Clone)] +pub struct AllocDecodingSession<'s> { + state: &'s AllocDecodingState, + session_id: DecodingSessionId, +} + +impl<'s> AllocDecodingSession<'s> { + + // Decodes an AllocId in a thread-safe way. + pub fn decode_alloc_id<'a, 'tcx, D>(&self, + decoder: &mut D) + -> Result + where D: TyDecoder<'a, 'tcx>, + 'tcx: 'a, + { + // Read the index of the allocation + let idx = decoder.read_u32()? as usize; + let pos = self.state.data_offsets[idx] as usize; + + // Decode the AllocKind now so that we know if we have to reserve an + // AllocId. + let (alloc_kind, pos) = decoder.with_position(pos, |decoder| { + let alloc_kind = AllocKind::decode(decoder)?; + Ok((alloc_kind, decoder.position())) + })?; + + // Check the decoding state, see if it's already decoded or if we should + // decode it here. + let alloc_id = { + let mut entry = self.state.decoding_state[idx].lock(); + + match *entry { + State::Done(alloc_id) => { + return Ok(alloc_id); + } + ref mut entry @ State::Empty => { + // We are allowed to decode + match alloc_kind { + AllocKind::Alloc => { + // If this is an allocation, we need to reserve an + // AllocId so we can decode cyclic graphs. + let alloc_id = decoder.tcx().alloc_map.lock().reserve(); + *entry = State::InProgress( + TinyList::new_single(self.session_id), + alloc_id); + Some(alloc_id) + }, + AllocKind::Fn | AllocKind::Static => { + // Fns and statics cannot be cyclic and their AllocId + // is determined later by interning + *entry = State::InProgressNonAlloc( + TinyList::new_single(self.session_id)); + None + } + } + } + State::InProgressNonAlloc(ref mut sessions) => { + if sessions.contains(&self.session_id) { + bug!("This should be unreachable") + } else { + // Start decoding concurrently + sessions.insert(self.session_id); + None + } + } + State::InProgress(ref mut sessions, alloc_id) => { + if sessions.contains(&self.session_id) { + // Don't recurse. + return Ok(alloc_id) + } else { + // Start decoding concurrently + sessions.insert(self.session_id); + Some(alloc_id) + } + } + } + }; + + // Now decode the actual data + let alloc_id = decoder.with_position(pos, |decoder| { + match alloc_kind { + AllocKind::Alloc => { + let allocation = <&'tcx Allocation as Decodable>::decode(decoder)?; + // We already have a reserved AllocId. + let alloc_id = alloc_id.unwrap(); + trace!("decoded alloc {:?} {:#?}", alloc_id, allocation); + decoder.tcx().alloc_map.lock().set_id_same_memory(alloc_id, allocation); + Ok(alloc_id) + }, + AllocKind::Fn => { + assert!(alloc_id.is_none()); + trace!("creating fn alloc id"); + let instance = ty::Instance::decode(decoder)?; + trace!("decoded fn alloc instance: {:?}", instance); + let alloc_id = decoder.tcx().alloc_map.lock().create_fn_alloc(instance); + Ok(alloc_id) + }, + AllocKind::Static => { + assert!(alloc_id.is_none()); + trace!("creating extern static alloc id at"); + let did = DefId::decode(decoder)?; + let alloc_id = decoder.tcx().alloc_map.lock().intern_static(did); + Ok(alloc_id) + } + } + })?; + + self.state.decoding_state[idx].with_lock(|entry| { + *entry = State::Done(alloc_id); + }); + + Ok(alloc_id) + } +} + +impl fmt::Display for AllocId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, RustcDecodable, RustcEncodable)] +pub enum AllocType<'tcx, M> { + /// The alloc id is used as a function pointer + Function(Instance<'tcx>), + /// The alloc id points to a "lazy" static variable that did not get computed (yet). + /// This is also used to break the cycle in recursive statics. + Static(DefId), + /// The alloc id points to memory + Memory(M) +} + +pub struct AllocMap<'tcx, M> { + /// Lets you know what an AllocId refers to + id_to_type: FxHashMap>, + + /// Used to ensure that functions and statics only get one associated AllocId + type_interner: FxHashMap, AllocId>, + + /// The AllocId to assign to the next requested id. + /// Always incremented, never gets smaller. + next_id: AllocId, +} + +impl<'tcx, M: fmt::Debug + Eq + Hash + Clone> AllocMap<'tcx, M> { + pub fn new() -> Self { + AllocMap { + id_to_type: Default::default(), + type_interner: Default::default(), + next_id: AllocId(0), + } + } + + /// obtains a new allocation ID that can be referenced but does not + /// yet have an allocation backing it. + pub fn reserve( + &mut self, + ) -> AllocId { + let next = self.next_id; + self.next_id.0 = self.next_id.0 + .checked_add(1) + .expect("You overflowed a u64 by incrementing by 1... \ + You've just earned yourself a free drink if we ever meet. \ + Seriously, how did you do that?!"); + next + } + + fn intern(&mut self, alloc_type: AllocType<'tcx, M>) -> AllocId { + if let Some(&alloc_id) = self.type_interner.get(&alloc_type) { + return alloc_id; + } + let id = self.reserve(); + debug!("creating alloc_type {:?} with id {}", alloc_type, id); + self.id_to_type.insert(id, alloc_type.clone()); + self.type_interner.insert(alloc_type, id); + id + } + + // FIXME: Check if functions have identity. If not, we should not intern these, + // but instead create a new id per use. + // Alternatively we could just make comparing function pointers an error. + pub fn create_fn_alloc(&mut self, instance: Instance<'tcx>) -> AllocId { + self.intern(AllocType::Function(instance)) + } + + pub fn get(&self, id: AllocId) -> Option> { + self.id_to_type.get(&id).cloned() + } + + pub fn unwrap_memory(&self, id: AllocId) -> M { + match self.get(id) { + Some(AllocType::Memory(mem)) => mem, + _ => bug!("expected allocation id {} to point to memory", id), + } + } + + pub fn intern_static(&mut self, static_id: DefId) -> AllocId { + self.intern(AllocType::Static(static_id)) + } + + pub fn allocate(&mut self, mem: M) -> AllocId { + let id = self.reserve(); + self.set_id_memory(id, mem); + id + } + + pub fn set_id_memory(&mut self, id: AllocId, mem: M) { + if let Some(old) = self.id_to_type.insert(id, AllocType::Memory(mem)) { + bug!("tried to set allocation id {}, but it was already existing as {:#?}", id, old); + } + } + + pub fn set_id_same_memory(&mut self, id: AllocId, mem: M) { + self.id_to_type.insert_same(id, AllocType::Memory(mem)); + } +} + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)] +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, + /// Maps from byte addresses to extra data for each pointer. + /// Only the first byte of a pointer is inserted into the map; i.e., + /// every entry in this map applies to `pointer_size` consecutive bytes starting + /// at the given offset. + pub relocations: Relocations, + /// Denotes undefined memory. Reading from undefined memory is forbidden in miri + pub undef_mask: UndefMask, + /// The alignment of the allocation to detect unaligned reads. + pub align: Align, + /// Whether the allocation is mutable. + /// 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 { + /// 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); + undef_mask.grow(Size::from_bytes(slice.len() as u64), true); + Self { + bytes: slice.to_owned(), + relocations: Relocations::new(), + undef_mask, + align, + mutability: Mutability::Immutable, + extra: Extra::default(), + } + } + + pub fn from_byte_aligned_bytes(slice: &[u8]) -> Self { + Allocation::from_bytes(slice, Align::from_bytes(1, 1).unwrap()) + } + + pub fn undef(size: Size, align: Align) -> Self { + assert_eq!(size.bytes() as usize as u64, size.bytes()); + Allocation { + bytes: vec![0; size.bytes() as usize], + relocations: Relocations::new(), + undef_mask: UndefMask::new(size), + align, + mutability: Mutability::Mutable, + extra: Extra::default(), + } + } +} + +impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, RustcEncodable, RustcDecodable)] +pub struct Relocations(SortedMap); + +impl Relocations { + pub fn new() -> Self { + Relocations(SortedMap::new()) + } + + // The caller must guarantee that the given relocations are already sorted + // by address and contain no duplicates. + pub fn from_presorted(r: Vec<(Size, (Tag, Id))>) -> Self { + Relocations(SortedMap::from_presorted_elements(r)) + } +} + +impl Deref for Relocations { + type Target = SortedMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Relocations { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Methods to access integers in the target endianness +//////////////////////////////////////////////////////////////////////////////// + +pub fn write_target_uint( + endianness: layout::Endian, + mut target: &mut [u8], + data: u128, +) -> Result<(), io::Error> { + let len = target.len(); + match endianness { + layout::Endian::Little => target.write_uint128::(data, len), + layout::Endian::Big => target.write_uint128::(data, len), + } +} + +pub fn read_target_uint(endianness: layout::Endian, mut source: &[u8]) -> Result { + match endianness { + layout::Endian::Little => source.read_uint128::(source.len()), + layout::Endian::Big => source.read_uint128::(source.len()), + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Methods to faciliate working with signed integers stored in a u128 +//////////////////////////////////////////////////////////////////////////////// + +pub fn sign_extend(value: u128, size: Size) -> u128 { + let size = size.bits(); + // sign extend + let shift = 128 - size; + // shift the unsigned value to the left + // and back to the right as signed (essentially fills with FF on the left) + (((value << shift) as i128) >> shift) as u128 +} + +pub fn truncate(value: u128, size: Size) -> u128 { + let size = size.bits(); + let shift = 128 - size; + // truncate (shift left to drop out leftover values, shift right to fill with zeroes) + (value << shift) >> shift +} + +//////////////////////////////////////////////////////////////////////////////// +// Undefined byte tracking +//////////////////////////////////////////////////////////////////////////////// + +type Block = u64; +const BLOCK_SIZE: u64 = 64; + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)] +pub struct UndefMask { + blocks: Vec, + len: Size, +} + +impl_stable_hash_for!(struct mir::interpret::UndefMask{blocks, len}); + +impl UndefMask { + pub fn new(size: Size) -> Self { + let mut m = UndefMask { + blocks: vec![], + len: Size::ZERO, + }; + m.grow(size, false); + m + } + + /// Check whether the range `start..end` (end-exclusive) is entirely defined. + /// + /// Returns `Ok(())` if it's defined. Otherwise returns the index of the byte + /// at which the first undefined access begins. + #[inline] + pub fn is_range_defined(&self, start: Size, end: Size) -> Result<(), Size> { + if end > self.len { + return Err(self.len); + } + + let idx = (start.bytes()..end.bytes()) + .map(|i| Size::from_bytes(i)) + .find(|&i| !self.get(i)); + + match idx { + Some(idx) => Err(idx), + None => Ok(()) + } + } + + pub fn set_range(&mut self, start: Size, end: Size, new_state: bool) { + let len = self.len; + if end > len { + self.grow(end - len, new_state); + } + self.set_range_inbounds(start, end, new_state); + } + + pub fn set_range_inbounds(&mut self, start: Size, end: Size, new_state: bool) { + for i in start.bytes()..end.bytes() { + self.set(Size::from_bytes(i), new_state); + } + } + + #[inline] + pub fn get(&self, i: Size) -> bool { + let (block, bit) = bit_index(i); + (self.blocks[block] & 1 << bit) != 0 + } + + #[inline] + pub fn set(&mut self, i: Size, new_state: bool) { + let (block, bit) = bit_index(i); + if new_state { + self.blocks[block] |= 1 << bit; + } else { + self.blocks[block] &= !(1 << bit); + } + } + + pub fn grow(&mut self, amount: Size, new_state: bool) { + let unused_trailing_bits = self.blocks.len() as u64 * BLOCK_SIZE - self.len.bytes(); + if amount.bytes() > unused_trailing_bits { + let additional_blocks = amount.bytes() / BLOCK_SIZE + 1; + assert_eq!(additional_blocks as usize as u64, additional_blocks); + self.blocks.extend( + iter::repeat(0).take(additional_blocks as usize), + ); + } + let start = self.len; + self.len += amount; + self.set_range_inbounds(start, start + amount, new_state); + } +} + +#[inline] +fn bit_index(bits: Size) -> (usize, usize) { + let bits = bits.bytes(); + let a = bits / BLOCK_SIZE; + let b = bits % BLOCK_SIZE; + assert_eq!(a as usize as u64, a); + assert_eq!(b as usize as u64, b); + (a as usize, b as usize) +} From 899214674f4cbc603288c58eb0ba2d6eb2746488 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 23 Oct 2018 18:05:32 +0200 Subject: [PATCH 02/20] Move `Allocation` into its own module --- src/librustc/mir/interpret/allocation.rs | 687 +---------------------- src/librustc/mir/interpret/mod.rs | 62 +- 2 files changed, 9 insertions(+), 740 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index 4c2b2b2d41d1b..8444cf5726f84 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -8,520 +8,15 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! An interpreter for MIR used in CTFE and by miri +//! The virtual memory representation of the MIR interpreter -#[macro_export] -macro_rules! err { - ($($tt:tt)*) => { Err($crate::mir::interpret::EvalErrorKind::$($tt)*.into()) }; -} - -mod error; -mod value; - -pub use self::error::{ - EvalError, EvalResult, EvalErrorKind, AssertMessage, ConstEvalErr, struct_error, - FrameInfo, ConstEvalResult, +use super::{ + UndefMask, + Relocations, }; -pub use self::value::{Scalar, ConstValue}; - -use std::fmt; -use mir; -use hir::def_id::DefId; -use ty::{self, TyCtxt, Instance}; -use ty::layout::{self, Align, HasDataLayout, Size}; -use middle::region; -use std::iter; -use std::io; -use std::ops::{Deref, DerefMut}; -use std::hash::Hash; +use ty::layout::{Size, Align}; use syntax::ast::Mutability; -use rustc_serialize::{Encoder, Decodable, Encodable}; -use rustc_data_structures::sorted_map::SortedMap; -use rustc_data_structures::fx::FxHashMap; -use rustc_data_structures::sync::{Lock as Mutex, HashMapExt}; -use rustc_data_structures::tiny_list::TinyList; -use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian, BigEndian}; -use ty::codec::TyDecoder; -use std::sync::atomic::{AtomicU32, Ordering}; -use std::num::NonZeroU32; - -#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)] -pub enum Lock { - NoLock, - WriteLock(DynamicLifetime), - /// This should never be empty -- that would be a read lock held and nobody - /// there to release it... - ReadLock(Vec), -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)] -pub struct DynamicLifetime { - pub frame: usize, - pub region: Option, // "None" indicates "until the function ends" -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)] -pub enum AccessKind { - Read, - Write, -} - -/// Uniquely identifies a specific constant or static. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, RustcEncodable, RustcDecodable)] -pub struct GlobalId<'tcx> { - /// For a constant or static, the `Instance` of the item itself. - /// For a promoted global, the `Instance` of the function they belong to. - pub instance: ty::Instance<'tcx>, - - /// The index for promoted globals within their function's `Mir`. - pub promoted: Option, -} - -//////////////////////////////////////////////////////////////////////////////// -// Pointer arithmetic -//////////////////////////////////////////////////////////////////////////////// - -pub trait PointerArithmetic: layout::HasDataLayout { - // These are not supposed to be overridden. - - #[inline(always)] - fn pointer_size(self) -> Size { - self.data_layout().pointer_size - } - - //// Trunace the given value to the pointer size; also return whether there was an overflow - fn truncate_to_ptr(self, val: u128) -> (u64, bool) { - let max_ptr_plus_1 = 1u128 << self.pointer_size().bits(); - ((val % max_ptr_plus_1) as u64, val >= max_ptr_plus_1) - } - - // Overflow checking only works properly on the range from -u64 to +u64. - fn overflowing_signed_offset(self, val: u64, i: i128) -> (u64, bool) { - // FIXME: is it possible to over/underflow here? - if i < 0 { - // trickery to ensure that i64::min_value() works fine - // this formula only works for true negative values, it panics for zero! - let n = u64::max_value() - (i as u64) + 1; - val.overflowing_sub(n) - } else { - self.overflowing_offset(val, i as u64) - } - } - - fn overflowing_offset(self, val: u64, i: u64) -> (u64, bool) { - let (res, over1) = val.overflowing_add(i); - let (res, over2) = self.truncate_to_ptr(res as u128); - (res, over1 || over2) - } - - fn signed_offset<'tcx>(self, val: u64, i: i64) -> EvalResult<'tcx, u64> { - let (res, over) = self.overflowing_signed_offset(val, i as i128); - if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) } - } - - fn offset<'tcx>(self, val: u64, i: u64) -> EvalResult<'tcx, u64> { - let (res, over) = self.overflowing_offset(val, i); - if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) } - } - - fn wrapping_signed_offset(self, val: u64, i: i64) -> u64 { - self.overflowing_signed_offset(val, i as i128).0 - } -} - -impl PointerArithmetic for T {} - - -/// Pointer is generic over the type that represents a reference to Allocations, -/// thus making it possible for the most convenient representation to be used in -/// each context. -/// -/// Defaults to the index based and loosely coupled AllocId. -/// -/// Pointer is also generic over the `Tag` associated with each pointer, -/// which is used to do provenance tracking during execution. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] -pub struct Pointer { - pub alloc_id: Id, - pub offset: Size, - pub tag: Tag, -} - -/// Produces a `Pointer` which points to the beginning of the Allocation -impl From for Pointer { - #[inline(always)] - fn from(alloc_id: AllocId) -> Self { - Pointer::new(alloc_id, Size::ZERO) - } -} - -impl<'tcx> Pointer<()> { - #[inline(always)] - pub fn new(alloc_id: AllocId, offset: Size) -> Self { - Pointer { alloc_id, offset, tag: () } - } - - #[inline(always)] - pub fn with_default_tag(self) -> Pointer - where Tag: Default - { - Pointer::new_with_tag(self.alloc_id, self.offset, Default::default()) - } -} - -impl<'tcx, Tag> Pointer { - #[inline(always)] - pub fn new_with_tag(alloc_id: AllocId, offset: Size, tag: Tag) -> Self { - Pointer { alloc_id, offset, tag } - } - - pub fn wrapping_signed_offset(self, i: i64, cx: C) -> Self { - Pointer::new_with_tag( - self.alloc_id, - Size::from_bytes(cx.data_layout().wrapping_signed_offset(self.offset.bytes(), i)), - self.tag, - ) - } - - pub fn overflowing_signed_offset(self, i: i128, cx: C) -> (Self, bool) { - let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset.bytes(), i); - (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over) - } - - pub fn signed_offset(self, i: i64, cx: C) -> EvalResult<'tcx, Self> { - Ok(Pointer::new_with_tag( - self.alloc_id, - Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?), - self.tag, - )) - } - - pub fn overflowing_offset(self, i: Size, cx: C) -> (Self, bool) { - let (res, over) = cx.data_layout().overflowing_offset(self.offset.bytes(), i.bytes()); - (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over) - } - - pub fn offset(self, i: Size, cx: C) -> EvalResult<'tcx, Self> { - Ok(Pointer::new_with_tag( - self.alloc_id, - Size::from_bytes(cx.data_layout().offset(self.offset.bytes(), i.bytes())?), - self.tag - )) - } - - #[inline] - pub fn erase_tag(self) -> Pointer { - Pointer { alloc_id: self.alloc_id, offset: self.offset, tag: () } - } -} - - -#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd, Debug)] -pub struct AllocId(pub u64); - -impl ::rustc_serialize::UseSpecializedEncodable for AllocId {} -impl ::rustc_serialize::UseSpecializedDecodable for AllocId {} - -#[derive(RustcDecodable, RustcEncodable)] -enum AllocKind { - Alloc, - Fn, - Static, -} - -pub fn specialized_encode_alloc_id< - 'a, 'tcx, - E: Encoder, ->( - encoder: &mut E, - tcx: TyCtxt<'a, 'tcx, 'tcx>, - alloc_id: AllocId, -) -> Result<(), E::Error> { - let alloc_type: AllocType<'tcx, &'tcx Allocation> = - tcx.alloc_map.lock().get(alloc_id).expect("no value for AllocId"); - match alloc_type { - AllocType::Memory(alloc) => { - trace!("encoding {:?} with {:#?}", alloc_id, alloc); - AllocKind::Alloc.encode(encoder)?; - alloc.encode(encoder)?; - } - AllocType::Function(fn_instance) => { - trace!("encoding {:?} with {:#?}", alloc_id, fn_instance); - AllocKind::Fn.encode(encoder)?; - fn_instance.encode(encoder)?; - } - AllocType::Static(did) => { - // referring to statics doesn't need to know about their allocations, - // just about its DefId - AllocKind::Static.encode(encoder)?; - did.encode(encoder)?; - } - } - Ok(()) -} - -// Used to avoid infinite recursion when decoding cyclic allocations. -type DecodingSessionId = NonZeroU32; - -#[derive(Clone)] -enum State { - Empty, - InProgressNonAlloc(TinyList), - InProgress(TinyList, AllocId), - Done(AllocId), -} - -pub struct AllocDecodingState { - // For each AllocId we keep track of which decoding state it's currently in. - decoding_state: Vec>, - // The offsets of each allocation in the data stream. - data_offsets: Vec, -} - -impl AllocDecodingState { - - pub fn new_decoding_session(&self) -> AllocDecodingSession<'_> { - static DECODER_SESSION_ID: AtomicU32 = AtomicU32::new(0); - let counter = DECODER_SESSION_ID.fetch_add(1, Ordering::SeqCst); - - // Make sure this is never zero - let session_id = DecodingSessionId::new((counter & 0x7FFFFFFF) + 1).unwrap(); - - AllocDecodingSession { - state: self, - session_id, - } - } - - pub fn new(data_offsets: Vec) -> AllocDecodingState { - let decoding_state: Vec<_> = ::std::iter::repeat(Mutex::new(State::Empty)) - .take(data_offsets.len()) - .collect(); - - AllocDecodingState { - decoding_state: decoding_state, - data_offsets, - } - } -} - -#[derive(Copy, Clone)] -pub struct AllocDecodingSession<'s> { - state: &'s AllocDecodingState, - session_id: DecodingSessionId, -} - -impl<'s> AllocDecodingSession<'s> { - - // Decodes an AllocId in a thread-safe way. - pub fn decode_alloc_id<'a, 'tcx, D>(&self, - decoder: &mut D) - -> Result - where D: TyDecoder<'a, 'tcx>, - 'tcx: 'a, - { - // Read the index of the allocation - let idx = decoder.read_u32()? as usize; - let pos = self.state.data_offsets[idx] as usize; - - // Decode the AllocKind now so that we know if we have to reserve an - // AllocId. - let (alloc_kind, pos) = decoder.with_position(pos, |decoder| { - let alloc_kind = AllocKind::decode(decoder)?; - Ok((alloc_kind, decoder.position())) - })?; - - // Check the decoding state, see if it's already decoded or if we should - // decode it here. - let alloc_id = { - let mut entry = self.state.decoding_state[idx].lock(); - - match *entry { - State::Done(alloc_id) => { - return Ok(alloc_id); - } - ref mut entry @ State::Empty => { - // We are allowed to decode - match alloc_kind { - AllocKind::Alloc => { - // If this is an allocation, we need to reserve an - // AllocId so we can decode cyclic graphs. - let alloc_id = decoder.tcx().alloc_map.lock().reserve(); - *entry = State::InProgress( - TinyList::new_single(self.session_id), - alloc_id); - Some(alloc_id) - }, - AllocKind::Fn | AllocKind::Static => { - // Fns and statics cannot be cyclic and their AllocId - // is determined later by interning - *entry = State::InProgressNonAlloc( - TinyList::new_single(self.session_id)); - None - } - } - } - State::InProgressNonAlloc(ref mut sessions) => { - if sessions.contains(&self.session_id) { - bug!("This should be unreachable") - } else { - // Start decoding concurrently - sessions.insert(self.session_id); - None - } - } - State::InProgress(ref mut sessions, alloc_id) => { - if sessions.contains(&self.session_id) { - // Don't recurse. - return Ok(alloc_id) - } else { - // Start decoding concurrently - sessions.insert(self.session_id); - Some(alloc_id) - } - } - } - }; - - // Now decode the actual data - let alloc_id = decoder.with_position(pos, |decoder| { - match alloc_kind { - AllocKind::Alloc => { - let allocation = <&'tcx Allocation as Decodable>::decode(decoder)?; - // We already have a reserved AllocId. - let alloc_id = alloc_id.unwrap(); - trace!("decoded alloc {:?} {:#?}", alloc_id, allocation); - decoder.tcx().alloc_map.lock().set_id_same_memory(alloc_id, allocation); - Ok(alloc_id) - }, - AllocKind::Fn => { - assert!(alloc_id.is_none()); - trace!("creating fn alloc id"); - let instance = ty::Instance::decode(decoder)?; - trace!("decoded fn alloc instance: {:?}", instance); - let alloc_id = decoder.tcx().alloc_map.lock().create_fn_alloc(instance); - Ok(alloc_id) - }, - AllocKind::Static => { - assert!(alloc_id.is_none()); - trace!("creating extern static alloc id at"); - let did = DefId::decode(decoder)?; - let alloc_id = decoder.tcx().alloc_map.lock().intern_static(did); - Ok(alloc_id) - } - } - })?; - - self.state.decoding_state[idx].with_lock(|entry| { - *entry = State::Done(alloc_id); - }); - - Ok(alloc_id) - } -} - -impl fmt::Display for AllocId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, RustcDecodable, RustcEncodable)] -pub enum AllocType<'tcx, M> { - /// The alloc id is used as a function pointer - Function(Instance<'tcx>), - /// The alloc id points to a "lazy" static variable that did not get computed (yet). - /// This is also used to break the cycle in recursive statics. - Static(DefId), - /// The alloc id points to memory - Memory(M) -} - -pub struct AllocMap<'tcx, M> { - /// Lets you know what an AllocId refers to - id_to_type: FxHashMap>, - - /// Used to ensure that functions and statics only get one associated AllocId - type_interner: FxHashMap, AllocId>, - - /// The AllocId to assign to the next requested id. - /// Always incremented, never gets smaller. - next_id: AllocId, -} - -impl<'tcx, M: fmt::Debug + Eq + Hash + Clone> AllocMap<'tcx, M> { - pub fn new() -> Self { - AllocMap { - id_to_type: Default::default(), - type_interner: Default::default(), - next_id: AllocId(0), - } - } - - /// obtains a new allocation ID that can be referenced but does not - /// yet have an allocation backing it. - pub fn reserve( - &mut self, - ) -> AllocId { - let next = self.next_id; - self.next_id.0 = self.next_id.0 - .checked_add(1) - .expect("You overflowed a u64 by incrementing by 1... \ - You've just earned yourself a free drink if we ever meet. \ - Seriously, how did you do that?!"); - next - } - - fn intern(&mut self, alloc_type: AllocType<'tcx, M>) -> AllocId { - if let Some(&alloc_id) = self.type_interner.get(&alloc_type) { - return alloc_id; - } - let id = self.reserve(); - debug!("creating alloc_type {:?} with id {}", alloc_type, id); - self.id_to_type.insert(id, alloc_type.clone()); - self.type_interner.insert(alloc_type, id); - id - } - - // FIXME: Check if functions have identity. If not, we should not intern these, - // but instead create a new id per use. - // Alternatively we could just make comparing function pointers an error. - pub fn create_fn_alloc(&mut self, instance: Instance<'tcx>) -> AllocId { - self.intern(AllocType::Function(instance)) - } - - pub fn get(&self, id: AllocId) -> Option> { - self.id_to_type.get(&id).cloned() - } - - pub fn unwrap_memory(&self, id: AllocId) -> M { - match self.get(id) { - Some(AllocType::Memory(mem)) => mem, - _ => bug!("expected allocation id {} to point to memory", id), - } - } - - pub fn intern_static(&mut self, static_id: DefId) -> AllocId { - self.intern(AllocType::Static(static_id)) - } - - pub fn allocate(&mut self, mem: M) -> AllocId { - let id = self.reserve(); - self.set_id_memory(id, mem); - id - } - - pub fn set_id_memory(&mut self, id: AllocId, mem: M) { - if let Some(old) = self.id_to_type.insert(id, AllocType::Memory(mem)) { - bug!("tried to set allocation id {}, but it was already existing as {:#?}", id, old); - } - } - - pub fn set_id_same_memory(&mut self, id: AllocId, mem: M) { - self.id_to_type.insert_same(id, AllocType::Memory(mem)); - } -} #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)] pub struct Allocation { @@ -578,175 +73,3 @@ impl Allocation { } impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {} - -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, RustcEncodable, RustcDecodable)] -pub struct Relocations(SortedMap); - -impl Relocations { - pub fn new() -> Self { - Relocations(SortedMap::new()) - } - - // The caller must guarantee that the given relocations are already sorted - // by address and contain no duplicates. - pub fn from_presorted(r: Vec<(Size, (Tag, Id))>) -> Self { - Relocations(SortedMap::from_presorted_elements(r)) - } -} - -impl Deref for Relocations { - type Target = SortedMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Relocations { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Methods to access integers in the target endianness -//////////////////////////////////////////////////////////////////////////////// - -pub fn write_target_uint( - endianness: layout::Endian, - mut target: &mut [u8], - data: u128, -) -> Result<(), io::Error> { - let len = target.len(); - match endianness { - layout::Endian::Little => target.write_uint128::(data, len), - layout::Endian::Big => target.write_uint128::(data, len), - } -} - -pub fn read_target_uint(endianness: layout::Endian, mut source: &[u8]) -> Result { - match endianness { - layout::Endian::Little => source.read_uint128::(source.len()), - layout::Endian::Big => source.read_uint128::(source.len()), - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Methods to faciliate working with signed integers stored in a u128 -//////////////////////////////////////////////////////////////////////////////// - -pub fn sign_extend(value: u128, size: Size) -> u128 { - let size = size.bits(); - // sign extend - let shift = 128 - size; - // shift the unsigned value to the left - // and back to the right as signed (essentially fills with FF on the left) - (((value << shift) as i128) >> shift) as u128 -} - -pub fn truncate(value: u128, size: Size) -> u128 { - let size = size.bits(); - let shift = 128 - size; - // truncate (shift left to drop out leftover values, shift right to fill with zeroes) - (value << shift) >> shift -} - -//////////////////////////////////////////////////////////////////////////////// -// Undefined byte tracking -//////////////////////////////////////////////////////////////////////////////// - -type Block = u64; -const BLOCK_SIZE: u64 = 64; - -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)] -pub struct UndefMask { - blocks: Vec, - len: Size, -} - -impl_stable_hash_for!(struct mir::interpret::UndefMask{blocks, len}); - -impl UndefMask { - pub fn new(size: Size) -> Self { - let mut m = UndefMask { - blocks: vec![], - len: Size::ZERO, - }; - m.grow(size, false); - m - } - - /// Check whether the range `start..end` (end-exclusive) is entirely defined. - /// - /// Returns `Ok(())` if it's defined. Otherwise returns the index of the byte - /// at which the first undefined access begins. - #[inline] - pub fn is_range_defined(&self, start: Size, end: Size) -> Result<(), Size> { - if end > self.len { - return Err(self.len); - } - - let idx = (start.bytes()..end.bytes()) - .map(|i| Size::from_bytes(i)) - .find(|&i| !self.get(i)); - - match idx { - Some(idx) => Err(idx), - None => Ok(()) - } - } - - pub fn set_range(&mut self, start: Size, end: Size, new_state: bool) { - let len = self.len; - if end > len { - self.grow(end - len, new_state); - } - self.set_range_inbounds(start, end, new_state); - } - - pub fn set_range_inbounds(&mut self, start: Size, end: Size, new_state: bool) { - for i in start.bytes()..end.bytes() { - self.set(Size::from_bytes(i), new_state); - } - } - - #[inline] - pub fn get(&self, i: Size) -> bool { - let (block, bit) = bit_index(i); - (self.blocks[block] & 1 << bit) != 0 - } - - #[inline] - pub fn set(&mut self, i: Size, new_state: bool) { - let (block, bit) = bit_index(i); - if new_state { - self.blocks[block] |= 1 << bit; - } else { - self.blocks[block] &= !(1 << bit); - } - } - - pub fn grow(&mut self, amount: Size, new_state: bool) { - let unused_trailing_bits = self.blocks.len() as u64 * BLOCK_SIZE - self.len.bytes(); - if amount.bytes() > unused_trailing_bits { - let additional_blocks = amount.bytes() / BLOCK_SIZE + 1; - assert_eq!(additional_blocks as usize as u64, additional_blocks); - self.blocks.extend( - iter::repeat(0).take(additional_blocks as usize), - ); - } - let start = self.len; - self.len += amount; - self.set_range_inbounds(start, start + amount, new_state); - } -} - -#[inline] -fn bit_index(bits: Size) -> (usize, usize) { - let bits = bits.bytes(); - let a = bits / BLOCK_SIZE; - let b = bits % BLOCK_SIZE; - assert_eq!(a as usize as u64, a); - assert_eq!(b as usize as u64, b); - (a as usize, b as usize) -} diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index 62cc3113a3d37..174ef2070ce17 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -17,6 +17,7 @@ macro_rules! err { mod error; mod value; +mod allocation; pub use self::error::{ EvalError, EvalResult, EvalErrorKind, AssertMessage, ConstEvalErr, struct_error, @@ -25,17 +26,18 @@ pub use self::error::{ pub use self::value::{Scalar, ConstValue}; +pub use self::allocation::Allocation; + use std::fmt; use mir; use hir::def_id::DefId; use ty::{self, TyCtxt, Instance}; -use ty::layout::{self, Align, HasDataLayout, Size}; +use ty::layout::{self, HasDataLayout, Size}; use middle::region; use std::iter; use std::io; use std::ops::{Deref, DerefMut}; use std::hash::Hash; -use syntax::ast::Mutability; use rustc_serialize::{Encoder, Decodable, Encodable}; use rustc_data_structures::sorted_map::SortedMap; use rustc_data_structures::fx::FxHashMap; @@ -523,62 +525,6 @@ impl<'tcx, M: fmt::Debug + Eq + Hash + Clone> AllocMap<'tcx, M> { } } -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)] -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, - /// Maps from byte addresses to extra data for each pointer. - /// Only the first byte of a pointer is inserted into the map; i.e., - /// every entry in this map applies to `pointer_size` consecutive bytes starting - /// at the given offset. - pub relocations: Relocations, - /// Denotes undefined memory. Reading from undefined memory is forbidden in miri - pub undef_mask: UndefMask, - /// The alignment of the allocation to detect unaligned reads. - pub align: Align, - /// Whether the allocation is mutable. - /// 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 { - /// 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); - undef_mask.grow(Size::from_bytes(slice.len() as u64), true); - Self { - bytes: slice.to_owned(), - relocations: Relocations::new(), - undef_mask, - align, - mutability: Mutability::Immutable, - extra: Extra::default(), - } - } - - pub fn from_byte_aligned_bytes(slice: &[u8]) -> Self { - Allocation::from_bytes(slice, Align::from_bytes(1, 1).unwrap()) - } - - pub fn undef(size: Size, align: Align) -> Self { - assert_eq!(size.bytes() as usize as u64, size.bytes()); - Allocation { - bytes: vec![0; size.bytes() as usize], - relocations: Relocations::new(), - undef_mask: UndefMask::new(size), - align, - mutability: Mutability::Mutable, - extra: Extra::default(), - } - } -} - -impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {} - #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, RustcEncodable, RustcDecodable)] pub struct Relocations(SortedMap); From 610e4c4b40ebf17dd9cc32689d0cce683cf94df2 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 23 Oct 2018 18:16:01 +0200 Subject: [PATCH 03/20] Copy various functions from `memory.rs` to `allocation.rs` --- src/librustc/mir/interpret/allocation.rs | 600 +++++++++++++++++++++++ src/librustc_mir/interpret/memory.rs | 546 --------------------- 2 files changed, 600 insertions(+), 546 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index 8444cf5726f84..bc185868c6a96 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -73,3 +73,603 @@ impl Allocation { } impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {} + +/// Byte accessors +impl<'tcx, Tag, Extra> Allocation { + /// The last argument controls whether we error out when there are undefined + /// or pointer bytes. You should never call this, call `get_bytes` or + /// `get_bytes_with_undef_and_ptr` instead, + /// + /// This function also guarantees that the resulting pointer will remain stable + /// even when new allocations are pushed to the `HashMap`. `copy_repeatedly` relies + /// on that. + fn get_bytes_internal( + &self, + ptr: Pointer, + size: Size, + align: Align, + check_defined_and_ptr: bool, + ) -> EvalResult<'tcx, &[u8]> { + assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`"); + self.check_align(ptr.into(), align)?; + self.check_bounds(ptr, size, true)?; + + if check_defined_and_ptr { + self.check_defined(ptr, size)?; + self.check_relocations(ptr, size)?; + } else { + // We still don't want relocations on the *edges* + self.check_relocation_edges(ptr, size)?; + } + + 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; + Ok(&alloc.bytes[offset..offset + size.bytes() as usize]) + } + + #[inline] + fn get_bytes( + &self, + ptr: Pointer, + size: Size, + align: Align + ) -> EvalResult<'tcx, &[u8]> { + self.get_bytes_internal(ptr, size, align, true) + } + + /// It is the caller's responsibility to handle undefined and pointer bytes. + /// However, this still checks that there are no relocations on the *edges*. + #[inline] + fn get_bytes_with_undef_and_ptr( + &self, + ptr: Pointer, + size: Size, + align: Align + ) -> EvalResult<'tcx, &[u8]> { + self.get_bytes_internal(ptr, size, align, false) + } + + /// Just calling this already marks everything as defined and removes relocations, + /// so be sure to actually put data there! + fn get_bytes_mut( + &mut self, + ptr: Pointer, + size: Size, + align: Align, + ) -> EvalResult<'tcx, &mut [u8]> { + assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`"); + self.check_align(ptr.into(), align)?; + self.check_bounds(ptr, size, true)?; + + self.mark_definedness(ptr, size, true)?; + 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; + Ok(&mut alloc.bytes[offset..offset + size.bytes() as usize]) + } +} + +/// Reading and writing +impl<'tcx, Tag, Extra> Allocation { + pub fn copy( + &mut self, + src: Scalar, + src_align: Align, + dest: Scalar, + dest_align: Align, + size: Size, + nonoverlapping: bool, + ) -> EvalResult<'tcx> { + self.copy_repeatedly(src, src_align, dest, dest_align, size, 1, nonoverlapping) + } + + pub fn copy_repeatedly( + &mut self, + src: Scalar, + src_align: Align, + dest: Scalar, + dest_align: Align, + size: Size, + length: u64, + nonoverlapping: bool, + ) -> EvalResult<'tcx> { + if size.bytes() == 0 { + // Nothing to do for ZST, other than checking alignment and non-NULLness. + self.check_align(src, src_align)?; + self.check_align(dest, dest_align)?; + return Ok(()); + } + let src = src.to_ptr()?; + let dest = dest.to_ptr()?; + + // first copy the relocations to a temporary buffer, because + // `get_bytes_mut` will clear the relocations, which is correct, + // since we don't want to keep any relocations at the target. + // (`get_bytes_with_undef_and_ptr` below checks that there are no + // relocations overlapping the edges; those would not be handled correctly). + let relocations = { + let relocations = self.relocations(src, size)?; + let mut new_relocations = Vec::with_capacity(relocations.len() * (length as usize)); + for i in 0..length { + new_relocations.extend( + relocations + .iter() + .map(|&(offset, reloc)| { + (offset + dest.offset - src.offset + (i * size * relocations.len() as u64), + reloc) + }) + ); + } + + new_relocations + }; + + // This also checks alignment, and relocation edges on the src. + let src_bytes = self.get_bytes_with_undef_and_ptr(src, size, src_align)?.as_ptr(); + let dest_bytes = self.get_bytes_mut(dest, size * length, dest_align)?.as_mut_ptr(); + + // SAFE: The above indexing would have panicked if there weren't at least `size` bytes + // behind `src` and `dest`. Also, we use the overlapping-safe `ptr::copy` if `src` and + // `dest` could possibly overlap. + // The pointers above remain valid even if the `HashMap` table is moved around because they + // point into the `Vec` storing the bytes. + unsafe { + assert_eq!(size.bytes() as usize as u64, size.bytes()); + if src.alloc_id == dest.alloc_id { + if nonoverlapping { + if (src.offset <= dest.offset && src.offset + size > dest.offset) || + (dest.offset <= src.offset && dest.offset + size > src.offset) + { + return err!(Intrinsic( + "copy_nonoverlapping called on overlapping ranges".to_string(), + )); + } + } + + for i in 0..length { + ptr::copy(src_bytes, + dest_bytes.offset((size.bytes() * i) as isize), + size.bytes() as usize); + } + } else { + for i in 0..length { + ptr::copy_nonoverlapping(src_bytes, + dest_bytes.offset((size.bytes() * i) as isize), + size.bytes() as usize); + } + } + } + + // copy definedness to the destination + self.copy_undef_mask(src, dest, size, length)?; + // copy the relocations to the destination + self.get_mut(dest.alloc_id)?.relocations.insert_presorted(relocations); + + Ok(()) + } + + pub fn read_c_str(&self, ptr: Pointer) -> EvalResult<'tcx, &[u8]> { + let alloc = self.get(ptr.alloc_id)?; + assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes()); + let offset = ptr.offset.bytes() as usize; + match alloc.bytes[offset..].iter().position(|&c| c == 0) { + Some(size) => { + let p1 = Size::from_bytes((size + 1) as u64); + self.check_relocations(ptr, p1)?; + self.check_defined(ptr, p1)?; + Ok(&alloc.bytes[offset..offset + size]) + } + None => err!(UnterminatedCString(ptr.erase_tag())), + } + } + + pub fn check_bytes( + &self, + ptr: Scalar, + size: Size, + allow_ptr_and_undef: bool, + ) -> EvalResult<'tcx> { + // Empty accesses don't need to be valid pointers, but they should still be non-NULL + let align = Align::from_bytes(1, 1).unwrap(); + if size.bytes() == 0 { + self.check_align(ptr, align)?; + return Ok(()); + } + let ptr = ptr.to_ptr()?; + // Check bounds, align and relocations on the edges + self.get_bytes_with_undef_and_ptr(ptr, size, align)?; + // Check undef and ptr + if !allow_ptr_and_undef { + self.check_defined(ptr, size)?; + self.check_relocations(ptr, size)?; + } + Ok(()) + } + + pub fn read_bytes(&self, ptr: Scalar, size: Size) -> EvalResult<'tcx, &[u8]> { + // Empty accesses don't need to be valid pointers, but they should still be non-NULL + let align = Align::from_bytes(1, 1).unwrap(); + if size.bytes() == 0 { + self.check_align(ptr, align)?; + return Ok(&[]); + } + self.get_bytes(ptr.to_ptr()?, size, align) + } + + pub fn write_bytes(&mut self, ptr: Scalar, src: &[u8]) -> EvalResult<'tcx> { + // Empty accesses don't need to be valid pointers, but they should still be non-NULL + let align = Align::from_bytes(1, 1).unwrap(); + if src.is_empty() { + self.check_align(ptr, align)?; + return Ok(()); + } + let bytes = self.get_bytes_mut(ptr.to_ptr()?, Size::from_bytes(src.len() as u64), align)?; + bytes.clone_from_slice(src); + Ok(()) + } + + pub fn write_repeat( + &mut self, + ptr: Scalar, + val: u8, + count: Size + ) -> EvalResult<'tcx> { + // Empty accesses don't need to be valid pointers, but they should still be non-NULL + let align = Align::from_bytes(1, 1).unwrap(); + if count.bytes() == 0 { + self.check_align(ptr, align)?; + return Ok(()); + } + let bytes = self.get_bytes_mut(ptr.to_ptr()?, count, align)?; + for b in bytes { + *b = val; + } + Ok(()) + } + + /// Read a *non-ZST* scalar + pub fn read_scalar( + &self, + ptr: Pointer, + ptr_align: Align, + size: Size + ) -> EvalResult<'tcx, ScalarMaybeUndef> { + // get_bytes_unchecked tests alignment and relocation edges + let bytes = self.get_bytes_with_undef_and_ptr( + ptr, size, ptr_align.min(self.int_align(size)) + )?; + // Undef check happens *after* we established that the alignment is correct. + // We must not return Ok() for unaligned pointers! + if self.check_defined(ptr, size).is_err() { + // this inflates undefined bytes to the entire scalar, even if only a few + // bytes are undefined + return Ok(ScalarMaybeUndef::Undef); + } + // Now we do the actual reading + let bits = read_target_uint(self.tcx.data_layout.endian, bytes).unwrap(); + // See if we got a pointer + if size != self.pointer_size() { + // *Now* better make sure that the inside also is free of relocations. + self.check_relocations(ptr, size)?; + } else { + let alloc = self.get(ptr.alloc_id)?; + match alloc.relocations.get(&ptr.offset) { + Some(&(tag, alloc_id)) => { + let ptr = Pointer::new_with_tag(alloc_id, Size::from_bytes(bits as u64), tag); + return Ok(ScalarMaybeUndef::Scalar(ptr.into())) + } + None => {}, + } + } + // We don't. Just return the bits. + Ok(ScalarMaybeUndef::Scalar(Scalar::from_uint(bits, size))) + } + + pub fn read_ptr_sized( + &self, + ptr: Pointer, + ptr_align: Align + ) -> EvalResult<'tcx, ScalarMaybeUndef> { + self.read_scalar(ptr, ptr_align, self.pointer_size()) + } + + /// Write a *non-ZST* scalar + pub fn write_scalar( + &mut self, + ptr: Pointer, + ptr_align: Align, + val: ScalarMaybeUndef, + type_size: Size, + ) -> EvalResult<'tcx> { + let val = match val { + ScalarMaybeUndef::Scalar(scalar) => scalar, + ScalarMaybeUndef::Undef => return self.mark_definedness(ptr, type_size, false), + }; + + let bytes = match val { + Scalar::Ptr(val) => { + assert_eq!(type_size, self.pointer_size()); + val.offset.bytes() as u128 + } + + Scalar::Bits { bits, size } => { + assert_eq!(size as u64, type_size.bytes()); + debug_assert_eq!(truncate(bits, Size::from_bytes(size.into())), bits, + "Unexpected value of size {} when writing to memory", size); + bits + }, + }; + + { + // get_bytes_mut checks alignment + let endian = self.tcx.data_layout.endian; + let dst = self.get_bytes_mut(ptr, type_size, ptr_align)?; + write_target_uint(endian, dst, bytes).unwrap(); + } + + // See if we have to also write a relocation + match val { + Scalar::Ptr(val) => { + self.get_mut(ptr.alloc_id)?.relocations.insert( + ptr.offset, + (val.tag, val.alloc_id), + ); + } + _ => {} + } + + Ok(()) + } + + pub fn write_ptr_sized( + &mut self, + ptr: Pointer, + ptr_align: Align, + val: ScalarMaybeUndef + ) -> EvalResult<'tcx> { + let ptr_size = self.pointer_size(); + self.write_scalar(ptr.into(), ptr_align, val, ptr_size) + } + + fn int_align(&self, size: Size) -> Align { + // We assume pointer-sized integers have the same alignment as pointers. + // We also assume signed and unsigned integers of the same size have the same alignment. + let ity = match size.bytes() { + 1 => layout::I8, + 2 => layout::I16, + 4 => layout::I32, + 8 => layout::I64, + 16 => layout::I128, + _ => bug!("bad integer size: {}", size.bytes()), + }; + ity.align(self) + } +} + +/// Relocations +impl<'tcx, Tag, Extra> Allocation { + /// Return all relocations overlapping with the given ptr-offset pair. + fn relocations( + &self, + ptr: Pointer, + size: Size, + ) -> EvalResult<'tcx, &[(Size, (M::PointerTag, AllocId))]> { + // We have to go back `pointer_size - 1` bytes, as that one would still overlap with + // the beginning of this range. + let start = ptr.offset.bytes().saturating_sub(self.pointer_size().bytes() - 1); + let end = ptr.offset + size; // this does overflow checking + Ok(self.get(ptr.alloc_id)?.relocations.range(Size::from_bytes(start)..end)) + } + + /// Check that there ar eno relocations overlapping with the given range. + #[inline(always)] + fn check_relocations(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { + if self.relocations(ptr, size)?.len() != 0 { + err!(ReadPointerAsBytes) + } else { + Ok(()) + } + } + + /// Remove all relocations inside the given range. + /// If there are relocations overlapping with the edges, they + /// are removed as well *and* the bytes they cover are marked as + /// uninitialized. This is a somewhat odd "spooky action at a distance", + /// but it allows strictly more code to run than if we would just error + /// immediately in that case. + fn clear_relocations(&mut self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { + // Find the start and end of the given range and its outermost relocations. + let (first, last) = { + // Find all relocations overlapping the given range. + let relocations = self.relocations(ptr, size)?; + if relocations.is_empty() { + return Ok(()); + } + + (relocations.first().unwrap().0, + relocations.last().unwrap().0 + self.pointer_size()) + }; + let start = ptr.offset; + let end = start + size; + + let alloc = self.get_mut(ptr.alloc_id)?; + + // Mark parts of the outermost relocations as undefined if they partially fall outside the + // given range. + if first < start { + alloc.undef_mask.set_range(first, start, false); + } + if last > end { + alloc.undef_mask.set_range(end, last, false); + } + + // Forget all the relocations. + alloc.relocations.remove_range(first..last); + + Ok(()) + } + + /// Error if there are relocations overlapping with the edges of the + /// given memory range. + #[inline] + fn check_relocation_edges(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { + self.check_relocations(ptr, Size::ZERO)?; + self.check_relocations(ptr.offset(size, self)?, Size::ZERO)?; + Ok(()) + } +} + +/// Undefined bytes +impl<'tcx, Tag, Extra> Allocation { + // FIXME: Add a fast version for the common, nonoverlapping case + fn copy_undef_mask( + &mut self, + src: Pointer, + dest: Pointer, + size: Size, + repeat: u64, + ) -> EvalResult<'tcx> { + // The bits have to be saved locally before writing to dest in case src and dest overlap. + assert_eq!(size.bytes() as usize as u64, size.bytes()); + + let undef_mask = self.get(src.alloc_id)?.undef_mask.clone(); + let dest_allocation = self.get_mut(dest.alloc_id)?; + + for i in 0..size.bytes() { + let defined = undef_mask.get(src.offset + Size::from_bytes(i)); + + for j in 0..repeat { + dest_allocation.undef_mask.set( + dest.offset + Size::from_bytes(i + (size.bytes() * j)), + defined + ); + } + } + + Ok(()) + } + + /// Checks that a range of bytes is defined. If not, returns the `ReadUndefBytes` + /// error which will report the first byte which is undefined. + #[inline] + fn check_defined(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { + let alloc = self.get(ptr.alloc_id)?; + alloc.undef_mask.is_range_defined( + ptr.offset, + ptr.offset + size, + ).or_else(|idx| err!(ReadUndefBytes(idx))) + } + + pub fn mark_definedness( + &mut self, + ptr: Pointer, + size: Size, + new_state: bool, + ) -> EvalResult<'tcx> { + if size.bytes() == 0 { + return Ok(()); + } + let alloc = self.get_mut(ptr.alloc_id)?; + alloc.undef_mask.set_range( + ptr.offset, + ptr.offset + size, + new_state, + ); + Ok(()) + } +} + +impl<'tcx, Tag, Extra> Allocation { + /// Check that the pointer is aligned AND non-NULL. This supports ZSTs in two ways: + /// You can pass a scalar, and a `Pointer` does not have to actually still be allocated. + pub fn check_align( + &self, + ptr: Scalar, + required_align: Align + ) -> EvalResult<'tcx> { + // Check non-NULL/Undef, extract offset + let (offset, alloc_align) = match ptr { + Scalar::Ptr(ptr) => { + let (size, align) = self.get_size_and_align(ptr.alloc_id); + // check this is not NULL -- which we can ensure only if this is in-bounds + // of some (potentially dead) allocation. + if ptr.offset > size { + return err!(PointerOutOfBounds { + ptr: ptr.erase_tag(), + access: true, + allocation_size: size, + }); + }; + // keep data for alignment check + (ptr.offset.bytes(), align) + } + Scalar::Bits { bits, size } => { + assert_eq!(size as u64, self.pointer_size().bytes()); + assert!(bits < (1u128 << self.pointer_size().bits())); + // check this is not NULL + if bits == 0 { + return err!(InvalidNullPointerUsage); + } + // the "base address" is 0 and hence always aligned + (bits as u64, required_align) + } + }; + // Check alignment + if alloc_align.abi() < required_align.abi() { + return err!(AlignmentCheckFailed { + has: alloc_align, + required: required_align, + }); + } + if offset % required_align.abi() == 0 { + Ok(()) + } else { + let has = offset % required_align.abi(); + err!(AlignmentCheckFailed { + has: Align::from_bytes(has, has).unwrap(), + required: required_align, + }) + } + } + + /// Check if the pointer is "in-bounds". Notice that a pointer pointing at the end + /// of an allocation (i.e., at the first *inaccessible* location) *is* considered + /// in-bounds! This follows C's/LLVM's rules. The `access` boolean is just used + /// for the error message. + /// If you want to check bounds before doing a memory access, be sure to + /// check the pointer one past the end of your access, then everything will + /// work out exactly. + pub fn check_bounds_ptr(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> { + let alloc = self.get(ptr.alloc_id)?; + let allocation_size = alloc.bytes.len() as u64; + if ptr.offset.bytes() > allocation_size { + return err!(PointerOutOfBounds { + ptr: ptr.erase_tag(), + access, + allocation_size: Size::from_bytes(allocation_size), + }); + } + Ok(()) + } + + /// Check if the memory range beginning at `ptr` and of size `Size` is "in-bounds". + #[inline(always)] + pub fn check_bounds( + &self, + ptr: Pointer, + size: Size, + access: bool + ) -> EvalResult<'tcx> { + // if ptr.offset is in bounds, then so is ptr (because offset checks for overflow) + self.check_bounds_ptr(ptr.offset(size, &*self)?, access) + } +} \ No newline at end of file diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs index 689a29cff6e9e..07d83097acccb 100644 --- a/src/librustc_mir/interpret/memory.rs +++ b/src/librustc_mir/interpret/memory.rs @@ -297,38 +297,6 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { }) } } - - /// Check if the pointer is "in-bounds". Notice that a pointer pointing at the end - /// of an allocation (i.e., at the first *inaccessible* location) *is* considered - /// in-bounds! This follows C's/LLVM's rules. The `access` boolean is just used - /// for the error message. - /// If you want to check bounds before doing a memory access, be sure to - /// check the pointer one past the end of your access, then everything will - /// work out exactly. - pub fn check_bounds_ptr(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> { - let alloc = self.get(ptr.alloc_id)?; - let allocation_size = alloc.bytes.len() as u64; - if ptr.offset.bytes() > allocation_size { - return err!(PointerOutOfBounds { - ptr: ptr.erase_tag(), - access, - allocation_size: Size::from_bytes(allocation_size), - }); - } - Ok(()) - } - - /// Check if the memory range beginning at `ptr` and of size `Size` is "in-bounds". - #[inline(always)] - pub fn check_bounds( - &self, - ptr: Pointer, - size: Size, - access: bool - ) -> EvalResult<'tcx> { - // if ptr.offset is in bounds, then so is ptr (because offset checks for overflow) - self.check_bounds_ptr(ptr.offset(size, &*self)?, access) - } } /// Allocation accessors @@ -615,90 +583,6 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { } } -/// Byte accessors -impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { - /// The last argument controls whether we error out when there are undefined - /// or pointer bytes. You should never call this, call `get_bytes` or - /// `get_bytes_with_undef_and_ptr` instead, - /// - /// This function also guarantees that the resulting pointer will remain stable - /// even when new allocations are pushed to the `HashMap`. `copy_repeatedly` relies - /// on that. - fn get_bytes_internal( - &self, - ptr: Pointer, - size: Size, - align: Align, - check_defined_and_ptr: bool, - ) -> EvalResult<'tcx, &[u8]> { - assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`"); - self.check_align(ptr.into(), align)?; - self.check_bounds(ptr, size, true)?; - - if check_defined_and_ptr { - self.check_defined(ptr, size)?; - self.check_relocations(ptr, size)?; - } else { - // We still don't want relocations on the *edges* - self.check_relocation_edges(ptr, size)?; - } - - 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; - Ok(&alloc.bytes[offset..offset + size.bytes() as usize]) - } - - #[inline] - fn get_bytes( - &self, - ptr: Pointer, - size: Size, - align: Align - ) -> EvalResult<'tcx, &[u8]> { - self.get_bytes_internal(ptr, size, align, true) - } - - /// It is the caller's responsibility to handle undefined and pointer bytes. - /// However, this still checks that there are no relocations on the *edges*. - #[inline] - fn get_bytes_with_undef_and_ptr( - &self, - ptr: Pointer, - size: Size, - align: Align - ) -> EvalResult<'tcx, &[u8]> { - self.get_bytes_internal(ptr, size, align, false) - } - - /// Just calling this already marks everything as defined and removes relocations, - /// so be sure to actually put data there! - fn get_bytes_mut( - &mut self, - ptr: Pointer, - size: Size, - align: Align, - ) -> EvalResult<'tcx, &mut [u8]> { - assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`"); - self.check_align(ptr.into(), align)?; - self.check_bounds(ptr, size, true)?; - - self.mark_definedness(ptr, size, true)?; - 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; - Ok(&mut alloc.bytes[offset..offset + size.bytes() as usize]) - } -} - /// Interning (for CTFE) impl<'a, 'mir, 'tcx, M> Memory<'a, 'mir, 'tcx, M> where @@ -742,433 +626,3 @@ where Ok(()) } } - -/// Reading and writing -impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { - pub fn copy( - &mut self, - src: Scalar, - src_align: Align, - dest: Scalar, - dest_align: Align, - size: Size, - nonoverlapping: bool, - ) -> EvalResult<'tcx> { - self.copy_repeatedly(src, src_align, dest, dest_align, size, 1, nonoverlapping) - } - - pub fn copy_repeatedly( - &mut self, - src: Scalar, - src_align: Align, - dest: Scalar, - dest_align: Align, - size: Size, - length: u64, - nonoverlapping: bool, - ) -> EvalResult<'tcx> { - if size.bytes() == 0 { - // Nothing to do for ZST, other than checking alignment and non-NULLness. - self.check_align(src, src_align)?; - self.check_align(dest, dest_align)?; - return Ok(()); - } - let src = src.to_ptr()?; - let dest = dest.to_ptr()?; - - // first copy the relocations to a temporary buffer, because - // `get_bytes_mut` will clear the relocations, which is correct, - // since we don't want to keep any relocations at the target. - // (`get_bytes_with_undef_and_ptr` below checks that there are no - // relocations overlapping the edges; those would not be handled correctly). - let relocations = { - let relocations = self.relocations(src, size)?; - let mut new_relocations = Vec::with_capacity(relocations.len() * (length as usize)); - for i in 0..length { - new_relocations.extend( - relocations - .iter() - .map(|&(offset, reloc)| { - (offset + dest.offset - src.offset + (i * size * relocations.len() as u64), - reloc) - }) - ); - } - - new_relocations - }; - - // This also checks alignment, and relocation edges on the src. - let src_bytes = self.get_bytes_with_undef_and_ptr(src, size, src_align)?.as_ptr(); - let dest_bytes = self.get_bytes_mut(dest, size * length, dest_align)?.as_mut_ptr(); - - // SAFE: The above indexing would have panicked if there weren't at least `size` bytes - // behind `src` and `dest`. Also, we use the overlapping-safe `ptr::copy` if `src` and - // `dest` could possibly overlap. - // The pointers above remain valid even if the `HashMap` table is moved around because they - // point into the `Vec` storing the bytes. - unsafe { - assert_eq!(size.bytes() as usize as u64, size.bytes()); - if src.alloc_id == dest.alloc_id { - if nonoverlapping { - if (src.offset <= dest.offset && src.offset + size > dest.offset) || - (dest.offset <= src.offset && dest.offset + size > src.offset) - { - return err!(Intrinsic( - "copy_nonoverlapping called on overlapping ranges".to_string(), - )); - } - } - - for i in 0..length { - ptr::copy(src_bytes, - dest_bytes.offset((size.bytes() * i) as isize), - size.bytes() as usize); - } - } else { - for i in 0..length { - ptr::copy_nonoverlapping(src_bytes, - dest_bytes.offset((size.bytes() * i) as isize), - size.bytes() as usize); - } - } - } - - // copy definedness to the destination - self.copy_undef_mask(src, dest, size, length)?; - // copy the relocations to the destination - self.get_mut(dest.alloc_id)?.relocations.insert_presorted(relocations); - - Ok(()) - } - - pub fn read_c_str(&self, ptr: Pointer) -> EvalResult<'tcx, &[u8]> { - let alloc = self.get(ptr.alloc_id)?; - assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes()); - let offset = ptr.offset.bytes() as usize; - match alloc.bytes[offset..].iter().position(|&c| c == 0) { - Some(size) => { - let p1 = Size::from_bytes((size + 1) as u64); - self.check_relocations(ptr, p1)?; - self.check_defined(ptr, p1)?; - Ok(&alloc.bytes[offset..offset + size]) - } - None => err!(UnterminatedCString(ptr.erase_tag())), - } - } - - pub fn check_bytes( - &self, - ptr: Scalar, - size: Size, - allow_ptr_and_undef: bool, - ) -> EvalResult<'tcx> { - // Empty accesses don't need to be valid pointers, but they should still be non-NULL - let align = Align::from_bytes(1, 1).unwrap(); - if size.bytes() == 0 { - self.check_align(ptr, align)?; - return Ok(()); - } - let ptr = ptr.to_ptr()?; - // Check bounds, align and relocations on the edges - self.get_bytes_with_undef_and_ptr(ptr, size, align)?; - // Check undef and ptr - if !allow_ptr_and_undef { - self.check_defined(ptr, size)?; - self.check_relocations(ptr, size)?; - } - Ok(()) - } - - pub fn read_bytes(&self, ptr: Scalar, size: Size) -> EvalResult<'tcx, &[u8]> { - // Empty accesses don't need to be valid pointers, but they should still be non-NULL - let align = Align::from_bytes(1, 1).unwrap(); - if size.bytes() == 0 { - self.check_align(ptr, align)?; - return Ok(&[]); - } - self.get_bytes(ptr.to_ptr()?, size, align) - } - - pub fn write_bytes(&mut self, ptr: Scalar, src: &[u8]) -> EvalResult<'tcx> { - // Empty accesses don't need to be valid pointers, but they should still be non-NULL - let align = Align::from_bytes(1, 1).unwrap(); - if src.is_empty() { - self.check_align(ptr, align)?; - return Ok(()); - } - let bytes = self.get_bytes_mut(ptr.to_ptr()?, Size::from_bytes(src.len() as u64), align)?; - bytes.clone_from_slice(src); - Ok(()) - } - - pub fn write_repeat( - &mut self, - ptr: Scalar, - val: u8, - count: Size - ) -> EvalResult<'tcx> { - // Empty accesses don't need to be valid pointers, but they should still be non-NULL - let align = Align::from_bytes(1, 1).unwrap(); - if count.bytes() == 0 { - self.check_align(ptr, align)?; - return Ok(()); - } - let bytes = self.get_bytes_mut(ptr.to_ptr()?, count, align)?; - for b in bytes { - *b = val; - } - Ok(()) - } - - /// Read a *non-ZST* scalar - pub fn read_scalar( - &self, - ptr: Pointer, - ptr_align: Align, - size: Size - ) -> EvalResult<'tcx, ScalarMaybeUndef> { - // get_bytes_unchecked tests alignment and relocation edges - let bytes = self.get_bytes_with_undef_and_ptr( - ptr, size, ptr_align.min(self.int_align(size)) - )?; - // Undef check happens *after* we established that the alignment is correct. - // We must not return Ok() for unaligned pointers! - if self.check_defined(ptr, size).is_err() { - // this inflates undefined bytes to the entire scalar, even if only a few - // bytes are undefined - return Ok(ScalarMaybeUndef::Undef); - } - // Now we do the actual reading - let bits = read_target_uint(self.tcx.data_layout.endian, bytes).unwrap(); - // See if we got a pointer - if size != self.pointer_size() { - // *Now* better make sure that the inside also is free of relocations. - self.check_relocations(ptr, size)?; - } else { - let alloc = self.get(ptr.alloc_id)?; - match alloc.relocations.get(&ptr.offset) { - Some(&(tag, alloc_id)) => { - let ptr = Pointer::new_with_tag(alloc_id, Size::from_bytes(bits as u64), tag); - return Ok(ScalarMaybeUndef::Scalar(ptr.into())) - } - None => {}, - } - } - // We don't. Just return the bits. - Ok(ScalarMaybeUndef::Scalar(Scalar::from_uint(bits, size))) - } - - pub fn read_ptr_sized( - &self, - ptr: Pointer, - ptr_align: Align - ) -> EvalResult<'tcx, ScalarMaybeUndef> { - self.read_scalar(ptr, ptr_align, self.pointer_size()) - } - - /// Write a *non-ZST* scalar - pub fn write_scalar( - &mut self, - ptr: Pointer, - ptr_align: Align, - val: ScalarMaybeUndef, - type_size: Size, - ) -> EvalResult<'tcx> { - let val = match val { - ScalarMaybeUndef::Scalar(scalar) => scalar, - ScalarMaybeUndef::Undef => return self.mark_definedness(ptr, type_size, false), - }; - - let bytes = match val { - Scalar::Ptr(val) => { - assert_eq!(type_size, self.pointer_size()); - val.offset.bytes() as u128 - } - - Scalar::Bits { bits, size } => { - assert_eq!(size as u64, type_size.bytes()); - debug_assert_eq!(truncate(bits, Size::from_bytes(size.into())), bits, - "Unexpected value of size {} when writing to memory", size); - bits - }, - }; - - { - // get_bytes_mut checks alignment - let endian = self.tcx.data_layout.endian; - let dst = self.get_bytes_mut(ptr, type_size, ptr_align)?; - write_target_uint(endian, dst, bytes).unwrap(); - } - - // See if we have to also write a relocation - match val { - Scalar::Ptr(val) => { - self.get_mut(ptr.alloc_id)?.relocations.insert( - ptr.offset, - (val.tag, val.alloc_id), - ); - } - _ => {} - } - - Ok(()) - } - - pub fn write_ptr_sized( - &mut self, - ptr: Pointer, - ptr_align: Align, - val: ScalarMaybeUndef - ) -> EvalResult<'tcx> { - let ptr_size = self.pointer_size(); - self.write_scalar(ptr.into(), ptr_align, val, ptr_size) - } - - fn int_align(&self, size: Size) -> Align { - // We assume pointer-sized integers have the same alignment as pointers. - // We also assume signed and unsigned integers of the same size have the same alignment. - let ity = match size.bytes() { - 1 => layout::I8, - 2 => layout::I16, - 4 => layout::I32, - 8 => layout::I64, - 16 => layout::I128, - _ => bug!("bad integer size: {}", size.bytes()), - }; - ity.align(self) - } -} - -/// Relocations -impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { - /// Return all relocations overlapping with the given ptr-offset pair. - fn relocations( - &self, - ptr: Pointer, - size: Size, - ) -> EvalResult<'tcx, &[(Size, (M::PointerTag, AllocId))]> { - // We have to go back `pointer_size - 1` bytes, as that one would still overlap with - // the beginning of this range. - let start = ptr.offset.bytes().saturating_sub(self.pointer_size().bytes() - 1); - let end = ptr.offset + size; // this does overflow checking - Ok(self.get(ptr.alloc_id)?.relocations.range(Size::from_bytes(start)..end)) - } - - /// Check that there ar eno relocations overlapping with the given range. - #[inline(always)] - fn check_relocations(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { - if self.relocations(ptr, size)?.len() != 0 { - err!(ReadPointerAsBytes) - } else { - Ok(()) - } - } - - /// Remove all relocations inside the given range. - /// If there are relocations overlapping with the edges, they - /// are removed as well *and* the bytes they cover are marked as - /// uninitialized. This is a somewhat odd "spooky action at a distance", - /// but it allows strictly more code to run than if we would just error - /// immediately in that case. - fn clear_relocations(&mut self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { - // Find the start and end of the given range and its outermost relocations. - let (first, last) = { - // Find all relocations overlapping the given range. - let relocations = self.relocations(ptr, size)?; - if relocations.is_empty() { - return Ok(()); - } - - (relocations.first().unwrap().0, - relocations.last().unwrap().0 + self.pointer_size()) - }; - let start = ptr.offset; - let end = start + size; - - let alloc = self.get_mut(ptr.alloc_id)?; - - // Mark parts of the outermost relocations as undefined if they partially fall outside the - // given range. - if first < start { - alloc.undef_mask.set_range(first, start, false); - } - if last > end { - alloc.undef_mask.set_range(end, last, false); - } - - // Forget all the relocations. - alloc.relocations.remove_range(first..last); - - Ok(()) - } - - /// Error if there are relocations overlapping with the edges of the - /// given memory range. - #[inline] - fn check_relocation_edges(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { - self.check_relocations(ptr, Size::ZERO)?; - self.check_relocations(ptr.offset(size, self)?, Size::ZERO)?; - Ok(()) - } -} - -/// Undefined bytes -impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { - // FIXME: Add a fast version for the common, nonoverlapping case - fn copy_undef_mask( - &mut self, - src: Pointer, - dest: Pointer, - size: Size, - repeat: u64, - ) -> EvalResult<'tcx> { - // The bits have to be saved locally before writing to dest in case src and dest overlap. - assert_eq!(size.bytes() as usize as u64, size.bytes()); - - let undef_mask = self.get(src.alloc_id)?.undef_mask.clone(); - let dest_allocation = self.get_mut(dest.alloc_id)?; - - for i in 0..size.bytes() { - let defined = undef_mask.get(src.offset + Size::from_bytes(i)); - - for j in 0..repeat { - dest_allocation.undef_mask.set( - dest.offset + Size::from_bytes(i + (size.bytes() * j)), - defined - ); - } - } - - Ok(()) - } - - /// Checks that a range of bytes is defined. If not, returns the `ReadUndefBytes` - /// error which will report the first byte which is undefined. - #[inline] - fn check_defined(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { - let alloc = self.get(ptr.alloc_id)?; - alloc.undef_mask.is_range_defined( - ptr.offset, - ptr.offset + size, - ).or_else(|idx| err!(ReadUndefBytes(idx))) - } - - pub fn mark_definedness( - &mut self, - ptr: Pointer, - size: Size, - new_state: bool, - ) -> EvalResult<'tcx> { - if size.bytes() == 0 { - return Ok(()); - } - let alloc = self.get_mut(ptr.alloc_id)?; - alloc.undef_mask.set_range( - ptr.offset, - ptr.offset + size, - new_state, - ); - Ok(()) - } -} From b667fe05ff6b4a4f8401bbf63122ea0bb51f9869 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 23 Oct 2018 18:18:22 +0200 Subject: [PATCH 04/20] Fixup various import errors --- src/librustc/mir/interpret/allocation.rs | 77 +++++++++++++----------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index bc185868c6a96..a9027d42e2a5b 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -13,9 +13,18 @@ use super::{ UndefMask, Relocations, + EvalResult, + Pointer, + AllocId, + Scalar, + ScalarMaybeUndef, + write_target_uint, + read_target_uint, + truncate, }; -use ty::layout::{Size, Align}; +use std::ptr; +use ty::layout::{self, Size, Align}; use syntax::ast::Mutability; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)] @@ -85,7 +94,7 @@ impl<'tcx, Tag, Extra> Allocation { /// on that. fn get_bytes_internal( &self, - ptr: Pointer, + ptr: Pointer, size: Size, align: Align, check_defined_and_ptr: bool, @@ -114,7 +123,7 @@ impl<'tcx, Tag, Extra> Allocation { #[inline] fn get_bytes( &self, - ptr: Pointer, + ptr: Pointer, size: Size, align: Align ) -> EvalResult<'tcx, &[u8]> { @@ -126,7 +135,7 @@ impl<'tcx, Tag, Extra> Allocation { #[inline] fn get_bytes_with_undef_and_ptr( &self, - ptr: Pointer, + ptr: Pointer, size: Size, align: Align ) -> EvalResult<'tcx, &[u8]> { @@ -137,7 +146,7 @@ impl<'tcx, Tag, Extra> Allocation { /// so be sure to actually put data there! fn get_bytes_mut( &mut self, - ptr: Pointer, + ptr: Pointer, size: Size, align: Align, ) -> EvalResult<'tcx, &mut [u8]> { @@ -162,9 +171,9 @@ impl<'tcx, Tag, Extra> Allocation { impl<'tcx, Tag, Extra> Allocation { pub fn copy( &mut self, - src: Scalar, + src: Scalar, src_align: Align, - dest: Scalar, + dest: Scalar, dest_align: Align, size: Size, nonoverlapping: bool, @@ -174,9 +183,9 @@ impl<'tcx, Tag, Extra> Allocation { pub fn copy_repeatedly( &mut self, - src: Scalar, + src: Scalar, src_align: Align, - dest: Scalar, + dest: Scalar, dest_align: Align, size: Size, length: u64, @@ -257,7 +266,7 @@ impl<'tcx, Tag, Extra> Allocation { Ok(()) } - pub fn read_c_str(&self, ptr: Pointer) -> EvalResult<'tcx, &[u8]> { + pub fn read_c_str(&self, ptr: Pointer) -> EvalResult<'tcx, &[u8]> { let alloc = self.get(ptr.alloc_id)?; assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes()); let offset = ptr.offset.bytes() as usize; @@ -274,7 +283,7 @@ impl<'tcx, Tag, Extra> Allocation { pub fn check_bytes( &self, - ptr: Scalar, + ptr: Scalar, size: Size, allow_ptr_and_undef: bool, ) -> EvalResult<'tcx> { @@ -295,7 +304,7 @@ impl<'tcx, Tag, Extra> Allocation { Ok(()) } - pub fn read_bytes(&self, ptr: Scalar, size: Size) -> EvalResult<'tcx, &[u8]> { + pub fn read_bytes(&self, ptr: Scalar, size: Size) -> EvalResult<'tcx, &[u8]> { // Empty accesses don't need to be valid pointers, but they should still be non-NULL let align = Align::from_bytes(1, 1).unwrap(); if size.bytes() == 0 { @@ -305,7 +314,7 @@ impl<'tcx, Tag, Extra> Allocation { self.get_bytes(ptr.to_ptr()?, size, align) } - pub fn write_bytes(&mut self, ptr: Scalar, src: &[u8]) -> EvalResult<'tcx> { + pub fn write_bytes(&mut self, ptr: Scalar, src: &[u8]) -> EvalResult<'tcx> { // Empty accesses don't need to be valid pointers, but they should still be non-NULL let align = Align::from_bytes(1, 1).unwrap(); if src.is_empty() { @@ -319,7 +328,7 @@ impl<'tcx, Tag, Extra> Allocation { pub fn write_repeat( &mut self, - ptr: Scalar, + ptr: Scalar, val: u8, count: Size ) -> EvalResult<'tcx> { @@ -339,10 +348,10 @@ impl<'tcx, Tag, Extra> Allocation { /// Read a *non-ZST* scalar pub fn read_scalar( &self, - ptr: Pointer, + ptr: Pointer, ptr_align: Align, size: Size - ) -> EvalResult<'tcx, ScalarMaybeUndef> { + ) -> EvalResult<'tcx, ScalarMaybeUndef> { // get_bytes_unchecked tests alignment and relocation edges let bytes = self.get_bytes_with_undef_and_ptr( ptr, size, ptr_align.min(self.int_align(size)) @@ -376,18 +385,18 @@ impl<'tcx, Tag, Extra> Allocation { pub fn read_ptr_sized( &self, - ptr: Pointer, + ptr: Pointer, ptr_align: Align - ) -> EvalResult<'tcx, ScalarMaybeUndef> { + ) -> EvalResult<'tcx, ScalarMaybeUndef> { self.read_scalar(ptr, ptr_align, self.pointer_size()) } /// Write a *non-ZST* scalar pub fn write_scalar( &mut self, - ptr: Pointer, + ptr: Pointer, ptr_align: Align, - val: ScalarMaybeUndef, + val: ScalarMaybeUndef, type_size: Size, ) -> EvalResult<'tcx> { let val = match val { @@ -432,9 +441,9 @@ impl<'tcx, Tag, Extra> Allocation { pub fn write_ptr_sized( &mut self, - ptr: Pointer, + ptr: Pointer, ptr_align: Align, - val: ScalarMaybeUndef + val: ScalarMaybeUndef ) -> EvalResult<'tcx> { let ptr_size = self.pointer_size(); self.write_scalar(ptr.into(), ptr_align, val, ptr_size) @@ -460,9 +469,9 @@ impl<'tcx, Tag, Extra> Allocation { /// Return all relocations overlapping with the given ptr-offset pair. fn relocations( &self, - ptr: Pointer, + ptr: Pointer, size: Size, - ) -> EvalResult<'tcx, &[(Size, (M::PointerTag, AllocId))]> { + ) -> EvalResult<'tcx, &[(Size, (Tag, AllocId))]> { // We have to go back `pointer_size - 1` bytes, as that one would still overlap with // the beginning of this range. let start = ptr.offset.bytes().saturating_sub(self.pointer_size().bytes() - 1); @@ -472,7 +481,7 @@ impl<'tcx, Tag, Extra> Allocation { /// Check that there ar eno relocations overlapping with the given range. #[inline(always)] - fn check_relocations(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { + fn check_relocations(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { if self.relocations(ptr, size)?.len() != 0 { err!(ReadPointerAsBytes) } else { @@ -486,7 +495,7 @@ impl<'tcx, Tag, Extra> Allocation { /// uninitialized. This is a somewhat odd "spooky action at a distance", /// but it allows strictly more code to run than if we would just error /// immediately in that case. - fn clear_relocations(&mut self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { + fn clear_relocations(&mut self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { // Find the start and end of the given range and its outermost relocations. let (first, last) = { // Find all relocations overlapping the given range. @@ -521,7 +530,7 @@ impl<'tcx, Tag, Extra> Allocation { /// Error if there are relocations overlapping with the edges of the /// given memory range. #[inline] - fn check_relocation_edges(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { + fn check_relocation_edges(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { self.check_relocations(ptr, Size::ZERO)?; self.check_relocations(ptr.offset(size, self)?, Size::ZERO)?; Ok(()) @@ -533,8 +542,8 @@ impl<'tcx, Tag, Extra> Allocation { // FIXME: Add a fast version for the common, nonoverlapping case fn copy_undef_mask( &mut self, - src: Pointer, - dest: Pointer, + src: Pointer, + dest: Pointer, size: Size, repeat: u64, ) -> EvalResult<'tcx> { @@ -561,7 +570,7 @@ impl<'tcx, Tag, Extra> Allocation { /// Checks that a range of bytes is defined. If not, returns the `ReadUndefBytes` /// error which will report the first byte which is undefined. #[inline] - fn check_defined(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { + fn check_defined(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { let alloc = self.get(ptr.alloc_id)?; alloc.undef_mask.is_range_defined( ptr.offset, @@ -571,7 +580,7 @@ impl<'tcx, Tag, Extra> Allocation { pub fn mark_definedness( &mut self, - ptr: Pointer, + ptr: Pointer, size: Size, new_state: bool, ) -> EvalResult<'tcx> { @@ -593,7 +602,7 @@ impl<'tcx, Tag, Extra> Allocation { /// You can pass a scalar, and a `Pointer` does not have to actually still be allocated. pub fn check_align( &self, - ptr: Scalar, + ptr: Scalar, required_align: Align ) -> EvalResult<'tcx> { // Check non-NULL/Undef, extract offset @@ -648,7 +657,7 @@ impl<'tcx, Tag, Extra> Allocation { /// If you want to check bounds before doing a memory access, be sure to /// check the pointer one past the end of your access, then everything will /// work out exactly. - pub fn check_bounds_ptr(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> { + pub fn check_bounds_ptr(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> { let alloc = self.get(ptr.alloc_id)?; let allocation_size = alloc.bytes.len() as u64; if ptr.offset.bytes() > allocation_size { @@ -665,7 +674,7 @@ impl<'tcx, Tag, Extra> Allocation { #[inline(always)] pub fn check_bounds( &self, - ptr: Pointer, + ptr: Pointer, size: Size, access: bool ) -> EvalResult<'tcx> { From 14aa7d04c3fca326df2361c52bad1750fe2652e2 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 23 Oct 2018 18:19:34 +0200 Subject: [PATCH 05/20] Move `ScalarMaybeUndef` back to rustc --- src/librustc/mir/interpret/mod.rs | 114 +++++++++++++++++++++++++ src/librustc_mir/interpret/operand.rs | 116 +------------------------- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index 174ef2070ce17..8a9ebabe96e35 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -696,3 +696,117 @@ fn bit_index(bits: Size) -> (usize, usize) { assert_eq!(b as usize as u64, b); (a as usize, b as usize) } + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] +pub enum ScalarMaybeUndef { + Scalar(Scalar), + Undef, +} + +impl From> for ScalarMaybeUndef { + #[inline(always)] + fn from(s: Scalar) -> Self { + ScalarMaybeUndef::Scalar(s) + } +} + +impl<'tcx> ScalarMaybeUndef<()> { + #[inline] + pub fn with_default_tag(self) -> ScalarMaybeUndef + where Tag: Default + { + match self { + ScalarMaybeUndef::Scalar(s) => ScalarMaybeUndef::Scalar(s.with_default_tag()), + ScalarMaybeUndef::Undef => ScalarMaybeUndef::Undef, + } + } +} + +impl<'tcx, Tag> ScalarMaybeUndef { + #[inline] + pub fn erase_tag(self) -> ScalarMaybeUndef + { + match self { + ScalarMaybeUndef::Scalar(s) => ScalarMaybeUndef::Scalar(s.erase_tag()), + ScalarMaybeUndef::Undef => ScalarMaybeUndef::Undef, + } + } + + #[inline] + pub fn not_undef(self) -> EvalResult<'static, Scalar> { + match self { + ScalarMaybeUndef::Scalar(scalar) => Ok(scalar), + ScalarMaybeUndef::Undef => err!(ReadUndefBytes(Size::from_bytes(0))), + } + } + + #[inline(always)] + pub fn to_ptr(self) -> EvalResult<'tcx, Pointer> { + self.not_undef()?.to_ptr() + } + + #[inline(always)] + pub fn to_bits(self, target_size: Size) -> EvalResult<'tcx, u128> { + self.not_undef()?.to_bits(target_size) + } + + #[inline(always)] + pub fn to_bool(self) -> EvalResult<'tcx, bool> { + self.not_undef()?.to_bool() + } + + #[inline(always)] + pub fn to_char(self) -> EvalResult<'tcx, char> { + self.not_undef()?.to_char() + } + + #[inline(always)] + pub fn to_f32(self) -> EvalResult<'tcx, f32> { + self.not_undef()?.to_f32() + } + + #[inline(always)] + pub fn to_f64(self) -> EvalResult<'tcx, f64> { + self.not_undef()?.to_f64() + } + + #[inline(always)] + pub fn to_u8(self) -> EvalResult<'tcx, u8> { + self.not_undef()?.to_u8() + } + + #[inline(always)] + pub fn to_u32(self) -> EvalResult<'tcx, u32> { + self.not_undef()?.to_u32() + } + + #[inline(always)] + pub fn to_u64(self) -> EvalResult<'tcx, u64> { + self.not_undef()?.to_u64() + } + + #[inline(always)] + pub fn to_usize(self, cx: impl HasDataLayout) -> EvalResult<'tcx, u64> { + self.not_undef()?.to_usize(cx) + } + + #[inline(always)] + pub fn to_i8(self) -> EvalResult<'tcx, i8> { + self.not_undef()?.to_i8() + } + + #[inline(always)] + pub fn to_i32(self) -> EvalResult<'tcx, i32> { + self.not_undef()?.to_i32() + } + + #[inline(always)] + pub fn to_i64(self) -> EvalResult<'tcx, i64> { + self.not_undef()?.to_i64() + } + + #[inline(always)] + pub fn to_isize(self, cx: impl HasDataLayout) -> EvalResult<'tcx, i64> { + self.not_undef()?.to_isize(cx) + } +} diff --git a/src/librustc_mir/interpret/operand.rs b/src/librustc_mir/interpret/operand.rs index d0a32161485b4..4e46be4ca481a 100644 --- a/src/librustc_mir/interpret/operand.rs +++ b/src/librustc_mir/interpret/operand.rs @@ -22,121 +22,7 @@ use rustc::mir::interpret::{ EvalResult, EvalErrorKind }; use super::{EvalContext, Machine, MemPlace, MPlaceTy, MemoryKind}; - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] -pub enum ScalarMaybeUndef { - Scalar(Scalar), - Undef, -} - -impl From> for ScalarMaybeUndef { - #[inline(always)] - fn from(s: Scalar) -> Self { - ScalarMaybeUndef::Scalar(s) - } -} - -impl<'tcx> ScalarMaybeUndef<()> { - #[inline] - pub fn with_default_tag(self) -> ScalarMaybeUndef - where Tag: Default - { - match self { - ScalarMaybeUndef::Scalar(s) => ScalarMaybeUndef::Scalar(s.with_default_tag()), - ScalarMaybeUndef::Undef => ScalarMaybeUndef::Undef, - } - } -} - -impl<'tcx, Tag> ScalarMaybeUndef { - #[inline] - pub fn erase_tag(self) -> ScalarMaybeUndef - { - match self { - ScalarMaybeUndef::Scalar(s) => ScalarMaybeUndef::Scalar(s.erase_tag()), - ScalarMaybeUndef::Undef => ScalarMaybeUndef::Undef, - } - } - - #[inline] - pub fn not_undef(self) -> EvalResult<'static, Scalar> { - match self { - ScalarMaybeUndef::Scalar(scalar) => Ok(scalar), - ScalarMaybeUndef::Undef => err!(ReadUndefBytes(Size::from_bytes(0))), - } - } - - #[inline(always)] - pub fn to_ptr(self) -> EvalResult<'tcx, Pointer> { - self.not_undef()?.to_ptr() - } - - #[inline(always)] - pub fn to_bits(self, target_size: Size) -> EvalResult<'tcx, u128> { - self.not_undef()?.to_bits(target_size) - } - - #[inline(always)] - pub fn to_bool(self) -> EvalResult<'tcx, bool> { - self.not_undef()?.to_bool() - } - - #[inline(always)] - pub fn to_char(self) -> EvalResult<'tcx, char> { - self.not_undef()?.to_char() - } - - #[inline(always)] - pub fn to_f32(self) -> EvalResult<'tcx, f32> { - self.not_undef()?.to_f32() - } - - #[inline(always)] - pub fn to_f64(self) -> EvalResult<'tcx, f64> { - self.not_undef()?.to_f64() - } - - #[inline(always)] - pub fn to_u8(self) -> EvalResult<'tcx, u8> { - self.not_undef()?.to_u8() - } - - #[inline(always)] - pub fn to_u32(self) -> EvalResult<'tcx, u32> { - self.not_undef()?.to_u32() - } - - #[inline(always)] - pub fn to_u64(self) -> EvalResult<'tcx, u64> { - self.not_undef()?.to_u64() - } - - #[inline(always)] - pub fn to_usize(self, cx: impl HasDataLayout) -> EvalResult<'tcx, u64> { - self.not_undef()?.to_usize(cx) - } - - #[inline(always)] - pub fn to_i8(self) -> EvalResult<'tcx, i8> { - self.not_undef()?.to_i8() - } - - #[inline(always)] - pub fn to_i32(self) -> EvalResult<'tcx, i32> { - self.not_undef()?.to_i32() - } - - #[inline(always)] - pub fn to_i64(self) -> EvalResult<'tcx, i64> { - self.not_undef()?.to_i64() - } - - #[inline(always)] - pub fn to_isize(self, cx: impl HasDataLayout) -> EvalResult<'tcx, i64> { - self.not_undef()?.to_isize(cx) - } -} - +pub use rustc::mir::interpret::ScalarMaybeUndef; /// A `Value` represents a single immediate self-contained Rust value. /// From 3dcd1c6731620338d103abf3f663f84f30c9d3f5 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 23 Oct 2018 18:32:50 +0200 Subject: [PATCH 06/20] Move the `memory_accessed` hook onto the `Extra` value --- src/librustc/mir/interpret/allocation.rs | 29 +++++++++++++++++++++--- src/librustc/mir/interpret/mod.rs | 2 +- src/librustc_mir/interpret/machine.rs | 22 ------------------ 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index a9027d42e2a5b..61bdfba27f1a6 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -27,6 +27,13 @@ use std::ptr; use ty::layout::{self, Size, Align}; use syntax::ast::Mutability; +/// Classifying memory accesses +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MemoryAccess { + Read, + Write, +} + #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)] pub struct Allocation { /// The actual bytes of the allocation. @@ -49,6 +56,22 @@ pub struct Allocation { pub extra: Extra, } +trait AllocationExtra { + /// 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. + fn memory_accessed( + &self, + ptr: Pointer, + size: Size, + access: MemoryAccess, + ) -> EvalResult<'tcx> { + Ok(()) + } +} + impl Allocation { /// Creates a read-only allocation initialized by the given bytes pub fn from_bytes(slice: &[u8], align: Align) -> Self { @@ -84,7 +107,7 @@ impl Allocation { impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {} /// Byte accessors -impl<'tcx, Tag, Extra> Allocation { +impl<'tcx, Tag, Extra: AllocationExtra> Allocation { /// The last argument controls whether we error out when there are undefined /// or pointer bytes. You should never call this, call `get_bytes` or /// `get_bytes_with_undef_and_ptr` instead, @@ -112,7 +135,7 @@ impl<'tcx, Tag, Extra> Allocation { } let alloc = self.get(ptr.alloc_id)?; - M::memory_accessed(alloc, ptr, size, MemoryAccess::Read)?; + Extra::memory_accessed(&self.extra, 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()); @@ -158,7 +181,7 @@ impl<'tcx, Tag, Extra> Allocation { self.clear_relocations(ptr, size)?; let alloc = self.get_mut(ptr.alloc_id)?; - M::memory_accessed(alloc, ptr, size, MemoryAccess::Write)?; + Extra::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()); diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index 8a9ebabe96e35..42fc891dadc1e 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -26,7 +26,7 @@ pub use self::error::{ pub use self::value::{Scalar, ConstValue}; -pub use self::allocation::Allocation; +pub use self::allocation::{Allocation, MemoryAccess}; use std::fmt; use mir; diff --git a/src/librustc_mir/interpret/machine.rs b/src/librustc_mir/interpret/machine.rs index 7811dcb0663d5..e0b75abbc1253 100644 --- a/src/librustc_mir/interpret/machine.rs +++ b/src/librustc_mir/interpret/machine.rs @@ -24,13 +24,6 @@ use super::{ EvalContext, PlaceTy, OpTy, Pointer, MemPlace, 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; @@ -181,21 +174,6 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized { 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( From f5a5a71713ebda01d2167efa2f99b66266785ec1 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 23 Oct 2018 19:17:17 +0200 Subject: [PATCH 07/20] Fixup all code to work directly on the allocation instead of going through `.get(alloc_id)` --- src/librustc/mir/interpret/allocation.rs | 364 ++++++++--------------- 1 file changed, 126 insertions(+), 238 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index 61bdfba27f1a6..bd3167e2325bf 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -23,9 +23,9 @@ use super::{ truncate, }; -use std::ptr; use ty::layout::{self, Size, Align}; use syntax::ast::Mutability; +use rustc_target::abi::HasDataLayout; /// Classifying memory accesses #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -56,7 +56,7 @@ pub struct Allocation { pub extra: Extra, } -trait AllocationExtra { +pub trait AllocationExtra { /// Hook for performing extra checks on a memory access. /// /// Takes read-only access to the allocation so we can keep all the memory read @@ -64,9 +64,9 @@ trait AllocationExtra { /// need to mutate. fn memory_accessed( &self, - ptr: Pointer, - size: Size, - access: MemoryAccess, + _ptr: Pointer, + _size: Size, + _access: MemoryAccess, ) -> EvalResult<'tcx> { Ok(()) } @@ -107,7 +107,7 @@ impl Allocation { impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {} /// Byte accessors -impl<'tcx, Tag, Extra: AllocationExtra> Allocation { +impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { /// The last argument controls whether we error out when there are undefined /// or pointer bytes. You should never call this, call `get_bytes` or /// `get_bytes_with_undef_and_ptr` instead, @@ -117,6 +117,7 @@ impl<'tcx, Tag, Extra: AllocationExtra> Allocation { /// on that. fn get_bytes_internal( &self, + cx: impl HasDataLayout, ptr: Pointer, size: Size, align: Align, @@ -124,33 +125,33 @@ impl<'tcx, Tag, Extra: AllocationExtra> Allocation { ) -> EvalResult<'tcx, &[u8]> { assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`"); self.check_align(ptr.into(), align)?; - self.check_bounds(ptr, size, true)?; + self.check_bounds(cx, ptr, size, true)?; if check_defined_and_ptr { self.check_defined(ptr, size)?; - self.check_relocations(ptr, size)?; + self.check_relocations(cx, ptr, size)?; } else { // We still don't want relocations on the *edges* - self.check_relocation_edges(ptr, size)?; + self.check_relocation_edges(cx, ptr, size)?; } - let alloc = self.get(ptr.alloc_id)?; Extra::memory_accessed(&self.extra, 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; - Ok(&alloc.bytes[offset..offset + size.bytes() as usize]) + Ok(&self.bytes[offset..offset + size.bytes() as usize]) } #[inline] fn get_bytes( &self, + cx: impl HasDataLayout, ptr: Pointer, size: Size, align: Align ) -> EvalResult<'tcx, &[u8]> { - self.get_bytes_internal(ptr, size, align, true) + self.get_bytes_internal(cx, ptr, size, align, true) } /// It is the caller's responsibility to handle undefined and pointer bytes. @@ -158,147 +159,50 @@ impl<'tcx, Tag, Extra: AllocationExtra> Allocation { #[inline] fn get_bytes_with_undef_and_ptr( &self, + cx: impl HasDataLayout, ptr: Pointer, size: Size, align: Align ) -> EvalResult<'tcx, &[u8]> { - self.get_bytes_internal(ptr, size, align, false) + self.get_bytes_internal(cx, ptr, size, align, false) } /// Just calling this already marks everything as defined and removes relocations, /// so be sure to actually put data there! fn get_bytes_mut( &mut self, + cx: impl HasDataLayout, ptr: Pointer, size: Size, align: Align, ) -> EvalResult<'tcx, &mut [u8]> { assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`"); self.check_align(ptr.into(), align)?; - self.check_bounds(ptr, size, true)?; + self.check_bounds(cx, ptr, size, true)?; self.mark_definedness(ptr, size, true)?; - self.clear_relocations(ptr, size)?; + self.clear_relocations(cx, ptr, size)?; - let alloc = self.get_mut(ptr.alloc_id)?; - Extra::memory_accessed(alloc, ptr, size, MemoryAccess::Write)?; + Extra::memory_accessed(&self.extra, 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; - Ok(&mut alloc.bytes[offset..offset + size.bytes() as usize]) + Ok(&mut self.bytes[offset..offset + size.bytes() as usize]) } } /// Reading and writing -impl<'tcx, Tag, Extra> Allocation { - pub fn copy( - &mut self, - src: Scalar, - src_align: Align, - dest: Scalar, - dest_align: Align, - size: Size, - nonoverlapping: bool, - ) -> EvalResult<'tcx> { - self.copy_repeatedly(src, src_align, dest, dest_align, size, 1, nonoverlapping) - } - - pub fn copy_repeatedly( - &mut self, - src: Scalar, - src_align: Align, - dest: Scalar, - dest_align: Align, - size: Size, - length: u64, - nonoverlapping: bool, - ) -> EvalResult<'tcx> { - if size.bytes() == 0 { - // Nothing to do for ZST, other than checking alignment and non-NULLness. - self.check_align(src, src_align)?; - self.check_align(dest, dest_align)?; - return Ok(()); - } - let src = src.to_ptr()?; - let dest = dest.to_ptr()?; - - // first copy the relocations to a temporary buffer, because - // `get_bytes_mut` will clear the relocations, which is correct, - // since we don't want to keep any relocations at the target. - // (`get_bytes_with_undef_and_ptr` below checks that there are no - // relocations overlapping the edges; those would not be handled correctly). - let relocations = { - let relocations = self.relocations(src, size)?; - let mut new_relocations = Vec::with_capacity(relocations.len() * (length as usize)); - for i in 0..length { - new_relocations.extend( - relocations - .iter() - .map(|&(offset, reloc)| { - (offset + dest.offset - src.offset + (i * size * relocations.len() as u64), - reloc) - }) - ); - } - - new_relocations - }; - - // This also checks alignment, and relocation edges on the src. - let src_bytes = self.get_bytes_with_undef_and_ptr(src, size, src_align)?.as_ptr(); - let dest_bytes = self.get_bytes_mut(dest, size * length, dest_align)?.as_mut_ptr(); - - // SAFE: The above indexing would have panicked if there weren't at least `size` bytes - // behind `src` and `dest`. Also, we use the overlapping-safe `ptr::copy` if `src` and - // `dest` could possibly overlap. - // The pointers above remain valid even if the `HashMap` table is moved around because they - // point into the `Vec` storing the bytes. - unsafe { - assert_eq!(size.bytes() as usize as u64, size.bytes()); - if src.alloc_id == dest.alloc_id { - if nonoverlapping { - if (src.offset <= dest.offset && src.offset + size > dest.offset) || - (dest.offset <= src.offset && dest.offset + size > src.offset) - { - return err!(Intrinsic( - "copy_nonoverlapping called on overlapping ranges".to_string(), - )); - } - } - - for i in 0..length { - ptr::copy(src_bytes, - dest_bytes.offset((size.bytes() * i) as isize), - size.bytes() as usize); - } - } else { - for i in 0..length { - ptr::copy_nonoverlapping(src_bytes, - dest_bytes.offset((size.bytes() * i) as isize), - size.bytes() as usize); - } - } - } - - // copy definedness to the destination - self.copy_undef_mask(src, dest, size, length)?; - // copy the relocations to the destination - self.get_mut(dest.alloc_id)?.relocations.insert_presorted(relocations); - - Ok(()) - } - - pub fn read_c_str(&self, ptr: Pointer) -> EvalResult<'tcx, &[u8]> { - let alloc = self.get(ptr.alloc_id)?; +impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { + pub fn read_c_str(&self, cx: impl HasDataLayout, ptr: Pointer) -> EvalResult<'tcx, &[u8]> { assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes()); let offset = ptr.offset.bytes() as usize; - match alloc.bytes[offset..].iter().position(|&c| c == 0) { + match self.bytes[offset..].iter().position(|&c| c == 0) { Some(size) => { let p1 = Size::from_bytes((size + 1) as u64); - self.check_relocations(ptr, p1)?; + self.check_relocations(cx, ptr, p1)?; self.check_defined(ptr, p1)?; - Ok(&alloc.bytes[offset..offset + size]) + Ok(&self.bytes[offset..offset + size]) } None => err!(UnterminatedCString(ptr.erase_tag())), } @@ -306,7 +210,8 @@ impl<'tcx, Tag, Extra> Allocation { pub fn check_bytes( &self, - ptr: Scalar, + cx: impl HasDataLayout, + ptr: Pointer, size: Size, allow_ptr_and_undef: bool, ) -> EvalResult<'tcx> { @@ -316,42 +221,52 @@ impl<'tcx, Tag, Extra> Allocation { self.check_align(ptr, align)?; return Ok(()); } - let ptr = ptr.to_ptr()?; // Check bounds, align and relocations on the edges - self.get_bytes_with_undef_and_ptr(ptr, size, align)?; + self.get_bytes_with_undef_and_ptr(cx, ptr, size, align)?; // Check undef and ptr if !allow_ptr_and_undef { self.check_defined(ptr, size)?; - self.check_relocations(ptr, size)?; + self.check_relocations(cx, ptr, size)?; } Ok(()) } - pub fn read_bytes(&self, ptr: Scalar, size: Size) -> EvalResult<'tcx, &[u8]> { + pub fn read_bytes( + &self, + cx: impl HasDataLayout, + ptr: Pointer, + size: Size, + ) -> EvalResult<'tcx, &[u8]> { // Empty accesses don't need to be valid pointers, but they should still be non-NULL let align = Align::from_bytes(1, 1).unwrap(); if size.bytes() == 0 { self.check_align(ptr, align)?; return Ok(&[]); } - self.get_bytes(ptr.to_ptr()?, size, align) + self.get_bytes(cx, ptr, size, align) } - pub fn write_bytes(&mut self, ptr: Scalar, src: &[u8]) -> EvalResult<'tcx> { + pub fn write_bytes( + &mut self, + cx: impl HasDataLayout, + ptr: Pointer, + src: &[u8], + ) -> EvalResult<'tcx> { // Empty accesses don't need to be valid pointers, but they should still be non-NULL let align = Align::from_bytes(1, 1).unwrap(); if src.is_empty() { self.check_align(ptr, align)?; return Ok(()); } - let bytes = self.get_bytes_mut(ptr.to_ptr()?, Size::from_bytes(src.len() as u64), align)?; + let bytes = self.get_bytes_mut(cx, ptr, Size::from_bytes(src.len() as u64), align)?; bytes.clone_from_slice(src); Ok(()) } pub fn write_repeat( &mut self, - ptr: Scalar, + cx: impl HasDataLayout, + ptr: Pointer, val: u8, count: Size ) -> EvalResult<'tcx> { @@ -361,7 +276,7 @@ impl<'tcx, Tag, Extra> Allocation { self.check_align(ptr, align)?; return Ok(()); } - let bytes = self.get_bytes_mut(ptr.to_ptr()?, count, align)?; + let bytes = self.get_bytes_mut(cx, ptr, count, align)?; for b in bytes { *b = val; } @@ -371,13 +286,14 @@ impl<'tcx, Tag, Extra> Allocation { /// Read a *non-ZST* scalar pub fn read_scalar( &self, + cx: impl HasDataLayout, ptr: Pointer, ptr_align: Align, size: Size ) -> EvalResult<'tcx, ScalarMaybeUndef> { // get_bytes_unchecked tests alignment and relocation edges let bytes = self.get_bytes_with_undef_and_ptr( - ptr, size, ptr_align.min(self.int_align(size)) + cx, ptr, size, ptr_align.min(int_align(cx, size)) )?; // Undef check happens *after* we established that the alignment is correct. // We must not return Ok() for unaligned pointers! @@ -387,14 +303,13 @@ impl<'tcx, Tag, Extra> Allocation { return Ok(ScalarMaybeUndef::Undef); } // Now we do the actual reading - let bits = read_target_uint(self.tcx.data_layout.endian, bytes).unwrap(); + let bits = read_target_uint(cx.data_layout().endian, bytes).unwrap(); // See if we got a pointer - if size != self.pointer_size() { + if size != cx.data_layout().pointer_size { // *Now* better make sure that the inside also is free of relocations. - self.check_relocations(ptr, size)?; + self.check_relocations(cx, ptr, size)?; } else { - let alloc = self.get(ptr.alloc_id)?; - match alloc.relocations.get(&ptr.offset) { + match self.relocations.get(&ptr.offset) { Some(&(tag, alloc_id)) => { let ptr = Pointer::new_with_tag(alloc_id, Size::from_bytes(bits as u64), tag); return Ok(ScalarMaybeUndef::Scalar(ptr.into())) @@ -408,15 +323,17 @@ impl<'tcx, Tag, Extra> Allocation { pub fn read_ptr_sized( &self, + cx: impl HasDataLayout, ptr: Pointer, ptr_align: Align ) -> EvalResult<'tcx, ScalarMaybeUndef> { - self.read_scalar(ptr, ptr_align, self.pointer_size()) + self.read_scalar(cx, ptr, ptr_align, cx.data_layout().pointer_size) } /// Write a *non-ZST* scalar pub fn write_scalar( &mut self, + cx: impl HasDataLayout, ptr: Pointer, ptr_align: Align, val: ScalarMaybeUndef, @@ -429,7 +346,7 @@ impl<'tcx, Tag, Extra> Allocation { let bytes = match val { Scalar::Ptr(val) => { - assert_eq!(type_size, self.pointer_size()); + assert_eq!(type_size, cx.data_layout().pointer_size); val.offset.bytes() as u128 } @@ -443,15 +360,15 @@ impl<'tcx, Tag, Extra> Allocation { { // get_bytes_mut checks alignment - let endian = self.tcx.data_layout.endian; - let dst = self.get_bytes_mut(ptr, type_size, ptr_align)?; + let endian = cx.data_layout().endian; + let dst = self.get_bytes_mut(cx, ptr, type_size, ptr_align)?; write_target_uint(endian, dst, bytes).unwrap(); } // See if we have to also write a relocation match val { Scalar::Ptr(val) => { - self.get_mut(ptr.alloc_id)?.relocations.insert( + self.relocations.insert( ptr.offset, (val.tag, val.alloc_id), ); @@ -464,48 +381,55 @@ impl<'tcx, Tag, Extra> Allocation { pub fn write_ptr_sized( &mut self, + cx: impl HasDataLayout, ptr: Pointer, ptr_align: Align, val: ScalarMaybeUndef ) -> EvalResult<'tcx> { - let ptr_size = self.pointer_size(); - self.write_scalar(ptr.into(), ptr_align, val, ptr_size) + let ptr_size = cx.data_layout().pointer_size; + self.write_scalar(cx, ptr.into(), ptr_align, val, ptr_size) } +} - fn int_align(&self, size: Size) -> Align { - // We assume pointer-sized integers have the same alignment as pointers. - // We also assume signed and unsigned integers of the same size have the same alignment. - let ity = match size.bytes() { - 1 => layout::I8, - 2 => layout::I16, - 4 => layout::I32, - 8 => layout::I64, - 16 => layout::I128, - _ => bug!("bad integer size: {}", size.bytes()), - }; - ity.align(self) - } +fn int_align(cx: impl HasDataLayout, size: Size) -> Align { + // We assume pointer-sized integers have the same alignment as pointers. + // We also assume signed and unsigned integers of the same size have the same alignment. + let ity = match size.bytes() { + 1 => layout::I8, + 2 => layout::I16, + 4 => layout::I32, + 8 => layout::I64, + 16 => layout::I128, + _ => bug!("bad integer size: {}", size.bytes()), + }; + ity.align(cx) } /// Relocations -impl<'tcx, Tag, Extra> Allocation { +impl<'tcx, Tag: Copy, Extra> Allocation { /// Return all relocations overlapping with the given ptr-offset pair. fn relocations( &self, + cx: impl HasDataLayout, ptr: Pointer, size: Size, ) -> EvalResult<'tcx, &[(Size, (Tag, AllocId))]> { // We have to go back `pointer_size - 1` bytes, as that one would still overlap with // the beginning of this range. - let start = ptr.offset.bytes().saturating_sub(self.pointer_size().bytes() - 1); + let start = ptr.offset.bytes().saturating_sub(cx.data_layout().pointer_size.bytes() - 1); let end = ptr.offset + size; // this does overflow checking - Ok(self.get(ptr.alloc_id)?.relocations.range(Size::from_bytes(start)..end)) + Ok(self.relocations.range(Size::from_bytes(start)..end)) } /// Check that there ar eno relocations overlapping with the given range. #[inline(always)] - fn check_relocations(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { - if self.relocations(ptr, size)?.len() != 0 { + fn check_relocations( + &self, + cx: impl HasDataLayout, + ptr: Pointer, + size: Size, + ) -> EvalResult<'tcx> { + if self.relocations(cx, ptr, size)?.len() != 0 { err!(ReadPointerAsBytes) } else { Ok(()) @@ -518,34 +442,37 @@ impl<'tcx, Tag, Extra> Allocation { /// uninitialized. This is a somewhat odd "spooky action at a distance", /// but it allows strictly more code to run than if we would just error /// immediately in that case. - fn clear_relocations(&mut self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { + fn clear_relocations( + &mut self, + cx: impl HasDataLayout, + ptr: Pointer, + size: Size, + ) -> EvalResult<'tcx> { // Find the start and end of the given range and its outermost relocations. let (first, last) = { // Find all relocations overlapping the given range. - let relocations = self.relocations(ptr, size)?; + let relocations = self.relocations(cx, ptr, size)?; if relocations.is_empty() { return Ok(()); } (relocations.first().unwrap().0, - relocations.last().unwrap().0 + self.pointer_size()) + relocations.last().unwrap().0 + cx.data_layout().pointer_size) }; let start = ptr.offset; let end = start + size; - let alloc = self.get_mut(ptr.alloc_id)?; - // Mark parts of the outermost relocations as undefined if they partially fall outside the // given range. if first < start { - alloc.undef_mask.set_range(first, start, false); + self.undef_mask.set_range(first, start, false); } if last > end { - alloc.undef_mask.set_range(end, last, false); + self.undef_mask.set_range(end, last, false); } // Forget all the relocations. - alloc.relocations.remove_range(first..last); + self.relocations.remove_range(first..last); Ok(()) } @@ -553,49 +480,25 @@ impl<'tcx, Tag, Extra> Allocation { /// Error if there are relocations overlapping with the edges of the /// given memory range. #[inline] - fn check_relocation_edges(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { - self.check_relocations(ptr, Size::ZERO)?; - self.check_relocations(ptr.offset(size, self)?, Size::ZERO)?; - Ok(()) - } -} - -/// Undefined bytes -impl<'tcx, Tag, Extra> Allocation { - // FIXME: Add a fast version for the common, nonoverlapping case - fn copy_undef_mask( - &mut self, - src: Pointer, - dest: Pointer, + fn check_relocation_edges( + &self, + cx: impl HasDataLayout, + ptr: Pointer, size: Size, - repeat: u64, ) -> EvalResult<'tcx> { - // The bits have to be saved locally before writing to dest in case src and dest overlap. - assert_eq!(size.bytes() as usize as u64, size.bytes()); - - let undef_mask = self.get(src.alloc_id)?.undef_mask.clone(); - let dest_allocation = self.get_mut(dest.alloc_id)?; - - for i in 0..size.bytes() { - let defined = undef_mask.get(src.offset + Size::from_bytes(i)); - - for j in 0..repeat { - dest_allocation.undef_mask.set( - dest.offset + Size::from_bytes(i + (size.bytes() * j)), - defined - ); - } - } - + self.check_relocations(cx, ptr, Size::ZERO)?; + self.check_relocations(cx, ptr.offset(size, cx)?, Size::ZERO)?; Ok(()) } +} +/// Undefined bytes +impl<'tcx, Tag: Copy, Extra> Allocation { /// Checks that a range of bytes is defined. If not, returns the `ReadUndefBytes` /// error which will report the first byte which is undefined. #[inline] fn check_defined(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> { - let alloc = self.get(ptr.alloc_id)?; - alloc.undef_mask.is_range_defined( + self.undef_mask.is_range_defined( ptr.offset, ptr.offset + size, ).or_else(|idx| err!(ReadUndefBytes(idx))) @@ -610,8 +513,7 @@ impl<'tcx, Tag, Extra> Allocation { if size.bytes() == 0 { return Ok(()); } - let alloc = self.get_mut(ptr.alloc_id)?; - alloc.undef_mask.set_range( + self.undef_mask.set_range( ptr.offset, ptr.offset + size, new_state, @@ -620,48 +522,34 @@ impl<'tcx, Tag, Extra> Allocation { } } -impl<'tcx, Tag, Extra> Allocation { +impl<'tcx, Tag: Copy, Extra> Allocation { /// Check that the pointer is aligned AND non-NULL. This supports ZSTs in two ways: /// You can pass a scalar, and a `Pointer` does not have to actually still be allocated. pub fn check_align( &self, - ptr: Scalar, + ptr: Pointer, required_align: Align ) -> EvalResult<'tcx> { // Check non-NULL/Undef, extract offset - let (offset, alloc_align) = match ptr { - Scalar::Ptr(ptr) => { - let (size, align) = self.get_size_and_align(ptr.alloc_id); - // check this is not NULL -- which we can ensure only if this is in-bounds - // of some (potentially dead) allocation. - if ptr.offset > size { - return err!(PointerOutOfBounds { - ptr: ptr.erase_tag(), - access: true, - allocation_size: size, - }); - }; - // keep data for alignment check - (ptr.offset.bytes(), align) - } - Scalar::Bits { bits, size } => { - assert_eq!(size as u64, self.pointer_size().bytes()); - assert!(bits < (1u128 << self.pointer_size().bits())); - // check this is not NULL - if bits == 0 { - return err!(InvalidNullPointerUsage); - } - // the "base address" is 0 and hence always aligned - (bits as u64, required_align) - } + + // check this is not NULL -- which we can ensure only if this is in-bounds + // of some (potentially dead) allocation. + let size = Size::from_bytes(self.bytes.len() as u64); + if ptr.offset > size { + return err!(PointerOutOfBounds { + ptr: ptr.erase_tag(), + access: true, + allocation_size: size, + }); }; // Check alignment - if alloc_align.abi() < required_align.abi() { + if self.align.abi() < required_align.abi() { return err!(AlignmentCheckFailed { - has: alloc_align, + has: self.align, required: required_align, }); } + let offset = ptr.offset.bytes(); if offset % required_align.abi() == 0 { Ok(()) } else { @@ -681,8 +569,7 @@ impl<'tcx, Tag, Extra> Allocation { /// check the pointer one past the end of your access, then everything will /// work out exactly. pub fn check_bounds_ptr(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> { - let alloc = self.get(ptr.alloc_id)?; - let allocation_size = alloc.bytes.len() as u64; + let allocation_size = self.bytes.len() as u64; if ptr.offset.bytes() > allocation_size { return err!(PointerOutOfBounds { ptr: ptr.erase_tag(), @@ -697,11 +584,12 @@ impl<'tcx, Tag, Extra> Allocation { #[inline(always)] pub fn check_bounds( &self, + cx: impl HasDataLayout, ptr: Pointer, size: Size, access: bool ) -> EvalResult<'tcx> { // if ptr.offset is in bounds, then so is ptr (because offset checks for overflow) - self.check_bounds_ptr(ptr.offset(size, &*self)?, access) + self.check_bounds_ptr(ptr.offset(size, cx)?, access) } } \ No newline at end of file From 9614f348d184808bf4982324feb029095f1d0fa2 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 23 Oct 2018 20:42:46 +0200 Subject: [PATCH 08/20] Forward some function calls of `Memory` to `Allocation` --- src/librustc/mir/interpret/allocation.rs | 13 +- src/librustc/mir/interpret/mod.rs | 7 +- src/librustc_mir/interpret/machine.rs | 4 +- src/librustc_mir/interpret/memory.rs | 267 ++++++++++++++++++++++- src/librustc_mir/interpret/mod.rs | 2 +- src/librustc_mir/interpret/place.rs | 3 +- src/librustc_mir/interpret/snapshot.rs | 5 - 7 files changed, 284 insertions(+), 17 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index bd3167e2325bf..e90466f044014 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -56,7 +56,7 @@ pub struct Allocation { pub extra: Extra, } -pub trait AllocationExtra { +pub trait AllocationExtra: ::std::fmt::Debug + Default + Clone { /// Hook for performing extra checks on a memory access. /// /// Takes read-only access to the allocation so we can keep all the memory read @@ -72,6 +72,9 @@ pub trait AllocationExtra { } } +/// For the const evaluator +impl AllocationExtra<()> for () {} + impl Allocation { /// Creates a read-only allocation initialized by the given bytes pub fn from_bytes(slice: &[u8], align: Align) -> Self { @@ -144,7 +147,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { } #[inline] - fn get_bytes( + pub fn get_bytes( &self, cx: impl HasDataLayout, ptr: Pointer, @@ -157,7 +160,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { /// It is the caller's responsibility to handle undefined and pointer bytes. /// However, this still checks that there are no relocations on the *edges*. #[inline] - fn get_bytes_with_undef_and_ptr( + pub fn get_bytes_with_undef_and_ptr( &self, cx: impl HasDataLayout, ptr: Pointer, @@ -169,7 +172,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { /// Just calling this already marks everything as defined and removes relocations, /// so be sure to actually put data there! - fn get_bytes_mut( + pub fn get_bytes_mut( &mut self, cx: impl HasDataLayout, ptr: Pointer, @@ -408,7 +411,7 @@ fn int_align(cx: impl HasDataLayout, size: Size) -> Align { /// Relocations impl<'tcx, Tag: Copy, Extra> Allocation { /// Return all relocations overlapping with the given ptr-offset pair. - fn relocations( + pub fn relocations( &self, cx: impl HasDataLayout, ptr: Pointer, diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index 42fc891dadc1e..ab3fc732a5866 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -26,7 +26,7 @@ pub use self::error::{ pub use self::value::{Scalar, ConstValue}; -pub use self::allocation::{Allocation, MemoryAccess}; +pub use self::allocation::{Allocation, MemoryAccess, AllocationExtra}; use std::fmt; use mir; @@ -810,3 +810,8 @@ impl<'tcx, Tag> ScalarMaybeUndef { self.not_undef()?.to_isize(cx) } } + +impl_stable_hash_for!(enum ::mir::interpret::ScalarMaybeUndef { + Scalar(v), + Undef +}); \ No newline at end of file diff --git a/src/librustc_mir/interpret/machine.rs b/src/librustc_mir/interpret/machine.rs index e0b75abbc1253..280af1f0096cb 100644 --- a/src/librustc_mir/interpret/machine.rs +++ b/src/librustc_mir/interpret/machine.rs @@ -21,7 +21,7 @@ use rustc::ty::{self, Ty, layout::{Size, TyLayout}, query::TyCtxtAt}; use super::{ Allocation, AllocId, EvalResult, Scalar, - EvalContext, PlaceTy, OpTy, Pointer, MemPlace, MemoryKind, + EvalContext, PlaceTy, OpTy, Pointer, MemPlace, MemoryKind, AllocationExtra, }; /// Whether this kind of memory is allowed to leak @@ -78,7 +78,7 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized { type PointerTag: ::std::fmt::Debug + Default + Copy + Eq + Hash + 'static; /// Extra data stored in every allocation. - type AllocExtra: ::std::fmt::Debug + Default + Clone; + type AllocExtra: AllocationExtra; /// Memory's allocation map type MemoryMap: diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs index 07d83097acccb..d0160be6bacf0 100644 --- a/src/librustc_mir/interpret/memory.rs +++ b/src/librustc_mir/interpret/memory.rs @@ -21,7 +21,7 @@ use std::ptr; use std::borrow::Cow; use rustc::ty::{self, Instance, ParamEnv, query::TyCtxtAt}; -use rustc::ty::layout::{self, Align, TargetDataLayout, Size, HasDataLayout}; +use rustc::ty::layout::{Align, TargetDataLayout, Size, HasDataLayout}; pub use rustc::mir::interpret::{truncate, write_target_uint, read_target_uint}; use rustc_data_structures::fx::{FxHashSet, FxHashMap}; @@ -30,7 +30,7 @@ use syntax::ast::Mutability; use super::{ Pointer, AllocId, Allocation, ConstValue, GlobalId, EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic, - Machine, MemoryAccess, AllocMap, MayLeak, ScalarMaybeUndef, ErrorHandled, + Machine, MemoryAccess, AllocMap, MayLeak, ScalarMaybeUndef, AllocationExtra, ErrorHandled, }; #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] @@ -297,6 +297,17 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { }) } } + + /// Convenience forwarding method for `Allocation::check_bounds`. + #[inline(always)] + pub fn check_bounds( + &self, + ptr: Pointer, + size: Size, + access: bool + ) -> EvalResult<'tcx> { + self.get(ptr.alloc_id)?.check_bounds(self, ptr, size, access) + } } /// Allocation accessors @@ -587,6 +598,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { impl<'a, 'mir, 'tcx, M> Memory<'a, 'mir, 'tcx, M> where M: Machine<'a, 'mir, 'tcx, PointerTag=(), AllocExtra=()>, + M::AllocExtra: AllocationExtra<()>, M::MemoryMap: AllocMap, Allocation)>, { /// mark an allocation as static and initialized, either mutable or not @@ -626,3 +638,254 @@ where Ok(()) } } + +/// Reading and writing +impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { + pub fn copy( + &mut self, + src: Scalar, + src_align: Align, + dest: Scalar, + dest_align: Align, + size: Size, + nonoverlapping: bool, + ) -> EvalResult<'tcx> { + self.copy_repeatedly(src, src_align, dest, dest_align, size, 1, nonoverlapping) + } + + pub fn copy_repeatedly( + &mut self, + src: Scalar, + src_align: Align, + dest: Scalar, + dest_align: Align, + size: Size, + length: u64, + nonoverlapping: bool, + ) -> EvalResult<'tcx> { + if size.bytes() == 0 { + // Nothing to do for ZST, other than checking alignment and non-NULLness. + self.check_align(src, src_align)?; + self.check_align(dest, dest_align)?; + return Ok(()); + } + let src = src.to_ptr()?; + let dest = dest.to_ptr()?; + + // first copy the relocations to a temporary buffer, because + // `get_bytes_mut` will clear the relocations, which is correct, + // since we don't want to keep any relocations at the target. + // (`get_bytes_with_undef_and_ptr` below checks that there are no + // relocations overlapping the edges; those would not be handled correctly). + let relocations = { + let relocations = self.relocations(src, size)?; + let mut new_relocations = Vec::with_capacity(relocations.len() * (length as usize)); + for i in 0..length { + new_relocations.extend( + relocations + .iter() + .map(|&(offset, reloc)| { + (offset + dest.offset - src.offset + (i * size * relocations.len() as u64), + reloc) + }) + ); + } + + new_relocations + }; + + let tcx = self.tcx.tcx; + + // This also checks alignment, and relocation edges on the src. + let src_bytes = self + .get(src.alloc_id)? + .get_bytes_with_undef_and_ptr(tcx, src, size, src_align)? + .as_ptr(); + let dest_bytes = self + .get_mut(dest.alloc_id)? + .get_bytes_mut(tcx, dest, size * length, dest_align)? + .as_mut_ptr(); + + // SAFE: The above indexing would have panicked if there weren't at least `size` bytes + // behind `src` and `dest`. Also, we use the overlapping-safe `ptr::copy` if `src` and + // `dest` could possibly overlap. + // The pointers above remain valid even if the `HashMap` table is moved around because they + // point into the `Vec` storing the bytes. + unsafe { + assert_eq!(size.bytes() as usize as u64, size.bytes()); + if src.alloc_id == dest.alloc_id { + if nonoverlapping { + if (src.offset <= dest.offset && src.offset + size > dest.offset) || + (dest.offset <= src.offset && dest.offset + size > src.offset) + { + return err!(Intrinsic( + "copy_nonoverlapping called on overlapping ranges".to_string(), + )); + } + } + + for i in 0..length { + ptr::copy(src_bytes, + dest_bytes.offset((size.bytes() * i) as isize), + size.bytes() as usize); + } + } else { + for i in 0..length { + ptr::copy_nonoverlapping(src_bytes, + dest_bytes.offset((size.bytes() * i) as isize), + size.bytes() as usize); + } + } + } + + // copy definedness to the destination + self.copy_undef_mask(src, dest, size, length)?; + // copy the relocations to the destination + self.get_mut(dest.alloc_id)?.relocations.insert_presorted(relocations); + + Ok(()) + } + + pub fn read_c_str(&self, ptr: Pointer) -> EvalResult<'tcx, &[u8]> { + self.get(ptr.alloc_id)?.read_c_str(self, ptr) + } + + pub fn check_bytes( + &self, + ptr: Scalar, + size: Size, + allow_ptr_and_undef: bool, + ) -> EvalResult<'tcx> { + // Empty accesses don't need to be valid pointers, but they should still be non-NULL + let align = Align::from_bytes(1, 1).unwrap(); + if size.bytes() == 0 { + self.check_align(ptr, align)?; + return Ok(()); + } + let ptr = ptr.to_ptr()?; + self.get(ptr.alloc_id)?.check_bytes(self, ptr, size, allow_ptr_and_undef) + } + + pub fn read_bytes(&self, ptr: Scalar, size: Size) -> EvalResult<'tcx, &[u8]> { + // Empty accesses don't need to be valid pointers, but they should still be non-NULL + let align = Align::from_bytes(1, 1).unwrap(); + if size.bytes() == 0 { + self.check_align(ptr, align)?; + return Ok(&[]); + } + let ptr = ptr.to_ptr()?; + self.get(ptr.alloc_id)?.get_bytes(self, ptr, size, align) + } + + pub fn write_bytes(&mut self, ptr: Scalar, src: &[u8]) -> EvalResult<'tcx> { + // Empty accesses don't need to be valid pointers, but they should still be non-NULL + let align = Align::from_bytes(1, 1).unwrap(); + if src.is_empty() { + self.check_align(ptr, align)?; + return Ok(()); + } + let ptr = ptr.to_ptr()?; + let tcx = self.tcx.tcx; + self.get_mut(ptr.alloc_id)?.write_bytes(tcx, ptr, src) + } + + pub fn write_repeat( + &mut self, + ptr: Scalar, + val: u8, + count: Size + ) -> EvalResult<'tcx> { + // Empty accesses don't need to be valid pointers, but they should still be non-NULL + let align = Align::from_bytes(1, 1).unwrap(); + if count.bytes() == 0 { + self.check_align(ptr, align)?; + return Ok(()); + } + let ptr = ptr.to_ptr()?; + let tcx = self.tcx.tcx; + self.get_mut(ptr.alloc_id)?.write_repeat(tcx, ptr, val, count) + } + + /// Read a *non-ZST* scalar + pub fn read_scalar( + &self, + ptr: Pointer, + ptr_align: Align, + size: Size + ) -> EvalResult<'tcx, ScalarMaybeUndef> { + self.get(ptr.alloc_id)?.read_scalar(self, ptr, ptr_align, size) + } + + pub fn read_ptr_sized( + &self, + ptr: Pointer, + ptr_align: Align + ) -> EvalResult<'tcx, ScalarMaybeUndef> { + self.read_scalar(ptr, ptr_align, self.pointer_size()) + } + + /// Write a *non-ZST* scalar + pub fn write_scalar( + &mut self, + ptr: Pointer, + ptr_align: Align, + val: ScalarMaybeUndef, + type_size: Size, + ) -> EvalResult<'tcx> { + let tcx = self.tcx.tcx; + self.get_mut(ptr.alloc_id)?.write_scalar(tcx, ptr, ptr_align, val, type_size) + } + + pub fn write_ptr_sized( + &mut self, + ptr: Pointer, + ptr_align: Align, + val: ScalarMaybeUndef + ) -> EvalResult<'tcx> { + let ptr_size = self.pointer_size(); + self.write_scalar(ptr, ptr_align, val, ptr_size) + } +} + +/// Relocations +impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { + /// Return all relocations overlapping with the given ptr-offset pair. + fn relocations( + &self, + ptr: Pointer, + size: Size, + ) -> EvalResult<'tcx, &[(Size, (M::PointerTag, AllocId))]> { + self.get(ptr.alloc_id)?.relocations(self, ptr, size) + } +} + +/// Undefined bytes +impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> { + // FIXME: Add a fast version for the common, nonoverlapping case + fn copy_undef_mask( + &mut self, + src: Pointer, + dest: Pointer, + size: Size, + repeat: u64, + ) -> EvalResult<'tcx> { + // The bits have to be saved locally before writing to dest in case src and dest overlap. + assert_eq!(size.bytes() as usize as u64, size.bytes()); + + let undef_mask = self.get(src.alloc_id)?.undef_mask.clone(); + let dest_allocation = self.get_mut(dest.alloc_id)?; + + for i in 0..size.bytes() { + let defined = undef_mask.get(src.offset + Size::from_bytes(i)); + + for j in 0..repeat { + dest_allocation.undef_mask.set( + dest.offset + Size::from_bytes(i + (size.bytes() * j)), + defined + ); + } + } + + Ok(()) + } +} diff --git a/src/librustc_mir/interpret/mod.rs b/src/librustc_mir/interpret/mod.rs index 55037a99e0124..5620ea4cee254 100644 --- a/src/librustc_mir/interpret/mod.rs +++ b/src/librustc_mir/interpret/mod.rs @@ -34,7 +34,7 @@ pub use self::place::{Place, PlaceTy, MemPlace, MPlaceTy}; pub use self::memory::{Memory, MemoryKind}; -pub use self::machine::{Machine, AllocMap, MemoryAccess, MayLeak}; +pub use self::machine::{Machine, AllocMap, 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 0eae2bfb226c6..530e77e7a597b 100644 --- a/src/librustc_mir/interpret/place.rs +++ b/src/librustc_mir/interpret/place.rs @@ -24,7 +24,7 @@ use rustc::mir::interpret::{ GlobalId, AllocId, Allocation, Scalar, EvalResult, Pointer, PointerArithmetic }; use super::{ - EvalContext, Machine, AllocMap, + EvalContext, Machine, AllocMap, AllocationExtra, Value, ValTy, ScalarMaybeUndef, Operand, OpTy, MemoryKind }; @@ -263,6 +263,7 @@ 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::AllocExtra: AllocationExtra, M::MemoryMap: AllocMap, Allocation)>, { /// Take a value, which represents a (thin or fat) reference, and make it a place. diff --git a/src/librustc_mir/interpret/snapshot.rs b/src/librustc_mir/interpret/snapshot.rs index cff2288fd8720..b727fd34626fe 100644 --- a/src/librustc_mir/interpret/snapshot.rs +++ b/src/librustc_mir/interpret/snapshot.rs @@ -195,11 +195,6 @@ impl<'a, Ctx> Snapshot<'a, Ctx> for Scalar } } -impl_stable_hash_for!(enum ::interpret::ScalarMaybeUndef { - Scalar(v), - Undef -}); - impl_snapshot_for!(enum ScalarMaybeUndef { Scalar(s), Undef, From 2d1d014b1849d96297a8589018c6974c0a31b4d3 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 23 Oct 2018 21:16:02 +0200 Subject: [PATCH 09/20] Update miri submodule --- src/tools/miri | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri b/src/tools/miri index bbb1d80703f27..c20e7333c6047 160000 --- a/src/tools/miri +++ b/src/tools/miri @@ -1 +1 @@ -Subproject commit bbb1d80703f272a5592ceeb3832a489776512251 +Subproject commit c20e7333c6047fe8c11fce7fa2041e9174748bb9 From f08b11723962ce0e84e7239fbbe031cceb5cc851 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 24 Oct 2018 09:50:47 +0200 Subject: [PATCH 10/20] Tidy all the way --- src/librustc/mir/interpret/allocation.rs | 2 +- src/librustc/mir/interpret/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index e90466f044014..90e95e5a04ca6 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -595,4 +595,4 @@ impl<'tcx, Tag: Copy, Extra> Allocation { // if ptr.offset is in bounds, then so is ptr (because offset checks for overflow) self.check_bounds_ptr(ptr.offset(size, cx)?, access) } -} \ No newline at end of file +} diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index ab3fc732a5866..884de0bf8e108 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -814,4 +814,4 @@ impl<'tcx, Tag> ScalarMaybeUndef { impl_stable_hash_for!(enum ::mir::interpret::ScalarMaybeUndef { Scalar(v), Undef -}); \ No newline at end of file +}); From d593a47a379bd447afcb45c86ca413c6c3edd043 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 24 Oct 2018 11:19:52 +0200 Subject: [PATCH 11/20] We actually have the allocation available... it can't be dangling --- src/librustc/mir/interpret/allocation.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index 90e95e5a04ca6..2ffa677382f01 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -536,7 +536,6 @@ impl<'tcx, Tag: Copy, Extra> Allocation { // Check non-NULL/Undef, extract offset // check this is not NULL -- which we can ensure only if this is in-bounds - // of some (potentially dead) allocation. let size = Size::from_bytes(self.bytes.len() as u64); if ptr.offset > size { return err!(PointerOutOfBounds { From 8a6f62b6bd6c7d926e4b09f6c131fce7fc92b03d Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 25 Oct 2018 16:09:42 +0200 Subject: [PATCH 12/20] Move UndefMask and Relocations into `allocation.rs` --- src/librustc/mir/interpret/allocation.rs | 136 +++++++++++++++++++++- src/librustc/mir/interpret/mod.rs | 137 +---------------------- 2 files changed, 138 insertions(+), 135 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index 2ffa677382f01..1e5bdb73e49c4 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -11,8 +11,6 @@ //! The virtual memory representation of the MIR interpreter use super::{ - UndefMask, - Relocations, EvalResult, Pointer, AllocId, @@ -26,6 +24,10 @@ use super::{ use ty::layout::{self, Size, Align}; use syntax::ast::Mutability; use rustc_target::abi::HasDataLayout; +use std::iter; +use mir; +use std::ops::{Deref, DerefMut}; +use rustc_data_structures::sorted_map::SortedMap; /// Classifying memory accesses #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -595,3 +597,133 @@ impl<'tcx, Tag: Copy, Extra> Allocation { self.check_bounds_ptr(ptr.offset(size, cx)?, access) } } + + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, RustcEncodable, RustcDecodable)] +pub struct Relocations(SortedMap); + +impl Relocations { + pub fn new() -> Self { + Relocations(SortedMap::new()) + } + + // The caller must guarantee that the given relocations are already sorted + // by address and contain no duplicates. + pub fn from_presorted(r: Vec<(Size, (Tag, Id))>) -> Self { + Relocations(SortedMap::from_presorted_elements(r)) + } +} + +impl Deref for Relocations { + type Target = SortedMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Relocations { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Undefined byte tracking +//////////////////////////////////////////////////////////////////////////////// + +type Block = u64; +const BLOCK_SIZE: u64 = 64; + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)] +pub struct UndefMask { + blocks: Vec, + len: Size, +} + +impl_stable_hash_for!(struct mir::interpret::UndefMask{blocks, len}); + +impl UndefMask { + pub fn new(size: Size) -> Self { + let mut m = UndefMask { + blocks: vec![], + len: Size::ZERO, + }; + m.grow(size, false); + m + } + + /// Check whether the range `start..end` (end-exclusive) is entirely defined. + /// + /// Returns `Ok(())` if it's defined. Otherwise returns the index of the byte + /// at which the first undefined access begins. + #[inline] + pub fn is_range_defined(&self, start: Size, end: Size) -> Result<(), Size> { + if end > self.len { + return Err(self.len); + } + + let idx = (start.bytes()..end.bytes()) + .map(|i| Size::from_bytes(i)) + .find(|&i| !self.get(i)); + + match idx { + Some(idx) => Err(idx), + None => Ok(()) + } + } + + pub fn set_range(&mut self, start: Size, end: Size, new_state: bool) { + let len = self.len; + if end > len { + self.grow(end - len, new_state); + } + self.set_range_inbounds(start, end, new_state); + } + + pub fn set_range_inbounds(&mut self, start: Size, end: Size, new_state: bool) { + for i in start.bytes()..end.bytes() { + self.set(Size::from_bytes(i), new_state); + } + } + + #[inline] + pub fn get(&self, i: Size) -> bool { + let (block, bit) = bit_index(i); + (self.blocks[block] & 1 << bit) != 0 + } + + #[inline] + pub fn set(&mut self, i: Size, new_state: bool) { + let (block, bit) = bit_index(i); + if new_state { + self.blocks[block] |= 1 << bit; + } else { + self.blocks[block] &= !(1 << bit); + } + } + + pub fn grow(&mut self, amount: Size, new_state: bool) { + let unused_trailing_bits = self.blocks.len() as u64 * BLOCK_SIZE - self.len.bytes(); + if amount.bytes() > unused_trailing_bits { + let additional_blocks = amount.bytes() / BLOCK_SIZE + 1; + assert_eq!(additional_blocks as usize as u64, additional_blocks); + self.blocks.extend( + iter::repeat(0).take(additional_blocks as usize), + ); + } + let start = self.len; + self.len += amount; + self.set_range_inbounds(start, start + amount, new_state); + } +} + +#[inline] +fn bit_index(bits: Size) -> (usize, usize) { + let bits = bits.bytes(); + let a = bits / BLOCK_SIZE; + let b = bits % BLOCK_SIZE; + assert_eq!(a as usize as u64, a); + assert_eq!(b as usize as u64, b); + (a as usize, b as usize) +} diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index 884de0bf8e108..6248791b20992 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -26,7 +26,10 @@ pub use self::error::{ pub use self::value::{Scalar, ConstValue}; -pub use self::allocation::{Allocation, MemoryAccess, AllocationExtra}; +pub use self::allocation::{ + Allocation, MemoryAccess, AllocationExtra, + Relocations, UndefMask, +}; use std::fmt; use mir; @@ -34,12 +37,9 @@ use hir::def_id::DefId; use ty::{self, TyCtxt, Instance}; use ty::layout::{self, HasDataLayout, Size}; use middle::region; -use std::iter; use std::io; -use std::ops::{Deref, DerefMut}; use std::hash::Hash; use rustc_serialize::{Encoder, Decodable, Encodable}; -use rustc_data_structures::sorted_map::SortedMap; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync::{Lock as Mutex, HashMapExt}; use rustc_data_structures::tiny_list::TinyList; @@ -525,35 +525,6 @@ impl<'tcx, M: fmt::Debug + Eq + Hash + Clone> AllocMap<'tcx, M> { } } -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, RustcEncodable, RustcDecodable)] -pub struct Relocations(SortedMap); - -impl Relocations { - pub fn new() -> Self { - Relocations(SortedMap::new()) - } - - // The caller must guarantee that the given relocations are already sorted - // by address and contain no duplicates. - pub fn from_presorted(r: Vec<(Size, (Tag, Id))>) -> Self { - Relocations(SortedMap::from_presorted_elements(r)) - } -} - -impl Deref for Relocations { - type Target = SortedMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Relocations { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - //////////////////////////////////////////////////////////////////////////////// // Methods to access integers in the target endianness //////////////////////////////////////////////////////////////////////////////// @@ -597,106 +568,6 @@ pub fn truncate(value: u128, size: Size) -> u128 { (value << shift) >> shift } -//////////////////////////////////////////////////////////////////////////////// -// Undefined byte tracking -//////////////////////////////////////////////////////////////////////////////// - -type Block = u64; -const BLOCK_SIZE: u64 = 64; - -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)] -pub struct UndefMask { - blocks: Vec, - len: Size, -} - -impl_stable_hash_for!(struct mir::interpret::UndefMask{blocks, len}); - -impl UndefMask { - pub fn new(size: Size) -> Self { - let mut m = UndefMask { - blocks: vec![], - len: Size::ZERO, - }; - m.grow(size, false); - m - } - - /// Check whether the range `start..end` (end-exclusive) is entirely defined. - /// - /// Returns `Ok(())` if it's defined. Otherwise returns the index of the byte - /// at which the first undefined access begins. - #[inline] - pub fn is_range_defined(&self, start: Size, end: Size) -> Result<(), Size> { - if end > self.len { - return Err(self.len); - } - - let idx = (start.bytes()..end.bytes()) - .map(|i| Size::from_bytes(i)) - .find(|&i| !self.get(i)); - - match idx { - Some(idx) => Err(idx), - None => Ok(()) - } - } - - pub fn set_range(&mut self, start: Size, end: Size, new_state: bool) { - let len = self.len; - if end > len { - self.grow(end - len, new_state); - } - self.set_range_inbounds(start, end, new_state); - } - - pub fn set_range_inbounds(&mut self, start: Size, end: Size, new_state: bool) { - for i in start.bytes()..end.bytes() { - self.set(Size::from_bytes(i), new_state); - } - } - - #[inline] - pub fn get(&self, i: Size) -> bool { - let (block, bit) = bit_index(i); - (self.blocks[block] & 1 << bit) != 0 - } - - #[inline] - pub fn set(&mut self, i: Size, new_state: bool) { - let (block, bit) = bit_index(i); - if new_state { - self.blocks[block] |= 1 << bit; - } else { - self.blocks[block] &= !(1 << bit); - } - } - - pub fn grow(&mut self, amount: Size, new_state: bool) { - let unused_trailing_bits = self.blocks.len() as u64 * BLOCK_SIZE - self.len.bytes(); - if amount.bytes() > unused_trailing_bits { - let additional_blocks = amount.bytes() / BLOCK_SIZE + 1; - assert_eq!(additional_blocks as usize as u64, additional_blocks); - self.blocks.extend( - iter::repeat(0).take(additional_blocks as usize), - ); - } - let start = self.len; - self.len += amount; - self.set_range_inbounds(start, start + amount, new_state); - } -} - -#[inline] -fn bit_index(bits: Size) -> (usize, usize) { - let bits = bits.bytes(); - let a = bits / BLOCK_SIZE; - let b = bits % BLOCK_SIZE; - assert_eq!(a as usize as u64, a); - assert_eq!(b as usize as u64, b); - (a as usize, b as usize) -} - #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] pub enum ScalarMaybeUndef { Scalar(Scalar), From df3cabba2c9889d1971200d23896d2efbd34e59c Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 25 Oct 2018 16:11:35 +0200 Subject: [PATCH 13/20] Move ScalarMaybeUndef into `value.rs` --- src/librustc/mir/interpret/mod.rs | 121 +--------------------------- src/librustc/mir/interpret/value.rs | 119 +++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 120 deletions(-) diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index 6248791b20992..c03828eda8c8e 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -24,7 +24,7 @@ pub use self::error::{ FrameInfo, ConstEvalResult, ErrorHandled, }; -pub use self::value::{Scalar, ConstValue}; +pub use self::value::{Scalar, ConstValue, ScalarMaybeUndef}; pub use self::allocation::{ Allocation, MemoryAccess, AllocationExtra, @@ -567,122 +567,3 @@ pub fn truncate(value: u128, size: Size) -> u128 { // truncate (shift left to drop out leftover values, shift right to fill with zeroes) (value << shift) >> shift } - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] -pub enum ScalarMaybeUndef { - Scalar(Scalar), - Undef, -} - -impl From> for ScalarMaybeUndef { - #[inline(always)] - fn from(s: Scalar) -> Self { - ScalarMaybeUndef::Scalar(s) - } -} - -impl<'tcx> ScalarMaybeUndef<()> { - #[inline] - pub fn with_default_tag(self) -> ScalarMaybeUndef - where Tag: Default - { - match self { - ScalarMaybeUndef::Scalar(s) => ScalarMaybeUndef::Scalar(s.with_default_tag()), - ScalarMaybeUndef::Undef => ScalarMaybeUndef::Undef, - } - } -} - -impl<'tcx, Tag> ScalarMaybeUndef { - #[inline] - pub fn erase_tag(self) -> ScalarMaybeUndef - { - match self { - ScalarMaybeUndef::Scalar(s) => ScalarMaybeUndef::Scalar(s.erase_tag()), - ScalarMaybeUndef::Undef => ScalarMaybeUndef::Undef, - } - } - - #[inline] - pub fn not_undef(self) -> EvalResult<'static, Scalar> { - match self { - ScalarMaybeUndef::Scalar(scalar) => Ok(scalar), - ScalarMaybeUndef::Undef => err!(ReadUndefBytes(Size::from_bytes(0))), - } - } - - #[inline(always)] - pub fn to_ptr(self) -> EvalResult<'tcx, Pointer> { - self.not_undef()?.to_ptr() - } - - #[inline(always)] - pub fn to_bits(self, target_size: Size) -> EvalResult<'tcx, u128> { - self.not_undef()?.to_bits(target_size) - } - - #[inline(always)] - pub fn to_bool(self) -> EvalResult<'tcx, bool> { - self.not_undef()?.to_bool() - } - - #[inline(always)] - pub fn to_char(self) -> EvalResult<'tcx, char> { - self.not_undef()?.to_char() - } - - #[inline(always)] - pub fn to_f32(self) -> EvalResult<'tcx, f32> { - self.not_undef()?.to_f32() - } - - #[inline(always)] - pub fn to_f64(self) -> EvalResult<'tcx, f64> { - self.not_undef()?.to_f64() - } - - #[inline(always)] - pub fn to_u8(self) -> EvalResult<'tcx, u8> { - self.not_undef()?.to_u8() - } - - #[inline(always)] - pub fn to_u32(self) -> EvalResult<'tcx, u32> { - self.not_undef()?.to_u32() - } - - #[inline(always)] - pub fn to_u64(self) -> EvalResult<'tcx, u64> { - self.not_undef()?.to_u64() - } - - #[inline(always)] - pub fn to_usize(self, cx: impl HasDataLayout) -> EvalResult<'tcx, u64> { - self.not_undef()?.to_usize(cx) - } - - #[inline(always)] - pub fn to_i8(self) -> EvalResult<'tcx, i8> { - self.not_undef()?.to_i8() - } - - #[inline(always)] - pub fn to_i32(self) -> EvalResult<'tcx, i32> { - self.not_undef()?.to_i32() - } - - #[inline(always)] - pub fn to_i64(self) -> EvalResult<'tcx, i64> { - self.not_undef()?.to_i64() - } - - #[inline(always)] - pub fn to_isize(self, cx: impl HasDataLayout) -> EvalResult<'tcx, i64> { - self.not_undef()?.to_isize(cx) - } -} - -impl_stable_hash_for!(enum ::mir::interpret::ScalarMaybeUndef { - Scalar(v), - Undef -}); diff --git a/src/librustc/mir/interpret/value.rs b/src/librustc/mir/interpret/value.rs index 4304f08a78f0c..2919dc6563a1b 100644 --- a/src/librustc/mir/interpret/value.rs +++ b/src/librustc/mir/interpret/value.rs @@ -355,3 +355,122 @@ impl From> for Scalar { Scalar::Ptr(ptr) } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] +pub enum ScalarMaybeUndef { + Scalar(Scalar), + Undef, +} + +impl From> for ScalarMaybeUndef { + #[inline(always)] + fn from(s: Scalar) -> Self { + ScalarMaybeUndef::Scalar(s) + } +} + +impl<'tcx> ScalarMaybeUndef<()> { + #[inline] + pub fn with_default_tag(self) -> ScalarMaybeUndef + where Tag: Default + { + match self { + ScalarMaybeUndef::Scalar(s) => ScalarMaybeUndef::Scalar(s.with_default_tag()), + ScalarMaybeUndef::Undef => ScalarMaybeUndef::Undef, + } + } +} + +impl<'tcx, Tag> ScalarMaybeUndef { + #[inline] + pub fn erase_tag(self) -> ScalarMaybeUndef + { + match self { + ScalarMaybeUndef::Scalar(s) => ScalarMaybeUndef::Scalar(s.erase_tag()), + ScalarMaybeUndef::Undef => ScalarMaybeUndef::Undef, + } + } + + #[inline] + pub fn not_undef(self) -> EvalResult<'static, Scalar> { + match self { + ScalarMaybeUndef::Scalar(scalar) => Ok(scalar), + ScalarMaybeUndef::Undef => err!(ReadUndefBytes(Size::from_bytes(0))), + } + } + + #[inline(always)] + pub fn to_ptr(self) -> EvalResult<'tcx, Pointer> { + self.not_undef()?.to_ptr() + } + + #[inline(always)] + pub fn to_bits(self, target_size: Size) -> EvalResult<'tcx, u128> { + self.not_undef()?.to_bits(target_size) + } + + #[inline(always)] + pub fn to_bool(self) -> EvalResult<'tcx, bool> { + self.not_undef()?.to_bool() + } + + #[inline(always)] + pub fn to_char(self) -> EvalResult<'tcx, char> { + self.not_undef()?.to_char() + } + + #[inline(always)] + pub fn to_f32(self) -> EvalResult<'tcx, f32> { + self.not_undef()?.to_f32() + } + + #[inline(always)] + pub fn to_f64(self) -> EvalResult<'tcx, f64> { + self.not_undef()?.to_f64() + } + + #[inline(always)] + pub fn to_u8(self) -> EvalResult<'tcx, u8> { + self.not_undef()?.to_u8() + } + + #[inline(always)] + pub fn to_u32(self) -> EvalResult<'tcx, u32> { + self.not_undef()?.to_u32() + } + + #[inline(always)] + pub fn to_u64(self) -> EvalResult<'tcx, u64> { + self.not_undef()?.to_u64() + } + + #[inline(always)] + pub fn to_usize(self, cx: impl HasDataLayout) -> EvalResult<'tcx, u64> { + self.not_undef()?.to_usize(cx) + } + + #[inline(always)] + pub fn to_i8(self) -> EvalResult<'tcx, i8> { + self.not_undef()?.to_i8() + } + + #[inline(always)] + pub fn to_i32(self) -> EvalResult<'tcx, i32> { + self.not_undef()?.to_i32() + } + + #[inline(always)] + pub fn to_i64(self) -> EvalResult<'tcx, i64> { + self.not_undef()?.to_i64() + } + + #[inline(always)] + pub fn to_isize(self, cx: impl HasDataLayout) -> EvalResult<'tcx, i64> { + self.not_undef()?.to_isize(cx) + } +} + +impl_stable_hash_for!(enum ::mir::interpret::ScalarMaybeUndef { + Scalar(v), + Undef +}); From de462bac03f39a20c4e54c003b57a5e53bd46817 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 25 Oct 2018 16:14:30 +0200 Subject: [PATCH 14/20] Move some functions around to keep order in sync with memory.rs --- src/librustc/mir/interpret/allocation.rs | 144 +++++++++++------------ 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index 1e5bdb73e49c4..0541daad7e65a 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -111,6 +111,78 @@ impl Allocation { impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {} +/// Various correctness checks for `Pointer`s +impl<'tcx, Tag: Copy, Extra> Allocation { + /// Check that the pointer is aligned AND non-NULL. This supports ZSTs in two ways: + /// You can pass a scalar, and a `Pointer` does not have to actually still be allocated. + pub fn check_align( + &self, + ptr: Pointer, + required_align: Align + ) -> EvalResult<'tcx> { + // Check non-NULL/Undef, extract offset + + // check this is not NULL -- which we can ensure only if this is in-bounds + let size = Size::from_bytes(self.bytes.len() as u64); + if ptr.offset > size { + return err!(PointerOutOfBounds { + ptr: ptr.erase_tag(), + access: true, + allocation_size: size, + }); + }; + // Check alignment + if self.align.abi() < required_align.abi() { + return err!(AlignmentCheckFailed { + has: self.align, + required: required_align, + }); + } + let offset = ptr.offset.bytes(); + if offset % required_align.abi() == 0 { + Ok(()) + } else { + let has = offset % required_align.abi(); + err!(AlignmentCheckFailed { + has: Align::from_bytes(has, has).unwrap(), + required: required_align, + }) + } + } + + /// Check if the pointer is "in-bounds". Notice that a pointer pointing at the end + /// of an allocation (i.e., at the first *inaccessible* location) *is* considered + /// in-bounds! This follows C's/LLVM's rules. The `access` boolean is just used + /// for the error message. + /// If you want to check bounds before doing a memory access, be sure to + /// check the pointer one past the end of your access, then everything will + /// work out exactly. + pub fn check_bounds_ptr(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> { + let allocation_size = self.bytes.len() as u64; + if ptr.offset.bytes() > allocation_size { + return err!(PointerOutOfBounds { + ptr: ptr.erase_tag(), + access, + allocation_size: Size::from_bytes(allocation_size), + }); + } + Ok(()) + } + + /// Check if the memory range beginning at `ptr` and of size `Size` is "in-bounds". + #[inline(always)] + pub fn check_bounds( + &self, + cx: impl HasDataLayout, + ptr: Pointer, + size: Size, + access: bool + ) -> EvalResult<'tcx> { + // if ptr.offset is in bounds, then so is ptr (because offset checks for overflow) + self.check_bounds_ptr(ptr.offset(size, cx)?, access) + } +} + /// Byte accessors impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { /// The last argument controls whether we error out when there are undefined @@ -527,78 +599,6 @@ impl<'tcx, Tag: Copy, Extra> Allocation { } } -impl<'tcx, Tag: Copy, Extra> Allocation { - /// Check that the pointer is aligned AND non-NULL. This supports ZSTs in two ways: - /// You can pass a scalar, and a `Pointer` does not have to actually still be allocated. - pub fn check_align( - &self, - ptr: Pointer, - required_align: Align - ) -> EvalResult<'tcx> { - // Check non-NULL/Undef, extract offset - - // check this is not NULL -- which we can ensure only if this is in-bounds - let size = Size::from_bytes(self.bytes.len() as u64); - if ptr.offset > size { - return err!(PointerOutOfBounds { - ptr: ptr.erase_tag(), - access: true, - allocation_size: size, - }); - }; - // Check alignment - if self.align.abi() < required_align.abi() { - return err!(AlignmentCheckFailed { - has: self.align, - required: required_align, - }); - } - let offset = ptr.offset.bytes(); - if offset % required_align.abi() == 0 { - Ok(()) - } else { - let has = offset % required_align.abi(); - err!(AlignmentCheckFailed { - has: Align::from_bytes(has, has).unwrap(), - required: required_align, - }) - } - } - - /// Check if the pointer is "in-bounds". Notice that a pointer pointing at the end - /// of an allocation (i.e., at the first *inaccessible* location) *is* considered - /// in-bounds! This follows C's/LLVM's rules. The `access` boolean is just used - /// for the error message. - /// If you want to check bounds before doing a memory access, be sure to - /// check the pointer one past the end of your access, then everything will - /// work out exactly. - pub fn check_bounds_ptr(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> { - let allocation_size = self.bytes.len() as u64; - if ptr.offset.bytes() > allocation_size { - return err!(PointerOutOfBounds { - ptr: ptr.erase_tag(), - access, - allocation_size: Size::from_bytes(allocation_size), - }); - } - Ok(()) - } - - /// Check if the memory range beginning at `ptr` and of size `Size` is "in-bounds". - #[inline(always)] - pub fn check_bounds( - &self, - cx: impl HasDataLayout, - ptr: Pointer, - size: Size, - access: bool - ) -> EvalResult<'tcx> { - // if ptr.offset is in bounds, then so is ptr (because offset checks for overflow) - self.check_bounds_ptr(ptr.offset(size, cx)?, access) - } -} - - #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, RustcEncodable, RustcDecodable)] pub struct Relocations(SortedMap); From 8cc3faa759c6d34e42ae3757f47036828530b751 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 25 Oct 2018 16:33:02 +0200 Subject: [PATCH 15/20] Add doc comment to `check_bytes` --- src/librustc/mir/interpret/allocation.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index 0541daad7e65a..9d00ecb07ac24 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -285,6 +285,10 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { } } + /// Checks whether the target range does not have relocations on the edge of the range + /// + /// if `allow_ptr_and_undef` is `false`, also check whether the entire range contains + /// neither undefined bytes nor any relocations pub fn check_bytes( &self, cx: impl HasDataLayout, From ed8b17edcc9c4f2902697dc23f0604973946b6e2 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 25 Oct 2018 16:42:53 +0200 Subject: [PATCH 16/20] Remove leftover comment --- src/librustc/mir/interpret/allocation.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index 9d00ecb07ac24..b6f935568635c 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -120,8 +120,6 @@ impl<'tcx, Tag: Copy, Extra> Allocation { ptr: Pointer, required_align: Align ) -> EvalResult<'tcx> { - // Check non-NULL/Undef, extract offset - // check this is not NULL -- which we can ensure only if this is in-bounds let size = Size::from_bytes(self.bytes.len() as u64); if ptr.offset > size { From 5c9b456430f246199a5fcf04bcec2f09f5b848d7 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 25 Oct 2018 17:04:33 +0200 Subject: [PATCH 17/20] Get rid of zst checks, the code works just fine without them --- src/librustc/mir/interpret/allocation.rs | 27 +----------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs index b6f935568635c..fd06d1fc81c64 100644 --- a/src/librustc/mir/interpret/allocation.rs +++ b/src/librustc/mir/interpret/allocation.rs @@ -198,7 +198,6 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { align: Align, check_defined_and_ptr: bool, ) -> EvalResult<'tcx, &[u8]> { - assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`"); self.check_align(ptr.into(), align)?; self.check_bounds(cx, ptr, size, true)?; @@ -251,7 +250,6 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { size: Size, align: Align, ) -> EvalResult<'tcx, &mut [u8]> { - assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`"); self.check_align(ptr.into(), align)?; self.check_bounds(cx, ptr, size, true)?; @@ -294,13 +292,8 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { size: Size, allow_ptr_and_undef: bool, ) -> EvalResult<'tcx> { - // Empty accesses don't need to be valid pointers, but they should still be non-NULL + // Check bounds and relocations on the edges let align = Align::from_bytes(1, 1).unwrap(); - if size.bytes() == 0 { - self.check_align(ptr, align)?; - return Ok(()); - } - // Check bounds, align and relocations on the edges self.get_bytes_with_undef_and_ptr(cx, ptr, size, align)?; // Check undef and ptr if !allow_ptr_and_undef { @@ -316,12 +309,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { ptr: Pointer, size: Size, ) -> EvalResult<'tcx, &[u8]> { - // Empty accesses don't need to be valid pointers, but they should still be non-NULL let align = Align::from_bytes(1, 1).unwrap(); - if size.bytes() == 0 { - self.check_align(ptr, align)?; - return Ok(&[]); - } self.get_bytes(cx, ptr, size, align) } @@ -331,12 +319,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { ptr: Pointer, src: &[u8], ) -> EvalResult<'tcx> { - // Empty accesses don't need to be valid pointers, but they should still be non-NULL let align = Align::from_bytes(1, 1).unwrap(); - if src.is_empty() { - self.check_align(ptr, align)?; - return Ok(()); - } let bytes = self.get_bytes_mut(cx, ptr, Size::from_bytes(src.len() as u64), align)?; bytes.clone_from_slice(src); Ok(()) @@ -349,12 +332,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra> Allocation { val: u8, count: Size ) -> EvalResult<'tcx> { - // Empty accesses don't need to be valid pointers, but they should still be non-NULL let align = Align::from_bytes(1, 1).unwrap(); - if count.bytes() == 0 { - self.check_align(ptr, align)?; - return Ok(()); - } let bytes = self.get_bytes_mut(cx, ptr, count, align)?; for b in bytes { *b = val; @@ -589,9 +567,6 @@ impl<'tcx, Tag: Copy, Extra> Allocation { size: Size, new_state: bool, ) -> EvalResult<'tcx> { - if size.bytes() == 0 { - return Ok(()); - } self.undef_mask.set_range( ptr.offset, ptr.offset + size, From 9ce08034481ab7f8a705d7cd38b0269b3eeba281 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 25 Oct 2018 18:23:09 +0200 Subject: [PATCH 18/20] Move `Pointer` to its own module --- src/librustc/mir/interpret/mod.rs | 144 +------------------------- src/librustc/mir/interpret/pointer.rs | 144 ++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 140 deletions(-) create mode 100644 src/librustc/mir/interpret/pointer.rs diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index c03828eda8c8e..1ed22ede358fc 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -18,6 +18,7 @@ macro_rules! err { mod error; mod value; mod allocation; +mod pointer; pub use self::error::{ EvalError, EvalResult, EvalErrorKind, AssertMessage, ConstEvalErr, struct_error, @@ -31,11 +32,13 @@ pub use self::allocation::{ Relocations, UndefMask, }; +pub use self::pointer::{Pointer, PointerArithmetic}; + use std::fmt; use mir; use hir::def_id::DefId; use ty::{self, TyCtxt, Instance}; -use ty::layout::{self, HasDataLayout, Size}; +use ty::layout::{self, Size}; use middle::region; use std::io; use std::hash::Hash; @@ -80,145 +83,6 @@ pub struct GlobalId<'tcx> { pub promoted: Option, } -//////////////////////////////////////////////////////////////////////////////// -// Pointer arithmetic -//////////////////////////////////////////////////////////////////////////////// - -pub trait PointerArithmetic: layout::HasDataLayout { - // These are not supposed to be overridden. - - #[inline(always)] - fn pointer_size(self) -> Size { - self.data_layout().pointer_size - } - - //// Trunace the given value to the pointer size; also return whether there was an overflow - fn truncate_to_ptr(self, val: u128) -> (u64, bool) { - let max_ptr_plus_1 = 1u128 << self.pointer_size().bits(); - ((val % max_ptr_plus_1) as u64, val >= max_ptr_plus_1) - } - - // Overflow checking only works properly on the range from -u64 to +u64. - fn overflowing_signed_offset(self, val: u64, i: i128) -> (u64, bool) { - // FIXME: is it possible to over/underflow here? - if i < 0 { - // trickery to ensure that i64::min_value() works fine - // this formula only works for true negative values, it panics for zero! - let n = u64::max_value() - (i as u64) + 1; - val.overflowing_sub(n) - } else { - self.overflowing_offset(val, i as u64) - } - } - - fn overflowing_offset(self, val: u64, i: u64) -> (u64, bool) { - let (res, over1) = val.overflowing_add(i); - let (res, over2) = self.truncate_to_ptr(res as u128); - (res, over1 || over2) - } - - fn signed_offset<'tcx>(self, val: u64, i: i64) -> EvalResult<'tcx, u64> { - let (res, over) = self.overflowing_signed_offset(val, i as i128); - if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) } - } - - fn offset<'tcx>(self, val: u64, i: u64) -> EvalResult<'tcx, u64> { - let (res, over) = self.overflowing_offset(val, i); - if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) } - } - - fn wrapping_signed_offset(self, val: u64, i: i64) -> u64 { - self.overflowing_signed_offset(val, i as i128).0 - } -} - -impl PointerArithmetic for T {} - - -/// Pointer is generic over the type that represents a reference to Allocations, -/// thus making it possible for the most convenient representation to be used in -/// each context. -/// -/// Defaults to the index based and loosely coupled AllocId. -/// -/// Pointer is also generic over the `Tag` associated with each pointer, -/// which is used to do provenance tracking during execution. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] -pub struct Pointer { - pub alloc_id: Id, - pub offset: Size, - pub tag: Tag, -} - -/// Produces a `Pointer` which points to the beginning of the Allocation -impl From for Pointer { - #[inline(always)] - fn from(alloc_id: AllocId) -> Self { - Pointer::new(alloc_id, Size::ZERO) - } -} - -impl<'tcx> Pointer<()> { - #[inline(always)] - pub fn new(alloc_id: AllocId, offset: Size) -> Self { - Pointer { alloc_id, offset, tag: () } - } - - #[inline(always)] - pub fn with_default_tag(self) -> Pointer - where Tag: Default - { - Pointer::new_with_tag(self.alloc_id, self.offset, Default::default()) - } -} - -impl<'tcx, Tag> Pointer { - #[inline(always)] - pub fn new_with_tag(alloc_id: AllocId, offset: Size, tag: Tag) -> Self { - Pointer { alloc_id, offset, tag } - } - - pub fn wrapping_signed_offset(self, i: i64, cx: impl HasDataLayout) -> Self { - Pointer::new_with_tag( - self.alloc_id, - Size::from_bytes(cx.data_layout().wrapping_signed_offset(self.offset.bytes(), i)), - self.tag, - ) - } - - pub fn overflowing_signed_offset(self, i: i128, cx: impl HasDataLayout) -> (Self, bool) { - let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset.bytes(), i); - (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over) - } - - pub fn signed_offset(self, i: i64, cx: impl HasDataLayout) -> EvalResult<'tcx, Self> { - Ok(Pointer::new_with_tag( - self.alloc_id, - Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?), - self.tag, - )) - } - - pub fn overflowing_offset(self, i: Size, cx: impl HasDataLayout) -> (Self, bool) { - let (res, over) = cx.data_layout().overflowing_offset(self.offset.bytes(), i.bytes()); - (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over) - } - - pub fn offset(self, i: Size, cx: impl HasDataLayout) -> EvalResult<'tcx, Self> { - Ok(Pointer::new_with_tag( - self.alloc_id, - Size::from_bytes(cx.data_layout().offset(self.offset.bytes(), i.bytes())?), - self.tag - )) - } - - #[inline] - pub fn erase_tag(self) -> Pointer { - Pointer { alloc_id: self.alloc_id, offset: self.offset, tag: () } - } -} - - #[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd, Debug)] pub struct AllocId(pub u64); diff --git a/src/librustc/mir/interpret/pointer.rs b/src/librustc/mir/interpret/pointer.rs new file mode 100644 index 0000000000000..3737456ba813e --- /dev/null +++ b/src/librustc/mir/interpret/pointer.rs @@ -0,0 +1,144 @@ + use mir; +use ty::layout::{self, HasDataLayout, Size}; + +use super::{ + AllocId, EvalResult, +}; + +//////////////////////////////////////////////////////////////////////////////// +// Pointer arithmetic +//////////////////////////////////////////////////////////////////////////////// + +pub trait PointerArithmetic: layout::HasDataLayout { + // These are not supposed to be overridden. + + #[inline(always)] + fn pointer_size(self) -> Size { + self.data_layout().pointer_size + } + + //// Trunace the given value to the pointer size; also return whether there was an overflow + fn truncate_to_ptr(self, val: u128) -> (u64, bool) { + let max_ptr_plus_1 = 1u128 << self.pointer_size().bits(); + ((val % max_ptr_plus_1) as u64, val >= max_ptr_plus_1) + } + + // Overflow checking only works properly on the range from -u64 to +u64. + fn overflowing_signed_offset(self, val: u64, i: i128) -> (u64, bool) { + // FIXME: is it possible to over/underflow here? + if i < 0 { + // trickery to ensure that i64::min_value() works fine + // this formula only works for true negative values, it panics for zero! + let n = u64::max_value() - (i as u64) + 1; + val.overflowing_sub(n) + } else { + self.overflowing_offset(val, i as u64) + } + } + + fn overflowing_offset(self, val: u64, i: u64) -> (u64, bool) { + let (res, over1) = val.overflowing_add(i); + let (res, over2) = self.truncate_to_ptr(res as u128); + (res, over1 || over2) + } + + fn signed_offset<'tcx>(self, val: u64, i: i64) -> EvalResult<'tcx, u64> { + let (res, over) = self.overflowing_signed_offset(val, i as i128); + if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) } + } + + fn offset<'tcx>(self, val: u64, i: u64) -> EvalResult<'tcx, u64> { + let (res, over) = self.overflowing_offset(val, i); + if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) } + } + + fn wrapping_signed_offset(self, val: u64, i: i64) -> u64 { + self.overflowing_signed_offset(val, i as i128).0 + } +} + +impl PointerArithmetic for T {} + + +/// Pointer is generic over the type that represents a reference to Allocations, +/// thus making it possible for the most convenient representation to be used in +/// each context. +/// +/// Defaults to the index based and loosely coupled AllocId. +/// +/// Pointer is also generic over the `Tag` associated with each pointer, +/// which is used to do provenance tracking during execution. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)] +pub struct Pointer { + pub alloc_id: Id, + pub offset: Size, + pub tag: Tag, +} + +/// Produces a `Pointer` which points to the beginning of the Allocation +impl From for Pointer { + #[inline(always)] + fn from(alloc_id: AllocId) -> Self { + Pointer::new(alloc_id, Size::ZERO) + } +} + +impl<'tcx> Pointer<()> { + #[inline(always)] + pub fn new(alloc_id: AllocId, offset: Size) -> Self { + Pointer { alloc_id, offset, tag: () } + } + + #[inline(always)] + pub fn with_default_tag(self) -> Pointer + where Tag: Default + { + Pointer::new_with_tag(self.alloc_id, self.offset, Default::default()) + } +} + +impl<'tcx, Tag> Pointer { + #[inline(always)] + pub fn new_with_tag(alloc_id: AllocId, offset: Size, tag: Tag) -> Self { + Pointer { alloc_id, offset, tag } + } + + pub fn wrapping_signed_offset(self, i: i64, cx: impl HasDataLayout) -> Self { + Pointer::new_with_tag( + self.alloc_id, + Size::from_bytes(cx.data_layout().wrapping_signed_offset(self.offset.bytes(), i)), + self.tag, + ) + } + + pub fn overflowing_signed_offset(self, i: i128, cx: impl HasDataLayout) -> (Self, bool) { + let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset.bytes(), i); + (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over) + } + + pub fn signed_offset(self, i: i64, cx: impl HasDataLayout) -> EvalResult<'tcx, Self> { + Ok(Pointer::new_with_tag( + self.alloc_id, + Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?), + self.tag, + )) + } + + pub fn overflowing_offset(self, i: Size, cx: impl HasDataLayout) -> (Self, bool) { + let (res, over) = cx.data_layout().overflowing_offset(self.offset.bytes(), i.bytes()); + (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over) + } + + pub fn offset(self, i: Size, cx: impl HasDataLayout) -> EvalResult<'tcx, Self> { + Ok(Pointer::new_with_tag( + self.alloc_id, + Size::from_bytes(cx.data_layout().offset(self.offset.bytes(), i.bytes())?), + self.tag + )) + } + + #[inline] + pub fn erase_tag(self) -> Pointer { + Pointer { alloc_id: self.alloc_id, offset: self.offset, tag: () } + } +} From dd194efd0463414c31ef29daf20027e0f9164053 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 25 Oct 2018 18:41:18 +0200 Subject: [PATCH 19/20] Revert "Update miri submodule" This reverts commit d50e401d96f02e072c43a340f534a64b8cccc697. --- src/tools/miri | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri b/src/tools/miri index c20e7333c6047..bbb1d80703f27 160000 --- a/src/tools/miri +++ b/src/tools/miri @@ -1 +1 @@ -Subproject commit c20e7333c6047fe8c11fce7fa2041e9174748bb9 +Subproject commit bbb1d80703f272a5592ceeb3832a489776512251 From 0aa2aa80a2cfdc9d4e59803f41f9ffd3b41493a7 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Fri, 26 Oct 2018 17:15:49 +0200 Subject: [PATCH 20/20] Rebase fallout --- src/librustc_mir/interpret/memory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs index d0160be6bacf0..a337a3564cace 100644 --- a/src/librustc_mir/interpret/memory.rs +++ b/src/librustc_mir/interpret/memory.rs @@ -30,7 +30,7 @@ use syntax::ast::Mutability; use super::{ Pointer, AllocId, Allocation, ConstValue, GlobalId, EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic, - Machine, MemoryAccess, AllocMap, MayLeak, ScalarMaybeUndef, AllocationExtra, ErrorHandled, + Machine, AllocMap, MayLeak, ScalarMaybeUndef, AllocationExtra, ErrorHandled, }; #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]