Skip to content

Commit

Permalink
Merge pull request rust-lang#286 from RalfJung/mir-validate
Browse files Browse the repository at this point in the history
Update MIR validation and test it
  • Loading branch information
RalfJung authored Aug 5, 2017
2 parents 8b449c3 + fb2ed45 commit 2a1d766
Show file tree
Hide file tree
Showing 22 changed files with 93 additions and 63 deletions.
2 changes: 0 additions & 2 deletions src/librustc_mir/interpret/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ pub struct EvalContext<'a, 'tcx: 'a, M: Machine<'tcx>> {
/// The virtual memory system.
pub memory: Memory<'a, 'tcx, M>,

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

Expand Down
10 changes: 0 additions & 10 deletions src/librustc_mir/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ mod range {
}

impl MemoryRange {
#[allow(dead_code)]
// FIXME(@RalfJung): validation branch
pub fn new(offset: u64, len: u64) -> MemoryRange {
assert!(len > 0);
MemoryRange {
Expand All @@ -61,8 +59,6 @@ mod range {
left..right
}

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

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

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

#[allow(dead_code)]
// FIXME(@RalfJung): validation branch
/// Release a write lock prematurely. If there's a read lock or someone else's lock, fail.
pub(crate) fn release_write_lock(&mut self, ptr: MemoryPointer, len: u64) -> EvalResult<'tcx> {
assert!(len > 0);
Expand Down
11 changes: 9 additions & 2 deletions src/librustc_mir/interpret/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,15 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
self.deallocate_local(old_val)?;
}

// NOPs for now.
EndRegion(_ce) => {}
// Validity checks.
Validate(op, ref lvalues) => {
for operand in lvalues {
self.validation_op(op, operand)?;
}
}
EndRegion(ce) => {
self.end_region(ce)?;
}

// Defined to do nothing. These are added by optimization passes, to avoid changing the
// size of MIR constantly.
Expand Down
79 changes: 32 additions & 47 deletions src/librustc_mir/interpret/validation.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// code for @RalfJung's validation branch is dead for now
#![allow(dead_code)]

use rustc::hir::Mutability;
use rustc::hir::Mutability::*;
use rustc::mir;
use rustc::mir::{self, ValidationOp, ValidationOperand};
use rustc::ty::{self, Ty, TypeFoldable};
use rustc::ty::subst::Subst;
use rustc::traits::Reveal;
Expand All @@ -14,28 +11,11 @@ use super::{
EvalError, EvalResult, EvalErrorKind,
EvalContext, DynamicLifetime,
AccessKind, LockInfo,
PrimVal, Value,
Value,
Lvalue, LvalueExtra,
Machine,
};

// FIXME remove this once it lands in rustc
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ValidationOp {
Acquire,
Release,
Suspend(CodeExtent),
}

#[derive(Clone, Debug)]
pub struct ValidationOperand<'tcx, T> {
pub lval: T,
pub ty: Ty<'tcx>,
pub re: Option<CodeExtent>,
pub mutbl: Mutability,
}
// FIXME end

pub type ValidationQuery<'tcx> = ValidationOperand<'tcx, Lvalue<'tcx>>;

#[derive(Copy, Clone, Debug)]
Expand All @@ -59,26 +39,35 @@ impl ValidationMode {
// Validity checks
impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
pub(crate) fn validation_op(&mut self, op: ValidationOp, operand: &ValidationOperand<'tcx, mir::Lvalue<'tcx>>) -> EvalResult<'tcx> {
// If mir-emit-validate is set to 0 (i.e., disabled), we may still see validation commands
// because other crates may have been compiled with mir-emit-validate > 0. Ignore those
// commands. This makes mir-emit-validate also a flag to control whether miri will do
// validation or not.
if self.tcx.sess.opts.debugging_opts.mir_emit_validate == 0 {
return Ok(());
}

// HACK: Determine if this method is whitelisted and hence we do not perform any validation.
// We currently insta-UB on anything passing around uninitialized memory, so we have to whitelist
// the places that are allowed to do that.
// The second group is stuff libstd does that is forbidden even under relaxed validation.
{
// The regexp we use for filtering
use regex::Regex;
lazy_static! {
static ref RE: Regex = Regex::new("^(\
std::mem::swap::|\
std::mem::uninitialized::|\
std::ptr::read::|\
std::panicking::try::do_call::|\
std::slice::from_raw_parts_mut::|\
<std::heap::Heap as std::heap::Alloc>::|\
<std::mem::ManuallyDrop<T>><std::heap::AllocErr>::new$|\
<std::mem::ManuallyDrop<T> as std::ops::DerefMut><std::heap::AllocErr>::deref_mut$|\
std::sync::atomic::AtomicBool::get_mut$|\
<std::boxed::Box<T>><[a-zA-Z0-9_\\[\\]]+>::from_raw|\
<[a-zA-Z0-9_:<>]+ as std::slice::SliceIndex<[a-zA-Z0-9_\\[\\]]+>><[a-zA-Z0-9_\\[\\]]+>::get_unchecked_mut$|\
<alloc::raw_vec::RawVec<T, std::heap::Heap>><[a-zA-Z0-9_\\[\\]]+>::into_box$|\
<std::vec::Vec<T>><[a-zA-Z0-9_\\[\\]]+>::into_boxed_slice$\
)").unwrap();
std::mem::uninitialized::|\
std::mem::forget::|\
<(std|alloc)::heap::Heap as (std::heap|alloc::allocator)::Alloc>::|\
<std::mem::ManuallyDrop<T>><.*>::new$|\
<std::mem::ManuallyDrop<T> as std::ops::DerefMut><.*>::deref_mut$|\
std::ptr::read::|\
\
<std::sync::Arc<T>><.*>::inner$|\
<std::sync::Arc<T>><.*>::drop_slow$|\
(std::heap|alloc::allocator)::Layout::for_value::|\
std::mem::(size|align)_of_val::\
)").unwrap();
}
// Now test
let name = self.stack[self.cur_frame()].instance.to_string();
Expand Down Expand Up @@ -167,10 +156,11 @@ std::sync::atomic::AtomicBool::get_mut$|\
fn validate(&mut self, query: ValidationQuery<'tcx>, mode: ValidationMode) -> EvalResult<'tcx>
{
match self.try_validate(query, mode) {
// HACK: If, during releasing, we hit memory we cannot use, we just ignore that.
// This can happen because releases are added before drop elaboration.
// TODO: Fix the MIR so that these releases do not happen.
res @ Err(EvalError{ kind: EvalErrorKind::DanglingPointerDeref, ..}) |
// Releasing an uninitalized variable is a NOP. This is needed because
// we have to release the return value of a function; due to destination-passing-style
// the callee may directly write there.
// TODO: Ideally we would know whether the destination is already initialized, and only
// release if it is.
res @ Err(EvalError{ kind: EvalErrorKind::ReadUndefBytes, ..}) => {
if let ValidationMode::Release = mode {
return Ok(());
Expand Down Expand Up @@ -199,18 +189,13 @@ std::sync::atomic::AtomicBool::get_mut$|\
}

// HACK: For now, bail out if we hit a dead local during recovery (can happen because sometimes we have
// StorageDead before EndRegion).
// StorageDead before EndRegion due to https://github.com/rust-lang/rust/issues/43481).
// TODO: We should rather fix the MIR.
// HACK: Releasing on dead/undef local variables is a NOP. This can happen because of releases being added
// before drop elaboration.
// TODO: Fix the MIR so that these releases do not happen.
match query.lval {
Lvalue::Local { frame, local } => {
let res = self.stack[frame].get_local(local);
match (res, mode) {
(Err(EvalError{ kind: EvalErrorKind::DeadLocal, ..}), ValidationMode::Recover(_)) |
(Err(EvalError{ kind: EvalErrorKind::DeadLocal, ..}), ValidationMode::Release) |
(Ok(Value::ByVal(PrimVal::Undef)), ValidationMode::Release) => {
(Err(EvalError{ kind: EvalErrorKind::DeadLocal, ..}), ValidationMode::Recover(_)) => {
return Ok(());
}
_ => {},
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_mir/interpret/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ impl<'a, 'tcx: 'a> Value {

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

ByVal(PrimVal::Undef) => err!(ReadUndefBytes),
_ => bug!("expected ptr and vtable, got {:?}", self),
}
}
Expand All @@ -216,6 +217,7 @@ impl<'a, 'tcx: 'a> Value {
assert_eq!(len as u64 as u128, len);
Ok((ptr.into(), len as u64))
},
ByVal(PrimVal::Undef) => err!(ReadUndefBytes),
ByVal(_) => bug!("expected ptr and length, got {:?}", self),
}
}
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/cast_box_int_to_fn_ptr.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Validation makes this fail in the wrong place
// compile-flags: -Zmir-emit-validate=0

fn main() {
let b = Box::new(42);
let g = unsafe {
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/cast_int_to_fn_ptr.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Validation makes this fail in the wrong place
// compile-flags: -Zmir-emit-validate=0

fn main() {
let g = unsafe {
std::mem::transmute::<usize, fn(i32)>(42)
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/execute_memory.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Validation makes this fail in the wrong place
// compile-flags: -Zmir-emit-validate=0

#![feature(box_syntax)]

fn main() {
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/fn_ptr_offset.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Validation makes this fail in the wrong place
// compile-flags: -Zmir-emit-validate=0

use std::mem;

fn f() {}
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/invalid_enum_discriminant.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Validation makes this fail in the wrong place
// compile-flags: -Zmir-emit-validate=0

#[repr(C)]
pub enum Foo {
A, B, C, D
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/memleak_rc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
// compile-flags: -Zmir-emit-validate=0

//error-pattern: the evaluated program leaked memory

use std::rc::Rc;
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/panic.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
// compile-flags: -Zmir-emit-validate=0

//error-pattern: the evaluated program panicked

fn main() {
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/static_memory_modification2.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Validation detects that we are casting & to &mut and so it changes why we fail
// compile-flags: -Zmir-emit-validate=0

use std::mem::transmute;

#[allow(mutable_transmutes)]
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/zst2.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
// compile-flags: -Zmir-emit-validate=0

// error-pattern: the evaluated program panicked

#[derive(Debug)]
Expand Down
3 changes: 3 additions & 0 deletions tests/compile-fail/zst3.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
// compile-flags: -Zmir-emit-validate=0

// error-pattern: the evaluated program panicked

#[derive(Debug)]
Expand Down
5 changes: 5 additions & 0 deletions tests/compiletest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fn compile_fail(sysroot: &Path, path: &str, target: &str, host: &str, fullmir: b
let mut config = compiletest::default_config();
config.mode = "compile-fail".parse().expect("Invalid mode");
config.rustc_path = MIRI_PATH.into();
let mut flags = Vec::new();
if fullmir {
if host != target {
// skip fullmir on nonhost
Expand All @@ -32,6 +33,8 @@ fn compile_fail(sysroot: &Path, path: &str, target: &str, host: &str, fullmir: b
config.target_rustcflags = Some(format!("--sysroot {}", sysroot.to_str().unwrap()));
config.src_base = PathBuf::from(path.to_string());
}
flags.push("-Zmir-emit-validate=1".to_owned());
config.target_rustcflags = Some(flags.join(" "));
config.target = target.to_owned();
compiletest::run_tests(&config);
}
Expand Down Expand Up @@ -72,6 +75,8 @@ fn miri_pass(path: &str, target: &str, host: &str, fullmir: bool, opt: bool) {
flags.push("-Zmir-opt-level=3".to_owned());
} else {
flags.push("-Zmir-opt-level=0".to_owned());
// For now, only validate without optimizations. Inlining breaks validation.
flags.push("-Zmir-emit-validate=1".to_owned());
}
config.target_rustcflags = Some(flags.join(" "));
// don't actually execute the final binary, it might be for other targets and we only care
Expand Down
3 changes: 2 additions & 1 deletion tests/run-pass-fullmir/regions-mock-trans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

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

#![feature(libc)]

Expand Down
3 changes: 3 additions & 0 deletions tests/run-pass/rc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
// compile-flags: -Zmir-emit-validate=0

use std::cell::RefCell;
use std::rc::Rc;

Expand Down
3 changes: 3 additions & 0 deletions tests/run-pass/recursive_static.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME: Disable validation until we figure out how to handle recursive statics.
// compile-flags: -Zmir-emit-validate=0

struct S(&'static S);
static S1: S = S(&S2);
static S2: S = S(&S1);
Expand Down
3 changes: 3 additions & 0 deletions tests/run-pass/send-is-not-static-par-for.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

//ignore-windows

// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
// compile-flags: -Zmir-emit-validate=0

use std::sync::Mutex;

fn par_for<I, F>(iter: I, f: F)
Expand Down
3 changes: 3 additions & 0 deletions tests/run-pass/std.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME: Due to https://github.com/rust-lang/rust/issues/43457 we have to disable validation
// compile-flags: -Zmir-emit-validate=0

use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::Arc;
Expand Down
2 changes: 1 addition & 1 deletion xargo/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash
cd "$(readlink -e "$(dirname "$0")")"
RUSTFLAGS='-Zalways-encode-mir' xargo build
RUSTFLAGS='-Zalways-encode-mir -Zmir-emit-validate=1' xargo build

0 comments on commit 2a1d766

Please sign in to comment.