diff --git a/src/error.rs b/src/error.rs index 496cefad33d..0c23038c3c3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -59,6 +59,8 @@ pub enum EvalError<'tcx> { ReallocatedStaticMemory, DeallocatedStaticMemory, Layout(layout::LayoutError<'tcx>), + HeapAllocZeroBytes, + HeapAllocNonPowerOfTwoAlignment(u64), Unreachable, Panic, } @@ -89,7 +91,7 @@ impl<'tcx> Error for EvalError<'tcx> { EvalError::ReadBytesAsPointer => "a memory access tried to interpret some bytes as a pointer", EvalError::InvalidPointerMath => - "attempted to do invalid arithmetic on pointers that would leak base addresses, e.g. compating pointers into different allocations", + "attempted to do invalid arithmetic on pointers that would leak base addresses, e.g. comparing pointers into different allocations", EvalError::ReadUndefBytes => "attempted to read undefined bytes", EvalError::DeadLocal => @@ -146,6 +148,10 @@ impl<'tcx> Error for EvalError<'tcx> { "rustc layout computation failed", EvalError::UnterminatedCString(_) => "attempted to get length of a null terminated string, but no null found before end of allocation", + EvalError::HeapAllocZeroBytes => + "tried to re-, de- or allocate zero bytes on the heap", + EvalError::HeapAllocNonPowerOfTwoAlignment(_) => + "tried to re-, de-, or allocate heap memory with alignment that is not a power of two", EvalError::Unreachable => "entered unreachable code", EvalError::Panic => diff --git a/src/eval_context.rs b/src/eval_context.rs index 9300ae3dbc2..7052a411f7e 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -362,7 +362,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { StackPopCleanup::None => {}, StackPopCleanup::Tls(key) => { // either fetch the next dtor or start new from the beginning, if any are left with a non-null data - if let Some((instance, ptr, key)) = self.memory.fetch_tls_dtor(key).or_else(|| self.memory.fetch_tls_dtor(None)) { + let dtor = match self.memory.fetch_tls_dtor(key)? { + dtor @ Some(_) => dtor, + None => self.memory.fetch_tls_dtor(None)?, + }; + if let Some((instance, ptr, key)) = dtor { trace!("Running TLS dtor {:?} on {:?}", instance, ptr); // TODO: Potentially, this has to support all the other possible instances? See eval_fn_call in terminator/mod.rs let mir = self.load_mir(instance.def)?; @@ -370,7 +374,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { instance, mir.span, mir, - Lvalue::zst(), + Lvalue::undef(), StackPopCleanup::Tls(Some(key)), )?; let arg_local = self.frame().mir.args_iter().next().ok_or(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned()))?; @@ -673,8 +677,14 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } NullaryOp(mir::NullOp::Box, ty) => { - let ptr = self.alloc_ptr(ty)?; - self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; + // FIXME: call the `exchange_malloc` lang item if available + if self.type_size(ty)?.expect("box only works with sized types") == 0 { + let align = self.type_align(ty)?; + self.write_primval(dest, PrimVal::Bytes(align.into()), dest_ty)?; + } else { + let ptr = self.alloc_ptr(ty)?; + self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; + } } NullaryOp(mir::NullOp::SizeOf, ty) => { @@ -904,11 +914,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let pointee_size = self.type_size(pointee_ty)?.expect("cannot offset a pointer to an unsized type") as i64; return if let Some(offset) = offset.checked_mul(pointee_size) { let ptr = ptr.signed_offset(offset, self.memory.layout)?; - // Do not do bounds-checking for integers or ZST; they can never alias a normal pointer anyway. + // Do not do bounds-checking for integers; they can never alias a normal pointer anyway. if let PrimVal::Ptr(ptr) = ptr { - if !(ptr.points_to_zst() && (offset == 0 || pointee_size == 0)) { - self.memory.check_bounds(ptr, false)?; - } + self.memory.check_bounds(ptr, false)?; } else if ptr.is_null()? { // We moved *to* a NULL pointer. That seems wrong, LLVM considers the NULL pointer its own small allocation. Reject this, for now. return Err(EvalError::NullPointerOutOfBounds); @@ -1697,7 +1705,7 @@ pub fn eval_main<'a, 'tcx: 'a>( main_instance, main_mir.span, main_mir, - Lvalue::zst(), + Lvalue::undef(), StackPopCleanup::Tls(None), )?; } diff --git a/src/lvalue.rs b/src/lvalue.rs index f409f373484..8492019a9bf 100644 --- a/src/lvalue.rs +++ b/src/lvalue.rs @@ -8,7 +8,7 @@ use eval_context::{EvalContext}; use memory::Pointer; use value::{PrimVal, Value}; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug)] pub enum Lvalue<'tcx> { /// An lvalue referring to a value allocated in the `Memory` system. Ptr { @@ -73,10 +73,6 @@ impl<'tcx> Lvalue<'tcx> { Lvalue::Ptr { ptr, extra: LvalueExtra::None } } - pub fn zst() -> Self { - Self::from_ptr(Pointer::zst_ptr()) - } - pub fn from_ptr(ptr: Pointer) -> Self { Self::from_primval_ptr(PrimVal::Ptr(ptr)) } diff --git a/src/memory.rs b/src/memory.rs index 4cb66f2acdc..5794dc783cf 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -81,14 +81,6 @@ impl Pointer { pub fn offset<'tcx>(self, i: u64, layout: &TargetDataLayout) -> EvalResult<'tcx, Self> { Ok(Pointer::new(self.alloc_id, value::offset(self.offset, i, layout)?)) } - - pub fn points_to_zst(&self) -> bool { - self.alloc_id == ZST_ALLOC_ID - } - - pub fn zst_ptr() -> Self { - Pointer::new(ZST_ALLOC_ID, 0) - } } pub type TlsKey = usize; @@ -157,15 +149,13 @@ pub struct Memory<'a, 'tcx> { next_thread_local: TlsKey, } -const ZST_ALLOC_ID: AllocId = AllocId(0); - impl<'a, 'tcx> Memory<'a, 'tcx> { pub fn new(layout: &'a TargetDataLayout, max_memory: u64) -> Self { Memory { alloc_map: HashMap::new(), functions: HashMap::new(), function_alloc_cache: HashMap::new(), - next_id: AllocId(2), + next_id: AllocId(0), layout, memory_size: max_memory, memory_usage: 0, @@ -206,10 +196,8 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } pub fn allocate(&mut self, size: u64, align: u64) -> EvalResult<'tcx, Pointer> { - if size == 0 { - return Ok(Pointer::zst_ptr()); - } assert_ne!(align, 0); + assert!(align.is_power_of_two()); if self.memory_size - self.memory_usage < size { return Err(EvalError::OutOfMemory { @@ -236,13 +224,11 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { // TODO(solson): Track which allocations were returned from __rust_allocate and report an error // when reallocating/deallocating any others. pub fn reallocate(&mut self, ptr: Pointer, new_size: u64, align: u64) -> EvalResult<'tcx, Pointer> { + assert!(align.is_power_of_two()); // TODO(solson): Report error about non-__rust_allocate'd pointer. if ptr.offset != 0 { return Err(EvalError::Unimplemented(format!("bad pointer offset: {}", ptr.offset))); } - if ptr.points_to_zst() { - return self.allocate(new_size, align); - } if self.get(ptr.alloc_id).ok().map_or(false, |alloc| alloc.static_kind != StaticKind::NotStatic) { return Err(EvalError::ReallocatedStaticMemory); } @@ -253,6 +239,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { let amount = new_size - size; self.memory_usage += amount; let alloc = self.get_mut(ptr.alloc_id)?; + // FIXME: check alignment here assert_eq!(amount as usize as u64, amount); alloc.bytes.extend(iter::repeat(0).take(amount as usize)); alloc.undef_mask.grow(amount, false); @@ -260,6 +247,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { self.memory_usage -= size - new_size; self.clear_relocations(ptr.offset(new_size, self.layout)?, size - new_size)?; let alloc = self.get_mut(ptr.alloc_id)?; + // FIXME: check alignment here // `as usize` is fine here, since it is smaller than `size`, which came from a usize alloc.bytes.truncate(new_size as usize); alloc.bytes.shrink_to_fit(); @@ -271,9 +259,6 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { // TODO(solson): See comment on `reallocate`. pub fn deallocate(&mut self, ptr: Pointer) -> EvalResult<'tcx> { - if ptr.points_to_zst() { - return Ok(()); - } if ptr.offset != 0 { // TODO(solson): Report error about non-__rust_allocate'd pointer. return Err(EvalError::Unimplemented(format!("bad pointer offset: {}", ptr.offset))); @@ -419,22 +404,22 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { /// with associated destructors, implementations may stop calling destructors, /// or they may continue calling destructors until no non-NULL values with /// associated destructors exist, even though this might result in an infinite loop. - pub(crate) fn fetch_tls_dtor(&mut self, key: Option<TlsKey>) -> Option<(ty::Instance<'tcx>, PrimVal, TlsKey)> { + pub(crate) fn fetch_tls_dtor(&mut self, key: Option<TlsKey>) -> EvalResult<'tcx, Option<(ty::Instance<'tcx>, PrimVal, TlsKey)>> { use std::collections::Bound::*; let start = match key { Some(key) => Excluded(key), None => Unbounded, }; for (&key, &mut TlsEntry { ref mut data, dtor }) in self.thread_local.range_mut((start, Unbounded)) { - if *data != PrimVal::Bytes(0) { + if !data.is_null()? { if let Some(dtor) = dtor { let ret = Some((dtor, *data, key)); *data = PrimVal::Bytes(0); - return ret; + return Ok(ret); } } } - return None; + return Ok(None); } } @@ -459,7 +444,6 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { Some(alloc) => Ok(alloc), None => match self.functions.get(&id) { Some(_) => Err(EvalError::DerefFunctionPointer), - None if id == ZST_ALLOC_ID => Err(EvalError::InvalidMemoryAccess), None => Err(EvalError::DanglingPointerDeref), } } @@ -474,7 +458,6 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { }, None => match self.functions.get(&id) { Some(_) => Err(EvalError::DerefFunctionPointer), - None if id == ZST_ALLOC_ID => Err(EvalError::InvalidMemoryAccess), None => Err(EvalError::DanglingPointerDeref), } } @@ -508,7 +491,6 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { let mut allocs_seen = HashSet::new(); while let Some(id) = allocs_to_print.pop_front() { - if id == ZST_ALLOC_ID { continue; } let mut msg = format!("Alloc {:<5} ", format!("{}:", id)); let prefix_len = msg.len(); let mut relocations = vec![]; @@ -556,10 +538,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { for (i, target_id) in relocations { // this `as usize` is fine, since we can't print more chars than `usize::MAX` write!(msg, "{:1$}", "", ((i - pos) * 3) as usize).unwrap(); - let target = match target_id { - ZST_ALLOC_ID => String::from("zst"), - _ => format!("({})", target_id), - }; + let target = format!("({})", target_id); // this `as usize` is fine, since we can't print more chars than `usize::MAX` write!(msg, "└{0:─^1$}┘ ", target, relocation_width as usize).unwrap(); pos = i + self.pointer_size(); @@ -637,7 +616,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { /// mark an allocation as being the entry point to a static (see `static_alloc` field) pub fn mark_static(&mut self, alloc_id: AllocId) { trace!("mark_static: {:?}", alloc_id); - if alloc_id != ZST_ALLOC_ID && !self.static_alloc.insert(alloc_id) { + if !self.static_alloc.insert(alloc_id) { bug!("tried to mark an allocation ({:?}) as static twice", alloc_id); } } @@ -667,7 +646,6 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { // mark recursively mem::replace(relocations, Default::default()) }, - None if alloc_id == ZST_ALLOC_ID => return Ok(()), None if !self.functions.contains_key(&alloc_id) => return Err(EvalError::DanglingPointerDeref), _ => return Ok(()), }; diff --git a/src/operator.rs b/src/operator.rs index ed69c804393..e5ed99b2434 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -159,10 +159,22 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { }, // These work on anything Eq if left_kind == right_kind => { - return Ok((PrimVal::from_bool(left == right), false)); + let result = match (left, right) { + (PrimVal::Bytes(left), PrimVal::Bytes(right)) => left == right, + (PrimVal::Ptr(left), PrimVal::Ptr(right)) => left == right, + (PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes), + _ => false, + }; + return Ok((PrimVal::from_bool(result), false)); } Ne if left_kind == right_kind => { - return Ok((PrimVal::from_bool(left != right), false)); + let result = match (left, right) { + (PrimVal::Bytes(left), PrimVal::Bytes(right)) => left != right, + (PrimVal::Ptr(left), PrimVal::Ptr(right)) => left != right, + (PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes), + _ => true, + }; + return Ok((PrimVal::from_bool(result), false)); } // These need both pointers to be in the same allocation Lt | Le | Gt | Ge | Sub diff --git a/src/terminator/drop.rs b/src/terminator/drop.rs index 663d05254e5..072a5d16a1b 100644 --- a/src/terminator/drop.rs +++ b/src/terminator/drop.rs @@ -49,7 +49,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { instance, span, mir, - Lvalue::zst(), + Lvalue::undef(), StackPopCleanup::None, )?; diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 90f0aed7f10..c773620cbb5 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -596,6 +596,12 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { "__rust_allocate" => { let size = self.value_to_primval(args[0], usize)?.to_u64()?; let align = self.value_to_primval(args[1], usize)?.to_u64()?; + if size == 0 { + return Err(EvalError::HeapAllocZeroBytes); + } + if !align.is_power_of_two() { + return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); + } let ptr = self.memory.allocate(size, align)?; self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; } @@ -603,6 +609,12 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { "__rust_allocate_zeroed" => { let size = self.value_to_primval(args[0], usize)?.to_u64()?; let align = self.value_to_primval(args[1], usize)?.to_u64()?; + if size == 0 { + return Err(EvalError::HeapAllocZeroBytes); + } + if !align.is_power_of_two() { + return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); + } let ptr = self.memory.allocate(size, align)?; self.memory.write_repeat(ptr, 0, size)?; self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; @@ -611,8 +623,14 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { "__rust_deallocate" => { let ptr = args[0].read_ptr(&self.memory)?.to_ptr()?; // FIXME: insert sanity check for size and align? - let _old_size = self.value_to_primval(args[1], usize)?.to_u64()?; - let _align = self.value_to_primval(args[2], usize)?.to_u64()?; + let old_size = self.value_to_primval(args[1], usize)?.to_u64()?; + let align = self.value_to_primval(args[2], usize)?.to_u64()?; + if old_size == 0 { + return Err(EvalError::HeapAllocZeroBytes); + } + if !align.is_power_of_two() { + return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); + } self.memory.deallocate(ptr)?; }, @@ -620,6 +638,12 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let ptr = args[0].read_ptr(&self.memory)?.to_ptr()?; let size = self.value_to_primval(args[2], usize)?.to_u64()?; let align = self.value_to_primval(args[3], usize)?.to_u64()?; + if size == 0 { + return Err(EvalError::HeapAllocZeroBytes); + } + if !align.is_power_of_two() { + return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); + } let new_ptr = self.memory.reallocate(ptr, size, align)?; self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?; } @@ -640,7 +664,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { f_instance, mir.span, mir, - Lvalue::zst(), + Lvalue::undef(), StackPopCleanup::Goto(dest_block), )?; diff --git a/src/value.rs b/src/value.rs index 9f7d3eafe1f..85f79b7831e 100644 --- a/src/value.rs +++ b/src/value.rs @@ -42,7 +42,7 @@ pub enum Value { /// `memory::Allocation`. It is in many ways like a small chunk of a `Allocation`, up to 8 bytes in /// size. Like a range of bytes in an `Allocation`, a `PrimVal` can either represent the raw bytes /// of a simple value, a pointer into another `Allocation`, or be undefined. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug)] pub enum PrimVal { /// The raw bytes of a simple value. Bytes(u128), diff --git a/tests/compile-fail/zst.rs b/tests/compile-fail/zst.rs index 970cc9abc9d..34398240479 100644 --- a/tests/compile-fail/zst.rs +++ b/tests/compile-fail/zst.rs @@ -1,4 +1,4 @@ fn main() { let x = &() as *const () as *const i32; - let _ = unsafe { *x }; //~ ERROR: tried to access memory through an invalid pointer + let _ = unsafe { *x }; //~ ERROR: tried to access memory with alignment 1, but alignment 4 is required } diff --git a/tests/compile-fail/zst2.rs b/tests/compile-fail/zst2.rs new file mode 100644 index 00000000000..1bf42062de6 --- /dev/null +++ b/tests/compile-fail/zst2.rs @@ -0,0 +1,11 @@ +// error-pattern: the evaluated program panicked + +#[derive(Debug)] +struct A; + +fn main() { + // can't use assert_eq, b/c that will try to print the pointer addresses with full MIR enabled + if &A as *const A as *const () != &() as *const _ { + panic!() + } +} diff --git a/tests/compile-fail/zst3.rs b/tests/compile-fail/zst3.rs new file mode 100644 index 00000000000..55cdd661d3d --- /dev/null +++ b/tests/compile-fail/zst3.rs @@ -0,0 +1,11 @@ +// error-pattern: the evaluated program panicked + +#[derive(Debug)] +struct A; + +fn main() { + // can't use assert_eq, b/c that will try to print the pointer addresses with full MIR enabled + if &A as *const A != &A as *const A { + panic!(); + } +} diff --git a/tests/run-pass/zst.rs b/tests/run-pass/zst.rs index 06e41e59e60..c1c88875c5c 100644 --- a/tests/run-pass/zst.rs +++ b/tests/run-pass/zst.rs @@ -13,8 +13,6 @@ fn use_zst() -> A { fn main() { assert_eq!(zst_ret(), A); assert_eq!(use_zst(), A); - assert_eq!(&A as *const A as *const (), &() as *const _); - assert_eq!(&A as *const A, &A as *const A); let x = 42 as *mut (); unsafe { *x = (); } }