Skip to content

Commit 579628f

Browse files
authored
Merge pull request rust-lang#28 from oli-obk/oflo
cleanup overflow binop code
2 parents c9d808e + 65de5dd commit 579628f

13 files changed

+287
-135
lines changed

src/error.rs

+12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::fmt;
33
use rustc::mir::repr as mir;
44
use rustc::ty::BareFnTy;
55
use memory::Pointer;
6+
use rustc_const_math::ConstMathErr;
7+
use syntax::codemap::Span;
68

79
#[derive(Clone, Debug)]
810
pub enum EvalError<'tcx> {
@@ -24,6 +26,8 @@ pub enum EvalError<'tcx> {
2426
Unimplemented(String),
2527
DerefFunctionPointer,
2628
ExecuteMemory,
29+
ArrayIndexOutOfBounds(Span, u64, u64),
30+
Math(Span, ConstMathErr),
2731
}
2832

2933
pub type EvalResult<'tcx, T> = Result<T, EvalError<'tcx>>;
@@ -58,6 +62,10 @@ impl<'tcx> Error for EvalError<'tcx> {
5862
"tried to dereference a function pointer",
5963
EvalError::ExecuteMemory =>
6064
"tried to treat a memory pointer as a function pointer",
65+
EvalError::ArrayIndexOutOfBounds(..) =>
66+
"array index out of bounds",
67+
EvalError::Math(..) =>
68+
"mathematical operation failed",
6169
}
6270
}
6371

@@ -73,6 +81,10 @@ impl<'tcx> fmt::Display for EvalError<'tcx> {
7381
},
7482
EvalError::FunctionPointerTyMismatch(expected, got) =>
7583
write!(f, "tried to call a function of type {:?} through a function pointer of type {:?}", expected, got),
84+
EvalError::ArrayIndexOutOfBounds(span, len, index) =>
85+
write!(f, "array index {} out of bounds {} at {:?}", index, len, span),
86+
EvalError::Math(span, ref err) =>
87+
write!(f, "mathematical operation at {:?} failed with {:?}", span, err),
7688
_ => write!(f, "{}", self.description()),
7789
}
7890
}

src/interpreter/mod.rs

+111-90
Large diffs are not rendered by default.

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
btree_range,
33
collections,
44
collections_bound,
5-
core_intrinsics,
65
filling_drop,
76
question_mark,
87
rustc_private,
@@ -14,6 +13,7 @@
1413
extern crate rustc_data_structures;
1514
extern crate rustc_mir;
1615
extern crate rustc_trans;
16+
extern crate rustc_const_math;
1717
extern crate syntax;
1818
#[macro_use] extern crate log;
1919
extern crate log_settings;

src/memory.rs

+18-8
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl Pointer {
4242
}
4343
}
4444

45-
#[derive(Debug, Copy, Clone)]
45+
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
4646
pub struct FunctionDefinition<'tcx> {
4747
pub def_id: DefId,
4848
pub substs: &'tcx Substs<'tcx>,
@@ -59,6 +59,8 @@ pub struct Memory<'tcx> {
5959
/// Function "allocations". They exist solely so pointers have something to point to, and
6060
/// we can figure out what they point to.
6161
functions: HashMap<AllocId, FunctionDefinition<'tcx>>,
62+
/// Inverse map of `functions` so we don't allocate a new pointer every time we need one
63+
function_alloc_cache: HashMap<FunctionDefinition<'tcx>, AllocId>,
6264
next_id: AllocId,
6365
pub pointer_size: usize,
6466
}
@@ -69,22 +71,29 @@ impl<'tcx> Memory<'tcx> {
6971
Memory {
7072
alloc_map: HashMap::new(),
7173
functions: HashMap::new(),
74+
function_alloc_cache: HashMap::new(),
7275
next_id: AllocId(0),
7376
pointer_size: pointer_size,
7477
}
7578
}
7679

77-
// FIXME: never create two pointers to the same def_id + substs combination
78-
// maybe re-use the statics cache of the EvalContext?
7980
pub fn create_fn_ptr(&mut self, def_id: DefId, substs: &'tcx Substs<'tcx>, fn_ty: &'tcx BareFnTy<'tcx>) -> Pointer {
80-
let id = self.next_id;
81-
debug!("creating fn ptr: {}", id);
82-
self.next_id.0 += 1;
83-
self.functions.insert(id, FunctionDefinition {
81+
let def = FunctionDefinition {
8482
def_id: def_id,
8583
substs: substs,
8684
fn_ty: fn_ty,
87-
});
85+
};
86+
if let Some(&alloc_id) = self.function_alloc_cache.get(&def) {
87+
return Pointer {
88+
alloc_id: alloc_id,
89+
offset: 0,
90+
};
91+
}
92+
let id = self.next_id;
93+
debug!("creating fn ptr: {}", id);
94+
self.next_id.0 += 1;
95+
self.functions.insert(id, def);
96+
self.function_alloc_cache.insert(def, id);
8897
Pointer {
8998
alloc_id: id,
9099
offset: 0,
@@ -361,6 +370,7 @@ impl<'tcx> Memory<'tcx> {
361370
PrimVal::U32(n) => self.write_uint(ptr, n as u64, 4),
362371
PrimVal::U64(n) => self.write_uint(ptr, n as u64, 8),
363372
PrimVal::IntegerPtr(n) => self.write_uint(ptr, n as u64, pointer_size),
373+
PrimVal::FnPtr(_p) |
364374
PrimVal::AbstractPtr(_p) => unimplemented!(),
365375
}
366376
}

src/primval.rs

+89-13
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,41 @@ pub enum PrimVal {
1010
U8(u8), U16(u16), U32(u32), U64(u64),
1111

1212
AbstractPtr(Pointer),
13+
FnPtr(Pointer),
1314
IntegerPtr(u64),
1415
}
1516

16-
pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> EvalResult<'tcx, PrimVal> {
17+
/// returns the result of the operation and whether the operation overflowed
18+
pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> EvalResult<'tcx, (PrimVal, bool)> {
1719
use rustc::mir::repr::BinOp::*;
1820
use self::PrimVal::*;
1921

22+
macro_rules! overflow {
23+
($v:ident, $v2:ident, $l:ident, $op:ident, $r:ident) => ({
24+
let (val, of) = $l.$op($r);
25+
if of {
26+
return Ok(($v(val), true));
27+
} else {
28+
$v(val)
29+
}
30+
})
31+
}
32+
2033
macro_rules! int_binops {
2134
($v:ident, $l:ident, $r:ident) => ({
2235
match bin_op {
23-
Add => $v($l + $r),
24-
Sub => $v($l - $r),
25-
Mul => $v($l * $r),
26-
Div => $v($l / $r),
27-
Rem => $v($l % $r),
36+
Add => overflow!($v, $v, $l, overflowing_add, $r),
37+
Sub => overflow!($v, $v, $l, overflowing_sub, $r),
38+
Mul => overflow!($v, $v, $l, overflowing_mul, $r),
39+
Div => overflow!($v, $v, $l, overflowing_div, $r),
40+
Rem => overflow!($v, $v, $l, overflowing_rem, $r),
2841
BitXor => $v($l ^ $r),
2942
BitAnd => $v($l & $r),
3043
BitOr => $v($l | $r),
3144

32-
// TODO(solson): Can have differently-typed RHS.
33-
Shl => $v($l << $r),
34-
Shr => $v($l >> $r),
45+
// these have already been handled
46+
Shl => unreachable!(),
47+
Shr => unreachable!(),
3548

3649
Eq => Bool($l == $r),
3750
Ne => Bool($l != $r),
@@ -53,6 +66,58 @@ pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> Eva
5366
}
5467
}
5568

69+
match bin_op {
70+
// can have rhs with a different numeric type
71+
Shl | Shr => {
72+
// these numbers are the maximum number a bitshift rhs could possibly have
73+
// e.g. u16 can be bitshifted by 0..16, so masking with 0b1111 (16 - 1) will ensure we are in that range
74+
let type_bits: u32 = match left {
75+
I8(_) | U8(_) => 8,
76+
I16(_) | U16(_) => 16,
77+
I32(_) | U32(_) => 32,
78+
I64(_) | U64(_) => 64,
79+
_ => unreachable!(),
80+
};
81+
assert!(type_bits.is_power_of_two());
82+
// turn into `u32` because `overflowing_sh{l,r}` only take `u32`
83+
let r = match right {
84+
I8(i) => i as u32,
85+
I16(i) => i as u32,
86+
I32(i) => i as u32,
87+
I64(i) => i as u32,
88+
U8(i) => i as u32,
89+
U16(i) => i as u32,
90+
U32(i) => i as u32,
91+
U64(i) => i as u32,
92+
_ => panic!("bad MIR: bitshift rhs is not integral"),
93+
};
94+
// apply mask
95+
let r = r & (type_bits - 1);
96+
macro_rules! shift {
97+
($v:ident, $l:ident, $r:ident) => ({
98+
match bin_op {
99+
Shl => overflow!($v, U32, $l, overflowing_shl, $r),
100+
Shr => overflow!($v, U32, $l, overflowing_shr, $r),
101+
_ => unreachable!(),
102+
}
103+
})
104+
}
105+
let val = match left {
106+
I8(l) => shift!(I8, l, r),
107+
I16(l) => shift!(I16, l, r),
108+
I32(l) => shift!(I32, l, r),
109+
I64(l) => shift!(I64, l, r),
110+
U8(l) => shift!(U8, l, r),
111+
U16(l) => shift!(U16, l, r),
112+
U32(l) => shift!(U32, l, r),
113+
U64(l) => shift!(U64, l, r),
114+
_ => unreachable!(),
115+
};
116+
return Ok((val, false));
117+
},
118+
_ => {},
119+
}
120+
56121
let val = match (left, right) {
57122
(I8(l), I8(r)) => int_binops!(I8, l, r),
58123
(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
80145

81146
(IntegerPtr(l), IntegerPtr(r)) => int_binops!(IntegerPtr, l, r),
82147

83-
(AbstractPtr(_), IntegerPtr(_)) | (IntegerPtr(_), AbstractPtr(_)) =>
84-
return unrelated_ptr_ops(bin_op),
148+
(AbstractPtr(_), IntegerPtr(_)) |
149+
(IntegerPtr(_), AbstractPtr(_)) |
150+
(FnPtr(_), AbstractPtr(_)) |
151+
(AbstractPtr(_), FnPtr(_)) |
152+
(FnPtr(_), IntegerPtr(_)) |
153+
(IntegerPtr(_), FnPtr(_)) =>
154+
unrelated_ptr_ops(bin_op)?,
155+
156+
(FnPtr(l_ptr), FnPtr(r_ptr)) => match bin_op {
157+
Eq => Bool(l_ptr == r_ptr),
158+
Ne => Bool(l_ptr != r_ptr),
159+
_ => return Err(EvalError::Unimplemented(format!("unimplemented fn ptr comparison: {:?}", bin_op))),
160+
},
85161

86162
(AbstractPtr(l_ptr), AbstractPtr(r_ptr)) => {
87163
if l_ptr.alloc_id != r_ptr.alloc_id {
88-
return unrelated_ptr_ops(bin_op);
164+
return Ok((unrelated_ptr_ops(bin_op)?, false));
89165
}
90166

91167
let l = l_ptr.offset;
@@ -105,7 +181,7 @@ pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> Eva
105181
(l, r) => return Err(EvalError::Unimplemented(format!("unimplemented binary op: {:?}, {:?}, {:?}", l, r, bin_op))),
106182
};
107183

108-
Ok(val)
184+
Ok((val, false))
109185
}
110186

111187
pub fn unary_op<'tcx>(un_op: mir::UnOp, val: PrimVal) -> EvalResult<'tcx, PrimVal> {
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// just making sure that fn -> unsafe fn casts are handled by rustc so miri doesn't have to
2+
fn main() {
3+
fn f() {}
4+
5+
let g = f as fn() as unsafe fn(i32); //~ERROR: non-scalar cast: `fn()` as `unsafe fn(i32)`
6+
7+
unsafe {
8+
g(42);
9+
}
10+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// just making sure that fn -> unsafe fn casts are handled by rustc so miri doesn't have to
2+
fn main() {
3+
fn f() {}
4+
5+
let g = f as fn() as fn(i32) as unsafe fn(i32); //~ERROR: non-scalar cast: `fn()` as `fn(i32)`
6+
7+
unsafe {
8+
g(42);
9+
}
10+
}

tests/compile-fail/env_args.rs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//error-pattern: no mir for `std
2+
3+
fn main() {
4+
let x = std::env::args();
5+
assert_eq!(x.count(), 1);
6+
}

tests/compiletest.rs

+14-12
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,28 @@ extern crate compiletest_rs as compiletest;
33
use std::path::{PathBuf, Path};
44
use std::io::Write;
55

6-
fn run_mode(dir: &'static str, mode: &'static str, sysroot: &str) {
7-
// Disable rustc's new error fomatting. It breaks these tests.
8-
std::env::remove_var("RUST_NEW_ERROR_FORMAT");
6+
fn compile_fail(sysroot: &str) {
97
let flags = format!("--sysroot {} -Dwarnings", sysroot);
108
for_all_targets(sysroot, |target| {
119
let mut config = compiletest::default_config();
1210
config.host_rustcflags = Some(flags.clone());
13-
config.mode = mode.parse().expect("Invalid mode");
11+
config.mode = "compile-fail".parse().expect("Invalid mode");
1412
config.run_lib_path = Path::new(sysroot).join("lib").join("rustlib").join(&target).join("lib");
1513
config.rustc_path = "target/debug/miri".into();
16-
config.src_base = PathBuf::from(format!("tests/{}", dir));
14+
config.src_base = PathBuf::from("tests/compile-fail".to_string());
1715
config.target = target.to_owned();
1816
config.target_rustcflags = Some(flags.clone());
1917
compiletest::run_tests(&config);
2018
});
2119
}
2220

21+
fn run_pass() {
22+
let mut config = compiletest::default_config();
23+
config.mode = "run-pass".parse().expect("Invalid mode");
24+
config.src_base = PathBuf::from("tests/run-pass".to_string());
25+
compiletest::run_tests(&config);
26+
}
27+
2328
fn for_all_targets<F: FnMut(String)>(sysroot: &str, mut f: F) {
2429
for target in std::fs::read_dir(format!("{}/lib/rustlib/", sysroot)).unwrap() {
2530
let target = target.unwrap();
@@ -38,7 +43,6 @@ fn for_all_targets<F: FnMut(String)>(sysroot: &str, mut f: F) {
3843

3944
#[test]
4045
fn compile_test() {
41-
let mut failed = false;
4246
// Taken from https://github.com/Manishearth/rust-clippy/pull/911.
4347
let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
4448
let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
@@ -48,7 +52,8 @@ fn compile_test() {
4852
.expect("need to specify RUST_SYSROOT env var or use rustup or multirust")
4953
.to_owned(),
5054
};
51-
run_mode("compile-fail", "compile-fail", &sysroot);
55+
compile_fail(&sysroot);
56+
run_pass();
5257
for_all_targets(&sysroot, |target| {
5358
for file in std::fs::read_dir("tests/run-pass").unwrap() {
5459
let file = file.unwrap();
@@ -72,21 +77,18 @@ fn compile_test() {
7277
match cmd.output() {
7378
Ok(ref output) if output.status.success() => writeln!(stderr.lock(), "ok").unwrap(),
7479
Ok(output) => {
75-
failed = true;
7680
writeln!(stderr.lock(), "FAILED with exit code {}", output.status.code().unwrap_or(0)).unwrap();
7781
writeln!(stderr.lock(), "stdout: \n {}", std::str::from_utf8(&output.stdout).unwrap()).unwrap();
7882
writeln!(stderr.lock(), "stderr: \n {}", std::str::from_utf8(&output.stderr).unwrap()).unwrap();
83+
panic!("some tests failed");
7984
}
8085
Err(e) => {
81-
failed = true;
8286
writeln!(stderr.lock(), "FAILED: {}", e).unwrap();
87+
panic!("some tests failed");
8388
},
8489
}
8590
}
8691
let stderr = std::io::stderr();
8792
writeln!(stderr.lock(), "").unwrap();
8893
});
89-
if failed {
90-
panic!("some tests failed");
91-
}
9294
}

tests/run-pass/cast_fn_ptr_unsafe.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
fn f() {}
3+
4+
let g = f as fn() as unsafe fn();
5+
unsafe {
6+
g();
7+
}
8+
}

tests/run-pass/function_pointers.rs

+2
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ fn call_fn_ptr() -> i32 {
1212

1313
fn main() {
1414
assert_eq!(call_fn_ptr(), 42);
15+
assert!(return_fn_ptr() == f);
16+
assert!(return_fn_ptr() as unsafe fn() -> i32 == f as fn() -> i32 as unsafe fn() -> i32);
1517
}

0 commit comments

Comments
 (0)