commit
579628f56d
13 changed files with 287 additions and 135 deletions
12
src/error.rs
12
src/error.rs
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
102
src/primval.rs
102
src/primval.rs
|
|
@ -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> {
|
||||
|
|
|
|||
10
tests/compile-fail/cast_fn_ptr_unsafe.rs
Normal file
10
tests/compile-fail/cast_fn_ptr_unsafe.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
10
tests/compile-fail/cast_fn_ptr_unsafe2.rs
Normal file
10
tests/compile-fail/cast_fn_ptr_unsafe2.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
6
tests/compile-fail/env_args.rs
Normal file
6
tests/compile-fail/env_args.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
//error-pattern: no mir for `std
|
||||
|
||||
fn main() {
|
||||
let x = std::env::args();
|
||||
assert_eq!(x.count(), 1);
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
tests/run-pass/cast_fn_ptr_unsafe.rs
Normal file
8
tests/run-pass/cast_fn_ptr_unsafe.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fn main() {
|
||||
fn f() {}
|
||||
|
||||
let g = f as fn() as unsafe fn();
|
||||
unsafe {
|
||||
g();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#![feature(specialization)]
|
||||
|
||||
trait IsUnit {
|
||||
fn is_unit() -> bool;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue