Skip to content

Commit 2a1d766

Browse files
authored
Merge pull request #286 from RalfJung/mir-validate
Update MIR validation and test it
2 parents 8b449c3 + fb2ed45 commit 2a1d766

22 files changed

+93
-63
lines changed

src/librustc_mir/interpret/eval_context.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ pub struct EvalContext<'a, 'tcx: 'a, M: Machine<'tcx>> {
3737
/// The virtual memory system.
3838
pub memory: Memory<'a, 'tcx, M>,
3939

40-
#[allow(dead_code)]
41-
// FIXME(@RalfJung): validation branch
4240
/// Lvalues that were suspended by the validation subsystem, and will be recovered later
4341
pub(crate) suspended: HashMap<DynamicLifetime, Vec<ValidationQuery<'tcx>>>,
4442

src/librustc_mir/interpret/memory.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ mod range {
3535
}
3636

3737
impl MemoryRange {
38-
#[allow(dead_code)]
39-
// FIXME(@RalfJung): validation branch
4038
pub fn new(offset: u64, len: u64) -> MemoryRange {
4139
assert!(len > 0);
4240
MemoryRange {
@@ -61,8 +59,6 @@ mod range {
6159
left..right
6260
}
6361

64-
#[allow(dead_code)]
65-
// FIXME(@RalfJung): validation branch
6662
pub fn contained_in(&self, offset: u64, len: u64) -> bool {
6763
assert!(len > 0);
6864
offset <= self.start && self.end <= (offset + len)
@@ -143,8 +139,6 @@ impl<M> Allocation<M> {
143139
.filter(move |&(range, _)| range.overlaps(offset, len))
144140
}
145141

146-
#[allow(dead_code)]
147-
// FIXME(@RalfJung): validation branch
148142
fn iter_locks_mut<'a>(&'a mut self, offset: u64, len: u64) -> impl Iterator<Item=(&'a MemoryRange, &'a mut LockInfo)> + 'a {
149143
self.locks.range_mut(MemoryRange::range(offset, len))
150144
.filter(move |&(range, _)| range.overlaps(offset, len))
@@ -474,8 +468,6 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
474468
.map_err(|lock| EvalErrorKind::MemoryLockViolation { ptr, len, frame, access, lock }.into())
475469
}
476470

477-
#[allow(dead_code)]
478-
// FIXME(@RalfJung): validation branch
479471
/// Acquire the lock for the given lifetime
480472
pub(crate) fn acquire_lock(&mut self, ptr: MemoryPointer, len: u64, region: Option<CodeExtent>, kind: AccessKind) -> EvalResult<'tcx> {
481473
use std::collections::btree_map::Entry::*;
@@ -504,8 +496,6 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
504496
Ok(())
505497
}
506498

507-
#[allow(dead_code)]
508-
// FIXME(@RalfJung): validation branch
509499
/// Release a write lock prematurely. If there's a read lock or someone else's lock, fail.
510500
pub(crate) fn release_write_lock(&mut self, ptr: MemoryPointer, len: u64) -> EvalResult<'tcx> {
511501
assert!(len > 0);

src/librustc_mir/interpret/step.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,15 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
133133
self.deallocate_local(old_val)?;
134134
}
135135

136-
// NOPs for now.
137-
EndRegion(_ce) => {}
136+
// Validity checks.
137+
Validate(op, ref lvalues) => {
138+
for operand in lvalues {
139+
self.validation_op(op, operand)?;
140+
}
141+
}
142+
EndRegion(ce) => {
143+
self.end_region(ce)?;
144+
}
138145

139146
// Defined to do nothing. These are added by optimization passes, to avoid changing the
140147
// size of MIR constantly.

src/librustc_mir/interpret/validation.rs

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
// code for @RalfJung's validation branch is dead for now
2-
#![allow(dead_code)]
3-
41
use rustc::hir::Mutability;
52
use rustc::hir::Mutability::*;
6-
use rustc::mir;
3+
use rustc::mir::{self, ValidationOp, ValidationOperand};
74
use rustc::ty::{self, Ty, TypeFoldable};
85
use rustc::ty::subst::Subst;
96
use rustc::traits::Reveal;
@@ -14,28 +11,11 @@ use super::{
1411
EvalError, EvalResult, EvalErrorKind,
1512
EvalContext, DynamicLifetime,
1613
AccessKind, LockInfo,
17-
PrimVal, Value,
14+
Value,
1815
Lvalue, LvalueExtra,
1916
Machine,
2017
};
2118

22-
// FIXME remove this once it lands in rustc
23-
#[derive(Copy, Clone, PartialEq, Eq)]
24-
pub enum ValidationOp {
25-
Acquire,
26-
Release,
27-
Suspend(CodeExtent),
28-
}
29-
30-
#[derive(Clone, Debug)]
31-
pub struct ValidationOperand<'tcx, T> {
32-
pub lval: T,
33-
pub ty: Ty<'tcx>,
34-
pub re: Option<CodeExtent>,
35-
pub mutbl: Mutability,
36-
}
37-
// FIXME end
38-
3919
pub type ValidationQuery<'tcx> = ValidationOperand<'tcx, Lvalue<'tcx>>;
4020

4121
#[derive(Copy, Clone, Debug)]
@@ -59,26 +39,35 @@ impl ValidationMode {
5939
// Validity checks
6040
impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
6141
pub(crate) fn validation_op(&mut self, op: ValidationOp, operand: &ValidationOperand<'tcx, mir::Lvalue<'tcx>>) -> EvalResult<'tcx> {
42+
// If mir-emit-validate is set to 0 (i.e., disabled), we may still see validation commands
43+
// because other crates may have been compiled with mir-emit-validate > 0. Ignore those
44+
// commands. This makes mir-emit-validate also a flag to control whether miri will do
45+
// validation or not.
46+
if self.tcx.sess.opts.debugging_opts.mir_emit_validate == 0 {
47+
return Ok(());
48+
}
49+
6250
// HACK: Determine if this method is whitelisted and hence we do not perform any validation.
51+
// We currently insta-UB on anything passing around uninitialized memory, so we have to whitelist
52+
// the places that are allowed to do that.
53+
// The second group is stuff libstd does that is forbidden even under relaxed validation.
6354
{
6455
// The regexp we use for filtering
6556
use regex::Regex;
6657
lazy_static! {
6758
static ref RE: Regex = Regex::new("^(\
68-
std::mem::swap::|\
69-
std::mem::uninitialized::|\
70-
std::ptr::read::|\
71-
std::panicking::try::do_call::|\
72-
std::slice::from_raw_parts_mut::|\
73-
<std::heap::Heap as std::heap::Alloc>::|\
74-
<std::mem::ManuallyDrop<T>><std::heap::AllocErr>::new$|\
75-
<std::mem::ManuallyDrop<T> as std::ops::DerefMut><std::heap::AllocErr>::deref_mut$|\
76-
std::sync::atomic::AtomicBool::get_mut$|\
77-
<std::boxed::Box<T>><[a-zA-Z0-9_\\[\\]]+>::from_raw|\
78-
<[a-zA-Z0-9_:<>]+ as std::slice::SliceIndex<[a-zA-Z0-9_\\[\\]]+>><[a-zA-Z0-9_\\[\\]]+>::get_unchecked_mut$|\
79-
<alloc::raw_vec::RawVec<T, std::heap::Heap>><[a-zA-Z0-9_\\[\\]]+>::into_box$|\
80-
<std::vec::Vec<T>><[a-zA-Z0-9_\\[\\]]+>::into_boxed_slice$\
81-
)").unwrap();
59+
std::mem::uninitialized::|\
60+
std::mem::forget::|\
61+
<(std|alloc)::heap::Heap as (std::heap|alloc::allocator)::Alloc>::|\
62+
<std::mem::ManuallyDrop<T>><.*>::new$|\
63+
<std::mem::ManuallyDrop<T> as std::ops::DerefMut><.*>::deref_mut$|\
64+
std::ptr::read::|\
65+
\
66+
<std::sync::Arc<T>><.*>::inner$|\
67+
<std::sync::Arc<T>><.*>::drop_slow$|\
68+
(std::heap|alloc::allocator)::Layout::for_value::|\
69+
std::mem::(size|align)_of_val::\
70+
)").unwrap();
8271
}
8372
// Now test
8473
let name = self.stack[self.cur_frame()].instance.to_string();
@@ -167,10 +156,11 @@ std::sync::atomic::AtomicBool::get_mut$|\
167156
fn validate(&mut self, query: ValidationQuery<'tcx>, mode: ValidationMode) -> EvalResult<'tcx>
168157
{
169158
match self.try_validate(query, mode) {
170-
// HACK: If, during releasing, we hit memory we cannot use, we just ignore that.
171-
// This can happen because releases are added before drop elaboration.
172-
// TODO: Fix the MIR so that these releases do not happen.
173-
res @ Err(EvalError{ kind: EvalErrorKind::DanglingPointerDeref, ..}) |
159+
// Releasing an uninitalized variable is a NOP. This is needed because
160+
// we have to release the return value of a function; due to destination-passing-style
161+
// the callee may directly write there.
162+
// TODO: Ideally we would know whether the destination is already initialized, and only
163+
// release if it is.
174164
res @ Err(EvalError{ kind: EvalErrorKind::ReadUndefBytes, ..}) => {
175165
if let ValidationMode::Release = mode {
176166
return Ok(());
@@ -199,18 +189,13 @@ std::sync::atomic::AtomicBool::get_mut$|\
199189
}
200190

201191
// HACK: For now, bail out if we hit a dead local during recovery (can happen because sometimes we have
202-
// StorageDead before EndRegion).
192+
// StorageDead before EndRegion due to https://github.com/rust-lang/rust/issues/43481).
203193
// TODO: We should rather fix the MIR.
204-
// HACK: Releasing on dead/undef local variables is a NOP. This can happen because of releases being added
205-
// before drop elaboration.
206-
// TODO: Fix the MIR so that these releases do not happen.
207194
match query.lval {
208195
Lvalue::Local { frame, local } => {
209196
let res = self.stack[frame].get_local(local);
210197
match (res, mode) {
211-
(Err(EvalError{ kind: EvalErrorKind::DeadLocal, ..}), ValidationMode::Recover(_)) |
212-
(Err(EvalError{ kind: EvalErrorKind::DeadLocal, ..}), ValidationMode::Release) |
213-
(Ok(Value::ByVal(PrimVal::Undef)), ValidationMode::Release) => {
198+
(Err(EvalError{ kind: EvalErrorKind::DeadLocal, ..}), ValidationMode::Recover(_)) => {
214199
return Ok(());
215200
}
216201
_ => {},

src/librustc_mir/interpret/value.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ impl<'a, 'tcx: 'a> Value {
197197

198198
ByValPair(ptr, vtable) => Ok((ptr.into(), vtable.to_ptr()?)),
199199

200+
ByVal(PrimVal::Undef) => err!(ReadUndefBytes),
200201
_ => bug!("expected ptr and vtable, got {:?}", self),
201202
}
202203
}
@@ -216,6 +217,7 @@ impl<'a, 'tcx: 'a> Value {
216217
assert_eq!(len as u64 as u128, len);
217218
Ok((ptr.into(), len as u64))
218219
},
220+
ByVal(PrimVal::Undef) => err!(ReadUndefBytes),
219221
ByVal(_) => bug!("expected ptr and length, got {:?}", self),
220222
}
221223
}

tests/compile-fail/cast_box_int_to_fn_ptr.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Validation makes this fail in the wrong place
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
fn main() {
25
let b = Box::new(42);
36
let g = unsafe {

tests/compile-fail/cast_int_to_fn_ptr.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Validation makes this fail in the wrong place
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
fn main() {
25
let g = unsafe {
36
std::mem::transmute::<usize, fn(i32)>(42)

tests/compile-fail/execute_memory.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Validation makes this fail in the wrong place
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
#![feature(box_syntax)]
25

36
fn main() {

tests/compile-fail/fn_ptr_offset.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Validation makes this fail in the wrong place
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
use std::mem;
25

36
fn f() {}

tests/compile-fail/invalid_enum_discriminant.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Validation makes this fail in the wrong place
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
#[repr(C)]
25
pub enum Foo {
36
A, B, C, D

tests/compile-fail/memleak_rc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
//error-pattern: the evaluated program leaked memory
25

36
use std::rc::Rc;

tests/compile-fail/panic.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
//error-pattern: the evaluated program panicked
25

36
fn main() {

tests/compile-fail/static_memory_modification2.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Validation detects that we are casting & to &mut and so it changes why we fail
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
use std::mem::transmute;
25

36
#[allow(mutable_transmutes)]

tests/compile-fail/zst2.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
// error-pattern: the evaluated program panicked
25

36
#[derive(Debug)]

tests/compile-fail/zst3.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
// error-pattern: the evaluated program panicked
25

36
#[derive(Debug)]

tests/compiletest.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ fn compile_fail(sysroot: &Path, path: &str, target: &str, host: &str, fullmir: b
2020
let mut config = compiletest::default_config();
2121
config.mode = "compile-fail".parse().expect("Invalid mode");
2222
config.rustc_path = MIRI_PATH.into();
23+
let mut flags = Vec::new();
2324
if fullmir {
2425
if host != target {
2526
// skip fullmir on nonhost
@@ -32,6 +33,8 @@ fn compile_fail(sysroot: &Path, path: &str, target: &str, host: &str, fullmir: b
3233
config.target_rustcflags = Some(format!("--sysroot {}", sysroot.to_str().unwrap()));
3334
config.src_base = PathBuf::from(path.to_string());
3435
}
36+
flags.push("-Zmir-emit-validate=1".to_owned());
37+
config.target_rustcflags = Some(flags.join(" "));
3538
config.target = target.to_owned();
3639
compiletest::run_tests(&config);
3740
}
@@ -72,6 +75,8 @@ fn miri_pass(path: &str, target: &str, host: &str, fullmir: bool, opt: bool) {
7275
flags.push("-Zmir-opt-level=3".to_owned());
7376
} else {
7477
flags.push("-Zmir-opt-level=0".to_owned());
78+
// For now, only validate without optimizations. Inlining breaks validation.
79+
flags.push("-Zmir-emit-validate=1".to_owned());
7580
}
7681
config.target_rustcflags = Some(flags.join(" "));
7782
// don't actually execute the final binary, it might be for other targets and we only care

tests/run-pass-fullmir/regions-mock-trans.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
// pretty-expanded FIXME #23616
11+
// FIXME: We handle uninitialzied storage here, which currently makes validation fail.
12+
// compile-flags: -Zmir-emit-validate=0
1213

1314
#![feature(libc)]
1415

tests/run-pass/rc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
use std::cell::RefCell;
25
use std::rc::Rc;
36

tests/run-pass/recursive_static.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME: Disable validation until we figure out how to handle recursive statics.
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
struct S(&'static S);
25
static S1: S = S(&S2);
36
static S2: S = S(&S1);

tests/run-pass/send-is-not-static-par-for.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
//ignore-windows
1212

13+
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
14+
// compile-flags: -Zmir-emit-validate=0
15+
1316
use std::sync::Mutex;
1417

1518
fn par_for<I, F>(iter: I, f: F)

tests/run-pass/std.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
2+
// compile-flags: -Zmir-emit-validate=0
3+
14
use std::cell::{Cell, RefCell};
25
use std::rc::Rc;
36
use std::sync::Arc;

xargo/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#!/bin/bash
22
cd "$(readlink -e "$(dirname "$0")")"
3-
RUSTFLAGS='-Zalways-encode-mir' xargo build
3+
RUSTFLAGS='-Zalways-encode-mir -Zmir-emit-validate=1' xargo build

0 commit comments

Comments
 (0)