diff --git a/src/memory.rs b/src/memory.rs index ac2d391f4217..4cf3ecb8215a 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -64,10 +64,20 @@ impl Pointer { Pointer::new(self.alloc_id, value::wrapping_signed_offset(self.offset, i, layout)) } + pub fn overflowing_signed_offset<'tcx>(self, i: i128, layout: &TargetDataLayout) -> (Self, bool) { + let (res, over) = value::overflowing_signed_offset(self.offset, i, layout); + (Pointer::new(self.alloc_id, res), over) + } + pub fn signed_offset<'tcx>(self, i: i64, layout: &TargetDataLayout) -> EvalResult<'tcx, Self> { Ok(Pointer::new(self.alloc_id, value::signed_offset(self.offset, i, layout)?)) } + pub fn overflowing_offset<'tcx>(self, i: u64, layout: &TargetDataLayout) -> (Self, bool) { + let (res, over) = value::overflowing_offset(self.offset, i, layout); + (Pointer::new(self.alloc_id, res), over) + } + pub fn offset<'tcx>(self, i: u64, layout: &TargetDataLayout) -> EvalResult<'tcx, Self> { Ok(Pointer::new(self.alloc_id, value::offset(self.offset, i, layout)?)) } @@ -470,11 +480,14 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } } - pub fn get_fn(&self, id: AllocId) -> EvalResult<'tcx, ty::Instance<'tcx>> { - debug!("reading fn ptr: {}", id); - match self.functions.get(&id) { + pub fn get_fn(&self, ptr: Pointer) -> EvalResult<'tcx, ty::Instance<'tcx>> { + if ptr.offset != 0 { + return Err(EvalError::InvalidFunctionPointer); + } + debug!("reading fn ptr: {}", ptr.alloc_id); + match self.functions.get(&ptr.alloc_id) { Some(&fndef) => Ok(fndef), - None => match self.alloc_map.get(&id) { + None => match self.alloc_map.get(&ptr.alloc_id) { Some(_) => Err(EvalError::ExecuteMemory), None => Err(EvalError::InvalidFunctionPointer), } diff --git a/src/operator.rs b/src/operator.rs index 0303fbe1f79f..7cba12594f9b 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -3,6 +3,7 @@ use rustc::ty::{self, Ty}; use error::{EvalError, EvalResult}; use eval_context::EvalContext; +use memory::Pointer; use lvalue::Lvalue; use value::{ PrimVal, @@ -145,46 +146,69 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let left_kind = self.ty_to_primval_kind(left_ty)?; let right_kind = self.ty_to_primval_kind(right_ty)?; + //trace!("Running binary op {:?}: {:?} ({:?}), {:?} ({:?})", bin_op, left, left_kind, right, right_kind); - // Offset is handled early, before we dispatch to unrelated_ptr_ops. We have to also catch the case where both arguments *are* convertible to integers. - if bin_op == Offset { - if left_kind == Ptr && right_kind == PrimValKind::from_uint_size(self.memory.pointer_size()) { - let pointee_ty = left_ty.builtin_deref(true, ty::LvaluePreference::NoPreference).expect("Offset called on non-ptr type").ty; - let ptr = self.pointer_offset(left, pointee_ty, right.to_bytes()? as i64)?; - return Ok((ptr, false)); - } else { - bug!("Offset used with wrong type"); - } - } - - // unrelated pointer ops - let op: Option bool> = match bin_op { - Eq => Some(PrimVal::eq), - Ne => Some(PrimVal::ne), - _ => None, - }; - if let Some(op) = op { - // only floats can't be binary compared - let ok = left_kind != F32 && left_kind != F64; - let ok = ok && right_kind != F32 && right_kind != F64; - if ok { - return Ok((PrimVal::from_bool(op(&left, &right)), false)); - } - } - - - if let (Ok(left), Ok(right)) = (left.to_ptr(), right.to_ptr()) { - if left.alloc_id == right.alloc_id { - return self.ptr_ops( - bin_op, - left.offset, - right.offset, - ); - } else { - return Err(EvalError::InvalidPointerMath); + // I: Handle operations that support pointers + let usize = PrimValKind::from_uint_size(self.memory.pointer_size()); + let isize = PrimValKind::from_int_size(self.memory.pointer_size()); + if !left_kind.is_float() && !right_kind.is_float() { + match bin_op { + Offset if left_kind == Ptr && right_kind == usize => { + let pointee_ty = left_ty.builtin_deref(true, ty::LvaluePreference::NoPreference).expect("Offset called on non-ptr type").ty; + let ptr = self.pointer_offset(left, pointee_ty, right.to_bytes()? as i64)?; + return Ok((ptr, false)); + }, + // These work on anything + Eq if left_kind == right_kind => { + return Ok((PrimVal::from_bool(left == right), false)); + } + Ne if left_kind == right_kind => { + return Ok((PrimVal::from_bool(left != right), false)); + } + // These need both pointers to be in the same allocation + Lt | Le | Gt | Ge | Sub + if left_kind == right_kind + && (left_kind == Ptr || left_kind == usize || left_kind == isize) + && left.is_ptr() && right.is_ptr() => { + let left = left.to_ptr()?; + let right = right.to_ptr()?; + if left.alloc_id == right.alloc_id { + let res = match bin_op { + Lt => left.offset < right.offset, + Le => left.offset <= right.offset, + Gt => left.offset > right.offset, + Ge => left.offset >= right.offset, + Sub => { + return int_arithmetic!(left_kind, overflowing_sub, left.offset, right.offset); + } + _ => bug!("We already established it has to be one of these operators."), + }; + return Ok((PrimVal::from_bool(res), false)); + } else { + // Both are pointers, but from different allocations. + return Err(EvalError::InvalidPointerMath); + } + } + // These work if one operand is a pointer, the other an integer + Add | Sub + if left_kind == right_kind && (left_kind == usize || left_kind == isize) + && left.is_ptr() && right.is_bytes() => { + // Cast to i128 is fine as we checked the kind to be ptr-sized + let (res, over) = self.ptr_int_arithmetic(bin_op, left.to_ptr()?, right.to_bytes()? as i128, left_kind == isize)?; + return Ok((PrimVal::Ptr(res), over)); + } + Add + if left_kind == right_kind && (left_kind == usize || left_kind == isize) + && left.is_bytes() && right.is_ptr() => { + // This is a commutative operation, just swap the operands + let (res, over) = self.ptr_int_arithmetic(bin_op, right.to_ptr()?, left.to_bytes()? as i128, left_kind == isize)?; + return Ok((PrimVal::Ptr(res), over)); + } + _ => {} } } + // II: From now on, everything must be bytes, no pointers let l = left.to_bytes()?; let r = right.to_bytes()?; @@ -229,8 +253,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { (Div, F64) => f64_arithmetic!(/, l, r), (Rem, F64) => f64_arithmetic!(%, l, r), - (Eq, _) => PrimVal::from_bool(l == r), - (Ne, _) => PrimVal::from_bool(l != r), (Lt, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) < (r as i128)), (Lt, _) => PrimVal::from_bool(l < r), (Le, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) <= (r as i128)), @@ -259,35 +281,25 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Ok((val, false)) } - fn ptr_ops( + fn ptr_int_arithmetic( &self, bin_op: mir::BinOp, - left: u64, - right: u64, - ) -> EvalResult<'tcx, (PrimVal, bool)> { + left: Pointer, + right: i128, + signed: bool, + ) -> EvalResult<'tcx, (Pointer, bool)> { use rustc::mir::BinOp::*; - let val = match bin_op { - Eq => PrimVal::from_bool(left == right), - Ne => PrimVal::from_bool(left != right), - Lt | Le | Gt | Ge => { - PrimVal::from_bool(match bin_op { - Lt => left < right, - Le => left <= right, - Gt => left > right, - Ge => left >= right, - _ => bug!("We already established it has to be a comparison operator."), - }) - } - Sub => { - let usize = PrimValKind::from_uint_size(self.memory.pointer_size()); - return int_arithmetic!(usize, overflowing_sub, left, right); - } - _ => { - return Err(EvalError::ReadPointerAsBytes); - } - }; - Ok((val, false)) + Ok(match bin_op { + Sub => + // The only way this can overflow is by underflowing, so signdeness of the right operands does not matter + left.overflowing_signed_offset(-right, self.memory.layout), + Add if signed => + left.overflowing_signed_offset(right, self.memory.layout), + Add if !signed => + left.overflowing_offset(right as u64, self.memory.layout), + _ => bug!("ptr_int_arithmetic called on unsupported operation") + }) } } diff --git a/src/terminator/intrinsic.rs b/src/terminator/intrinsic.rs index f6ef46ecbfb8..8ede9fa7559b 100644 --- a/src/terminator/intrinsic.rs +++ b/src/terminator/intrinsic.rs @@ -424,9 +424,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let ty_align = self.type_align(ty)?; let val_byte = self.value_to_primval(arg_vals[1], u8)?.to_u128()? as u8; let size = self.type_size(ty)?.expect("write_bytes() type must be sized"); - let ptr = arg_vals[0].read_ptr(&self.memory)?.to_ptr()?; + let ptr = arg_vals[0].read_ptr(&self.memory)?; let count = self.value_to_primval(arg_vals[2], usize)?.to_u64()?; if count > 0 { + let ptr = ptr.to_ptr()?; self.memory.check_align(ptr, ty_align, size * count)?; self.memory.write_repeat(ptr, val_byte, size * count)?; } diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index d4e169cbcfb6..22b1bf309ce8 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -65,7 +65,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let (fn_def, sig) = match func_ty.sty { ty::TyFnPtr(sig) => { let fn_ptr = self.eval_operand_to_primval(func)?.to_ptr()?; - let instance = self.memory.get_fn(fn_ptr.alloc_id)?; + let instance = self.memory.get_fn(fn_ptr)?; let instance_ty = instance.def.def_ty(self.tcx); let instance_ty = self.monomorphize(instance_ty, instance.substs); match instance_ty.sty { @@ -388,7 +388,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let ptr_size = self.memory.pointer_size(); let (_, vtable) = self.eval_operand(&arg_operands[0])?.expect_ptr_vtable_pair(&self.memory)?; let fn_ptr = self.memory.read_ptr(vtable.offset(ptr_size * (idx as u64 + 3), self.memory.layout)?)?; - let instance = self.memory.get_fn(fn_ptr.to_ptr()?.alloc_id)?; + let instance = self.memory.get_fn(fn_ptr.to_ptr()?)?; let mut arg_operands = arg_operands.to_vec(); let ty = self.operand_ty(&arg_operands[0]); let ty = self.get_field_ty(ty, 0)?; @@ -596,7 +596,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let u8_ptr_ty = self.tcx.mk_mut_ptr(self.tcx.types.u8); let f = args[0].read_ptr(&self.memory)?.to_ptr()?; let data = args[1].read_ptr(&self.memory)?; - let f_instance = self.memory.get_fn(f.alloc_id)?; + let f_instance = self.memory.get_fn(f)?; self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; // Now we make a function call. TODO: Consider making this re-usable? EvalContext::step does sth. similar for the TLS dtors, @@ -614,7 +614,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let arg_dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; self.write_primval(arg_dest, data, u8_ptr_ty)?; - // We ourselbes return 0 + // We ourselves return 0 self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; // Don't fall through @@ -723,7 +723,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // Extract the function type out of the signature (that seems easier than constructing it ourselves...) let dtor = match args[1].read_ptr(&self.memory)? { - PrimVal::Ptr(dtor_ptr) => Some(self.memory.get_fn(dtor_ptr.alloc_id)?), + PrimVal::Ptr(dtor_ptr) => Some(self.memory.get_fn(dtor_ptr)?), PrimVal::Bytes(0) => None, PrimVal::Bytes(_) => return Err(EvalError::ReadBytesAsPointer), PrimVal::Undef => return Err(EvalError::ReadUndefBytes), diff --git a/src/traits.rs b/src/traits.rs index 3862e8c631a6..680776968f98 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -78,7 +78,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { match self.read_ptr(vtable, self.tcx.mk_nil_ptr())? { // some values don't need to call a drop impl, so the value is null Value::ByVal(PrimVal::Bytes(0)) => Ok(None), - Value::ByVal(PrimVal::Ptr(drop_fn)) => self.memory.get_fn(drop_fn.alloc_id).map(Some), + Value::ByVal(PrimVal::Ptr(drop_fn)) => self.memory.get_fn(drop_fn).map(Some), _ => Err(EvalError::ReadBytesAsPointer), } } diff --git a/src/value.rs b/src/value.rs index ef11c7a8e475..519c048f3ef8 100644 --- a/src/value.rs +++ b/src/value.rs @@ -160,6 +160,27 @@ impl<'tcx> PrimVal { } } + pub fn is_bytes(self) -> bool { + match self { + PrimVal::Bytes(_) => true, + _ => false, + } + } + + pub fn is_ptr(self) -> bool { + match self { + PrimVal::Ptr(_) => true, + _ => false, + } + } + + pub fn is_undef(self) -> bool { + match self { + PrimVal::Undef => true, + _ => false, + } + } + pub fn to_u128(self) -> EvalResult<'tcx, u128> { self.to_bytes() } @@ -239,32 +260,45 @@ impl<'tcx> PrimVal { } } -pub fn signed_offset<'tcx>(val: u64, i: i64, layout: &TargetDataLayout) -> EvalResult<'tcx, u64> { +// Overflow checking only works properly on the range from -u64 to +u64. +pub fn overflowing_signed_offset<'tcx>(val: u64, i: i128, layout: &TargetDataLayout) -> (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.checked_sub(n).ok_or(EvalError::OverflowingMath) + val.overflowing_sub(n) } else { - offset(val, i as u64, layout) + overflowing_offset(val, i as u64, layout) + } +} + +pub fn overflowing_offset<'tcx>(val: u64, i: u64, layout: &TargetDataLayout) -> (u64, bool) { + let (res, over) = val.overflowing_add(i); + ((res as u128 % (1u128 << layout.pointer_size.bits())) as u64, + over || res as u128 >= (1u128 << layout.pointer_size.bits())) +} + +pub fn signed_offset<'tcx>(val: u64, i: i64, layout: &TargetDataLayout) -> EvalResult<'tcx, u64> { + let (res, over) = overflowing_signed_offset(val, i as i128, layout); + if over { + Err(EvalError::OverflowingMath) + } else { + Ok(res) } } pub fn offset<'tcx>(val: u64, i: u64, layout: &TargetDataLayout) -> EvalResult<'tcx, u64> { - if let Some(res) = val.checked_add(i) { - if res as u128 >= (1u128 << layout.pointer_size.bits()) { - Err(EvalError::OverflowingMath) - } else { - Ok(res) - } - } else { + let (res, over) = overflowing_offset(val, i, layout); + if over { Err(EvalError::OverflowingMath) + } else { + Ok(res) } } pub fn wrapping_signed_offset<'tcx>(val: u64, i: i64, layout: &TargetDataLayout) -> u64 { - (val.wrapping_add(i as u64) as u128 % (1u128 << layout.pointer_size.bits())) as u64 + overflowing_signed_offset(val, i as i128, layout).0 } impl PrimValKind { @@ -284,6 +318,14 @@ impl PrimValKind { } } + pub fn is_float(self) -> bool { + use self::PrimValKind::*; + match self { + F32 | F64 => true, + _ => false, + } + } + pub fn from_uint_size(size: u64) -> Self { match size { 1 => PrimValKind::U8, diff --git a/tests/compile-fail/fn_ptr_offset.rs b/tests/compile-fail/fn_ptr_offset.rs new file mode 100644 index 000000000000..2d240b6a55ad --- /dev/null +++ b/tests/compile-fail/fn_ptr_offset.rs @@ -0,0 +1,11 @@ +use std::mem; + +fn f() {} + +fn main() { + let x : fn() = f; + let y : *mut u8 = unsafe { mem::transmute(x) }; + let y = y.wrapping_offset(1); + let x : fn() = unsafe { mem::transmute(y) }; + x(); //~ ERROR: tried to use an integer pointer or a dangling pointer as a function pointer +} diff --git a/tests/run-pass/hashmap.rs b/tests/run-pass-fullmir/hashmap.rs similarity index 73% rename from tests/run-pass/hashmap.rs rename to tests/run-pass-fullmir/hashmap.rs index 775dee252f62..c0c396240733 100644 --- a/tests/run-pass/hashmap.rs +++ b/tests/run-pass-fullmir/hashmap.rs @@ -1,10 +1,6 @@ use std::collections::{self, HashMap}; use std::hash::BuildHasherDefault; -// This disables the test completely: -// ignore-stage1 -// TODO: The tests actually passes against rustc and miri with MIR-libstd, but right now, we cannot express that in the test flags - fn main() { let map : HashMap> = Default::default(); assert_eq!(map.values().fold(0, |x, y| x+y), 0); diff --git a/tests/run-pass/ptr_int_casts.rs b/tests/run-pass/ptr_int_casts.rs index e245cb22475d..b7b17089efc7 100644 --- a/tests/run-pass/ptr_int_casts.rs +++ b/tests/run-pass/ptr_int_casts.rs @@ -1,15 +1,32 @@ +use std::mem; + fn eq_ref(x: &T, y: &T) -> bool { x as *const _ == y as *const _ } +fn f() -> i32 { 42 } + fn main() { // int-ptr-int assert_eq!(1 as *const i32 as usize, 1); + assert_eq!((1 as *const i32).wrapping_offset(4) as usize, 1 + 4*4); { // ptr-int-ptr let x = 13; - let y = &x as *const _ as usize; + let mut y = &x as *const _ as usize; + y += 13; + y -= 13; let y = y as *const _; assert!(eq_ref(&x, unsafe { &*y })); } + + { // fnptr-int-fnptr + let x : fn() -> i32 = f; + let y : *mut u8 = unsafe { mem::transmute(x) }; + let mut y = y as usize; + y += 13; + y -= 13; + let x : fn() -> i32 = unsafe { mem::transmute(y as *mut u8) }; + assert_eq!(x(), 42); + } } diff --git a/tests/run-pass/thread-local-no-dtor.rs b/tests/run-pass/thread-local-no-dtor.rs deleted file mode 100644 index 4fb43793eaec..000000000000 --- a/tests/run-pass/thread-local-no-dtor.rs +++ /dev/null @@ -1,18 +0,0 @@ -//ignore-windows - -#![feature(libc)] -extern crate libc; - -use std::mem; - -pub type Key = libc::pthread_key_t; - -pub unsafe fn create(dtor: Option) -> Key { - let mut key = 0; - assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0); - key -} - -fn main() { - let _ = unsafe { create(None) }; -} diff --git a/tests/run-pass/thread-local.rs b/tests/run-pass/thread-local.rs new file mode 100644 index 000000000000..34aeef23b1ad --- /dev/null +++ b/tests/run-pass/thread-local.rs @@ -0,0 +1,67 @@ +//ignore-windows + +#![feature(libc)] +extern crate libc; + +use std::mem; + +pub type Key = libc::pthread_key_t; + +static mut RECORD : usize = 0; +static mut KEYS : [Key; 2] = [0; 2]; +static mut GLOBALS : [u64; 2] = [1, 0]; + +static mut CANNARY : *mut u64 = 0 as *mut _; // this serves as a cannary: if TLS dtors are not run properly, this will not get deallocated, making the test fail. + +pub unsafe fn create(dtor: Option) -> Key { + let mut key = 0; + assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0); + key +} + +pub unsafe fn set(key: Key, value: *mut u8) { + let r = libc::pthread_setspecific(key, value as *mut _); + assert_eq!(r, 0); +} + +pub fn record(r: usize) { + assert!(r < 10); + unsafe { RECORD = RECORD*10 + r }; +} + +unsafe extern fn dtor(mut ptr: *mut u64) { + assert!(CANNARY != 0 as *mut _); // make sure we do not get run too often + let val = *ptr; + + let which_key = GLOBALS.iter().position(|global| global as *const _ == ptr).expect("Should find my global"); + record(which_key); + + if val > 0 { + *ptr = val-1; + set(KEYS[which_key], ptr as *mut _); + } + + // Check if the records matches what we expect. If yes, clear the cannary. + // If the record is wrong, the cannary will never get cleared, leading to a leak -> test fails. + // If the record is incomplete (i.e., more dtor calls happen), the check at the beginning of this function will fail -> test fails. + // The correct sequence is: First key 0, then key 1, then key 0. + if RECORD == 0_1_0 { + drop(Box::from_raw(CANNARY)); + CANNARY = 0 as *mut _; + } +} + +fn main() { + unsafe { + create(None); // check that the no-dtor case works + + // Initialize the keys we use to check destructor ordering + for (key, global) in KEYS.iter_mut().zip(GLOBALS.iter()) { + *key = create(Some(mem::transmute(dtor as unsafe extern fn(*mut u64)))); + set(*key, global as *const _ as *mut _); + } + + // Initialize cannary + CANNARY = Box::into_raw(Box::new(0u64)); + } +}