diff --git a/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/borrow_tracker/tree_borrows/diagnostics.rs index b2fd9b2bf0..d430e9e679 100644 --- a/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -504,7 +504,7 @@ impl DisplayFmt { if let Some(perm) = perm { format!( "{ac}{st}", - ac = if perm.is_initialized() { self.accessed.yes } else { self.accessed.no }, + ac = if perm.is_accessed() { self.accessed.yes } else { self.accessed.no }, st = perm.permission().short_name(), ) } else { diff --git a/src/borrow_tracker/tree_borrows/mod.rs b/src/borrow_tracker/tree_borrows/mod.rs index f3e32e75f2..411ae89da9 100644 --- a/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/borrow_tracker/tree_borrows/mod.rs @@ -3,6 +3,8 @@ use rustc_middle::mir::{Mutability, RetagKind}; use rustc_middle::ty::layout::HasTypingEnv; use rustc_middle::ty::{self, Ty}; +use self::foreign_access_skipping::IdempotentForeignAccess; +use self::tree::LocationState; use crate::borrow_tracker::{GlobalState, GlobalStateInner, ProtectorKind}; use crate::concurrency::data_race::NaReadType; use crate::*; @@ -95,7 +97,7 @@ impl<'tcx> Tree { /// A tag just lost its protector. /// /// This emits a special kind of access that is only applied - /// to initialized locations, as a protection against other + /// to accessed locations, as a protection against other /// tags not having been made aware of the existence of this /// protector. pub fn release_protector( @@ -113,16 +115,19 @@ impl<'tcx> Tree { /// Policy for a new borrow. #[derive(Debug, Clone, Copy)] -struct NewPermission { - /// Which permission should the pointer start with. - initial_state: Permission, +pub struct NewPermission { + /// Permission for the frozen part of the range. + freeze_perm: Permission, + /// Whether a read access should be performed on the frozen part on a retag. + freeze_access: bool, + /// Permission for the non-frozen part of the range. + nonfreeze_perm: Permission, + /// Whether a read access should be performed on the non-frozen + /// part on a retag. + nonfreeze_access: bool, /// Whether this pointer is part of the arguments of a function call. /// `protector` is `Some(_)` for all pointers marked `noalias`. protector: Option, - /// Whether a read should be performed on a retag. This should be `false` - /// for `Cell` because this could cause data races when using thread-safe - /// data types like `Mutex`. - initial_read: bool, } impl<'tcx> NewPermission { @@ -133,27 +138,42 @@ impl<'tcx> NewPermission { kind: RetagKind, cx: &crate::MiriInterpCx<'tcx>, ) -> Option { - let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env()); let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env()); let is_protected = kind == RetagKind::FnEntry; - // As demonstrated by `tests/fail/tree_borrows/reservedim_spurious_write.rs`, - // interior mutability and protectors interact poorly. - // To eliminate the case of Protected Reserved IM we override interior mutability - // in the case of a protected reference: protected references are always considered - // "freeze" in their reservation phase. - let (initial_state, initial_read) = match mutability { + let protector = is_protected.then_some(ProtectorKind::StrongProtector); + + Some(match mutability { Mutability::Mut if ty_is_unpin => - (Permission::new_reserved(ty_is_freeze, is_protected), true), - Mutability::Not if ty_is_freeze => (Permission::new_frozen(), true), - Mutability::Not if !ty_is_freeze => (Permission::new_cell(), false), - // Raw pointers never enter this function so they are not handled. - // However raw pointers are not the only pointers that take the parent - // tag, this also happens for `!Unpin` `&mut`s, which are excluded above. + NewPermission { + freeze_perm: Permission::new_reserved( + /* ty_is_freeze */ true, + is_protected, + ), + freeze_access: true, + nonfreeze_perm: Permission::new_reserved( + /* ty_is_freeze */ false, + is_protected, + ), + // If we have a mutable reference, then the non-frozen part will + // have state `ReservedIM` or `Reserved`, which can have an initial read access + // performed on it because you cannot have multiple mutable borrows. + nonfreeze_access: true, + protector, + }, + Mutability::Not => + NewPermission { + freeze_perm: Permission::new_frozen(), + freeze_access: true, + nonfreeze_perm: Permission::new_cell(), + // If it is a shared reference, then the non-frozen + // part will have state `Cell`, which should not have an initial access, + // as this can cause data races when using thread-safe data types like + // `Mutex`. + nonfreeze_access: false, + protector, + }, _ => return None, - }; - - let protector = is_protected.then_some(ProtectorKind::StrongProtector); - Some(Self { initial_state, protector, initial_read }) + }) } /// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled). @@ -168,13 +188,17 @@ impl<'tcx> NewPermission { pointee.is_unpin(*cx.tcx, cx.typing_env()).then_some(()).map(|()| { // Regular `Unpin` box, give it `noalias` but only a weak protector // because it is valid to deallocate it within the function. - let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env()); - let protected = kind == RetagKind::FnEntry; - let initial_state = Permission::new_reserved(ty_is_freeze, protected); - Self { - initial_state, - protector: protected.then_some(ProtectorKind::WeakProtector), - initial_read: true, + let is_protected = kind == RetagKind::FnEntry; + let protector = is_protected.then_some(ProtectorKind::WeakProtector); + NewPermission { + freeze_perm: Permission::new_reserved(/* ty_is_freeze */ true, is_protected), + freeze_access: true, + nonfreeze_perm: Permission::new_reserved( + /* ty_is_freeze */ false, + is_protected, + ), + nonfreeze_access: true, + protector, } }) } @@ -194,8 +218,6 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { new_tag: BorTag, ) -> InterpResult<'tcx, Option> { let this = self.eval_context_mut(); - // Make sure the new permission makes sense as the initial permission of a fresh tag. - assert!(new_perm.initial_state.is_initial()); // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050). this.check_ptr_access(place.ptr(), ptr_size, CheckInAllocMsg::Dereferenceable)?; @@ -206,7 +228,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let global = this.machine.borrow_tracker.as_ref().unwrap().borrow(); let ty = place.layout.ty; if global.tracked_pointer_tags.contains(&new_tag) { - let kind_str = format!("initial state {} (pointee type {ty})", new_perm.initial_state); + let ty_is_freeze = ty.is_freeze(*this.tcx, this.typing_env()); + let kind_str = + if ty_is_freeze { + format!("initial state {} (pointee type {ty})", new_perm.freeze_perm) + } else { + format!("initial state {}/{} outside/inside UnsafeCell (pointee type {ty})", new_perm.freeze_perm, new_perm.nonfreeze_perm) + }; this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag( new_tag.inner(), Some(kind_str), @@ -285,43 +313,103 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let span = this.machine.current_span(); let alloc_extra = this.get_alloc_extra(alloc_id)?; - let range = alloc_range(base_offset, ptr_size); let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut(); - // All reborrows incur a (possibly zero-sized) read access to the parent - if new_perm.initial_read { - tree_borrows.perform_access( - orig_tag, - Some((range, AccessKind::Read, diagnostics::AccessCause::Reborrow)), - this.machine.borrow_tracker.as_ref().unwrap(), - alloc_id, - this.machine.current_span(), - )?; - } + // Store initial permissions and their corresponding range. + let mut perms_map: RangeMap = RangeMap::new( + ptr_size, + LocationState::new_accessed(Permission::new_disabled(), IdempotentForeignAccess::None), // this will be overwritten + ); + // Keep track of whether the node has any part that allows for interior mutability. + // FIXME: This misses `PhantomData>` which could be considered a marker + // for requesting interior mutability. + let mut has_unsafe_cell = false; + + // When adding a new node, the SIFA of its parents needs to be updated, potentially across + // the entire memory range. For the parts that are being accessed below, the access itself + // trivially takes care of that. However, we have to do some more work to also deal with + // the parts that are not being accessed. Specifically what we do is that we + // call `update_last_accessed_after_retag` on the SIFA of the permission set for the part of + // memory outside `perm_map` -- so that part is definitely taken care of. The remaining concern + // is the part of memory that is in the range of `perms_map`, but not accessed below. + // There we have two cases: + // * If we do have an `UnsafeCell` (`has_unsafe_cell` becomes true), then the non-accessed part + // uses `nonfreeze_perm`, so the `nonfreeze_perm` initialized parts are also fine. We enforce + // the `freeze_perm` parts to be accessed, and thus everything is taken care of. + // * If there is no `UnsafeCell`, then `freeze_perm` is used everywhere (both inside and outside the initial range), + // and we update everything to have the `freeze_perm`'s SIFA, so there are no issues. (And this assert below is not + // actually needed in this case). + assert!(new_perm.freeze_access); + + let protected = new_perm.protector.is_some(); + this.visit_freeze_sensitive(place, ptr_size, |range, frozen| { + has_unsafe_cell = has_unsafe_cell || !frozen; + + // We are only ever `Frozen` inside the frozen bits. + let (perm, access) = if frozen { + (new_perm.freeze_perm, new_perm.freeze_access) + } else { + (new_perm.nonfreeze_perm, new_perm.nonfreeze_access) + }; + + // Store initial permissions. + for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) { + let sifa = perm.strongest_idempotent_foreign_access(protected); + // NOTE: Currently, `access` is false if and only if `perm` is Cell, so this `if` + // doesn't not change whether any code is UB or not. We could just always use + // `new_accessed` and everything would stay the same. But that seems conceptually + // odd, so we keep the initial "accessed" bit of the `LocationState` in sync with whether + // a read access is performed below. + if access { + *loc = LocationState::new_accessed(perm, sifa); + } else { + *loc = LocationState::new_non_accessed(perm, sifa); + } + } + + // Some reborrows incur a read access to the parent. + if access { + // Adjust range to be relative to allocation start (rather than to `place`). + let mut range_in_alloc = range; + range_in_alloc.start += base_offset; + + tree_borrows.perform_access( + orig_tag, + Some((range_in_alloc, AccessKind::Read, diagnostics::AccessCause::Reborrow)), + this.machine.borrow_tracker.as_ref().unwrap(), + alloc_id, + this.machine.current_span(), + )?; + + // Also inform the data race model (but only if any bytes are actually affected). + if range.size.bytes() > 0 { + if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() { + data_race.read( + alloc_id, + range_in_alloc, + NaReadType::Retag, + Some(place.layout.ty), + &this.machine, + )? + } + } + } + interp_ok(()) + })?; + // Record the parent-child pair in the tree. tree_borrows.new_child( + base_offset, orig_tag, new_tag, - new_perm.initial_state, - range, + perms_map, + // Allow lazily writing to surrounding data if we found an `UnsafeCell`. + if has_unsafe_cell { new_perm.nonfreeze_perm } else { new_perm.freeze_perm }, + protected, span, - new_perm.protector.is_some(), )?; drop(tree_borrows); - // Also inform the data race model (but only if any bytes are actually affected). - if range.size.bytes() > 0 && new_perm.initial_read { - if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() { - data_race.read( - alloc_id, - range, - NaReadType::Retag, - Some(place.layout.ty), - &this.machine, - )?; - } - } - interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag })) } @@ -508,15 +596,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> { let this = self.eval_context_mut(); - // Note: if we were to inline `new_reserved` below we would find out that - // `ty_is_freeze` is eventually unused because it appears in a `ty_is_freeze || true`. - // We are nevertheless including it here for clarity. - let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env()); // Retag it. With protection! That is the entire point. let new_perm = NewPermission { - initial_state: Permission::new_reserved(ty_is_freeze, /* protected */ true), + // Note: If we are creating a protected Reserved, which can + // never be ReservedIM, the value of the `ty_is_freeze` + // argument doesn't matter + // (`ty_is_freeze || true` in `new_reserved` will always be `true`). + freeze_perm: Permission::new_reserved( + /* ty_is_freeze */ true, /* protected */ true, + ), + freeze_access: true, + nonfreeze_perm: Permission::new_reserved( + /* ty_is_freeze */ false, /* protected */ true, + ), + nonfreeze_access: true, protector: Some(ProtectorKind::StrongProtector), - initial_read: true, }; this.tb_retag_place(place, new_perm) } diff --git a/src/borrow_tracker/tree_borrows/perms.rs b/src/borrow_tracker/tree_borrows/perms.rs index 087f6fc3f2..38863ca073 100644 --- a/src/borrow_tracker/tree_borrows/perms.rs +++ b/src/borrow_tracker/tree_borrows/perms.rs @@ -94,6 +94,7 @@ impl PermissionPriv { } /// Reject `ReservedIM` that cannot exist in the presence of a protector. + #[cfg(test)] fn compatible_with_protector(&self) -> bool { // FIXME(TB-Cell): It is unclear what to do here. // `Cell` will occur with a protector but won't provide the guarantees @@ -253,10 +254,6 @@ impl Permission { pub fn is_disabled(&self) -> bool { self.inner == Disabled } - /// Check if `self` is the post-child-write state of a pointer (is `Active`). - pub fn is_active(&self) -> bool { - self.inner == Active - } /// Check if `self` is the never-allow-writes-again state of a pointer (is `Frozen`). pub fn is_frozen(&self) -> bool { self.inner == Frozen @@ -289,6 +286,11 @@ impl Permission { /// is a protector is relevant because being protected takes priority over being /// interior mutable) pub fn new_reserved(ty_is_freeze: bool, protected: bool) -> Self { + // As demonstrated by `tests/fail/tree_borrows/reservedim_spurious_write.rs`, + // interior mutability and protectors interact poorly. + // To eliminate the case of Protected Reserved IM we override interior mutability + // in the case of a protected reference: protected references are always considered + // "freeze" in their reservation phase. if ty_is_freeze || protected { Self::new_reserved_frz() } else { Self::new_reserved_im() } } @@ -309,6 +311,7 @@ impl Permission { } /// Reject `ReservedIM` that cannot exist in the presence of a protector. + #[cfg(test)] pub fn compatible_with_protector(&self) -> bool { self.inner.compatible_with_protector() } @@ -393,11 +396,6 @@ impl PermTransition { self.from <= self.to } - pub fn from(from: Permission, to: Permission) -> Option { - let t = Self { from: from.inner, to: to.inner }; - t.is_possible().then_some(t) - } - pub fn is_noop(self) -> bool { self.from == self.to } @@ -407,11 +405,6 @@ impl PermTransition { (starting_point.inner == self.from).then_some(Permission { inner: self.to }) } - /// Extract starting point of a transition - pub fn started(self) -> Permission { - Permission { inner: self.from } - } - /// Determines if this transition would disable the permission. pub fn produces_disabled(self) -> bool { self.to == Disabled diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index 47ccaadbb9..48e4a19e26 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -10,6 +10,7 @@ //! and the relative position of the access; //! - idempotency properties asserted in `perms.rs` (for optimizations) +use std::ops::Range; use std::{fmt, mem}; use rustc_abi::Size; @@ -32,18 +33,18 @@ mod tests; /// Data for a single *location*. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(super) struct LocationState { - /// A location is initialized when it is child-accessed for the first time (and the initial + /// A location is "accessed" when it is child-accessed for the first time (and the initial /// retag initializes the location for the range covered by the type), and it then stays - /// initialized forever. - /// For initialized locations, "permission" is the current permission. However, for - /// uninitialized locations, we still need to track the "future initial permission": this will + /// accessed forever. + /// For accessed locations, "permission" is the current permission. However, for + /// non-accessed locations, we still need to track the "future initial permission": this will /// start out to be `default_initial_perm`, but foreign accesses need to be taken into account. /// Crucially however, while transitions to `Disabled` would usually be UB if this location is - /// protected, that is *not* the case for uninitialized locations. Instead we just have a latent + /// protected, that is *not* the case for non-accessed locations. Instead we just have a latent /// "future initial permission" of `Disabled`, causing UB only if an access is ever actually /// performed. - /// Note that the tree root is also always initialized, as if the allocation was a write access. - initialized: bool, + /// Note that the tree root is also always accessed, as if the allocation was a write access. + accessed: bool, /// This pointer's current permission / future initial permission. permission: Permission, /// See `foreign_access_skipping.rs`. @@ -58,30 +59,30 @@ impl LocationState { /// to any foreign access yet. /// The permission is not allowed to be `Active`. /// `sifa` is the (strongest) idempotent foreign access, see `foreign_access_skipping.rs` - fn new_uninit(permission: Permission, sifa: IdempotentForeignAccess) -> Self { + pub fn new_non_accessed(permission: Permission, sifa: IdempotentForeignAccess) -> Self { assert!(permission.is_initial() || permission.is_disabled()); - Self { permission, initialized: false, idempotent_foreign_access: sifa } + Self { permission, accessed: false, idempotent_foreign_access: sifa } } /// Constructs a new initial state. It has not yet been subjected /// to any foreign access. However, it is already marked as having been accessed. /// `sifa` is the (strongest) idempotent foreign access, see `foreign_access_skipping.rs` - fn new_init(permission: Permission, sifa: IdempotentForeignAccess) -> Self { - Self { permission, initialized: true, idempotent_foreign_access: sifa } + pub fn new_accessed(permission: Permission, sifa: IdempotentForeignAccess) -> Self { + Self { permission, accessed: true, idempotent_foreign_access: sifa } } - /// Check if the location has been initialized, i.e. if it has + /// Check if the location has been accessed, i.e. if it has /// ever been accessed through a child pointer. - pub fn is_initialized(&self) -> bool { - self.initialized + pub fn is_accessed(&self) -> bool { + self.accessed } /// Check if the state can exist as the initial permission of a pointer. /// - /// Do not confuse with `is_initialized`, the two are almost orthogonal - /// as apart from `Active` which is not initial and must be initialized, + /// Do not confuse with `is_accessed`, the two are almost orthogonal + /// as apart from `Active` which is not initial and must be accessed, /// any other permission can have an arbitrary combination of being - /// initial/initialized. + /// initial/accessed. /// FIXME: when the corresponding `assert` in `tree_borrows/mod.rs` finally /// passes and can be uncommented, remove this `#[allow(dead_code)]`. #[cfg_attr(not(test), allow(dead_code))] @@ -95,8 +96,8 @@ impl LocationState { /// Apply the effect of an access to one location, including /// - applying `Permission::perform_access` to the inner `Permission`, - /// - emitting protector UB if the location is initialized, - /// - updating the initialized status (child accesses produce initialized locations). + /// - emitting protector UB if the location is accessed, + /// - updating the accessed status (child accesses produce accessed locations). fn perform_access( &mut self, access_kind: AccessKind, @@ -106,14 +107,14 @@ impl LocationState { let old_perm = self.permission; let transition = Permission::perform_access(access_kind, rel_pos, old_perm, protected) .ok_or(TransitionError::ChildAccessForbidden(old_perm))?; - self.initialized |= !rel_pos.is_foreign(); + self.accessed |= !rel_pos.is_foreign(); self.permission = transition.applied(old_perm).unwrap(); - // Why do only initialized locations cause protector errors? + // Why do only accessed locations cause protector errors? // Consider two mutable references `x`, `y` into disjoint parts of // the same allocation. A priori, these may actually both be used to // access the entire allocation, as long as only reads occur. However, // a write to `y` needs to somehow record that `x` can no longer be used - // on that location at all. For these uninitialized locations (i.e., locations + // on that location at all. For these non-accessed locations (i.e., locations // that haven't been accessed with `x` yet), we track the "future initial state": // it defaults to whatever the initial state of the tag is, // but the access to `y` moves that "future initial state" of `x` to `Disabled`. @@ -121,8 +122,8 @@ impl LocationState { // So clearly protectors shouldn't fire for such "future initial state" transitions. // // See the test `two_mut_protected_same_alloc` in `tests/pass/tree_borrows/tree-borrows.rs` - // for an example of safe code that would be UB if we forgot to check `self.initialized`. - if protected && self.initialized && transition.produces_disabled() { + // for an example of safe code that would be UB if we forgot to check `self.accessed`. + if protected && self.accessed && transition.produces_disabled() { return Err(TransitionError::ProtectedDisabled(old_perm)); } Ok(transition) @@ -157,11 +158,11 @@ impl LocationState { self.idempotent_foreign_access.can_skip_foreign_access(happening_now); if self.permission.is_disabled() { // A foreign access to a `Disabled` tag will have almost no observable effect. - // It's a theorem that `Disabled` node have no protected initialized children, + // It's a theorem that `Disabled` node have no protected accessed children, // and so this foreign access will never trigger any protector. - // (Intuition: You're either protected initialized, and thus can't become Disabled - // or you're already Disabled protected, but not initialized, and then can't - // become initialized since that requires a child access, which Disabled blocks.) + // (Intuition: You're either protected accessed, and thus can't become Disabled + // or you're already Disabled protected, but not accessed, and then can't + // become accessed since that requires a child access, which Disabled blocks.) // Further, the children will never be able to read or write again, since they // have a `Disabled` parent. So this only affects diagnostics, such that the // blocking write will still be identified directly, just at a different tag. @@ -217,7 +218,7 @@ impl LocationState { impl fmt::Display for LocationState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.permission)?; - if !self.initialized { + if !self.accessed { write!(f, "?")?; } Ok(()) @@ -598,12 +599,15 @@ impl Tree { let rperms = { let mut perms = UniValMap::default(); // We manually set it to `Active` on all in-bounds positions. - // We also ensure that it is initialized, so that no `Active` but - // not yet initialized nodes exist. Essentially, we pretend there + // We also ensure that it is accessed, so that no `Active` but + // not yet accessed nodes exist. Essentially, we pretend there // was a write that initialized these to `Active`. perms.insert( root_idx, - LocationState::new_init(Permission::new_active(), IdempotentForeignAccess::None), + LocationState::new_accessed( + Permission::new_active(), + IdempotentForeignAccess::None, + ), ); RangeMap::new(size, perms) }; @@ -612,20 +616,32 @@ impl Tree { } impl<'tcx> Tree { - /// Insert a new tag in the tree - pub fn new_child( + /// Insert a new tag in the tree. + /// + /// `initial_perms` defines the initial permissions for the part of memory + /// that is already considered "initialized" immediately. The ranges in this + /// map are relative to `base_offset`. + /// `default_perm` defines the initial permission for the rest of the allocation. + /// + /// For all non-accessed locations in the RangeMap (those that haven't had an + /// implicit read), their SIFA must be weaker than or as weak as the SIFA of + /// `default_perm`. + pub(super) fn new_child( &mut self, + base_offset: Size, parent_tag: BorTag, new_tag: BorTag, - default_initial_perm: Permission, - reborrow_range: AllocRange, + initial_perms: RangeMap, + default_perm: Permission, + protected: bool, span: Span, - prot: bool, ) -> InterpResult<'tcx> { - assert!(!self.tag_mapping.contains_key(&new_tag)); let idx = self.tag_mapping.insert(new_tag); let parent_idx = self.tag_mapping.get(&parent_tag).unwrap(); - let strongest_idempotent = default_initial_perm.strongest_idempotent_foreign_access(prot); + assert!(default_perm.is_initial()); + + let default_strongest_idempotent = + default_perm.strongest_idempotent_foreign_access(protected); // Create the node self.nodes.insert( idx, @@ -633,25 +649,36 @@ impl<'tcx> Tree { tag: new_tag, parent: Some(parent_idx), children: SmallVec::default(), - default_initial_perm, - default_initial_idempotent_foreign_access: strongest_idempotent, - debug_info: NodeDebugInfo::new(new_tag, default_initial_perm, span), + default_initial_perm: default_perm, + default_initial_idempotent_foreign_access: default_strongest_idempotent, + debug_info: NodeDebugInfo::new(new_tag, default_perm, span), }, ); // Register new_tag as a child of parent_tag self.nodes.get_mut(parent_idx).unwrap().children.push(idx); - // Initialize perms - let perm = LocationState::new_init(default_initial_perm, strongest_idempotent); - for (_perms_range, perms) in self.rperms.iter_mut(reborrow_range.start, reborrow_range.size) + + for (Range { start, end }, &perm) in + initial_perms.iter(Size::from_bytes(0), initial_perms.size()) { - perms.insert(idx, perm); + assert!(perm.is_initial()); + for (_perms_range, perms) in self + .rperms + .iter_mut(Size::from_bytes(start) + base_offset, Size::from_bytes(end - start)) + { + assert!( + default_strongest_idempotent + >= perm.permission.strongest_idempotent_foreign_access(protected) + ); + perms.insert(idx, perm); + } } // Inserting the new perms might have broken the SIFA invariant (see `foreign_access_skipping.rs`). // We now weaken the recorded SIFA for our parents, until the invariant is restored. // We could weaken them all to `LocalAccess`, but it is more efficient to compute the SIFA // for the new permission statically, and use that. - self.update_last_accessed_after_retag(parent_idx, strongest_idempotent); + // See the comment in `tb_reborrow` for why it is correct to use the SIFA of `default_uninit_perm`. + self.update_last_accessed_after_retag(parent_idx, default_strongest_idempotent); interp_ok(()) } @@ -758,14 +785,14 @@ impl<'tcx> Tree { /// /// If `access_range_and_kind` is `None`, this is interpreted as the special /// access that is applied on protector release: - /// - the access will be applied only to initialized locations of the allocation, + /// - the access will be applied only to accessed locations of the allocation, /// - it will not be visible to children, /// - it will be recorded as a `FnExit` diagnostic access /// - and it will be a read except if the location is `Active`, i.e. has been written to, /// in which case it will be a write. /// /// `LocationState::perform_access` will take care of raising transition - /// errors and updating the `initialized` status of each location, + /// errors and updating the `accessed` status of each location, /// this traversal adds to that: /// - inserting into the map locations that do not exist yet, /// - trimming the traversal, @@ -858,7 +885,7 @@ impl<'tcx> Tree { } } else { // This is a special access through the entire allocation. - // It actually only affects `initialized` locations, so we need + // It actually only affects `accessed` locations, so we need // to filter on those before initiating the traversal. // // In addition this implicit access should not be visible to children, @@ -868,10 +895,10 @@ impl<'tcx> Tree { // why this is important. for (perms_range, perms) in self.rperms.iter_mut_all() { let idx = self.tag_mapping.get(&tag).unwrap(); - // Only visit initialized permissions + // Only visit accessed permissions if let Some(p) = perms.get(idx) && let Some(access_kind) = p.permission.protector_end_access() - && p.initialized + && p.accessed { let access_cause = diagnostics::AccessCause::FnExit(access_kind); TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms } @@ -1035,7 +1062,7 @@ impl Tree { impl Node { pub fn default_location_state(&self) -> LocationState { - LocationState::new_uninit( + LocationState::new_non_accessed( self.default_initial_perm, self.default_initial_idempotent_foreign_access, ) @@ -1073,15 +1100,4 @@ impl AccessRelatedness { pub fn is_foreign(self) -> bool { matches!(self, AccessRelatedness::AncestorAccess | AccessRelatedness::CousinAccess) } - - /// Given the AccessRelatedness for the parent node, compute the AccessRelatedness - /// for the child node. This function assumes that we propagate away from the initial - /// access. - pub fn for_child(self) -> Self { - use AccessRelatedness::*; - match self { - AncestorAccess | This => AncestorAccess, - StrictChildAccess | CousinAccess => CousinAccess, - } - } } diff --git a/src/borrow_tracker/tree_borrows/tree/tests.rs b/src/borrow_tracker/tree_borrows/tree/tests.rs index dbfa9807e3..bb3fc2d80b 100644 --- a/src/borrow_tracker/tree_borrows/tree/tests.rs +++ b/src/borrow_tracker/tree_borrows/tree/tests.rs @@ -9,10 +9,10 @@ use crate::borrow_tracker::tree_borrows::exhaustive::{Exhaustive, precondition}; impl Exhaustive for LocationState { fn exhaustive() -> Box> { // We keep `latest_foreign_access` at `None` as that's just a cache. - Box::new(<(Permission, bool)>::exhaustive().map(|(permission, initialized)| { + Box::new(<(Permission, bool)>::exhaustive().map(|(permission, accessed)| { Self { permission, - initialized, + accessed, idempotent_foreign_access: IdempotentForeignAccess::default(), } })) @@ -76,8 +76,8 @@ fn as_protected(b: bool) -> &'static str { if b { " (protected)" } else { "" } } -fn as_lazy_or_init(b: bool) -> &'static str { - if b { "initialized" } else { "lazy" } +fn as_lazy_or_accessed(b: bool) -> &'static str { + if b { "accessed" } else { "lazy" } } /// Test that tree compacting (as performed by the GC) is sound. @@ -106,7 +106,7 @@ fn tree_compacting_is_sound() { as_foreign_or_child(rel), kind, parent.permission(), - as_lazy_or_init(child.is_initialized()), + as_lazy_or_accessed(child.is_accessed()), child.permission(), as_protected(child_protected), np.permission(), @@ -122,7 +122,7 @@ fn tree_compacting_is_sound() { as_foreign_or_child(rel), kind, parent.permission(), - as_lazy_or_init(child.is_initialized()), + as_lazy_or_accessed(child.is_accessed()), child.permission(), as_protected(child_protected), nc.permission() @@ -435,19 +435,19 @@ mod spurious_read { Ok(Self { x, y, ..self }) } - /// Perform a read on the given pointer if its state is `initialized`. + /// Perform a read on the given pointer if its state is `accessed`. /// Must be called just after reborrowing a pointer, and just after /// removing a protector. - fn read_if_initialized(self, ptr: PtrSelector) -> Result { - let initialized = match ptr { - PtrSelector::X => self.x.state.initialized, - PtrSelector::Y => self.y.state.initialized, + fn read_if_accessed(self, ptr: PtrSelector) -> Result { + let accessed = match ptr { + PtrSelector::X => self.x.state.accessed, + PtrSelector::Y => self.y.state.accessed, PtrSelector::Other => panic!( - "the `initialized` status of `PtrSelector::Other` is unknown, do not pass it to `read_if_initialized`" + "the `accessed` status of `PtrSelector::Other` is unknown, do not pass it to `read_if_accessed`" ), }; - if initialized { + if accessed { self.perform_test_access(&TestAccess { ptr, kind: AccessKind::Read }) } else { Ok(self) @@ -457,13 +457,13 @@ mod spurious_read { /// Remove the protector of `x`, including the implicit read on function exit. fn end_protector_x(self) -> Result { let x = self.x.end_protector(); - Self { x, ..self }.read_if_initialized(PtrSelector::X) + Self { x, ..self }.read_if_accessed(PtrSelector::X) } /// Remove the protector of `y`, including the implicit read on function exit. fn end_protector_y(self) -> Result { let y = self.y.end_protector(); - Self { y, ..self }.read_if_initialized(PtrSelector::Y) + Self { y, ..self }.read_if_accessed(PtrSelector::Y) } fn retag_y(self, new_y: LocStateProt) -> Result { @@ -473,7 +473,7 @@ mod spurious_read { } // `xy_rel` changes to "mutually foreign" now: `y` can no longer be a parent of `x`. Self { y: new_y, xy_rel: RelPosXY::MutuallyForeign, ..self } - .read_if_initialized(PtrSelector::Y) + .read_if_accessed(PtrSelector::Y) } fn perform_test_event(self, evt: &TestEvent) -> Result { @@ -602,14 +602,14 @@ mod spurious_read { xy_rel: RelPosXY::MutuallyForeign, x: LocStateProt { // For the tests, the strongest idempotent foreign access does not matter, so we use `Default::default` - state: LocationState::new_init( + state: LocationState::new_accessed( Permission::new_frozen(), IdempotentForeignAccess::default(), ), prot: true, }, y: LocStateProt { - state: LocationState::new_uninit( + state: LocationState::new_non_accessed( Permission::new_reserved(/* freeze */ true, /* protected */ true), IdempotentForeignAccess::default(), ), @@ -650,8 +650,8 @@ mod spurious_read { for xy_rel in RelPosXY::exhaustive() { for (x_retag_perm, y_current_perm) in <(LocationState, LocationState)>::exhaustive() { - // We can only do spurious reads for initialized locations anyway. - precondition!(x_retag_perm.initialized); + // We can only do spurious reads for accessed locations anyway. + precondition!(x_retag_perm.accessed); // And `x` just got retagged, so it must be initial. precondition!(x_retag_perm.permission.is_initial()); // As stated earlier, `x` is always protected in the patterns we consider here. @@ -696,7 +696,7 @@ mod spurious_read { fn initial_state(&self) -> Result { let (x, y) = self.retag_permissions(); let state = LocStateProtPair { xy_rel: self.xy_rel, x, y }; - state.read_if_initialized(PtrSelector::X) + state.read_if_accessed(PtrSelector::X) } } diff --git a/src/range_map.rs b/src/range_map.rs index 2c2484cd0b..29a5a8537a 100644 --- a/src/range_map.rs +++ b/src/range_map.rs @@ -31,6 +31,11 @@ impl RangeMap { RangeMap { v } } + pub fn size(&self) -> Size { + let size = self.v.last().map(|x| x.range.end).unwrap_or(0); + Size::from_bytes(size) + } + /// Finds the index containing the given offset. fn find_offset(&self, offset: u64) -> usize { self.v @@ -71,10 +76,7 @@ impl RangeMap { }; // The first offset that is not included any more. let end = offset + len; - assert!( - end <= self.v.last().unwrap().range.end, - "iterating beyond the bounds of this RangeMap" - ); + assert!(end <= self.size().bytes(), "iterating beyond the bounds of this RangeMap"); slice .iter() .take_while(move |elem| elem.range.start < end) @@ -327,4 +329,16 @@ mod tests { let map = RangeMap::::new(Size::from_bytes(20), -1); let _ = map.iter(Size::from_bytes(11), Size::from_bytes(11)); } + + #[test] + fn empty_map_iter() { + let map = RangeMap::::new(Size::from_bytes(0), -1); + let _ = map.iter(Size::from_bytes(0), Size::from_bytes(0)); + } + + #[test] + fn empty_map_iter_mut() { + let mut map = RangeMap::::new(Size::from_bytes(0), -1); + let _ = map.iter_mut(Size::from_bytes(0), Size::from_bytes(0)); + } } diff --git a/tests/fail/tree_borrows/cell-inside-struct.rs b/tests/fail/tree_borrows/cell-inside-struct.rs new file mode 100644 index 0000000000..ff79787768 --- /dev/null +++ b/tests/fail/tree_borrows/cell-inside-struct.rs @@ -0,0 +1,33 @@ +//! A version of `cell_inside_struct` that dumps the tree so that we can see what is happening. +//@compile-flags: -Zmiri-tree-borrows +#[path = "../../utils/mod.rs"] +#[macro_use] +mod utils; + +use std::cell::Cell; + +struct Foo { + field1: u32, + field2: Cell, +} + +pub fn main() { + let root = Foo { field1: 42, field2: Cell::new(88) }; + unsafe { + let a = &root; + + name!(a as *const Foo, "a"); + + let a: *const Foo = a as *const Foo; + let a: *mut Foo = a as *mut Foo; + + let alloc_id = alloc_id!(a); + print_state!(alloc_id); + + // Writing to `field2`, which is interior mutable, should be allowed. + (*a).field2.set(10); + + // Writing to `field1`, which is frozen, should not be allowed. + (*a).field1 = 88; //~ ERROR: /write access through .* is forbidden/ + } +} diff --git a/tests/fail/tree_borrows/cell-inside-struct.stderr b/tests/fail/tree_borrows/cell-inside-struct.stderr new file mode 100644 index 0000000000..717f141945 --- /dev/null +++ b/tests/fail/tree_borrows/cell-inside-struct.stderr @@ -0,0 +1,26 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 4.. 8 +| Act | Act | └─┬── +| Frz |?Cel | └──── +────────────────────────────────────────────────── +error: Undefined Behavior: write access through (a) at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/cell-inside-struct.rs:LL:CC + | +LL | (*a).field1 = 88; + | ^^^^^^^^^^^^^^^^ write access through (a) at ALLOC[0x0] is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag (a) has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Cell + --> tests/fail/tree_borrows/cell-inside-struct.rs:LL:CC + | +LL | let a = &root; + | ^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at tests/fail/tree_borrows/cell-inside-struct.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/pass/both_borrows/basic_aliasing_model.rs b/tests/pass/both_borrows/basic_aliasing_model.rs index c2b6a7e68b..c06dba6252 100644 --- a/tests/pass/both_borrows/basic_aliasing_model.rs +++ b/tests/pass/both_borrows/basic_aliasing_model.rs @@ -1,6 +1,7 @@ //@revisions: stack tree //@[tree]compile-flags: -Zmiri-tree-borrows #![feature(allocator_api)] +use std::cell::Cell; use std::ptr; // Test various aliasing-model-related things. @@ -23,6 +24,7 @@ fn main() { not_unpin_not_protected(); write_does_not_invalidate_all_aliases(); box_into_raw_allows_interior_mutable_alias(); + cell_inside_struct() } // Make sure that reading from an `&mut` does, like reborrowing to `&`, @@ -260,7 +262,7 @@ fn write_does_not_invalidate_all_aliases() { fn box_into_raw_allows_interior_mutable_alias() { unsafe { - let b = Box::new(std::cell::Cell::new(42)); + let b = Box::new(Cell::new(42)); let raw = Box::into_raw(b); let c = &*raw; let d = raw.cast::(); // bypassing `Cell` -- only okay in Miri tests @@ -270,3 +272,19 @@ fn box_into_raw_allows_interior_mutable_alias() { drop(Box::from_raw(raw)); } } + +fn cell_inside_struct() { + struct Foo { + field1: u32, + field2: Cell, + } + + let mut root = Foo { field1: 42, field2: Cell::new(88) }; + let a = &mut root; + + // Writing to `field2`, which is interior mutable, should be allowed. + (*a).field2.set(10); + + // Writing to `field1`, which is reserved, should also be allowed. + (*a).field1 = 88; +} diff --git a/tests/pass/tree_borrows/cell-alternate-writes.stderr b/tests/pass/tree_borrows/cell-alternate-writes.stderr index 75a30c9a08..e09aed2cf5 100644 --- a/tests/pass/tree_borrows/cell-alternate-writes.stderr +++ b/tests/pass/tree_borrows/cell-alternate-writes.stderr @@ -3,8 +3,8 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── | ReIM| └─┬── -| Cel | ├──── -| Cel | └──── +|?Cel | ├──── +|?Cel | └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. diff --git a/tests/pass/tree_borrows/cell-lazy-write-to-surrounding.rs b/tests/pass/tree_borrows/cell-lazy-write-to-surrounding.rs new file mode 100644 index 0000000000..abe08f2cd2 --- /dev/null +++ b/tests/pass/tree_borrows/cell-lazy-write-to-surrounding.rs @@ -0,0 +1,22 @@ +//@compile-flags: -Zmiri-tree-borrows + +use std::cell::Cell; + +fn foo(x: &Cell) { + unsafe { + let ptr = x as *const Cell as *mut Cell as *mut i32; + ptr.offset(1).write(0); + } +} + +fn main() { + let arr = [Cell::new(1), Cell::new(1)]; + foo(&arr[0]); + + let pair = (Cell::new(1), 1); + // TODO: Ideally, this would result in UB since the second element + // in `pair` is Frozen. We would need some way to express a + // "shared reference with permission to access surrounding + // interior mutable data". + foo(&pair.0); +}