Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cleanup overflow binop code #28

Merged
merged 19 commits into from
Jun 20, 2016
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ 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;
use primval::PrimVal;

#[derive(Clone, Debug)]
pub enum EvalError<'tcx> {
Expand All @@ -24,6 +27,10 @@ pub enum EvalError<'tcx> {
Unimplemented(String),
DerefFunctionPointer,
ExecuteMemory,
ArrayIndexOutOfBounds(Span, u64, u64),
Math(Span, ConstMathErr),
InvalidBitShiftRhs(PrimVal),
Copy link
Member

@eddyb eddyb Jun 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not actually a thing, the semantics of shifting in Rust is that the RHS gets masked with BITS - 1 (assuming bit widths are always a power of 2) and interpreted as unsigned.

EDIT: corrected description of the mask.

(misplaced on another PR)

Overflow(PrimVal, PrimVal, mir::BinOp, PrimVal),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this get ignored outside of intrinsic_overflowing? Again, the semantics are 2's complement + wrapping, unless explicit checking is done and reported with Assert.

Copy link
Contributor Author

@oli-obk oli-obk Jun 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the current tests are only run in debug mode, the overflow checks prevent the overflow error from ever triggering.

Copy link
Member

@solson solson Jun 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvalError::Overflow seems to only be used for checking if eval_binop overflowed, so if eval_binop just did overflowing math internally and also returned a boolean whether something overflowed, could we remove EvalError::Overflow?

I think I want to avoid using Err cases for non-errors (though I currently violate this myself with some pointer reads somewhere that I catch). Unless you think this way is cleaner.

}

pub type EvalResult<'tcx, T> = Result<T, EvalError<'tcx>>;
Expand Down Expand Up @@ -58,6 +65,14 @@ 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",
EvalError::InvalidBitShiftRhs(..) =>
"bit shift rhs negative or not an int",
EvalError::Overflow(..) =>
"mathematical operation overflowed",
}
}

Expand All @@ -73,6 +88,12 @@ 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),
EvalError::Overflow(l, r, op, val) =>
write!(f, "mathematical operation overflowed: {:?} {} {:?} => {:?}", l, op.to_hir_binop().as_str(), r, val),
_ => write!(f, "{}", self.description()),
}
}
Expand Down
219 changes: 129 additions & 90 deletions src/interpreter/mod.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
btree_range,
collections,
collections_bound,
core_intrinsics,
filling_drop,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay. I hated this hack and the unsafe code I added with it.

question_mark,
rustc_private,
Expand All @@ -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;
Expand Down
26 changes: 18 additions & 8 deletions src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -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_definitions: HashMap<FunctionDefinition<'tcx>, AllocId>,
Copy link
Member

@solson solson Jun 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a name like function_allocs or function_alloc_cache since the values we get out of it are allocations. I'm not sure what's best, but I find functions vs. function_defintions a bit confusing.

next_id: AllocId,
pub pointer_size: usize,
}
Expand All @@ -69,22 +71,29 @@ impl<'tcx> Memory<'tcx> {
Memory {
alloc_map: HashMap::new(),
functions: HashMap::new(),
function_definitions: 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_definitions.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_definitions.insert(def, id);
Pointer {
alloc_id: id,
offset: 0,
Expand Down Expand Up @@ -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!(),
}
}
Expand Down
80 changes: 71 additions & 9 deletions src/primval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,40 @@ 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> {
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 Err(EvalError::Overflow($v($l), $v2($r), bin_op, $v(val)));
} 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),
Expand All @@ -53,6 +65,45 @@ 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 => {
let r = match right {
I8(i) if i >= 0 => i as u32,
I16(i) if i >= 0 => i as u32,
I32(i) if i >= 0 => i as u32,
I64(i) if i >= 0 && i as i32 as i64 == i => i as u32,
U8(i) => i as u32,
U16(i) => i as u32,
U32(i) => i,
U64(i) if i as u32 as u64 == i => i as u32,
_ => return Err(EvalError::InvalidBitShiftRhs(right)),
};
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);
},
_ => {},
}

let val = match (left, right) {
(I8(l), I8(r)) => int_binops!(I8, l, r),
(I16(l), I16(r)) => int_binops!(I16, l, r),
Expand Down Expand Up @@ -80,9 +131,20 @@ 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(_)) =>
(AbstractPtr(_), IntegerPtr(_)) |
(IntegerPtr(_), AbstractPtr(_)) |
(FnPtr(_), AbstractPtr(_)) |
(AbstractPtr(_), FnPtr(_)) |
(FnPtr(_), IntegerPtr(_)) |
(IntegerPtr(_), FnPtr(_)) =>
return 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);
Expand Down
10 changes: 10 additions & 0 deletions tests/compile-fail/cast_fn_ptr_unsafe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// just making sure that fn -> unsafe fn casts are handled by rustc so miri doesn't have to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by these two tests. Whether fns can be casted to unsafe fn is to some extent irrelevant to Miri - they can still always be transmuted. Miri has no safe/unsafe distinctions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, I just wanted to make sure it's not possible to cause a mir safe to unsafe cast.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized just after I posted that, this would matter for implementing casting. I guess that's fine.

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 changes: 10 additions & 0 deletions tests/compile-fail/cast_fn_ptr_unsafe2.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions tests/compile-fail/env_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//error-pattern: no mir for `std
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mir-for-all branch of rustc gets to here:

<core macros>:2:1: 1:1 error: can't call C ABI function: pthread_mutex_lock
src/libstd/sys/common/mutex.rs:41:33: 41:46 note: inside call to std::sys::mutex::Mutex::lock

😃

I need to prepare that PR for enabling this in rustc optionally soon.


fn main() {
let x = std::env::args();
assert_eq!(x.count(), 1);
}
26 changes: 14 additions & 12 deletions tests/compiletest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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"));
Expand All @@ -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();
Expand All @@ -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 changes: 8 additions & 0 deletions tests/run-pass/cast_fn_ptr_unsafe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main() {
fn f() {}

let g = f as fn() as unsafe fn();
unsafe {
g();
}
}
2 changes: 2 additions & 0 deletions tests/run-pass/function_pointers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading