Merge pull request #28 from oli-obk/oflo

cleanup overflow binop code
This commit is contained in:
Scott Olson 2016-06-20 12:20:54 -05:00 committed by GitHub
commit 579628f56d
13 changed files with 287 additions and 135 deletions

View file

@ -3,6 +3,8 @@ use std::fmt;
use rustc::mir::repr as mir;
use rustc::ty::BareFnTy;
use memory::Pointer;
use rustc_const_math::ConstMathErr;
use syntax::codemap::Span;
#[derive(Clone, Debug)]
pub enum EvalError<'tcx> {
@ -24,6 +26,8 @@ pub enum EvalError<'tcx> {
Unimplemented(String),
DerefFunctionPointer,
ExecuteMemory,
ArrayIndexOutOfBounds(Span, u64, u64),
Math(Span, ConstMathErr),
}
pub type EvalResult<'tcx, T> = Result<T, EvalError<'tcx>>;
@ -58,6 +62,10 @@ impl<'tcx> Error for EvalError<'tcx> {
"tried to dereference a function pointer",
EvalError::ExecuteMemory =>
"tried to treat a memory pointer as a function pointer",
EvalError::ArrayIndexOutOfBounds(..) =>
"array index out of bounds",
EvalError::Math(..) =>
"mathematical operation failed",
}
}
@ -73,6 +81,10 @@ impl<'tcx> fmt::Display for EvalError<'tcx> {
},
EvalError::FunctionPointerTyMismatch(expected, got) =>
write!(f, "tried to call a function of type {:?} through a function pointer of type {:?}", expected, got),
EvalError::ArrayIndexOutOfBounds(span, len, index) =>
write!(f, "array index {} out of bounds {} at {:?}", index, len, span),
EvalError::Math(span, ref err) =>
write!(f, "mathematical operation at {:?} failed with {:?}", span, err),
_ => write!(f, "{}", self.description()),
}
}

View file

@ -474,15 +474,23 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.frame_mut().block = target;
}
Assert { ref cond, expected, ref msg, target, cleanup } => {
let actual_ptr = self.eval_operand(cond)?;
let actual = self.memory.read_bool(actual_ptr)?;
if actual == expected {
Assert { ref cond, expected, ref msg, target, .. } => {
let cond_ptr = self.eval_operand(cond)?;
if expected == self.memory.read_bool(cond_ptr)? {
self.frame_mut().block = target;
} else {
panic!("unimplemented: jump to {:?} and print {:?}", cleanup, msg);
return match *msg {
mir::AssertMessage::BoundsCheck { ref len, ref index } => {
let len = self.eval_operand(len).expect("can't eval len");
let len = self.memory.read_usize(len).expect("can't read len");
let index = self.eval_operand(index).expect("can't eval index");
let index = self.memory.read_usize(index).expect("can't read index");
Err(EvalError::ArrayIndexOutOfBounds(terminator.source_info.span, len, index))
},
mir::AssertMessage::Math(ref err) => Err(EvalError::Math(terminator.source_info.span, err.clone())),
}
}
}
},
DropAndReplace { .. } => unimplemented!(),
Resume => unimplemented!(),
@ -507,9 +515,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
let name = self.tcx.item_name(def_id).as_str();
match fn_ty.sig.0.output {
ty::FnConverging(ty) => {
let size = self.type_size(ty);
let layout = self.type_layout(ty);
let ret = return_ptr.unwrap();
self.call_intrinsic(&name, substs, args, ret, size)
self.call_intrinsic(&name, substs, args, ret, layout)
}
ty::FnDiverging => unimplemented!(),
}
@ -659,87 +667,112 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Ok(if not_null { nndiscr } else { 1 - nndiscr })
}
/// applies the binary operation `op` to the two operands and writes a tuple of the result
/// and a boolean signifying the potential overflow to the destination
fn intrinsic_with_overflow(
&mut self,
op: mir::BinOp,
left: &mir::Operand<'tcx>,
right: &mir::Operand<'tcx>,
dest: Pointer,
dest_layout: &'tcx Layout,
) -> EvalResult<'tcx, ()> {
use rustc::ty::layout::Layout::*;
let tup_layout = match *dest_layout {
Univariant { ref variant, .. } => variant,
_ => panic!("checked bin op returns something other than a tuple"),
};
let overflowed = self.intrinsic_overflowing(op, left, right, dest)?;
let offset = tup_layout.field_offset(1).bytes() as isize;
self.memory.write_bool(dest.offset(offset), overflowed)
}
/// Applies the binary operation `op` to the arguments and writes the result to the destination.
/// Returns `true` if the operation overflowed.
fn intrinsic_overflowing(
&mut self,
op: mir::BinOp,
left: &mir::Operand<'tcx>,
right: &mir::Operand<'tcx>,
dest: Pointer,
) -> EvalResult<'tcx, bool> {
let left_ptr = self.eval_operand(left)?;
let left_ty = self.operand_ty(left);
let left_val = self.read_primval(left_ptr, left_ty)?;
let right_ptr = self.eval_operand(right)?;
let right_ty = self.operand_ty(right);
let right_val = self.read_primval(right_ptr, right_ty)?;
let (val, overflow) = primval::binary_op(op, left_val, right_val)?;
self.memory.write_primval(dest, val)?;
Ok(overflow)
}
fn call_intrinsic(
&mut self,
name: &str,
substs: &'tcx Substs<'tcx>,
args: &[mir::Operand<'tcx>],
dest: Pointer,
dest_size: usize
dest_layout: &'tcx Layout,
) -> EvalResult<'tcx, ()> {
let args_res: EvalResult<Vec<Pointer>> = args.iter()
.map(|arg| self.eval_operand(arg))
.collect();
let args = args_res?;
let args_ptrs = args_res?;
let pointer_size = self.memory.pointer_size;
match name {
// FIXME(solson): Handle different integer types correctly.
"add_with_overflow" => {
let ty = *substs.types.get(subst::FnSpace, 0);
let size = self.type_size(ty);
let left = self.memory.read_int(args[0], size)?;
let right = self.memory.read_int(args[1], size)?;
let (n, overflowed) = unsafe {
::std::intrinsics::add_with_overflow::<i64>(left, right)
};
self.memory.write_int(dest, n, size)?;
self.memory.write_bool(dest.offset(size as isize), overflowed)?;
}
"add_with_overflow" => self.intrinsic_with_overflow(mir::BinOp::Add, &args[0], &args[1], dest, dest_layout)?,
"sub_with_overflow" => self.intrinsic_with_overflow(mir::BinOp::Sub, &args[0], &args[1], dest, dest_layout)?,
"mul_with_overflow" => self.intrinsic_with_overflow(mir::BinOp::Mul, &args[0], &args[1], dest, dest_layout)?,
// FIXME: turn into an assertion to catch wrong `assume` that would cause UB in llvm
"assume" => {}
"copy_nonoverlapping" => {
let elem_ty = *substs.types.get(subst::FnSpace, 0);
let elem_size = self.type_size(elem_ty);
let src = self.memory.read_ptr(args[0])?;
let dest = self.memory.read_ptr(args[1])?;
let count = self.memory.read_isize(args[2])?;
let src = self.memory.read_ptr(args_ptrs[0])?;
let dest = self.memory.read_ptr(args_ptrs[1])?;
let count = self.memory.read_isize(args_ptrs[2])?;
self.memory.copy(src, dest, count as usize * elem_size)?;
}
"discriminant_value" => {
let ty = *substs.types.get(subst::FnSpace, 0);
let adt_ptr = self.memory.read_ptr(args[0])?;
let adt_ptr = self.memory.read_ptr(args_ptrs[0])?;
let discr_val = self.read_discriminant_value(adt_ptr, ty)?;
self.memory.write_uint(dest, discr_val, dest_size)?;
self.memory.write_uint(dest, discr_val, 8)?;
}
"forget" => {
let arg_ty = *substs.types.get(subst::FnSpace, 0);
let arg_size = self.type_size(arg_ty);
self.memory.drop_fill(args[0], arg_size)?;
self.memory.drop_fill(args_ptrs[0], arg_size)?;
}
"init" => self.memory.write_repeat(dest, 0, dest_size)?,
"init" => self.memory.write_repeat(dest, 0, dest_layout.size(&self.tcx.data_layout).bytes() as usize)?,
"min_align_of" => {
self.memory.write_int(dest, 1, dest_size)?;
// FIXME: use correct value
self.memory.write_int(dest, 1, pointer_size)?;
}
"move_val_init" => {
let ty = *substs.types.get(subst::FnSpace, 0);
let ptr = self.memory.read_ptr(args[0])?;
self.move_(args[1], ptr, ty)?;
}
// FIXME(solson): Handle different integer types correctly.
"mul_with_overflow" => {
let ty = *substs.types.get(subst::FnSpace, 0);
let size = self.type_size(ty);
let left = self.memory.read_int(args[0], size)?;
let right = self.memory.read_int(args[1], size)?;
let (n, overflowed) = unsafe {
::std::intrinsics::mul_with_overflow::<i64>(left, right)
};
self.memory.write_int(dest, n, size)?;
self.memory.write_bool(dest.offset(size as isize), overflowed)?;
let ptr = self.memory.read_ptr(args_ptrs[0])?;
self.move_(args_ptrs[1], ptr, ty)?;
}
"offset" => {
let pointee_ty = *substs.types.get(subst::FnSpace, 0);
let pointee_size = self.type_size(pointee_ty) as isize;
let ptr_arg = args[0];
let offset = self.memory.read_isize(args[1])?;
let ptr_arg = args_ptrs[0];
let offset = self.memory.read_isize(args_ptrs[1])?;
match self.memory.read_ptr(ptr_arg) {
Ok(ptr) => {
@ -755,35 +788,35 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}
}
// FIXME(solson): Handle different integer types correctly. Use primvals?
"overflowing_sub" => {
let ty = *substs.types.get(subst::FnSpace, 0);
let size = self.type_size(ty);
let left = self.memory.read_int(args[0], size)?;
let right = self.memory.read_int(args[1], size)?;
let n = left.wrapping_sub(right);
self.memory.write_int(dest, n, size)?;
self.intrinsic_overflowing(mir::BinOp::Sub, &args[0], &args[1], dest)?;
}
"overflowing_mul" => {
self.intrinsic_overflowing(mir::BinOp::Mul, &args[0], &args[1], dest)?;
}
"overflowing_add" => {
self.intrinsic_overflowing(mir::BinOp::Add, &args[0], &args[1], dest)?;
}
"size_of" => {
let ty = *substs.types.get(subst::FnSpace, 0);
let size = self.type_size(ty) as u64;
self.memory.write_uint(dest, size, dest_size)?;
self.memory.write_uint(dest, size, pointer_size)?;
}
"size_of_val" => {
let ty = *substs.types.get(subst::FnSpace, 0);
if self.type_is_sized(ty) {
let size = self.type_size(ty) as u64;
self.memory.write_uint(dest, size, dest_size)?;
self.memory.write_uint(dest, size, pointer_size)?;
} else {
match ty.sty {
ty::TySlice(_) | ty::TyStr => {
let elem_ty = ty.sequence_element_type(self.tcx);
let elem_size = self.type_size(elem_ty) as u64;
let ptr_size = self.memory.pointer_size as isize;
let n = self.memory.read_usize(args[0].offset(ptr_size))?;
self.memory.write_uint(dest, n * elem_size, dest_size)?;
let n = self.memory.read_usize(args_ptrs[0].offset(ptr_size))?;
self.memory.write_uint(dest, n * elem_size, pointer_size)?;
}
_ => return Err(EvalError::Unimplemented(format!("unimplemented: size_of_val::<{:?}>", ty))),
@ -793,9 +826,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
"transmute" => {
let ty = *substs.types.get(subst::FnSpace, 0);
self.move_(args[0], dest, ty)?;
self.move_(args_ptrs[0], dest, ty)?;
}
"uninit" => self.memory.mark_definedness(dest, dest_size, false)?,
"uninit" => self.memory.mark_definedness(dest, dest_layout.size(&self.tcx.data_layout).bytes() as usize, false)?,
name => return Err(EvalError::Unimplemented(format!("unimplemented intrinsic: {}", name))),
}
@ -900,35 +933,12 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}
BinaryOp(bin_op, ref left, ref right) => {
let left_ptr = self.eval_operand(left)?;
let left_ty = self.operand_ty(left);
let left_val = self.read_primval(left_ptr, left_ty)?;
let right_ptr = self.eval_operand(right)?;
let right_ty = self.operand_ty(right);
let right_val = self.read_primval(right_ptr, right_ty)?;
let val = primval::binary_op(bin_op, left_val, right_val)?;
self.memory.write_primval(dest, val)?;
// ignore overflow bit, rustc inserts check branches for us
self.intrinsic_overflowing(bin_op, left, right, dest)?;
}
// FIXME(solson): Factor this out with BinaryOp.
CheckedBinaryOp(bin_op, ref left, ref right) => {
let left_ptr = self.eval_operand(left)?;
let left_ty = self.operand_ty(left);
let left_val = self.read_primval(left_ptr, left_ty)?;
let right_ptr = self.eval_operand(right)?;
let right_ty = self.operand_ty(right);
let right_val = self.read_primval(right_ptr, right_ty)?;
let val = primval::binary_op(bin_op, left_val, right_val)?;
self.memory.write_primval(dest, val)?;
// FIXME(solson): Find the result type size properly. Perhaps refactor out
// Projection calculations so we can do the equivalent of `dest.1` here.
let s = self.type_size(left_ty);
self.memory.write_bool(dest.offset(s as isize), false)?;
self.intrinsic_with_overflow(bin_op, left, right, dest, dest_layout)?;
}
UnaryOp(un_op, ref operand) => {
@ -1103,9 +1113,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
// Hack to support fat pointer -> thin pointer casts to keep tests for
// other things passing for now.
let is_fat_ptr_cast = pointee_type(src_ty).map(|ty| {
!self.type_is_sized(ty)
}).unwrap_or(false);
let is_fat_ptr_cast = pointee_type(src_ty).map_or(false, |ty| !self.type_is_sized(ty));
if dest_size == src_size || is_fat_ptr_cast {
self.memory.copy(src, dest, dest_size)?;
@ -1122,7 +1130,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
ref other => panic!("reify fn pointer on {:?}", other),
},
_ => return Err(EvalError::Unimplemented(format!("can't handle cast: {:?}", rvalue))),
UnsafeFnPointer => match dest_ty.sty {
ty::TyFnPtr(unsafe_fn_ty) => {
let src = self.eval_operand(operand)?;
let ptr = self.memory.read_ptr(src)?;
let fn_def = self.memory.get_fn(ptr.alloc_id)?;
let fn_ptr = self.memory.create_fn_ptr(fn_def.def_id, fn_def.substs, unsafe_fn_ty);
self.memory.write_ptr(dest, fn_ptr)?;
},
ref other => panic!("fn to unsafe fn cast on {:?}", other),
},
}
}
@ -1369,6 +1386,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
(8, &ty::TyUint(UintTy::Us)) |
(_, &ty::TyUint(UintTy::U64)) => PrimVal::U64(self.memory.read_uint(ptr, 8)? as u64),
(_, &ty::TyFnDef(def_id, substs, fn_ty)) => {
PrimVal::FnPtr(self.memory.create_fn_ptr(def_id, substs, fn_ty))
},
(_, &ty::TyFnPtr(_)) => self.memory.read_ptr(ptr).map(PrimVal::FnPtr)?,
(_, &ty::TyRef(_, ty::TypeAndMut { ty, .. })) |
(_, &ty::TyRawPtr(ty::TypeAndMut { ty, .. })) => {
if self.type_is_sized(ty) {

View file

@ -2,7 +2,6 @@
btree_range,
collections,
collections_bound,
core_intrinsics,
filling_drop,
question_mark,
rustc_private,
@ -14,6 +13,7 @@
extern crate rustc_data_structures;
extern crate rustc_mir;
extern crate rustc_trans;
extern crate rustc_const_math;
extern crate syntax;
#[macro_use] extern crate log;
extern crate log_settings;

View file

@ -42,7 +42,7 @@ impl Pointer {
}
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct FunctionDefinition<'tcx> {
pub def_id: DefId,
pub substs: &'tcx Substs<'tcx>,
@ -59,6 +59,8 @@ pub struct Memory<'tcx> {
/// Function "allocations". They exist solely so pointers have something to point to, and
/// we can figure out what they point to.
functions: HashMap<AllocId, FunctionDefinition<'tcx>>,
/// Inverse map of `functions` so we don't allocate a new pointer every time we need one
function_alloc_cache: HashMap<FunctionDefinition<'tcx>, AllocId>,
next_id: AllocId,
pub pointer_size: usize,
}
@ -69,22 +71,29 @@ impl<'tcx> Memory<'tcx> {
Memory {
alloc_map: HashMap::new(),
functions: HashMap::new(),
function_alloc_cache: HashMap::new(),
next_id: AllocId(0),
pointer_size: pointer_size,
}
}
// FIXME: never create two pointers to the same def_id + substs combination
// maybe re-use the statics cache of the EvalContext?
pub fn create_fn_ptr(&mut self, def_id: DefId, substs: &'tcx Substs<'tcx>, fn_ty: &'tcx BareFnTy<'tcx>) -> Pointer {
let id = self.next_id;
debug!("creating fn ptr: {}", id);
self.next_id.0 += 1;
self.functions.insert(id, FunctionDefinition {
let def = FunctionDefinition {
def_id: def_id,
substs: substs,
fn_ty: fn_ty,
});
};
if let Some(&alloc_id) = self.function_alloc_cache.get(&def) {
return Pointer {
alloc_id: alloc_id,
offset: 0,
};
}
let id = self.next_id;
debug!("creating fn ptr: {}", id);
self.next_id.0 += 1;
self.functions.insert(id, def);
self.function_alloc_cache.insert(def, id);
Pointer {
alloc_id: id,
offset: 0,
@ -361,6 +370,7 @@ impl<'tcx> Memory<'tcx> {
PrimVal::U32(n) => self.write_uint(ptr, n as u64, 4),
PrimVal::U64(n) => self.write_uint(ptr, n as u64, 8),
PrimVal::IntegerPtr(n) => self.write_uint(ptr, n as u64, pointer_size),
PrimVal::FnPtr(_p) |
PrimVal::AbstractPtr(_p) => unimplemented!(),
}
}

View file

@ -10,28 +10,41 @@ pub enum PrimVal {
U8(u8), U16(u16), U32(u32), U64(u64),
AbstractPtr(Pointer),
FnPtr(Pointer),
IntegerPtr(u64),
}
pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> EvalResult<'tcx, PrimVal> {
/// returns the result of the operation and whether the operation overflowed
pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> EvalResult<'tcx, (PrimVal, bool)> {
use rustc::mir::repr::BinOp::*;
use self::PrimVal::*;
macro_rules! overflow {
($v:ident, $v2:ident, $l:ident, $op:ident, $r:ident) => ({
let (val, of) = $l.$op($r);
if of {
return Ok(($v(val), true));
} else {
$v(val)
}
})
}
macro_rules! int_binops {
($v:ident, $l:ident, $r:ident) => ({
match bin_op {
Add => $v($l + $r),
Sub => $v($l - $r),
Mul => $v($l * $r),
Div => $v($l / $r),
Rem => $v($l % $r),
Add => overflow!($v, $v, $l, overflowing_add, $r),
Sub => overflow!($v, $v, $l, overflowing_sub, $r),
Mul => overflow!($v, $v, $l, overflowing_mul, $r),
Div => overflow!($v, $v, $l, overflowing_div, $r),
Rem => overflow!($v, $v, $l, overflowing_rem, $r),
BitXor => $v($l ^ $r),
BitAnd => $v($l & $r),
BitOr => $v($l | $r),
// TODO(solson): Can have differently-typed RHS.
Shl => $v($l << $r),
Shr => $v($l >> $r),
// these have already been handled
Shl => unreachable!(),
Shr => unreachable!(),
Eq => Bool($l == $r),
Ne => Bool($l != $r),
@ -53,6 +66,58 @@ pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> Eva
}
}
match bin_op {
// can have rhs with a different numeric type
Shl | Shr => {
// these numbers are the maximum number a bitshift rhs could possibly have
// e.g. u16 can be bitshifted by 0..16, so masking with 0b1111 (16 - 1) will ensure we are in that range
let type_bits: u32 = match left {
I8(_) | U8(_) => 8,
I16(_) | U16(_) => 16,
I32(_) | U32(_) => 32,
I64(_) | U64(_) => 64,
_ => unreachable!(),
};
assert!(type_bits.is_power_of_two());
// turn into `u32` because `overflowing_sh{l,r}` only take `u32`
let r = match right {
I8(i) => i as u32,
I16(i) => i as u32,
I32(i) => i as u32,
I64(i) => i as u32,
U8(i) => i as u32,
U16(i) => i as u32,
U32(i) => i as u32,
U64(i) => i as u32,
_ => panic!("bad MIR: bitshift rhs is not integral"),
};
// apply mask
let r = r & (type_bits - 1);
macro_rules! shift {
($v:ident, $l:ident, $r:ident) => ({
match bin_op {
Shl => overflow!($v, U32, $l, overflowing_shl, $r),
Shr => overflow!($v, U32, $l, overflowing_shr, $r),
_ => unreachable!(),
}
})
}
let val = match left {
I8(l) => shift!(I8, l, r),
I16(l) => shift!(I16, l, r),
I32(l) => shift!(I32, l, r),
I64(l) => shift!(I64, l, r),
U8(l) => shift!(U8, l, r),
U16(l) => shift!(U16, l, r),
U32(l) => shift!(U32, l, r),
U64(l) => shift!(U64, l, r),
_ => unreachable!(),
};
return Ok((val, false));
},
_ => {},
}
let val = match (left, right) {
(I8(l), I8(r)) => int_binops!(I8, l, r),
(I16(l), I16(r)) => int_binops!(I16, l, r),
@ -80,12 +145,23 @@ pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> Eva
(IntegerPtr(l), IntegerPtr(r)) => int_binops!(IntegerPtr, l, r),
(AbstractPtr(_), IntegerPtr(_)) | (IntegerPtr(_), AbstractPtr(_)) =>
return unrelated_ptr_ops(bin_op),
(AbstractPtr(_), IntegerPtr(_)) |
(IntegerPtr(_), AbstractPtr(_)) |
(FnPtr(_), AbstractPtr(_)) |
(AbstractPtr(_), FnPtr(_)) |
(FnPtr(_), IntegerPtr(_)) |
(IntegerPtr(_), FnPtr(_)) =>
unrelated_ptr_ops(bin_op)?,
(FnPtr(l_ptr), FnPtr(r_ptr)) => match bin_op {
Eq => Bool(l_ptr == r_ptr),
Ne => Bool(l_ptr != r_ptr),
_ => return Err(EvalError::Unimplemented(format!("unimplemented fn ptr comparison: {:?}", bin_op))),
},
(AbstractPtr(l_ptr), AbstractPtr(r_ptr)) => {
if l_ptr.alloc_id != r_ptr.alloc_id {
return unrelated_ptr_ops(bin_op);
return Ok((unrelated_ptr_ops(bin_op)?, false));
}
let l = l_ptr.offset;
@ -105,7 +181,7 @@ pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> Eva
(l, r) => return Err(EvalError::Unimplemented(format!("unimplemented binary op: {:?}, {:?}, {:?}", l, r, bin_op))),
};
Ok(val)
Ok((val, false))
}
pub fn unary_op<'tcx>(un_op: mir::UnOp, val: PrimVal) -> EvalResult<'tcx, PrimVal> {

View file

@ -0,0 +1,10 @@
// just making sure that fn -> unsafe fn casts are handled by rustc so miri doesn't have to
fn main() {
fn f() {}
let g = f as fn() as unsafe fn(i32); //~ERROR: non-scalar cast: `fn()` as `unsafe fn(i32)`
unsafe {
g(42);
}
}

View file

@ -0,0 +1,10 @@
// just making sure that fn -> unsafe fn casts are handled by rustc so miri doesn't have to
fn main() {
fn f() {}
let g = f as fn() as fn(i32) as unsafe fn(i32); //~ERROR: non-scalar cast: `fn()` as `fn(i32)`
unsafe {
g(42);
}
}

View file

@ -0,0 +1,6 @@
//error-pattern: no mir for `std
fn main() {
let x = std::env::args();
assert_eq!(x.count(), 1);
}

View file

@ -3,23 +3,28 @@ extern crate compiletest_rs as compiletest;
use std::path::{PathBuf, Path};
use std::io::Write;
fn run_mode(dir: &'static str, mode: &'static str, sysroot: &str) {
// Disable rustc's new error fomatting. It breaks these tests.
std::env::remove_var("RUST_NEW_ERROR_FORMAT");
fn compile_fail(sysroot: &str) {
let flags = format!("--sysroot {} -Dwarnings", sysroot);
for_all_targets(sysroot, |target| {
let mut config = compiletest::default_config();
config.host_rustcflags = Some(flags.clone());
config.mode = mode.parse().expect("Invalid mode");
config.mode = "compile-fail".parse().expect("Invalid mode");
config.run_lib_path = Path::new(sysroot).join("lib").join("rustlib").join(&target).join("lib");
config.rustc_path = "target/debug/miri".into();
config.src_base = PathBuf::from(format!("tests/{}", dir));
config.src_base = PathBuf::from("tests/compile-fail".to_string());
config.target = target.to_owned();
config.target_rustcflags = Some(flags.clone());
compiletest::run_tests(&config);
});
}
fn run_pass() {
let mut config = compiletest::default_config();
config.mode = "run-pass".parse().expect("Invalid mode");
config.src_base = PathBuf::from("tests/run-pass".to_string());
compiletest::run_tests(&config);
}
fn for_all_targets<F: FnMut(String)>(sysroot: &str, mut f: F) {
for target in std::fs::read_dir(format!("{}/lib/rustlib/", sysroot)).unwrap() {
let target = target.unwrap();
@ -38,7 +43,6 @@ fn for_all_targets<F: FnMut(String)>(sysroot: &str, mut f: F) {
#[test]
fn compile_test() {
let mut failed = false;
// Taken from https://github.com/Manishearth/rust-clippy/pull/911.
let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
@ -48,7 +52,8 @@ fn compile_test() {
.expect("need to specify RUST_SYSROOT env var or use rustup or multirust")
.to_owned(),
};
run_mode("compile-fail", "compile-fail", &sysroot);
compile_fail(&sysroot);
run_pass();
for_all_targets(&sysroot, |target| {
for file in std::fs::read_dir("tests/run-pass").unwrap() {
let file = file.unwrap();
@ -72,21 +77,18 @@ fn compile_test() {
match cmd.output() {
Ok(ref output) if output.status.success() => writeln!(stderr.lock(), "ok").unwrap(),
Ok(output) => {
failed = true;
writeln!(stderr.lock(), "FAILED with exit code {}", output.status.code().unwrap_or(0)).unwrap();
writeln!(stderr.lock(), "stdout: \n {}", std::str::from_utf8(&output.stdout).unwrap()).unwrap();
writeln!(stderr.lock(), "stderr: \n {}", std::str::from_utf8(&output.stderr).unwrap()).unwrap();
panic!("some tests failed");
}
Err(e) => {
failed = true;
writeln!(stderr.lock(), "FAILED: {}", e).unwrap();
panic!("some tests failed");
},
}
}
let stderr = std::io::stderr();
writeln!(stderr.lock(), "").unwrap();
});
if failed {
panic!("some tests failed");
}
}

View file

@ -0,0 +1,8 @@
fn main() {
fn f() {}
let g = f as fn() as unsafe fn();
unsafe {
g();
}
}

View file

@ -12,4 +12,6 @@ fn call_fn_ptr() -> i32 {
fn main() {
assert_eq!(call_fn_ptr(), 42);
assert!(return_fn_ptr() == f);
assert!(return_fn_ptr() as unsafe fn() -> i32 == f as fn() -> i32 as unsafe fn() -> i32);
}

View file

@ -1,34 +1,25 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]
#[miri_run]
fn ret() -> i64 {
1
}
#[miri_run]
fn neg() -> i64 {
-1
}
#[miri_run]
fn add() -> i64 {
1 + 2
}
#[miri_run]
fn indirect_add() -> i64 {
let x = 1;
let y = 2;
x + y
}
#[miri_run]
fn arith() -> i32 {
3*3 + 4*4
}
#[miri_run]
fn match_int() -> i16 {
let n = 2;
match n {
@ -40,7 +31,6 @@ fn match_int() -> i16 {
}
}
#[miri_run]
fn match_int_range() -> i64 {
let n = 42;
match n {
@ -53,7 +43,6 @@ fn match_int_range() -> i64 {
}
}
#[miri_run]
fn main() {
assert_eq!(ret(), 1);
assert_eq!(neg(), -1);
@ -62,4 +51,8 @@ fn main() {
assert_eq!(arith(), 5*5);
assert_eq!(match_int(), 20);
assert_eq!(match_int_range(), 4);
assert_eq!(i64::min_value().overflowing_mul(-1), (i64::min_value(), true));
assert_eq!(i32::min_value().overflowing_mul(-1), (i32::min_value(), true));
assert_eq!(i16::min_value().overflowing_mul(-1), (i16::min_value(), true));
assert_eq!(i8::min_value().overflowing_mul(-1), (i8::min_value(), true));
}

View file

@ -1,3 +1,5 @@
#![feature(specialization)]
trait IsUnit {
fn is_unit() -> bool;
}