Merge pull request #15 from oli-obk/step_by_step

4 byte pointers + tests
This commit is contained in:
Scott Olson 2016-06-01 07:44:18 -06:00
commit c8151703b2
8 changed files with 131 additions and 86 deletions

View file

@ -9,6 +9,10 @@ version = "0.1.0"
[[bin]]
doc = false
name = "miri"
test = false
[lib]
test = false
[dependencies]
byteorder = "0.4.2"

View file

@ -1,16 +1,24 @@
use std::error::Error;
use std::fmt;
use rustc::mir::repr as mir;
use memory::Pointer;
#[derive(Clone, Debug)]
pub enum EvalError {
DanglingPointerDeref,
InvalidBool,
InvalidDiscriminant,
PointerOutOfBounds,
PointerOutOfBounds {
ptr: Pointer,
size: usize,
allocation_size: usize,
},
ReadPointerAsBytes,
ReadBytesAsPointer,
InvalidPointerMath,
ReadUndefBytes,
InvalidBoolOp(mir::BinOp),
Unimplemented(String),
}
pub type EvalResult<T> = Result<T, EvalError>;
@ -24,7 +32,7 @@ impl Error for EvalError {
"invalid boolean value read",
EvalError::InvalidDiscriminant =>
"invalid enum discriminant value read",
EvalError::PointerOutOfBounds =>
EvalError::PointerOutOfBounds { .. } =>
"pointer offset outside bounds of allocation",
EvalError::ReadPointerAsBytes =>
"a raw memory access tried to access part of a pointer value as raw bytes",
@ -34,6 +42,9 @@ impl Error for EvalError {
"attempted to do math or a comparison on pointers into different allocations",
EvalError::ReadUndefBytes =>
"attempted to read undefined bytes",
EvalError::InvalidBoolOp(_) =>
"invalid boolean operation",
EvalError::Unimplemented(ref msg) => msg,
}
}
@ -42,6 +53,12 @@ impl Error for EvalError {
impl fmt::Display for EvalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description())
match *self {
EvalError::PointerOutOfBounds { ptr, size, allocation_size } => {
write!(f, "memory access of {}..{} outside bounds of allocation {} which has size {}",
ptr.offset, ptr.offset + size, ptr.alloc_id, allocation_size)
},
_ => write!(f, "{}", self.description()),
}
}
}

View file

@ -128,7 +128,11 @@ impl<'a, 'tcx> GlobalEvalContext<'a, 'tcx> {
tcx: tcx,
mir_map: mir_map,
mir_cache: RefCell::new(DefIdMap()),
memory: Memory::new(),
memory: Memory::new(tcx.sess
.target
.uint_type
.bit_width()
.expect("Session::target::uint_type was usize")/8),
substs_stack: Vec::new(),
name_stack: Vec::new(),
}
@ -392,11 +396,11 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
TerminatorTarget::Call
}
abi => panic!("can't handle function with {:?} ABI", abi),
abi => return Err(EvalError::Unimplemented(format!("can't handle function with {:?} ABI", abi))),
}
}
_ => panic!("can't handle callee of type {:?}", func_ty),
_ => return Err(EvalError::Unimplemented(format!("can't handle callee of type {:?}", func_ty))),
}
}
@ -470,7 +474,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
}
StructWrappedNullablePointer { nndiscr, ref discrfield, .. } => {
let offset = self.nonnull_offset(adt_ty, nndiscr, discrfield);
let offset = self.nonnull_offset(adt_ty, nndiscr, discrfield)?;
let nonnull = adt_ptr.offset(offset.bytes() as isize);
self.read_nonnull_discriminant_value(nonnull, nndiscr)?
}
@ -620,7 +624,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
self.memory.write_uint(dest, n * elem_size, dest_size)?;
}
_ => panic!("unimplemented: size_of_val::<{:?}>", ty),
_ => return Err(EvalError::Unimplemented(format!("unimplemented: size_of_val::<{:?}>", ty))),
}
}
}
@ -631,7 +635,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
}
"uninit" => self.memory.mark_definedness(dest, dest_size, false)?,
name => panic!("can't handle intrinsic: {}", name),
name => return Err(EvalError::Unimplemented(format!("unimplemented intrinsic: {}", name))),
}
// Since we pushed no stack frame, the main loop will act
@ -693,7 +697,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
self.memory.write_int(dest, result, dest_size)?;
}
_ => panic!("can't call C ABI function: {}", link_name),
_ => return Err(EvalError::Unimplemented(format!("can't call C ABI function: {}", link_name))),
}
// Since we pushed no stack frame, the main loop will act
@ -748,7 +752,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
let ptr = self.eval_operand(operand)?;
let ty = self.operand_ty(operand);
let val = self.read_primval(ptr, ty)?;
self.memory.write_primval(dest, primval::unary_op(un_op, val))?;
self.memory.write_primval(dest, primval::unary_op(un_op, val)?)?;
}
Aggregate(ref kind, ref operands) => {
@ -809,7 +813,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
try!(self.assign_fields(dest, offsets, operands));
} else {
assert_eq!(operands.len(), 0);
let offset = self.nonnull_offset(dest_ty, nndiscr, discrfield);
let offset = self.nonnull_offset(dest_ty, nndiscr, discrfield)?;
let dest = dest.offset(offset.bytes() as isize);
try!(self.memory.write_isize(dest, 0));
}
@ -834,8 +838,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
}
}
_ => panic!("can't handle destination layout {:?} when assigning {:?}",
dest_layout, kind),
_ => return Err(EvalError::Unimplemented(format!("can't handle destination layout {:?} when assigning {:?}", dest_layout, kind))),
}
}
@ -904,7 +907,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
self.memory.write_usize(len_ptr, length as u64)?;
}
_ => panic!("can't handle cast: {:?}", rvalue),
_ => return Err(EvalError::Unimplemented(format!("can't handle cast: {:?}", rvalue))),
}
}
@ -914,7 +917,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
self.memory.copy(src, dest, size)?;
}
_ => panic!("can't handle cast: {:?}", rvalue),
_ => return Err(EvalError::Unimplemented(format!("can't handle cast: {:?}", rvalue))),
}
}
@ -925,7 +928,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
Ok(())
}
fn nonnull_offset(&self, ty: Ty<'tcx>, nndiscr: u64, discrfield: &[u32]) -> Size {
fn nonnull_offset(&self, ty: Ty<'tcx>, nndiscr: u64, discrfield: &[u32]) -> EvalResult<Size> {
// Skip the constant 0 at the start meant for LLVM GEP.
let mut path = discrfield.iter().skip(1).map(|&i| i as usize);
@ -946,49 +949,49 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
self.field_path_offset(inner_ty, path)
}
fn field_path_offset<I: Iterator<Item = usize>>(&self, mut ty: Ty<'tcx>, path: I) -> Size {
fn field_path_offset<I: Iterator<Item = usize>>(&self, mut ty: Ty<'tcx>, path: I) -> EvalResult<Size> {
let mut offset = Size::from_bytes(0);
// Skip the initial 0 intended for LLVM GEP.
for field_index in path {
let field_offset = self.get_field_offset(ty, field_index);
ty = self.get_field_ty(ty, field_index);
let field_offset = self.get_field_offset(ty, field_index)?;
ty = self.get_field_ty(ty, field_index)?;
offset = offset.checked_add(field_offset, &self.tcx.data_layout).unwrap();
}
offset
Ok(offset)
}
fn get_field_ty(&self, ty: Ty<'tcx>, field_index: usize) -> Ty<'tcx> {
fn get_field_ty(&self, ty: Ty<'tcx>, field_index: usize) -> EvalResult<Ty<'tcx>> {
match ty.sty {
ty::TyStruct(adt_def, substs) => {
adt_def.struct_variant().fields[field_index].ty(self.tcx, substs)
Ok(adt_def.struct_variant().fields[field_index].ty(self.tcx, substs))
}
ty::TyRef(_, ty::TypeAndMut { ty, .. }) |
ty::TyRawPtr(ty::TypeAndMut { ty, .. }) |
ty::TyBox(ty) => {
assert_eq!(field_index, 0);
ty
Ok(ty)
}
_ => panic!("can't handle type: {:?}", ty),
_ => Err(EvalError::Unimplemented(format!("can't handle type: {:?}", ty))),
}
}
fn get_field_offset(&self, ty: Ty<'tcx>, field_index: usize) -> Size {
fn get_field_offset(&self, ty: Ty<'tcx>, field_index: usize) -> EvalResult<Size> {
let layout = self.type_layout(ty);
use rustc::ty::layout::Layout::*;
match *layout {
Univariant { .. } => {
assert_eq!(field_index, 0);
Size::from_bytes(0)
Ok(Size::from_bytes(0))
}
FatPointer { .. } => {
let bytes = layout::FAT_PTR_ADDR * self.memory.pointer_size;
Size::from_bytes(bytes as u64)
Ok(Size::from_bytes(bytes as u64))
}
_ => panic!("can't handle type: {:?}, with layout: {:?}", ty, layout),
_ => Err(EvalError::Unimplemented(format!("can't handle type: {:?}, with layout: {:?}", ty, layout))),
}
}
@ -1197,23 +1200,25 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
pub fn read_primval(&mut self, ptr: Pointer, ty: Ty<'tcx>) -> EvalResult<PrimVal> {
use syntax::ast::{IntTy, UintTy};
let val = match ty.sty {
ty::TyBool => PrimVal::Bool(self.memory.read_bool(ptr)?),
ty::TyInt(IntTy::I8) => PrimVal::I8(self.memory.read_int(ptr, 1)? as i8),
ty::TyInt(IntTy::I16) => PrimVal::I16(self.memory.read_int(ptr, 2)? as i16),
ty::TyInt(IntTy::I32) => PrimVal::I32(self.memory.read_int(ptr, 4)? as i32),
ty::TyInt(IntTy::I64) => PrimVal::I64(self.memory.read_int(ptr, 8)? as i64),
ty::TyUint(UintTy::U8) => PrimVal::U8(self.memory.read_uint(ptr, 1)? as u8),
ty::TyUint(UintTy::U16) => PrimVal::U16(self.memory.read_uint(ptr, 2)? as u16),
ty::TyUint(UintTy::U32) => PrimVal::U32(self.memory.read_uint(ptr, 4)? as u32),
ty::TyUint(UintTy::U64) => PrimVal::U64(self.memory.read_uint(ptr, 8)? as u64),
let val = match (self.memory.pointer_size, &ty.sty) {
(_, &ty::TyBool) => PrimVal::Bool(self.memory.read_bool(ptr)?),
(_, &ty::TyInt(IntTy::I8)) => PrimVal::I8(self.memory.read_int(ptr, 1)? as i8),
(2, &ty::TyInt(IntTy::Is)) |
(_, &ty::TyInt(IntTy::I16)) => PrimVal::I16(self.memory.read_int(ptr, 2)? as i16),
(4, &ty::TyInt(IntTy::Is)) |
(_, &ty::TyInt(IntTy::I32)) => PrimVal::I32(self.memory.read_int(ptr, 4)? as i32),
(8, &ty::TyInt(IntTy::Is)) |
(_, &ty::TyInt(IntTy::I64)) => PrimVal::I64(self.memory.read_int(ptr, 8)? as i64),
(_, &ty::TyUint(UintTy::U8)) => PrimVal::U8(self.memory.read_uint(ptr, 1)? as u8),
(2, &ty::TyUint(UintTy::Us)) |
(_, &ty::TyUint(UintTy::U16)) => PrimVal::U16(self.memory.read_uint(ptr, 2)? as u16),
(4, &ty::TyUint(UintTy::Us)) |
(_, &ty::TyUint(UintTy::U32)) => PrimVal::U32(self.memory.read_uint(ptr, 4)? as u32),
(8, &ty::TyUint(UintTy::Us)) |
(_, &ty::TyUint(UintTy::U64)) => PrimVal::U64(self.memory.read_uint(ptr, 8)? as u64),
// TODO(solson): Pick the PrimVal dynamically.
ty::TyInt(IntTy::Is) => PrimVal::I64(self.memory.read_isize(ptr)?),
ty::TyUint(UintTy::Us) => PrimVal::U64(self.memory.read_usize(ptr)?),
ty::TyRef(_, ty::TypeAndMut { ty, .. }) |
ty::TyRawPtr(ty::TypeAndMut { ty, .. }) => {
(_, &ty::TyRef(_, ty::TypeAndMut { ty, .. })) |
(_, &ty::TyRawPtr(ty::TypeAndMut { ty, .. })) => {
if self.type_is_sized(ty) {
match self.memory.read_ptr(ptr) {
Ok(p) => PrimVal::AbstractPtr(p),
@ -1223,7 +1228,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> {
Err(e) => return Err(e),
}
} else {
panic!("unimplemented: primitive read of fat pointer type: {:?}", ty);
return Err(EvalError::Unimplemented(format!("unimplemented: primitive read of fat pointer type: {:?}", ty)));
}
}

View file

@ -49,14 +49,12 @@ pub struct Memory {
}
impl Memory {
pub fn new() -> Self {
// FIXME: pass tcx.data_layout (This would also allow it to use primitive type alignments to diagnose unaligned memory accesses.)
pub fn new(pointer_size: usize) -> Self {
Memory {
alloc_map: HashMap::new(),
next_id: AllocId(0),
// FIXME(solson): This should work for both 4 and 8, but it currently breaks some things
// when set to 4.
pointer_size: 8,
pointer_size: pointer_size,
}
}
@ -80,7 +78,7 @@ impl Memory {
pub fn reallocate(&mut self, ptr: Pointer, new_size: usize) -> EvalResult<()> {
if ptr.offset != 0 {
// TODO(solson): Report error about non-__rust_allocate'd pointer.
panic!()
return Err(EvalError::Unimplemented(format!("bad pointer offset: {}", ptr.offset)));
}
let alloc = self.get_mut(ptr.alloc_id)?;
@ -90,7 +88,7 @@ impl Memory {
alloc.bytes.extend(iter::repeat(0).take(amount));
alloc.undef_mask.grow(amount, false);
} else if size > new_size {
unimplemented!()
return Err(EvalError::Unimplemented(format!("unimplemented allocation relocation")));
// alloc.bytes.truncate(new_size);
// alloc.undef_mask.len = new_size;
// TODO: potentially remove relocations
@ -103,7 +101,7 @@ impl Memory {
pub fn deallocate(&mut self, ptr: Pointer) -> EvalResult<()> {
if ptr.offset != 0 {
// TODO(solson): Report error about non-__rust_allocate'd pointer.
panic!()
return Err(EvalError::Unimplemented(format!("bad pointer offset: {}", ptr.offset)));
}
if self.alloc_map.remove(&ptr.alloc_id).is_none() {
@ -183,7 +181,11 @@ impl Memory {
fn get_bytes_unchecked(&self, ptr: Pointer, size: usize) -> EvalResult<&[u8]> {
let alloc = self.get(ptr.alloc_id)?;
if ptr.offset + size > alloc.bytes.len() {
return Err(EvalError::PointerOutOfBounds);
return Err(EvalError::PointerOutOfBounds {
ptr: ptr,
size: size,
allocation_size: alloc.bytes.len(),
});
}
Ok(&alloc.bytes[ptr.offset..ptr.offset + size])
}
@ -191,7 +193,11 @@ impl Memory {
fn get_bytes_unchecked_mut(&mut self, ptr: Pointer, size: usize) -> EvalResult<&mut [u8]> {
let alloc = self.get_mut(ptr.alloc_id)?;
if ptr.offset + size > alloc.bytes.len() {
return Err(EvalError::PointerOutOfBounds);
return Err(EvalError::PointerOutOfBounds {
ptr: ptr,
size: size,
allocation_size: alloc.bytes.len(),
});
}
Ok(&mut alloc.bytes[ptr.offset..ptr.offset + size])
}

View file

@ -74,8 +74,7 @@ pub fn binary_op(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> EvalResul
BitOr => l | r,
BitXor => l ^ r,
BitAnd => l & r,
Add | Sub | Mul | Div | Rem | Shl | Shr =>
panic!("invalid binary operation on booleans: {:?}", bin_op),
Add | Sub | Mul | Div | Rem | Shl | Shr => return Err(EvalError::InvalidBoolOp(bin_op)),
})
}
@ -99,33 +98,33 @@ pub fn binary_op(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> EvalResul
Le => Bool(l <= r),
Gt => Bool(l > r),
Ge => Bool(l >= r),
_ => unimplemented!(),
_ => return Err(EvalError::Unimplemented(format!("unimplemented ptr op: {:?}", bin_op))),
}
}
_ => unimplemented!(),
(l, r) => return Err(EvalError::Unimplemented(format!("unimplemented binary op: {:?}, {:?}, {:?}", l, r, bin_op))),
};
Ok(val)
}
pub fn unary_op(un_op: mir::UnOp, val: PrimVal) -> PrimVal {
pub fn unary_op(un_op: mir::UnOp, val: PrimVal) -> EvalResult<PrimVal> {
use rustc::mir::repr::UnOp::*;
use self::PrimVal::*;
match (un_op, val) {
(Not, Bool(b)) => Bool(!b),
(Not, I8(n)) => I8(!n),
(Neg, I8(n)) => I8(-n),
(Not, I16(n)) => I16(!n),
(Neg, I16(n)) => I16(-n),
(Not, I32(n)) => I32(!n),
(Neg, I32(n)) => I32(-n),
(Not, I64(n)) => I64(!n),
(Neg, I64(n)) => I64(-n),
(Not, U8(n)) => U8(!n),
(Not, U16(n)) => U16(!n),
(Not, U32(n)) => U32(!n),
(Not, U64(n)) => U64(!n),
_ => unimplemented!(),
(Not, Bool(b)) => Ok(Bool(!b)),
(Not, I8(n)) => Ok(I8(!n)),
(Neg, I8(n)) => Ok(I8(-n)),
(Not, I16(n)) => Ok(I16(!n)),
(Neg, I16(n)) => Ok(I16(-n)),
(Not, I32(n)) => Ok(I32(!n)),
(Neg, I32(n)) => Ok(I32(-n)),
(Not, I64(n)) => Ok(I64(!n)),
(Neg, I64(n)) => Ok(I64(-n)),
(Not, U8(n)) => Ok(U8(!n)),
(Not, U16(n)) => Ok(U16(!n)),
(Not, U32(n)) => Ok(U32(!n)),
(Not, U64(n)) => Ok(U64(!n)),
_ => Err(EvalError::Unimplemented(format!("unimplemented unary op: {:?}, {:?}", un_op, val))),
}
}

View file

@ -6,7 +6,9 @@ fn overwriting_part_of_relocation_makes_the_rest_undefined() -> i32 {
let mut p = &42;
unsafe {
let ptr: *mut _ = &mut p;
*(ptr as *mut u32) = 123;
*(ptr as *mut u8) = 123; // if we ever support 8 bit pointers, this is gonna cause
// "attempted to interpret some raw bytes as a pointer address" instead of
// "attempted to read undefined bytes"
}
*p //~ ERROR: attempted to read undefined bytes
}
@ -34,7 +36,7 @@ fn undefined_byte_read() -> u8 {
#[miri_run]
fn out_of_bounds_read() -> u8 {
let v: Vec<u8> = vec![1, 2];
unsafe { *v.get_unchecked(5) } //~ ERROR: pointer offset outside bounds of allocation
unsafe { *v.get_unchecked(5) } //~ ERROR: memory access of 5..6 outside bounds of allocation 11 which has size 2
}
#[miri_run]

View file

@ -3,17 +3,24 @@ extern crate compiletest_rs as compiletest;
use std::path::PathBuf;
fn run_mode(mode: &'static str) {
let mut config = compiletest::default_config();
config.rustc_path = "target/debug/miri".into();
let path = std::env::var("RUST_SYSROOT").expect("env variable `RUST_SYSROOT` not set");
config.target_rustcflags = Some(format!("--sysroot {}", path));
config.host_rustcflags = Some(format!("--sysroot {}", path));
let cfg_mode = mode.parse().ok().expect("Invalid mode");
// FIXME: read directories in sysroot/lib/rustlib and generate the test targets from that
let targets = &["x86_64-unknown-linux-gnu", "i686-unknown-linux-gnu"];
config.mode = cfg_mode;
config.src_base = PathBuf::from(format!("tests/{}", mode));
for &target in targets {
let mut config = compiletest::default_config();
config.rustc_path = "target/debug/miri".into();
let path = std::env::var("RUST_SYSROOT").expect("env variable `RUST_SYSROOT` not set");
config.run_lib_path = format!("{}/lib/rustlib/{}/lib", path, target);
let path = format!("--sysroot {}", path);
config.target_rustcflags = Some(path.clone());
config.host_rustcflags = Some(path);
let cfg_mode = mode.parse().ok().expect("Invalid mode");
compiletest::run_tests(&config);
config.mode = cfg_mode;
config.src_base = PathBuf::from(format!("tests/{}", mode));
config.target = target.to_owned();
compiletest::run_tests(&config);
}
}
#[test]

View file

@ -21,4 +21,9 @@ fn hello_bytes_fat() -> &'static [u8] {
b"Hello, world!"
}
#[miri_run]
fn fat_pointer_on_32_bit() {
Some(5).expect("foo");
}
fn main() {}