about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--src/librustc_mir/Cargo.toml1
-rw-r--r--src/librustc_mir/interpret/memory.rs103
-rw-r--r--src/librustc_mir/interpret/range_map.rs66
-rw-r--r--src/librustc_mir/interpret/validation.rs200
-rw-r--r--tests/compile-fail/oom2.rs2
-rw-r--r--tests/compile-fail/validation_aliasing_mut1.rs10
-rw-r--r--tests/compile-fail/validation_aliasing_mut2.rs10
-rw-r--r--tests/compile-fail/validation_aliasing_mut3.rs10
-rw-r--r--tests/compile-fail/validation_aliasing_mut4.rs13
-rw-r--r--tests/compile-fail/validation_buggy_split_at_mut.rs22
-rw-r--r--tests/compile-fail/validation_illegal_write.rs15
-rw-r--r--tests/compile-fail/validation_pointer_smuggling.rs20
-rw-r--r--tests/compile-fail/validation_recover1.rs16
-rw-r--r--tests/compile-fail/validation_recover2.rs14
-rw-r--r--tests/compile-fail/validation_recover3.rs15
-rw-r--r--tests/run-pass-fullmir/catch.rs (renamed from tests/run-pass/catch.rs)0
-rw-r--r--tests/run-pass-fullmir/hashmap.rs2
-rw-r--r--tests/run-pass-fullmir/integer-ops.rs5
-rw-r--r--tests/run-pass/packed_struct.rs3
20 files changed, 412 insertions, 117 deletions
diff --git a/.travis.yml b/.travis.yml
index 78de5b657ed..ef15fa98d3f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,7 +16,7 @@ script:
 - |
   # Test plain miri
   cargo build --release --features "cargo_miri" &&
-  cargo test --release &&
+  cargo test --release --all &&
   cargo install --features "cargo_miri"
 - |
   # Test cargo miri
diff --git a/src/librustc_mir/Cargo.toml b/src/librustc_mir/Cargo.toml
index 1a64bb8c7c7..4189f240c58 100644
--- a/src/librustc_mir/Cargo.toml
+++ b/src/librustc_mir/Cargo.toml
@@ -8,7 +8,6 @@ version = "0.1.0"
 workspace = "../.."
 
 [lib]
-test = false
 path = "lib.rs"
 
 [dependencies]
diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs
index f068bc839d1..75acdbe778c 100644
--- a/src/librustc_mir/interpret/memory.rs
+++ b/src/librustc_mir/interpret/memory.rs
@@ -29,19 +29,14 @@ pub enum AccessKind {
 /// Information about a lock that is currently held.
 #[derive(Clone, Debug)]
 struct LockInfo {
-    suspended: Vec<SuspendedWriteLock>,
+    /// Stores for which lifetimes (of the original write lock) we got
+    /// which suspensions.
+    suspended: HashMap<DynamicLifetime, Vec<CodeExtent>>,
+    /// The current state of the lock that's actually effective.
     active: Lock,
 }
 
-#[derive(Clone, Debug)]
-struct SuspendedWriteLock  {
-    /// Original lifetime of the lock that is now suspended
-    lft: DynamicLifetime,
-    /// Regions that all have to end to reenable this suspension
-    suspensions: Vec<CodeExtent>,
-}
-
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
 pub enum Lock {
     NoLock,
     WriteLock(DynamicLifetime),
@@ -57,7 +52,7 @@ impl Default for LockInfo {
 
 impl LockInfo {
     fn new(lock: Lock) -> LockInfo {
-        LockInfo { suspended: Vec::new(), active: lock }
+        LockInfo { suspended: HashMap::new(), active: lock }
     }
 
     fn access_permitted(&self, frame: Option<usize>, access: AccessKind) -> bool {
@@ -513,9 +508,10 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
     }
 
     /// Release or suspend a write lock of the given lifetime prematurely.
-    /// When releasing, if there is no write lock or someone else's write lock, that's an error.
+    /// When releasing, if there is a read lock or someone else's write lock, that's an error.
+    /// We *do* accept relasing a NoLock, as this can happen when a local is first acquired and later force_allocate'd.
     /// When suspending, the same cases are fine; we just register an additional suspension.
-    pub(crate) fn release_write_lock(&mut self, ptr: MemoryPointer, len: u64,
+    pub(crate) fn suspend_write_lock(&mut self, ptr: MemoryPointer, len: u64,
                                      lock_region: Option<CodeExtent>, suspend: Option<CodeExtent>) -> EvalResult<'tcx> {
         assert!(len > 0);
         let cur_frame = self.cur_frame;
@@ -523,7 +519,6 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
         let alloc = self.get_mut_unchecked(ptr.alloc_id)?;
 
         'locks: for lock in alloc.locks.iter_mut(ptr.offset, len) {
-            trace!("Releasing {:?}", lock);
             let is_our_lock = match lock.active {
                 WriteLock(lft) => {
                     lft == lock_lft
@@ -533,30 +528,25 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
                 }
             };
             if is_our_lock {
+                trace!("Releasing {:?} at {:?}", lock.active, lock_lft);
                 // Disable the lock
                 lock.active = NoLock;
+            } else {
+                trace!("Not touching {:?} at {:?} as its not our lock", lock.active, lock_lft);
             }
             match suspend {
                 Some(suspend_region) => {
-                    if is_our_lock {
-                        // We just released this lock, so add a new suspension
-                        lock.suspended.push(SuspendedWriteLock { lft: lock_lft, suspensions: vec![suspend_region] });
-                    } else {
-                        // Find our lock in the suspended ones
-                        for suspended_lock in lock.suspended.iter_mut().rev() {
-                            if suspended_lock.lft == lock_lft {
-                                // Found it!
-                                suspended_lock.suspensions.push(suspend_region);
-                                continue 'locks;
-                            }
-                        }
-                        // We did not find it.  Someone else had the lock and we have not suspended it, that's just wrong.
-                        return err!(InvalidMemoryLockRelease { ptr, len, frame: cur_frame, lock: lock.active.clone() });
-                    }
+                    trace!("Adding suspension to {:?} at {:?}", lock.active, lock_lft);
+                    // We just released this lock, so add a new suspension.
+                    // FIXME: Really, if there ever already is a suspension when is_our_lock, or if there is no suspension when !is_our_lock, something is amiss.
+                    // But this model is not good enough yet to prevent that.
+                    lock.suspended.entry(lock_lft)
+                        .or_insert_with(|| Vec::new())
+                        .push(suspend_region);
                 }
                 None => {
-                    // If we do not suspend, make sure we actually released something
-                    if !is_our_lock {
+                    // Make sure we did not try to release someone else's lock.
+                    if !is_our_lock && lock.active != NoLock {
                         return err!(InvalidMemoryLockRelease { ptr, len, frame: cur_frame, lock: lock.active.clone() });
                     }
                 }
@@ -577,34 +567,33 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
         let alloc = self.get_mut_unchecked(ptr.alloc_id)?;
 
         for lock in alloc.locks.iter_mut(ptr.offset, len) {
-            // If we have a suspension here, it will be the topmost one
-            let (got_the_lock, pop_suspension) = match lock.suspended.last_mut() {
-                None => (true, false),
-                Some(suspended_lock) => {
-                    if suspended_lock.lft == lock_lft {
-                        // That's us!  Remove suspension (it should be in there).  The same suspension can
-                        // occur multiple times (when there are multiple shared borrows of this that have the same
-                        // lifetime); only remove one of them.
-                        let idx = match suspended_lock.suspensions.iter().enumerate().find(|&(_, re)| re == &suspended_region) {
-                            None => // TODO: Can the user trigger this?
-                                bug!("We have this lock suspended, but not for the given region."),
-                            Some((idx, _)) => idx
-                        };
-                        suspended_lock.suspensions.remove(idx);
-                        let got_lock = suspended_lock.suspensions.is_empty();
-                        (got_lock, got_lock)
-                    } else {
-                        // Someone else's suspension up top, we should be able to grab the lock
-                        (true, false)
+            // Check if we have a suspension here
+            let (got_the_lock, remove_suspension) = match lock.suspended.get_mut(&lock_lft) {
+                None => {
+                    trace!("No suspension around, we can just acquire");
+                    (true, false)
+                }
+                Some(suspensions) => {
+                    trace!("Found suspension of {:?}, removing it", lock_lft);
+                    // That's us!  Remove suspension (it should be in there).  The same suspension can
+                    // occur multiple times (when there are multiple shared borrows of this that have the same
+                    // lifetime); only remove one of them.
+                    let idx = match suspensions.iter().enumerate().find(|&(_, re)| re == &suspended_region) {
+                        None => // TODO: Can the user trigger this?
+                            bug!("We have this lock suspended, but not for the given region."),
+                        Some((idx, _)) => idx
+                    };
+                    suspensions.remove(idx);
+                    let got_lock = suspensions.is_empty();
+                    if got_lock {
+                        trace!("All suspensions are gone, we can have the lock again");
                     }
+                    (got_lock, got_lock)
                 }
             };
-            if pop_suspension { // with NLL; we could do that up in the match above...
-                lock.suspended.pop();
-            } else {
-                // Sanity check: Our lock should not be in the suspension list
-                let found = lock.suspended.iter().find(|suspended_lock| suspended_lock.lft == lock_lft);
-                assert!(found.is_none());
+            if remove_suspension { // with NLL, we could do that up in the match above...
+                assert!(got_the_lock);
+                lock.suspended.remove(&lock_lft);
             }
             if got_the_lock {
                 match lock.active {
@@ -653,7 +642,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
                     lock.active = NoLock;
                 }
                 // Also clean up suspended write locks
-                lock.suspended.retain(|suspended_lock| !has_ended(&suspended_lock.lft));
+                lock.suspended.retain(|lft, _suspensions| !has_ended(lft));
             }
             // Clean up the map
             alloc.locks.retain(|lock| {
diff --git a/src/librustc_mir/interpret/range_map.rs b/src/librustc_mir/interpret/range_map.rs
index 7c8debc270d..e4db9b0e0fc 100644
--- a/src/librustc_mir/interpret/range_map.rs
+++ b/src/librustc_mir/interpret/range_map.rs
@@ -1,4 +1,9 @@
-//! Implements a map from disjoint non-empty integer ranges to data associated with those ranges
+//! Implements a map from integer indices to data.
+//! Rather than storing data for every index, internally, this maps entire ranges to the data.
+//! To this end, the APIs all work on ranges, not on individual integers. Ranges are split as
+//! necessary (e.g. when [0,5) is first associated with X, and then [1,2) is mutated).
+//! Users must not depend on whether a range is coalesced or not, even though this is observable
+//! via the iteration APIs.
 use std::collections::{BTreeMap};
 use std::ops;
 
@@ -21,6 +26,7 @@ struct Range {
 
 impl Range {
     fn range(offset: u64, len: u64) -> ops::Range<Range> {
+        assert!(len > 0);
         // We select all elements that are within
         // the range given by the offset into the allocation and the length.
         // This is sound if all ranges that intersect with the argument range, are in the
@@ -36,6 +42,7 @@ impl Range {
         left..right
     }
 
+    /// Tests if all of [offset, offset+len) are contained in this range.
     fn overlaps(&self, offset: u64, len: u64) -> bool {
         assert!(len > 0);
         offset < self.end && offset+len >= self.start
@@ -48,6 +55,7 @@ impl<T> RangeMap<T> {
     }
 
     fn iter_with_range<'a>(&'a self, offset: u64, len: u64) -> impl Iterator<Item=(&'a Range, &'a T)> + 'a {
+        assert!(len > 0);
         self.map.range(Range::range(offset, len))
             .filter_map(move |(range, data)| {
                 if range.overlaps(offset, len) {
@@ -63,7 +71,7 @@ impl<T> RangeMap<T> {
     }
 
     fn split_entry_at(&mut self, offset: u64) where T: Clone {
-        let range = match self.iter_with_range(offset, 0).next() {
+        let range = match self.iter_with_range(offset, 1).next() {
             Some((&range, _)) => range,
             None => return,
         };
@@ -88,6 +96,7 @@ impl<T> RangeMap<T> {
     pub fn iter_mut_with_gaps<'a>(&'a mut self, offset: u64, len: u64) -> impl Iterator<Item=&'a mut T> + 'a
         where T: Clone
     {
+        assert!(len > 0);
         // Preparation: Split first and last entry as needed.
         self.split_entry_at(offset);
         self.split_entry_at(offset+len);
@@ -112,14 +121,15 @@ impl<T> RangeMap<T> {
     {
         // Do a first iteration to collect the gaps
         let mut gaps = Vec::new();
-        let mut last_end = None;
+        let mut last_end = offset;
         for (range, _) in self.iter_with_range(offset, len) {
-            if let Some(last_end) = last_end {
-                if last_end < range.start {
-                    gaps.push(Range { start: last_end, end: range.start });
-                }
+            if last_end < range.start {
+                gaps.push(Range { start: last_end, end: range.start });
             }
-            last_end = Some(range.end);
+            last_end = range.end;
+        }
+        if last_end < offset+len {
+            gaps.push(Range { start: last_end, end: offset+len });
         }
 
         // Add default for all gaps
@@ -147,3 +157,43 @@ impl<T> RangeMap<T> {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    /// Query the map at every offset in the range and collect the results.
+    fn to_vec<T: Copy>(map: &RangeMap<T>, offset: u64, len: u64) -> Vec<T> {
+        (offset..offset+len).into_iter().map(|i| *map.iter(i, 1).next().unwrap()).collect()
+    }
+
+    #[test]
+    fn basic_insert() {
+        let mut map = RangeMap::<i32>::new();
+        // Insert
+        for x in map.iter_mut(10, 1) {
+            *x = 42;
+        }
+        // Check
+        assert_eq!(to_vec(&map, 10, 1), vec![42]);
+    }
+
+    #[test]
+    fn gaps() {
+        let mut map = RangeMap::<i32>::new();
+        for x in map.iter_mut(11, 1) {
+            *x = 42;
+        }
+        for x in map.iter_mut(15, 1) {
+            *x = 42;
+        }
+
+        // Now request a range that needs three gaps filled
+        for x in map.iter_mut(10, 10) {
+            if *x != 42 { *x = 23; }
+        }
+
+        assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 42, 23, 23, 23, 23]);
+        assert_eq!(to_vec(&map, 13, 5), vec![23, 23, 42, 23, 23]);
+    }
+}
diff --git a/src/librustc_mir/interpret/validation.rs b/src/librustc_mir/interpret/validation.rs
index b291c639b9c..6a8df1b5246 100644
--- a/src/librustc_mir/interpret/validation.rs
+++ b/src/librustc_mir/interpret/validation.rs
@@ -1,10 +1,11 @@
 use rustc::hir::Mutability;
 use rustc::hir::Mutability::*;
 use rustc::mir::{self, ValidationOp, ValidationOperand};
-use rustc::ty::{self, Ty, TypeFoldable};
-use rustc::ty::subst::Subst;
+use rustc::ty::{self, Ty, TypeFoldable, TyCtxt};
+use rustc::ty::subst::{Substs, Subst};
+use rustc::traits;
+use rustc::infer::InferCtxt;
 use rustc::traits::Reveal;
-use rustc::infer::TransNormalize;
 use rustc::middle::region::CodeExtent;
 
 use super::{
@@ -18,7 +19,7 @@ use super::{
 
 pub type ValidationQuery<'tcx> = ValidationOperand<'tcx, Lvalue>;
 
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, PartialEq)]
 enum ValidationMode {
     Acquire,
     /// Recover because the given region ended
@@ -110,6 +111,109 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
         Ok(())
     }
 
+    fn normalize_type_unerased(&self, ty: Ty<'tcx>) -> Ty<'tcx> {
+        return normalize_associated_type(self.tcx, &ty);
+
+        use syntax::codemap::{Span, DUMMY_SP};
+
+        // We copy a bunch of stuff from rustc/infer/mod.rs to be able to tweak its behavior
+        fn normalize_projections_in<'a, 'gcx, 'tcx, T>(
+                self_: &InferCtxt<'a, 'gcx, 'tcx>,
+                param_env: ty::ParamEnv<'tcx>,
+                value: &T)
+                -> T::Lifted
+            where T: TypeFoldable<'tcx> + ty::Lift<'gcx>
+        {
+            let mut selcx = traits::SelectionContext::new(self_);
+            let cause = traits::ObligationCause::dummy();
+            let traits::Normalized { value: result, obligations } =
+                traits::normalize(&mut selcx, param_env, cause, value);
+
+            let mut fulfill_cx = traits::FulfillmentContext::new();
+
+            for obligation in obligations {
+                fulfill_cx.register_predicate_obligation(self_, obligation);
+            }
+
+            drain_fulfillment_cx_or_panic(self_, DUMMY_SP, &mut fulfill_cx, &result)
+        }
+
+        fn drain_fulfillment_cx_or_panic<'a, 'gcx, 'tcx, T>(
+                                                self_: &InferCtxt<'a, 'gcx, 'tcx>,
+                                                span: Span,
+                                                fulfill_cx: &mut traits::FulfillmentContext<'tcx>,
+                                                result: &T)
+                                                -> T::Lifted
+            where T: TypeFoldable<'tcx> + ty::Lift<'gcx>
+        {
+            // In principle, we only need to do this so long as `result`
+            // contains unbound type parameters. It could be a slight
+            // optimization to stop iterating early.
+            match fulfill_cx.select_all_or_error(self_) {
+                Ok(()) => { }
+                Err(errors) => {
+                    span_bug!(span, "Encountered errors `{:?}` resolving bounds after type-checking",
+                                errors);
+                }
+            }
+
+            let result = self_.resolve_type_vars_if_possible(result);
+            let result = self_.tcx.fold_regions(&result, &mut false, |r, _| match *r { ty::ReVar(_) => self_.tcx.types.re_erased, _ => r });
+
+            match self_.tcx.lift_to_global(&result) {
+                Some(result) => result,
+                None => {
+                    span_bug!(span, "Uninferred types/regions in `{:?}`", result);
+                }
+            }
+        }
+
+        trait MyTransNormalize<'gcx>: TypeFoldable<'gcx> {
+            fn my_trans_normalize<'a, 'tcx>(&self,
+                                        infcx: &InferCtxt<'a, 'gcx, 'tcx>,
+                                        param_env: ty::ParamEnv<'tcx>)
+                                        -> Self;
+        }
+
+        macro_rules! items { ($($item:item)+) => ($($item)+) }
+        macro_rules! impl_trans_normalize {
+            ($lt_gcx:tt, $($ty:ty),+) => {
+                items!($(impl<$lt_gcx> MyTransNormalize<$lt_gcx> for $ty {
+                    fn my_trans_normalize<'a, 'tcx>(&self,
+                                                infcx: &InferCtxt<'a, $lt_gcx, 'tcx>,
+                                                param_env: ty::ParamEnv<'tcx>)
+                                                -> Self {
+                        normalize_projections_in(infcx, param_env, self)
+                    }
+                })+);
+            }
+        }
+
+        impl_trans_normalize!('gcx,
+            Ty<'gcx>,
+            &'gcx Substs<'gcx>,
+            ty::FnSig<'gcx>,
+            ty::PolyFnSig<'gcx>,
+            ty::ClosureSubsts<'gcx>,
+            ty::PolyTraitRef<'gcx>,
+            ty::ExistentialTraitRef<'gcx>
+        );
+
+        fn normalize_associated_type<'a, 'tcx, T>(self_: TyCtxt<'a, 'tcx, 'tcx>, value: &T) -> T
+            where T: MyTransNormalize<'tcx>
+        {
+            let param_env = ty::ParamEnv::empty(Reveal::All);
+
+            if !value.has_projection_types() {
+                return value.clone();
+            }
+
+            self_.infer_ctxt().enter(|infcx| {
+                value.my_trans_normalize(&infcx, param_env)
+            })
+        }
+    }
+
     fn validate_variant(
         &mut self,
         query: ValidationQuery<'tcx>,
@@ -142,16 +246,15 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
     fn validate(&mut self, query: ValidationQuery<'tcx>, mode: ValidationMode) -> EvalResult<'tcx>
     {
         match self.try_validate(query, mode) {
-            // Releasing an uninitalized variable is a NOP.  This is needed because
+            // ReleaseUntil(None) of 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 !mode.acquiring() {
-                    return Ok(());
-                }
-                res
+            // release if it is.  But of course that can't even always be statically determined.
+            Err(EvalError{ kind: EvalErrorKind::ReadUndefBytes, ..})
+                if mode == ValidationMode::ReleaseUntil(None)
+            => {
+                return Ok(());
             }
             res => res,
         }
@@ -190,14 +293,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
             _ => {}
         }
 
-        // This is essentially a copy of normalize_associated_type, but without erasure
-        if query.ty.has_projection_types() {
-            let param_env = ty::ParamEnv::empty(Reveal::All);
-            let old_ty = query.ty;
-            query.ty = self.tcx.infer_ctxt().enter(move |infcx| {
-                old_ty.trans_normalize(&infcx, param_env)
-            })
-        }
+        query.ty = self.normalize_type_unerased(&query.ty);
         trace!("{:?} on {:?}", mode, query);
 
         // Decide whether this type *owns* the memory it covers (like integers), or whether it
@@ -212,38 +308,46 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
             TyParam(_) | TyInfer(_) | TyProjection(_) | TyAnon(..) | TyError => bug!("I got an incomplete/unnormalized type for validation"),
         };
         if is_owning {
-            match query.lval {
-                Lvalue::Ptr { ptr, extra } => {
-                    // Determine the size
-                    // FIXME: Can we reuse size_and_align_of_dst for Lvalues?
-                    let len = match self.type_size(query.ty)? {
-                        Some(size) => {
-                            assert_eq!(extra, LvalueExtra::None, "Got a fat ptr to a sized type");
-                            size
-                        }
-                        None => {
-                            // The only unsized typ we concider "owning" is TyStr.
-                            assert_eq!(query.ty.sty, TyStr, "Found a surprising unsized owning type");
-                            // The extra must be the length, in bytes.
-                            match extra {
-                                LvalueExtra::Length(len) => len,
-                                _ => bug!("TyStr must have a length as extra"),
-                            }
+            // We need to lock.  So we need memory.  So we have to force_acquire.
+            // Tracking the same state for locals not backed by memory would just duplicate too
+            // much machinery.
+            // FIXME: We ignore alignment.
+            let (ptr, extra) = self.force_allocation(query.lval)?.to_ptr_extra_aligned();
+            // Determine the size
+            // FIXME: Can we reuse size_and_align_of_dst for Lvalues?
+            let len = match self.type_size(query.ty)? {
+                Some(size) => {
+                    assert_eq!(extra, LvalueExtra::None, "Got a fat ptr to a sized type");
+                    size
+                }
+                None => {
+                    // The only unsized typ we concider "owning" is TyStr.
+                    assert_eq!(query.ty.sty, TyStr, "Found a surprising unsized owning type");
+                    // The extra must be the length, in bytes.
+                    match extra {
+                        LvalueExtra::Length(len) => len,
+                        _ => bug!("TyStr must have a length as extra"),
+                    }
+                }
+            };
+            // Handle locking
+            if len > 0 {
+                let ptr = ptr.to_ptr()?;
+                match query.mutbl {
+                    MutImmutable =>
+                        if mode.acquiring() {
+                            self.memory.acquire_lock(ptr, len, query.re, AccessKind::Read)?;
                         }
-                    };
-                    // Handle locking
-                    if len > 0 {
-                        let ptr = ptr.to_ptr()?;
-                        let access = match query.mutbl { MutMutable => AccessKind::Write, MutImmutable => AccessKind::Read };
+                        // No releasing of read locks, ever.
+                    MutMutable =>
                         match mode {
-                            ValidationMode::Acquire => self.memory.acquire_lock(ptr, len, query.re, access)?,
-                            ValidationMode::Recover(ending_ce) => self.memory.recover_write_lock(ptr, len, query.re, ending_ce)?,
-                            ValidationMode::ReleaseUntil(suspended_ce) => self.memory.release_write_lock(ptr, len, query.re, suspended_ce)?,
+                            ValidationMode::Acquire =>
+                                self.memory.acquire_lock(ptr, len, query.re, AccessKind::Write)?,
+                            ValidationMode::Recover(ending_ce) =>
+                                self.memory.recover_write_lock(ptr, len, query.re, ending_ce)?,
+                            ValidationMode::ReleaseUntil(suspended_ce) =>
+                                self.memory.suspend_write_lock(ptr, len, query.re, suspended_ce)?,
                         }
-                    }
-                }
-                Lvalue::Local { .. }  => {
-                    // Not backed by memory, so we have nothing to do.
                 }
             }
         }
diff --git a/tests/compile-fail/oom2.rs b/tests/compile-fail/oom2.rs
index 1a4a47efe68..f439ac8c130 100644
--- a/tests/compile-fail/oom2.rs
+++ b/tests/compile-fail/oom2.rs
@@ -1,3 +1,5 @@
+// Validation forces more allocation; disable it.
+// compile-flags: -Zmir-emit-validate=0
 #![feature(box_syntax, custom_attribute, attr_literals)]
 #![miri(memory_size=2048)]
 
diff --git a/tests/compile-fail/validation_aliasing_mut1.rs b/tests/compile-fail/validation_aliasing_mut1.rs
new file mode 100644
index 00000000000..86aa57447fe
--- /dev/null
+++ b/tests/compile-fail/validation_aliasing_mut1.rs
@@ -0,0 +1,10 @@
+#![allow(unused_variables)]
+
+mod safe {
+    pub fn safe(x: &mut i32, y: &mut i32) {} //~ ERROR: in conflict with lock WriteLock
+}
+
+fn main() {
+    let x = &mut 0 as *mut _;
+    unsafe { safe::safe(&mut *x, &mut *x) };
+}
diff --git a/tests/compile-fail/validation_aliasing_mut2.rs b/tests/compile-fail/validation_aliasing_mut2.rs
new file mode 100644
index 00000000000..ed7497e5e54
--- /dev/null
+++ b/tests/compile-fail/validation_aliasing_mut2.rs
@@ -0,0 +1,10 @@
+#![allow(unused_variables)]
+
+mod safe {
+    pub fn safe(x: &i32, y: &mut i32) {} //~ ERROR: in conflict with lock ReadLock
+}
+
+fn main() {
+    let x = &mut 0 as *mut _;
+    unsafe { safe::safe(&*x, &mut *x) };
+}
diff --git a/tests/compile-fail/validation_aliasing_mut3.rs b/tests/compile-fail/validation_aliasing_mut3.rs
new file mode 100644
index 00000000000..69fbbc167ca
--- /dev/null
+++ b/tests/compile-fail/validation_aliasing_mut3.rs
@@ -0,0 +1,10 @@
+#![allow(unused_variables)]
+
+mod safe {
+    pub fn safe(x: &mut i32, y: &i32) {} //~ ERROR: in conflict with lock WriteLock
+}
+
+fn main() {
+    let x = &mut 0 as *mut _;
+    unsafe { safe::safe(&mut *x, &*x) };
+}
diff --git a/tests/compile-fail/validation_aliasing_mut4.rs b/tests/compile-fail/validation_aliasing_mut4.rs
new file mode 100644
index 00000000000..3dac55aeaac
--- /dev/null
+++ b/tests/compile-fail/validation_aliasing_mut4.rs
@@ -0,0 +1,13 @@
+#![allow(unused_variables)]
+
+mod safe {
+    use std::cell::Cell;
+
+    // Make sure &mut UnsafeCell also has a lock to it
+    pub fn safe(x: &mut Cell<i32>, y: &i32) {} //~ ERROR: in conflict with lock WriteLock
+}
+
+fn main() {
+    let x = &mut 0 as *mut _;
+    unsafe { safe::safe(&mut *(x as *mut _), &*x) };
+}
diff --git a/tests/compile-fail/validation_buggy_split_at_mut.rs b/tests/compile-fail/validation_buggy_split_at_mut.rs
new file mode 100644
index 00000000000..9e67b2a4ab1
--- /dev/null
+++ b/tests/compile-fail/validation_buggy_split_at_mut.rs
@@ -0,0 +1,22 @@
+#![allow(unused_variables)]
+
+mod safe {
+    use std::slice::from_raw_parts_mut;
+
+    pub fn split_at_mut<T>(self_: &mut [T], mid: usize) -> (&mut [T], &mut [T]) {
+        let len = self_.len();
+        let ptr = self_.as_mut_ptr();
+
+        unsafe {
+            assert!(mid <= len);
+
+            (from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid"
+            from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
+        }
+    }
+}
+
+fn main() {
+    let mut array = [1,2,3,4];
+    let _x = safe::split_at_mut(&mut array, 0); //~ ERROR: in conflict with lock WriteLock
+}
diff --git a/tests/compile-fail/validation_illegal_write.rs b/tests/compile-fail/validation_illegal_write.rs
new file mode 100644
index 00000000000..1432f4cc9f1
--- /dev/null
+++ b/tests/compile-fail/validation_illegal_write.rs
@@ -0,0 +1,15 @@
+#![allow(unused_variables)]
+
+mod safe {
+    pub(crate) fn safe(x: &u32) {
+        let x : &mut u32 = unsafe { &mut *(x as *const _ as *mut _) };
+        *x = 42; //~ ERROR: in conflict with lock ReadLock
+    }
+}
+
+fn main() {
+    let target = &mut 42;
+    let target_ref = &target;
+    // do a reborrow, but we keep the lock
+    safe::safe(&*target);
+}
diff --git a/tests/compile-fail/validation_pointer_smuggling.rs b/tests/compile-fail/validation_pointer_smuggling.rs
new file mode 100644
index 00000000000..3320d2a89d3
--- /dev/null
+++ b/tests/compile-fail/validation_pointer_smuggling.rs
@@ -0,0 +1,20 @@
+#![allow(unused_variables)]
+
+static mut PTR: *mut u8 = 0 as *mut _;
+
+fn fun1(x: &mut u8) {
+    unsafe {
+        PTR = x;
+    }
+}
+
+fn fun2() {
+    // Now we use a pointer we are not allowed to use
+    let _x = unsafe { *PTR }; //~ ERROR: in conflict with lock WriteLock
+}
+
+fn main() {
+    let mut val = 0;
+    fun1(&mut val);
+    fun2();
+}
diff --git a/tests/compile-fail/validation_recover1.rs b/tests/compile-fail/validation_recover1.rs
new file mode 100644
index 00000000000..55c38a694c5
--- /dev/null
+++ b/tests/compile-fail/validation_recover1.rs
@@ -0,0 +1,16 @@
+#![allow(unused_variables)]
+
+#[repr(u32)]
+enum Bool { True }
+
+mod safe {
+    pub(crate) fn safe(x: &mut super::Bool) {
+        let x = x as *mut _ as *mut u32;
+        unsafe { *x = 44; } // out-of-bounds enum discriminant
+    }
+}
+
+fn main() {
+    let mut x = Bool::True;
+    safe::safe(&mut x); //~ ERROR: invalid enum discriminant
+}
diff --git a/tests/compile-fail/validation_recover2.rs b/tests/compile-fail/validation_recover2.rs
new file mode 100644
index 00000000000..756be9fde6f
--- /dev/null
+++ b/tests/compile-fail/validation_recover2.rs
@@ -0,0 +1,14 @@
+#![allow(unused_variables)]
+
+mod safe {
+    // This makes a ref that was passed to us via &mut alias with things it should not alias with
+    pub(crate) fn safe(x: &mut &u32, target: &mut u32) {
+        unsafe { *x = &mut *(target as *mut _); }
+    }
+}
+
+fn main() {
+    let target = &mut 42;
+    let mut target_alias = &42; // initial dummy value
+    safe::safe(&mut target_alias, target); //~ ERROR: in conflict with lock ReadLock
+}
diff --git a/tests/compile-fail/validation_recover3.rs b/tests/compile-fail/validation_recover3.rs
new file mode 100644
index 00000000000..afe6fe7c0bb
--- /dev/null
+++ b/tests/compile-fail/validation_recover3.rs
@@ -0,0 +1,15 @@
+#![allow(unused_variables)]
+
+mod safe {
+    pub(crate) fn safe(x: *mut u32) {
+        unsafe { *x = 42; } //~ ERROR: in conflict with lock WriteLock
+    }
+}
+
+fn main() {
+    let target = &mut 42u32;
+    let target2 = target as *mut _;
+    drop(&mut *target); // reborrow
+    // Now make sure we still got the lock
+    safe::safe(target2);
+}
diff --git a/tests/run-pass/catch.rs b/tests/run-pass-fullmir/catch.rs
index 439edc82dde..439edc82dde 100644
--- a/tests/run-pass/catch.rs
+++ b/tests/run-pass-fullmir/catch.rs
diff --git a/tests/run-pass-fullmir/hashmap.rs b/tests/run-pass-fullmir/hashmap.rs
index f4a358174f5..892518011db 100644
--- a/tests/run-pass-fullmir/hashmap.rs
+++ b/tests/run-pass-fullmir/hashmap.rs
@@ -1,3 +1,5 @@
+// FIXME: disable validation until we figure out how to handle <https://github.com/solson/miri/issues/296>.
+// compile-flags: -Zmir-emit-validate=0
 use std::collections::{self, HashMap};
 use std::hash::BuildHasherDefault;
 
diff --git a/tests/run-pass-fullmir/integer-ops.rs b/tests/run-pass-fullmir/integer-ops.rs
index 7f66dbd521f..44030c3301c 100644
--- a/tests/run-pass-fullmir/integer-ops.rs
+++ b/tests/run-pass-fullmir/integer-ops.rs
@@ -8,8 +8,9 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-// FIXME: remove the next line once https://github.com/rust-lang/rust/issues/43359 is fixed
-// compile-flags: -Zmir-opt-level=0
+// FIXME: remove -Zmir-opt-level once https://github.com/rust-lang/rust/issues/43359 is fixed
+// FIXME: remove -Zmir-emit-validate=0 once https://github.com/rust-lang/rust/pull/43748 is merged
+// compile-flags: -Zmir-opt-level=0 -Zmir-emit-validate=0
 
 use std::i32;
 
diff --git a/tests/run-pass/packed_struct.rs b/tests/run-pass/packed_struct.rs
index 0c478119828..e0387a5f405 100644
--- a/tests/run-pass/packed_struct.rs
+++ b/tests/run-pass/packed_struct.rs
@@ -1,3 +1,6 @@
+// FIXME: We have to disable this, force_allocation fails.
+// TODO: I think this can be triggered even without validation.
+// compile-flags: -Zmir-emit-validate=0
 #![allow(dead_code)]
 #![feature(unsize, coerce_unsized)]