]> git.lizzy.rs Git - rust.git/commitdiff
Separate deref and access into different operations; add special exception for creati...
authorRalf Jung <post@ralfj.de>
Fri, 9 Nov 2018 09:53:28 +0000 (10:53 +0100)
committerRalf Jung <post@ralfj.de>
Thu, 15 Nov 2018 08:35:40 +0000 (09:35 +0100)
16 files changed:
src/lib.rs
src/stacked_borrows.rs
tests/compile-fail/stacked_borrows/alias_through_mutation.rs
tests/compile-fail/stacked_borrows/illegal_read5.rs [new file with mode: 0644]
tests/compile-fail/stacked_borrows/illegal_write1.rs
tests/compile-fail/stacked_borrows/illegal_write3.rs
tests/compile-fail/stacked_borrows/illegal_write4.rs
tests/compile-fail/stacked_borrows/illegal_write5.rs
tests/compile-fail/stacked_borrows/load_invalid_mut.rs
tests/compile-fail/stacked_borrows/load_invalid_shr.rs
tests/compile-fail/stacked_borrows/pass_invalid_mut.rs
tests/compile-fail/stacked_borrows/pass_invalid_shr.rs
tests/compile-fail/stacked_borrows/return_invalid_mut.rs
tests/compile-fail/stacked_borrows/return_invalid_shr.rs
tests/compiletest.rs
tests/run-pass/refcell.rs

index e8691409b1ab4f0dd8c3cabf1395365d7eec7f68..a0ed7c8e4fdbd5d3c23da38864cf5080dd673696 100644 (file)
@@ -308,16 +308,18 @@ fn enforce_validity(ecx: &EvalContext<'a, 'mir, 'tcx, Self>) -> bool {
 
         // Some functions are whitelisted until we figure out how to fix them.
         // We walk up the stack a few frames to also cover their callees.
-        const WHITELIST: &[&str] = &[
+        const WHITELIST: &[(&str, &str)] = &[
             // Uses mem::uninitialized
-            "std::ptr::read",
-            "std::sys::windows::mutex::Mutex::",
+            ("std::ptr::read", ""),
+            ("std::sys::windows::mutex::Mutex::", ""),
+            // Should directly take a raw reference
+            ("<std::cell::UnsafeCell<T>>", "::get"),
         ];
         for frame in ecx.stack().iter()
             .rev().take(3)
         {
             let name = frame.instance.to_string();
-            if WHITELIST.iter().any(|white| name.starts_with(white)) {
+            if WHITELIST.iter().any(|(prefix, suffix)| name.starts_with(prefix) && name.ends_with(suffix)) {
                 return false;
             }
         }
@@ -453,7 +455,9 @@ fn tag_dereference(
         let (size, _) = ecx.size_and_align_of_mplace(place)?
             // for extern types, just cover what we can
             .unwrap_or_else(|| place.layout.size_and_align());
-        if !ecx.machine.validate || size == Size::ZERO {
+        if !ecx.tcx.sess.opts.debugging_opts.mir_emit_retag ||
+            !Self::enforce_validity(ecx) || size == Size::ZERO
+        {
             // No tracking
             Ok(place.ptr)
         } else {
index 56688ca10f49a85d99450063148b72b7f78daf41..ab536b5785c9869882503f39b42684cfa8fbc65b 100644 (file)
@@ -1,10 +1,10 @@
 use std::cell::RefCell;
 
 use rustc::ty::{self, layout::Size};
-use rustc::hir;
+use rustc::hir::{Mutability, MutMutable, MutImmutable};
 
 use crate::{
-    EvalResult, MiriEvalContext, HelpersEvalContextExt,
+    EvalResult, EvalErrorKind, MiriEvalContext, HelpersEvalContextExt,
     MemoryKind, MiriMemoryKind, RangeMap, AllocId, Allocation, AllocationExtra,
     Pointer, MemPlace, Scalar, Immediate, ImmTy, PlaceTy, MPlaceTy,
 };
@@ -27,7 +27,7 @@ pub enum Borrow {
 
 impl Borrow {
     #[inline(always)]
-    pub fn is_shr(self) -> bool {
+    pub fn is_shared(self) -> bool {
         match self {
             Borrow::Shr(_) => true,
             _ => false,
@@ -35,7 +35,7 @@ pub fn is_shr(self) -> bool {
     }
 
     #[inline(always)]
-    pub fn is_uniq(self) -> bool {
+    pub fn is_unique(self) -> bool {
         match self {
             Borrow::Uniq(_) => true,
             _ => false,
@@ -96,27 +96,17 @@ pub fn is_frozen(&self) -> bool {
     }
 }
 
-/// What kind of usage of the pointer are we talking about?
+/// What kind of reference is being used?
 #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
-pub enum UsageKind {
-    /// Write, or create &mut
-    Write,
-    /// Read, or create &
-    Read,
-    /// Create * (raw ptr)
+pub enum RefKind {
+    /// &mut
+    Unique,
+    /// & without interior mutability
+    Frozen,
+    /// * (raw pointer) or & to `UnsafeCell`
     Raw,
 }
 
-impl From<Option<hir::Mutability>> for UsageKind {
-    fn from(mutbl: Option<hir::Mutability>) -> Self {
-        match mutbl {
-            None => UsageKind::Raw,
-            Some(hir::MutMutable) => UsageKind::Write,
-            Some(hir::MutImmutable) => UsageKind::Read,
-        }
-    }
-}
-
 /// Extra global machine state
 #[derive(Clone, Debug)]
 pub struct State {
@@ -127,6 +117,12 @@ impl State {
     pub fn new() -> State {
         State { clock: 0 }
     }
+
+    fn increment_clock(&mut self) -> Timestamp {
+        let val = self.clock;
+        self.clock = val + 1;
+        val
+    }
 }
 
 /// Extra per-allocation state
@@ -136,52 +132,45 @@ pub struct Stacks {
     stacks: RefCell<RangeMap<Stack>>,
 }
 
-/// Core operations
+/// Core per-location operations: deref, access, create.
+/// We need to make at least the following things true:
+///
+/// U1: After creating a Uniq, it is at the top (+unfrozen).
+/// U2: If the top is Uniq (+unfrozen), accesses must be through that Uniq or pop it.
+/// U3: If an access (deref sufficient?) happens with a Uniq, it requires the Uniq to be in the stack.
+///
+/// F1: After creating a &, the parts outside `UnsafeCell` are frozen.
+/// F2: If a write access happens, it unfreezes.
+/// F3: If an access (well, a deref) happens with an & outside `UnsafeCell`, it requires the location to still be frozen.
 impl<'tcx> Stack {
-    /// Check if `bor` could be activated by unfreezing and popping.
-    /// `is_write` indicates whether this is being used to write (or, equivalently, to
-    /// borrow as &mut).
-    /// Returns `Err` if the answer is "no"; otherwise the return value indicates what to
-    /// do: With `Some(n)` you need to unfreeze, and then additionally pop `n` items.
-    fn reactivatable(&self, bor: Borrow, is_write: bool) -> Result<Option<usize>, String> {
-        // Check if we can match the frozen "item".  Not possible on writes!
-        if !is_write {
-            // For now, we do NOT check the timestamp.  That might be surprising, but
-            // we cannot even notice when a location should be frozen but is not!
-            // Those checks are both done in `tag_dereference`, where we have type information.
-            // Either way, it is crucial that the frozen "item" matches raw pointers:
-            // Reading through a raw should not unfreeze.
-            match (self.frozen_since, bor) {
-                (Some(_), Borrow::Shr(_)) => {
-                    return Ok(None)
+    /// Deref `bor`: Check if the location is frozen and the tag in the stack.
+    /// This dos *not* constitute an access!  "Deref" refers to the `*` operator
+    /// in Rust, and includs cases like `&*x` or `(*x).foo` where no or only part
+    /// of the memory actually gets accessed.  Also we cannot know if we are
+    /// going to read or write.
+    /// Returns the index of the item we matched, `None` if it was the frozen one.
+    /// `kind` indicates which kind of reference is being dereferenced.
+    fn deref(&self, bor: Borrow, kind: RefKind) -> Result<Option<usize>, String> {
+        // Checks related to freezing
+        match bor {
+            Borrow::Shr(Some(bor_t)) if kind == RefKind::Frozen => {
+                // We need the location to be frozen. This ensures F3.
+                let frozen = self.frozen_since.map_or(false, |itm_t| itm_t <= bor_t);
+                return if frozen { Ok(None) } else {
+                    Err(format!("Location is not frozen long enough"))
                 }
-                _ => {},
             }
+            Borrow::Shr(_) if self.frozen_since.is_some() => {
+                return Ok(None) // Shared deref to frozen location, looking good
+            }
+            _ => {} // Not sufficient, go on looking.
         }
-        // See if we can find this borrow.
-        for (idx, &itm) in self.borrows.iter().rev().enumerate() {
-            // Check borrow and stack item for compatibility.
+        // If we got here, we have to look for our item in the stack.
+        for (idx, &itm) in self.borrows.iter().enumerate().rev() {
             match (itm, bor) {
-                (BorStackItem::FnBarrier(_), _) => {
-                    return Err(format!("Trying to reactivate a borrow ({:?}) that lives \
-                                        behind a barrier", bor))
-                }
+                (BorStackItem::FnBarrier(_), _) => break,
                 (BorStackItem::Uniq(itm_t), Borrow::Uniq(bor_t)) if itm_t == bor_t => {
-                    // Found matching unique item.  This is *always* required to use a `Uniq`:
-                    // The item must still be on the stack.
-                    if !is_write {
-                        // As a special case, if we are reading, let us see if it would be
-                        // beneficial to pretend we are a raw pointer instead.  If
-                        // raw pointers are allowed to read while popping *less* than we
-                        // would have to pop, there is no reason not to let them do this.
-                        match self.reactivatable(Borrow::default(), is_write) {
-                            // If we got something better (popping less) that `idx`, use that
-                            Ok(None) => return Ok(None),
-                            Ok(Some(shr_idx)) if shr_idx <= idx => return Ok(Some(shr_idx)),
-                            // Otherwise just go on.
-                            _ => {},
-                        }
-                    }
+                    // Found matching unique item.  This satisfies U3.
                     return Ok(Some(idx))
                 }
                 (BorStackItem::Shr, Borrow::Shr(_)) => {
@@ -192,154 +181,182 @@ fn reactivatable(&self, bor: Borrow, is_write: bool) -> Result<Option<usize>, St
                 _ => {}
             }
         }
-        // Nothing to be found.
-        Err(format!("Borrow-to-reactivate {:?} does not exist on the stack", bor))
+        // If we got here, we did not find our item.  We have to error to satisfy U3.
+        Err(format!(
+            "Borrow being dereferenced ({:?}) does not exist on the stack, or is guarded by a barrier",
+            bor
+        ))
     }
 
-    /// Reactive `bor` for this stack.  `is_write` indicates whether this is being
-    /// used to write (or, equivalently, to borrow as &mut).
-    fn reactivate(&mut self, bor: Borrow, is_write: bool) -> EvalResult<'tcx> {
-        let mut pop = match self.reactivatable(bor, is_write) {
-            Ok(None) => return Ok(()),
-            Ok(Some(pop)) => pop,
-            Err(err) => return err!(MachineError(err)),
-        };
-        // Pop what `reactivatable` told us to pop. Always unfreeze.
+    /// Perform an actual memory access using `bor`.  We do not know any types here
+    /// or whether things should be frozen, but we *do* know if this is reading
+    /// or writing.
+    fn access(&mut self, bor: Borrow, is_write: bool) -> EvalResult<'tcx> {
+        // Check if we can match the frozen "item".
+        // Not possible on writes!
         if self.is_frozen() {
-            trace!("reactivate: Unfreezing");
+            if !is_write {
+                // When we are frozen, we just accept all reads.  No harm in this.
+                // The deref already checked that `Uniq` items are in the stack, and that
+                // the location is frozen if it should be.
+                return Ok(());
+            }
+            trace!("access: Unfreezing");
         }
+        // Unfreeze on writes.  This ensures F2.
         self.frozen_since = None;
-        while pop > 0 {
-            let itm = self.borrows.pop().unwrap();
-            trace!("reactivate: Popping {:?}", itm);
-            pop -= 1;
+        // Pop the stack until we have something matching.
+        while let Some(&itm) = self.borrows.last() {
+            match (itm, bor) {
+                (BorStackItem::FnBarrier(_), _) => break,
+                (BorStackItem::Uniq(itm_t), Borrow::Uniq(bor_t)) if itm_t == bor_t => {
+                    // Found matching unique item.
+                    return Ok(())
+                }
+                (BorStackItem::Shr, _) if !is_write => {
+                    // When reading, everything can use a shared item!
+                    // We do not want to do this when writing: Writing to an `&mut`
+                    // should reaffirm its exclusivity (i.e., make sure it is
+                    // on top of the stack).
+                    return Ok(())
+                }
+                (BorStackItem::Shr, Borrow::Shr(_)) => {
+                    // Found matching shared item.
+                    return Ok(())
+                }
+                _ => {
+                    // Pop this.  This ensures U2.
+                    let itm = self.borrows.pop().unwrap();
+                    trace!("access: Popping {:?}", itm);
+                }
+            }
         }
-        Ok(())
+        // If we got here, we did not find our item.
+        err!(MachineError(format!(
+            "Borrow being accessed ({:?}) does not exist on the stack, or is guarded by a barrier",
+            bor
+        )))
     }
 
     /// Initiate `bor`; mostly this means pushing.
     /// This operation cannot fail; it is up to the caller to ensure that the precondition
     /// is met: We cannot push `Uniq` onto frozen stacks.
-    /// Crucially, this makes pushing a `Shr` onto a frozen location a NOP.  We do not want
-    /// such a location to get mutably shared this way!
-    fn initiate(&mut self, bor: Borrow) {
-        if let Some(_) = self.frozen_since {
+    /// `kind` indicates which kind of reference is being created.
+    fn create(&mut self, bor: Borrow, kind: RefKind) {
+        // First, push the item.  We do this even if we will later freeze, because we
+        // will allow mutation of shared data at the expense of unfreezing.
+        if let Some(itm_t) = self.frozen_since {
             // A frozen location, we won't change anything here!
             match bor {
                 Borrow::Uniq(_) => bug!("Trying to create unique ref to frozen location"),
-                Borrow::Shr(_) => trace!("initiate: New shared ref to frozen location is a NOP"),
+                Borrow::Shr(Some(bor_t)) if kind == RefKind::Frozen => {
+                    // Make sure we are frozen long enough.  This is part 1 of ensuring F1.
+                    assert!(itm_t <= bor_t, "Trying to freeze shorter than it was frozen?");
+                    trace!("create: Freezing a frozen location is a NOP");
+                }
+                Borrow::Shr(_) => trace!("create: Sharing a frozen location is a NOP"),
             }
         } else {
-            // Just push.
+            // First push.
             let itm = match bor {
                 Borrow::Uniq(t) => BorStackItem::Uniq(t),
-                Borrow::Shr(_) if *self.borrows.last().unwrap() == BorStackItem::Shr => {
-                    // Optimization: Don't push a Shr onto a Shr.
-                    trace!("initiate: New shared ref to already shared location is a NOP");
-                    return
-                },
                 Borrow::Shr(_) => BorStackItem::Shr,
             };
-            trace!("initiate: Pushing {:?}", itm);
-            self.borrows.push(itm)
-        }
-    }
-
-    /// Check if this location is "frozen enough".
-    fn check_frozen(&self, bor_t: Timestamp) -> EvalResult<'tcx> {
-        let frozen = self.frozen_since.map_or(false, |itm_t| itm_t <= bor_t);
-        if !frozen {
-            err!(MachineError(format!("Location is not frozen long enough")))
-        } else {
-            Ok(())
-        }
-    }
-
-    /// Freeze this location, since `bor_t`.
-    fn freeze(&mut self, bor_t: Timestamp) {
-        if let Some(itm_t) = self.frozen_since {
-            assert!(itm_t <= bor_t, "Trying to freeze shorter than it was frozen?");
-        } else {
-            trace!("Freezing");
-            self.frozen_since = Some(bor_t);
+            if *self.borrows.last().unwrap() == itm {
+                assert!(bor.is_shared());
+                trace!("create: Sharing a shared location is a NOP");
+            } else {
+                // This ensures U1.
+                trace!("create: Pushing {:?}", itm);
+                self.borrows.push(itm);
+            }
+            // Now, maybe freeze.  This is part 2 of ensuring F1.
+            if kind == RefKind::Frozen {
+                let bor_t = match bor {
+                    Borrow::Shr(Some(t)) => t,
+                    _ => bug!("Creating illegal borrow {:?} for frozen ref", bor),
+                };
+                trace!("create: Freezing");
+                self.frozen_since = Some(bor_t);
+            }
         }
     }
 }
 
-impl State {
-    fn increment_clock(&mut self) -> Timestamp {
-        let val = self.clock;
-        self.clock = val + 1;
-        val
-    }
-}
-
-/// Higher-level operations
+/// Higher-level per-location operations: deref, access, reborrow.
 impl<'tcx> Stacks {
-    /// `ptr` got used, reflect that in the stack.
-    fn reactivate(
+    /// Check that this stack is fine with being dereferenced
+    fn deref(
         &self,
         ptr: Pointer<Borrow>,
         size: Size,
-        usage: UsageKind,
+        kind: RefKind,
     ) -> EvalResult<'tcx> {
-        trace!("use_borrow of tag {:?} as {:?}: {:?}, size {}",
-            ptr.tag, usage, ptr, size.bytes());
+        trace!("deref for tag {:?} as {:?}: {:?}, size {}",
+            ptr.tag, kind, ptr, size.bytes());
         let mut stacks = self.stacks.borrow_mut();
+        // We need `iter_mut` because `iter` would skip gaps!
         for stack in stacks.iter_mut(ptr.offset, size) {
-            stack.reactivate(ptr.tag, usage == UsageKind::Write)?;
+            stack.deref(ptr.tag, kind).map_err(EvalErrorKind::MachineError)?;
         }
         Ok(())
     }
 
-    /// Create a new borrow, the ptr must already have the new tag.
-    /// Also freezes the location if `freeze` is set and the tag is a timestamped `Shr`.
-    fn initiate(
+    /// `ptr` got used, reflect that in the stack.
+    fn access(
         &self,
         ptr: Pointer<Borrow>,
         size: Size,
-        freeze: bool,
-    ) {
-        trace!("reborrow for tag {:?}: {:?}, size {}",
+        is_write: bool,
+    ) -> EvalResult<'tcx> {
+        trace!("{} access of tag {:?}: {:?}, size {}",
+            if is_write { "read" } else { "write" },
             ptr.tag, ptr, size.bytes());
         let mut stacks = self.stacks.borrow_mut();
         for stack in stacks.iter_mut(ptr.offset, size) {
-            stack.initiate(ptr.tag);
-            if freeze {
-                if let Borrow::Shr(Some(bor_t)) = ptr.tag {
-                    stack.freeze(bor_t);
-                }
-            }
+            stack.access(ptr.tag, is_write)?;
         }
+        Ok(())
     }
 
-    /// Check that this stack is fine with being dereferenced
-    fn check_deref(
+    /// Reborrow the given pointer to the new tag for the given kind of reference.
+    fn reborrow(
         &self,
         ptr: Pointer<Borrow>,
         size: Size,
-        frozen: bool,
+        new_bor: Borrow,
+        new_kind: RefKind,
     ) -> EvalResult<'tcx> {
+        trace!("reborrow for tag {:?} to {:?} as {:?}: {:?}, size {}",
+            ptr.tag, new_bor, new_kind, ptr, size.bytes());
         let mut stacks = self.stacks.borrow_mut();
-        // We need `iter_mut` because `iter` would skip gaps!
         for stack in stacks.iter_mut(ptr.offset, size) {
-            // Conservatively assume we will just read
-            if let Err(err) = stack.reactivatable(ptr.tag, /*is_write*/false) {
-                return err!(MachineError(format!(
-                    "Encountered reference with non-reactivatable tag: {}",
-                    err
-                )))
-            }
-            // Sometimes we also need to be frozen.
-            // In this case we *both* push `Shr` and then freeze.  This means that a `&mut`
-            // to `*const` to `*mut` cast through `&` actually works.
-            if frozen {
-                // Even shared refs can have uniq tags (after transmute).  That's not an error
-                // but they do not get any freezing benefits.
-                if let Borrow::Shr(Some(bor_t)) = ptr.tag {
-                    stack.check_frozen(bor_t)?;
+            // Access source `ptr`, create new ref.
+            let ptr_idx = stack.deref(ptr.tag, new_kind).map_err(EvalErrorKind::MachineError)?;
+            if new_kind == RefKind::Raw {
+                assert!(new_bor.is_shared());
+                // Raw references do not get quite as many guarantees as the other kinds:
+                // If we can deref the new tag already, and if that tag lives higher on
+                // the stack than the one we come from, just use that.
+                // IOW, we check if `new_bor` *already* is "derived from" `ptr.tag`.
+                match (ptr_idx, stack.deref(new_bor, new_kind)) {
+                    // If the new borrow works with the forzen item, or else if it lives
+                    // above the old one in the stack, our job here is done.
+                    (_, Ok(None)) => {
+                        trace!("reborrow-to-raw on a frozen location is a NOP");
+                        continue
+                    },
+                    (Some(ptr_idx), Ok(Some(new_idx))) if new_idx >= ptr_idx => {
+                        trace!("reborrow-to-raw is a NOP because the src ptr already got reborrowed-to-raw");
+                        continue
+                    },
+                    _ => {},
                 }
             }
+            // Non-raw reborrows should behave exactly as if we also did a
+            // read/write to the given location.
+            stack.access(ptr.tag, new_kind == RefKind::Unique)?;
+            stack.create(new_bor, new_kind);
         }
         Ok(())
     }
@@ -353,8 +370,7 @@ fn memory_read<'tcx>(
         ptr: Pointer<Borrow>,
         size: Size,
     ) -> EvalResult<'tcx> {
-        // Reads behave exactly like the first half of a reborrow-to-shr
-        alloc.extra.reactivate(ptr, size, UsageKind::Read)
+        alloc.extra.access(ptr, size, /*is_write*/false)
     }
 
     #[inline(always)]
@@ -363,8 +379,7 @@ fn memory_written<'tcx>(
         ptr: Pointer<Borrow>,
         size: Size,
     ) -> EvalResult<'tcx> {
-        // Writes behave exactly like the first half of a reborrow-to-mut
-        alloc.extra.reactivate(ptr, size, UsageKind::Read)
+        alloc.extra.access(ptr, size, /*is_write*/true)
     }
 
     #[inline(always)]
@@ -374,7 +389,7 @@ fn memory_deallocated<'tcx>(
         size: Size,
     ) -> EvalResult<'tcx> {
         // This is like mutating
-        alloc.extra.reactivate(ptr, size, UsageKind::Write)
+        alloc.extra.access(ptr, size, /*is_write*/true)
         // FIXME: Error out of there are any barriers?
     }
 }
@@ -402,7 +417,7 @@ fn tag_dereference(
         &self,
         place: MPlaceTy<'tcx, Borrow>,
         size: Size,
-        usage: UsageKind,
+        mutability: Option<Mutability>,
     ) -> EvalResult<'tcx, Borrow>;
 
     fn tag_new_allocation(
@@ -412,10 +427,10 @@ fn tag_new_allocation(
     ) -> Borrow;
 
     /// Retag an indidual pointer, returning the retagged version.
-    fn retag_ptr(
+    fn reborrow(
         &mut self,
         ptr: ImmTy<'tcx, Borrow>,
-        mutbl: hir::Mutability,
+        mutbl: Mutability,
     ) -> EvalResult<'tcx, Immediate<Borrow>>;
 
     fn retag(
@@ -461,7 +476,7 @@ fn tag_new_allocation(
         Borrow::Uniq(time)
     }
 
-    /// Called for value-to-place conversion.
+    /// Called for value-to-place conversion.  `mutability` is `None` for raw pointers.
     ///
     /// Note that this does NOT mean that all this memory will actually get accessed/referenced!
     /// We could be in the middle of `&(*var).1`.
@@ -469,38 +484,41 @@ fn tag_dereference(
         &self,
         place: MPlaceTy<'tcx, Borrow>,
         size: Size,
-        usage: UsageKind,
+        mutability: Option<Mutability>,
     ) -> EvalResult<'tcx, Borrow> {
-        trace!("tag_dereference: Accessing reference ({:?}) for {:?} (pointee {})",
-            usage, place.ptr, place.layout.ty);
+        trace!("tag_dereference: Accessing {} reference for {:?} (pointee {})",
+            if let Some(mutability) = mutability { format!("{:?}", mutability) } else { format!("raw") },
+            place.ptr, place.layout.ty);
         let ptr = place.ptr.to_ptr()?;
         // In principle we should not have to do anything here.  However, with transmutes involved,
-        // it can happen that the tag of `ptr` does not actually match `usage`, and we
+        // it can happen that the tag of `ptr` does not actually match `mutability`, and we
         // should adjust for that.
         // Notably, the compiler can introduce such transmutes by optimizing away `&[mut]*`.
         // That can transmute a raw ptr to a (shared/mut) ref, and a mut ref to a shared one.
-        match (usage, ptr.tag) {
-            (UsageKind::Raw, _) => {
+        match (mutability, ptr.tag) {
+            (None, _) => {
                 // Don't use the tag, this is a raw access!  They should happen tagless.
+                // This is needed for `*mut` to make any sense: Writes *do* enforce the
+                // `Uniq` tag to be up top, but we must make sure raw writes do not do that.
                 // This does mean, however, that `&*foo` is *not* a NOP *if* `foo` is a raw ptr.
                 // Also don't do any further validation, this is raw after all.
                 return Ok(Borrow::default());
             }
-            (UsageKind::Write, Borrow::Uniq(_)) |
-            (UsageKind::Read, Borrow::Shr(_)) => {
+            (Some(MutMutable), Borrow::Uniq(_)) |
+            (Some(MutImmutable), Borrow::Shr(_)) => {
                 // Expected combinations.  Nothing to do.
             }
-            (UsageKind::Write, Borrow::Shr(None)) => {
+            (Some(MutMutable), Borrow::Shr(None)) => {
                 // Raw transmuted to mut ref.  Keep this as raw access.
                 // We cannot reborrow here; there might be a raw in `&(*var).1` where
                 // `var` is an `&mut`.  The other field of the struct might be already frozen,
                 // also using `var`, and that would be okay.
             }
-            (UsageKind::Read, Borrow::Uniq(_)) => {
+            (Some(MutImmutable), Borrow::Uniq(_)) => {
                 // A mut got transmuted to shr.  Can happen even from compiler transformations:
                 // `&*x` gets optimized to `x` even when `x` is a `&mut`.
             }
-            (UsageKind::Write, Borrow::Shr(Some(_))) => {
+            (Some(MutMutable), Borrow::Shr(Some(_))) => {
                 // This is just invalid: A shr got transmuted to a mut.
                 // If we ever allow this, we have to consider what we do when a turn a
                 // `Raw`-tagged `&mut` into a raw pointer pointing to a frozen location.
@@ -515,13 +533,16 @@ fn tag_dereference(
         let alloc = self.memory().get(ptr.alloc_id).expect("We checked that the ptr is fine!");
         // If we got here, we do some checking, *but* we leave the tag unchanged.
         if let Borrow::Shr(Some(_)) = ptr.tag {
+            assert_eq!(mutability, Some(MutImmutable));
             // We need a frozen-sensitive check
             self.visit_freeze_sensitive(place, size, |cur_ptr, size, frozen| {
-                alloc.extra.check_deref(cur_ptr, size, frozen)
+                let kind = if frozen { RefKind::Frozen } else { RefKind::Raw };
+                alloc.extra.deref(cur_ptr, size, kind)
             })?;
         } else {
             // Just treat this as one big chunk
-            alloc.extra.check_deref(ptr, size, /*frozen*/false)?;
+            let kind = if mutability == Some(MutMutable) { RefKind::Unique } else { RefKind::Raw };
+            alloc.extra.deref(ptr, size, kind)?;
         }
 
         // All is good, and do not change the tag
@@ -534,23 +555,20 @@ fn escape_to_raw(
         place: MPlaceTy<'tcx, Borrow>,
         size: Size,
     ) -> EvalResult<'tcx> {
-        trace!("self: {:?} is now accessible by raw pointers", *place);
+        trace!("escape_to_raw: {:?} is now accessible by raw pointers", *place);
         // Get the allocation
-        let mut ptr = place.ptr.to_ptr()?;
+        let ptr = place.ptr.to_ptr()?;
         self.memory().check_bounds(ptr, size, false)?; // `ptr_dereference` wouldn't do any checks if this is a raw ptr
         let alloc = self.memory().get(ptr.alloc_id).expect("We checked that the ptr is fine!");
         // Re-borrow to raw.  This is a NOP for shared borrows, but we do not know the borrow
         // type here and that's also okay.  Freezing does not matter here.
-        alloc.extra.reactivate(ptr, size, UsageKind::Raw)?;
-        ptr.tag = Borrow::default();
-        alloc.extra.initiate(ptr, size, /*freeze*/false);
-        Ok(())
+        alloc.extra.reborrow(ptr, size, Borrow::default(), RefKind::Raw)
     }
 
-    fn retag_ptr(
+    fn reborrow(
         &mut self,
         val: ImmTy<'tcx, Borrow>,
-        mutbl: hir::Mutability,
+        mutbl: Mutability,
     ) -> EvalResult<'tcx, Immediate<Borrow>> {
         // We want a place for where the ptr *points to*, so we get one.
         let place = self.ref_to_mplace(val)?;
@@ -566,30 +584,29 @@ fn retag_ptr(
         let ptr = place.ptr.to_ptr()?;
         let time = self.machine.stacked_borrows.increment_clock();
         let new_bor = match mutbl {
-            hir::MutMutable => Borrow::Uniq(time),
-            hir::MutImmutable => Borrow::Shr(Some(time)),
+            MutMutable => Borrow::Uniq(time),
+            MutImmutable => Borrow::Shr(Some(time)),
         };
-        let new_ptr = Pointer::new_with_tag(ptr.alloc_id, ptr.offset, new_bor);
-        trace!("retag: Creating new reference ({:?}) for {:?} (pointee {}): {:?}",
+        trace!("reborrow: Creating new {:?} reference for {:?} (pointee {}): {:?}",
             mutbl, ptr, place.layout.ty, new_bor);
 
-        // Get the allocation
-        self.memory().check_bounds(ptr, size, false)?; // `ptr_dereference` wouldn't do any checks if this is a raw ptr
+        // Get the allocation.  It might not be mutable, so we cannot use `get_mut`.
+        self.memory().check_bounds(ptr, size, false)?;
         let alloc = self.memory().get(ptr.alloc_id).expect("We checked that the ptr is fine!");
-        // Update the stacks.  First use old borrow, then initiate new one.
-        alloc.extra.reactivate(ptr, size, Some(mutbl).into())?;
-        if mutbl == hir::MutImmutable {
-            // We need a frozen-sensitive initiate
-            self.visit_freeze_sensitive(place, size, |mut cur_ptr, size, frozen| {
-                cur_ptr.tag = new_bor;
-                Ok(alloc.extra.initiate(cur_ptr, size, frozen))
+        // Update the stacks.
+        if mutbl == MutImmutable {
+            // Shared reference. We need a frozen-sensitive reborrow.
+            self.visit_freeze_sensitive(place, size, |cur_ptr, size, frozen| {
+                let kind = if frozen { RefKind::Frozen } else { RefKind::Raw };
+                alloc.extra.reborrow(cur_ptr, size, new_bor, kind)
             })?;
         } else {
-            // Just treat this as one big chunk
-            alloc.extra.initiate(new_ptr, size, /*frozen*/false);
+            // Mutable reference. Just treat this as one big chunk.
+            alloc.extra.reborrow(ptr, size, new_bor, RefKind::Unique)?;
         }
 
         // Return new ptr
+        let new_ptr = Pointer::new_with_tag(ptr.alloc_id, ptr.offset, new_bor);
         let new_place = MemPlace { ptr: Scalar::Ptr(new_ptr), ..*place };
         Ok(new_place.to_ref())
     }
@@ -608,7 +625,7 @@ fn retag(
         };
         // Retag the pointer and write it back.
         let val = self.read_immediate(self.place_to_op(place)?)?;
-        let val = self.retag_ptr(val, mutbl)?;
+        let val = self.reborrow(val, mutbl)?;
         self.write_immediate(val, place)?;
         Ok(())
     }
index 0b2d459366ceb56857e7d696d41ef046521a01b5..092f3f09ed196dff9f93b2675fb67221d05f744e 100644 (file)
@@ -11,5 +11,5 @@ fn main() {
     retarget(&mut target_alias, target);
     // now `target_alias` points to the same thing as `target`
     *target = 13;
-    let _val = *target_alias; //~ ERROR reference with non-reactivatable tag
+    let _val = *target_alias; //~ ERROR does not exist on the stack
 }
diff --git a/tests/compile-fail/stacked_borrows/illegal_read5.rs b/tests/compile-fail/stacked_borrows/illegal_read5.rs
new file mode 100644 (file)
index 0000000..863649a
--- /dev/null
@@ -0,0 +1,16 @@
+// We *can* have aliasing &RefCell<T> and &mut T, but we cannot read through the former.
+// Else we couldn't optimize based on the assumption that `xref` below is truly unique.
+
+use std::cell::RefCell;
+use std::{mem, ptr};
+
+fn main() {
+    let rc = RefCell::new(0);
+    let mut refmut = rc.borrow_mut();
+    let xref: &mut i32 = &mut *refmut;
+    let xshr = &rc; // creating this is okay
+    let _val = *xref; // we can even still use our mutable reference
+    mem::forget(unsafe { ptr::read(xshr) }); // but after reading through the shared ref
+    let _val = *xref; // the mutable one is dead and gone
+    //~^ ERROR does not exist on the stack
+}
index 7378907fa7456165b4c07ecee7534b32680d1dc5..b106cc8dc403cc7d8e0387263950c1c0ee538c45 100644 (file)
@@ -8,5 +8,5 @@ fn main() {
     let target = Box::new(42); // has an implicit raw
     let ref_ = &*target;
     evil(ref_); // invalidates shared ref, activates raw
-    let _x = *ref_; //~ ERROR is not frozen long enough
+    let _x = *ref_; //~ ERROR is not frozen
 }
index c82da1e9c46771459b8dd370375e23ca601d2921..01559af21e7c64fbcea94f6923d9bba9d8cd8770 100644 (file)
@@ -4,5 +4,5 @@ fn main() {
     let r#ref = &target; // freeze
     let ptr = r#ref as *const _ as *mut _; // raw ptr, with raw tag
     unsafe { *ptr = 42; }
-    let _val = *r#ref; //~ ERROR is not frozen long enough
+    let _val = *r#ref; //~ ERROR is not frozen
 }
index 49bf9279faa2e0f8e6d97d819304d03db0b9056a..37ae0f055f0ee52ca6f4951d1e4ab226f3b775bb 100644 (file)
@@ -9,5 +9,5 @@ fn main() {
     let ptr = reference as *const _ as *mut i32; // raw ptr, with raw tag
     let _mut_ref: &mut i32 = unsafe { mem::transmute(ptr) }; // &mut, with raw tag
     // Now we retag, making our ref top-of-stack -- and, in particular, unfreezing.
-    let _val = *reference; //~ ERROR is not frozen long enough
+    let _val = *reference; //~ ERROR is not frozen
 }
index f4704ad57161f37529ca04bada9247c412c6808f..57b2ca87d810236ad8c75fb77bd79dd4ef63af90 100644 (file)
@@ -7,7 +7,7 @@ fn main() {
     let xref = unsafe { &mut *xraw }; // derived from raw, so using raw is still okay...
     callee(xraw);
     let _val = *xref; // ...but any use of raw will invalidate our ref.
-    //~^ ERROR: reference with non-reactivatable tag
+    //~^ ERROR: does not exist on the stack
 }
 
 fn callee(xraw: *mut i32) {
index d3db462343e846e02ec5cc774e48c5a7c311678f..98b9451eda87e77a2217ad9d043f9f7cb13ef5ee 100644 (file)
@@ -4,6 +4,6 @@ fn main() {
     let xraw = x as *mut _;
     let xref = unsafe { &mut *xraw };
     let xref_in_mem = Box::new(xref);
-    let _val = *x; // invalidate xraw
+    let _val = unsafe { *xraw }; // invalidate xref
     let _val = *xref_in_mem; //~ ERROR does not exist on the stack
 }
index 71b578817a7748cef67560d41d280fd85a0c780f..6599924f0f4c49e92c9fa0e36dbfbbdda8cb1179 100644 (file)
@@ -4,6 +4,6 @@ fn main() {
     let xraw = x as *mut _;
     let xref = unsafe { &*xraw };
     let xref_in_mem = Box::new(xref);
-    *x = 42; // invalidate xraw
-    let _val = *xref_in_mem; //~ ERROR does not exist on the stack
+    unsafe { *xraw = 42 }; // unfreeze
+    let _val = *xref_in_mem; //~ ERROR is not frozen
 }
index 41cf89d874d71d96ebc98f6c0b698748ec823a7f..28288c6c63623154d91d8915f6900c665ea86325 100644 (file)
@@ -5,6 +5,6 @@ fn main() {
     let x = &mut 42;
     let xraw = x as *mut _;
     let xref = unsafe { &mut *xraw };
-    let _val = *x; // invalidate xraw
+    let _val = unsafe { *xraw }; // invalidate xref
     foo(xref); //~ ERROR does not exist on the stack
 }
index 0bdb1b4a41e591836cd3b743c8e21967697f9dee..67bbc88e40fb0dc8a423a8d366618818d0b55831 100644 (file)
@@ -3,8 +3,8 @@ fn foo(_: &i32) {}
 
 fn main() {
     let x = &mut 42;
-    let xraw = &*x as *const _;
+    let xraw = &*x as *const _ as *mut _;
     let xref = unsafe { &*xraw };
-    *x = 42; // invalidate xraw
-    foo(xref); //~ ERROR does not exist on the stack
+    unsafe { *xraw = 42 }; // unfreeze
+    foo(xref); //~ ERROR is not frozen
 }
index aef8fafdf5d5991f0405ef6e6cce1d1eda7c52c1..e7f0b9bc9ddd0e127715a1e03373fca2b0b430f9 100644 (file)
@@ -2,7 +2,7 @@
 fn foo(x: &mut (i32, i32)) -> &mut i32 {
     let xraw = x as *mut (i32, i32);
     let ret = unsafe { &mut (*xraw).1 };
-    let _val = *x; // invalidate xraw and its children
+    let _val = unsafe { *xraw }; // invalidate xref
     ret //~ ERROR does not exist on the stack
 }
 
index 074942eb95bcaf787047555dc879372922e80505..986dd18b2e0b4e1c1467c1bf68e1f9c69d4b2574 100644 (file)
@@ -2,8 +2,8 @@
 fn foo(x: &mut (i32, i32)) -> &i32 {
     let xraw = x as *mut (i32, i32);
     let ret = unsafe { &(*xraw).1 };
-    x.1 = 42; // invalidate xraw on the 2nd field
-    ret //~ ERROR does not exist on the stack
+    unsafe { *xraw = (42, 23) }; // unfreeze
+    ret //~ ERROR is not frozen
 }
 
 fn main() {
index 55eaa7bfbab6885ca6395ab420ba75ba294aec3f..f393fcb2c226fd2daeac2212419f8ce1b50f395c 100644 (file)
@@ -99,7 +99,9 @@ fn miri_pass(sysroot: &Path, path: &str, target: &str, host: &str, need_fullmir:
     flags.push(format!("--sysroot {}", sysroot.display()));
     flags.push("-Dwarnings -Dunused".to_owned()); // overwrite the -Aunused in compiletest-rs
     if opt {
-        flags.push("-Zmir-opt-level=3".to_owned());
+        // FIXME: We use opt level 1 because MIR inlining defeats the validation
+        // whitelist.
+        flags.push("-Zmir-opt-level=1".to_owned());
     }
 
     let mut config = mk_config("ui");
index 52dcdbd36d0fb2d012039d644f7d108f4e1b368e..5f2f3523b96b1e916fb8c5bc79b5d60b1b03bd90 100644 (file)
@@ -32,6 +32,23 @@ fn lots_of_funny_borrows() {
     }
 }
 
+fn aliasing_mut_and_shr() {
+    fn inner(rc: &RefCell<i32>, aliasing: &mut i32) {
+        *aliasing += 4;
+        let _escape_to_raw = rc as *const _;
+        *aliasing += 4;
+        let _shr = &*rc;
+        *aliasing += 4;
+    }
+
+    let rc = RefCell::new(23);
+    let mut bmut = rc.borrow_mut();
+    inner(&rc, &mut *bmut);
+    drop(bmut);
+    assert_eq!(*rc.borrow(), 23+12);
+}
+
 fn main() {
     lots_of_funny_borrows();
+    aliasing_mut_and_shr();
 }