diff --git a/Cargo.toml b/Cargo.toml index 8bb4f1c160..a2c2ad56eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ harness = false default = ["stack-cache", "native-lib"] genmc = ["dep:genmc-sys"] stack-cache = [] -stack-cache-consistency-check = ["stack-cache"] +expensive-consistency-checks = ["stack-cache"] tracing = ["serde_json"] native-lib = ["dep:libffi", "dep:libloading", "dep:capstone", "dep:ipc-channel", "dep:nix", "dep:serde"] diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 920fc29481..83c2f028c8 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -513,7 +513,6 @@ fn main() { Some(BorrowTrackerMethod::TreeBorrows(TreeBorrowsParams { precise_interior_mut: true, })); - miri_config.provenance_mode = ProvenanceMode::Strict; } else if arg == "-Zmiri-tree-borrows-no-precise-interior-mut" { match &mut miri_config.borrow_tracker { Some(BorrowTrackerMethod::TreeBorrows(params)) => { @@ -711,17 +710,6 @@ fn main() { rustc_args.push(arg); } } - // Tree Borrows implies strict provenance, and is not compatible with native calls. - if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows { .. })) { - if miri_config.provenance_mode != ProvenanceMode::Strict { - fatal_error!( - "Tree Borrows does not support integer-to-pointer casts, and hence requires strict provenance" - ); - } - if !miri_config.native_lib.is_empty() { - fatal_error!("Tree Borrows is not compatible with calling native functions"); - } - } // Native calls and strict provenance are not compatible. if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict { diff --git a/src/borrow_tracker/mod.rs b/src/borrow_tracker/mod.rs index ebca7377fd..66f60ae958 100644 --- a/src/borrow_tracker/mod.rs +++ b/src/borrow_tracker/mod.rs @@ -372,6 +372,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let borrow_tracker = this.machine.borrow_tracker.as_ref().unwrap(); // The body of this loop needs `borrow_tracker` immutably // so we can't move this code inside the following `end_call`. + for (alloc_id, tag) in &frame .extra .borrow_tracker @@ -398,6 +399,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } borrow_tracker.borrow_mut().end_call(&frame.extra); + interp_ok(()) } } diff --git a/src/borrow_tracker/stacked_borrows/stack.rs b/src/borrow_tracker/stacked_borrows/stack.rs index fcd6216fc3..d8eaba8ec2 100644 --- a/src/borrow_tracker/stacked_borrows/stack.rs +++ b/src/borrow_tracker/stacked_borrows/stack.rs @@ -153,7 +153,7 @@ impl<'tcx> Stack { /// Panics if any of the caching mechanisms have broken, /// - The StackCache indices don't refer to the parallel items, /// - There are no Unique items outside of first_unique..last_unique - #[cfg(feature = "stack-cache-consistency-check")] + #[cfg(feature = "expensive-consistency-checks")] fn verify_cache_consistency(&self) { // Only a full cache needs to be valid. Also see the comments in find_granting_cache // and set_unknown_bottom. @@ -197,7 +197,7 @@ impl<'tcx> Stack { tag: ProvenanceExtra, exposed_tags: &FxHashSet, ) -> Result, ()> { - #[cfg(feature = "stack-cache-consistency-check")] + #[cfg(feature = "expensive-consistency-checks")] self.verify_cache_consistency(); let ProvenanceExtra::Concrete(tag) = tag else { @@ -334,7 +334,7 @@ impl<'tcx> Stack { // This primes the cache for the next access, which is almost always the just-added tag. self.cache.add(new_idx, new); - #[cfg(feature = "stack-cache-consistency-check")] + #[cfg(feature = "expensive-consistency-checks")] self.verify_cache_consistency(); } @@ -417,7 +417,7 @@ impl<'tcx> Stack { self.unique_range.end = self.unique_range.end.min(disable_start); } - #[cfg(feature = "stack-cache-consistency-check")] + #[cfg(feature = "expensive-consistency-checks")] self.verify_cache_consistency(); interp_ok(()) @@ -472,7 +472,7 @@ impl<'tcx> Stack { self.unique_range = 0..0; } - #[cfg(feature = "stack-cache-consistency-check")] + #[cfg(feature = "expensive-consistency-checks")] self.verify_cache_consistency(); interp_ok(()) } diff --git a/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/borrow_tracker/tree_borrows/diagnostics.rs index f2410a0862..fb26691d8e 100644 --- a/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -291,9 +291,10 @@ pub(super) struct TbError<'node> { pub conflicting_info: &'node NodeDebugInfo, // What kind of access caused this error (read, write, reborrow, deallocation) pub access_cause: AccessCause, - /// Which tag the access that caused this error was made through, i.e. + /// Which tag, if any, the access that caused this error was made through, i.e. /// which tag was used to read/write/deallocate. - pub accessed_info: &'node NodeDebugInfo, + /// Not set on wildcard accesses. + pub accessed_info: Option<&'node NodeDebugInfo>, } impl TbError<'_> { @@ -302,10 +303,20 @@ impl TbError<'_> { use TransitionError::*; let cause = self.access_cause; let accessed = self.accessed_info; + let accessed_str = + self.accessed_info.map(|v| format!("{v}")).unwrap_or_else(|| "".into()); let conflicting = self.conflicting_info; - let accessed_is_conflicting = accessed.tag == conflicting.tag; + // An access is considered conflicting if it happened through a + // different tag, then the one who caused UB. + // When doing a wildcard access (where `accessed` is `None`) we + // do not know which precise tag the accessed happened from, + // however we can be certain that it did not come from the + // conflicting tag. + // This is because the wildcard data structure already removes + // all tags through which an access would cause UB. + let accessed_is_conflicting = accessed.map(|a| a.tag) == Some(conflicting.tag); let title = format!( - "{cause} through {accessed} at {alloc_id:?}[{offset:#x}] is forbidden", + "{cause} through {accessed_str} at {alloc_id:?}[{offset:#x}] is forbidden", alloc_id = self.alloc_id, offset = self.error_offset ); @@ -316,7 +327,7 @@ impl TbError<'_> { let mut details = Vec::new(); if !accessed_is_conflicting { details.push(format!( - "the accessed tag {accessed} is a child of the conflicting tag {conflicting}" + "the accessed tag {accessed_str} is a child of the conflicting tag {conflicting}" )); } let access = cause.print_as_access(/* is_foreign */ false); @@ -330,7 +341,7 @@ impl TbError<'_> { let access = cause.print_as_access(/* is_foreign */ true); let details = vec![ format!( - "the accessed tag {accessed} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)" + "the accessed tag {accessed_str} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)" ), format!( "this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled" @@ -343,7 +354,7 @@ impl TbError<'_> { let conflicting_tag_name = "strongly protected"; let details = vec![ format!( - "the allocation of the accessed tag {accessed} also contains the {conflicting_tag_name} tag {conflicting}" + "the allocation of the accessed tag {accessed_str} also contains the {conflicting_tag_name} tag {conflicting}" ), format!("the {conflicting_tag_name} tag {conflicting} disallows deallocations"), ]; @@ -351,8 +362,10 @@ impl TbError<'_> { } }; let mut history = HistoryData::default(); - if !accessed_is_conflicting { - history.extend(self.accessed_info.history.forget(), "accessed", false); + if let Some(accessed_info) = self.accessed_info + && !accessed_is_conflicting + { + history.extend(accessed_info.history.forget(), "accessed", false); } history.extend( self.conflicting_info.history.extract_relevant(self.error_offset, self.error_kind), @@ -363,6 +376,20 @@ impl TbError<'_> { } } +/// Cannot access this allocation with wildcard provenance, as there are no +/// valid exposed references for this access kind. +pub fn no_valid_exposed_references_error<'tcx>( + alloc_id: AllocId, + offset: u64, + access_cause: AccessCause, +) -> InterpErrorKind<'tcx> { + let title = + format!("{access_cause} through at {alloc_id:?}[{offset:#x}] is forbidden"); + let details = vec![format!("there are no exposed tags who may perform this access here")]; + let history = HistoryData::default(); + err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history }) +} + type S = &'static str; /// Pretty-printing details /// @@ -623,10 +650,10 @@ impl DisplayRepr { } else { // We take this node let rperm = tree - .rperms + .locations .iter_all() - .map(move |(_offset, perms)| { - let perm = perms.get(idx); + .map(move |(_offset, loc)| { + let perm = loc.perms.get(idx); perm.cloned() }) .collect::>(); @@ -788,7 +815,7 @@ impl<'tcx> Tree { show_unnamed: bool, ) -> InterpResult<'tcx> { let mut indenter = DisplayIndent::new(); - let ranges = self.rperms.iter_all().map(|(range, _perms)| range).collect::>(); + let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::>(); if let Some(repr) = DisplayRepr::from(self, show_unnamed) { repr.print( &DEFAULT_FORMATTER, diff --git a/src/borrow_tracker/tree_borrows/mod.rs b/src/borrow_tracker/tree_borrows/mod.rs index 720c5b2394..95ad24e8b3 100644 --- a/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/borrow_tracker/tree_borrows/mod.rs @@ -14,6 +14,7 @@ mod foreign_access_skipping; mod perms; mod tree; mod unimap; +mod wildcard; #[cfg(test)] mod exhaustive; @@ -54,16 +55,10 @@ impl<'tcx> Tree { interpret::Pointer::new(alloc_id, range.start), range.size.bytes(), ); - // TODO: for now we bail out on wildcard pointers. Eventually we should - // handle them as much as we can. - let tag = match prov { - ProvenanceExtra::Concrete(tag) => tag, - ProvenanceExtra::Wildcard => return interp_ok(()), - }; let global = machine.borrow_tracker.as_ref().unwrap(); let span = machine.current_user_relevant_span(); self.perform_access( - tag, + prov, Some((range, access_kind, diagnostics::AccessCause::Explicit(access_kind))), global, alloc_id, @@ -79,19 +74,9 @@ impl<'tcx> Tree { size: Size, machine: &MiriMachine<'tcx>, ) -> InterpResult<'tcx> { - // TODO: for now we bail out on wildcard pointers. Eventually we should - // handle them as much as we can. - let tag = match prov { - ProvenanceExtra::Concrete(tag) => tag, - ProvenanceExtra::Wildcard => return interp_ok(()), - }; let global = machine.borrow_tracker.as_ref().unwrap(); let span = machine.current_user_relevant_span(); - self.dealloc(tag, alloc_range(Size::ZERO, size), global, alloc_id, span) - } - - pub fn expose_tag(&mut self, _tag: BorTag) { - // TODO + self.dealloc(prov, alloc_range(Size::ZERO, size), global, alloc_id, span) } /// A tag just lost its protector. @@ -109,7 +94,11 @@ impl<'tcx> Tree { ) -> InterpResult<'tcx> { let span = machine.current_user_relevant_span(); // `None` makes it the magic on-protector-end operation - self.perform_access(tag, None, global, alloc_id, span) + self.perform_access(ProvenanceExtra::Concrete(tag), None, global, alloc_id, span)?; + + self.release_protector_wildcard(tag); + + interp_ok(()) } } @@ -239,21 +228,22 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here // This pointer doesn't come with an AllocId, so there's no // memory to do retagging in. + let new_prov = place.ptr().provenance; trace!( - "reborrow of size 0: reference {:?} derived from {:?} (pointee {})", - new_tag, + "reborrow of size 0: reusing {:?} (pointee {})", place.ptr(), place.layout.ty, ); log_creation(this, None)?; // Keep original provenance. - return interp_ok(place.ptr().provenance); + return interp_ok(new_prov); } }; + log_creation(this, Some((alloc_id, base_offset, parent_prov)))?; let orig_tag = match parent_prov { - ProvenanceExtra::Wildcard => return interp_ok(place.ptr().provenance), // TODO: handle wildcard pointers + ProvenanceExtra::Wildcard => return interp_ok(place.ptr().provenance), // TODO: handle retagging wildcard pointers ProvenanceExtra::Concrete(tag) => tag, }; @@ -356,7 +346,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; tree_borrows.perform_access( - orig_tag, + parent_prov, Some((range_in_alloc, AccessKind::Read, diagnostics::AccessCause::Reborrow)), this.machine.borrow_tracker.as_ref().unwrap(), alloc_id, @@ -606,7 +596,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // uncovers a non-supported `extern static`. let alloc_extra = this.get_alloc_extra(alloc_id)?; trace!("Tree Borrows tag {tag:?} exposed in {alloc_id:?}"); - alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag); + + let global = this.machine.borrow_tracker.as_ref().unwrap(); + let protected_tags = &global.borrow().protected_tags; + let protected = protected_tags.contains_key(&tag); + alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag, protected); } AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => { // No tree borrows on these allocations. diff --git a/src/borrow_tracker/tree_borrows/perms.rs b/src/borrow_tracker/tree_borrows/perms.rs index b84ebd5165..bd4573f940 100644 --- a/src/borrow_tracker/tree_borrows/perms.rs +++ b/src/borrow_tracker/tree_borrows/perms.rs @@ -53,6 +53,7 @@ enum PermissionPriv { } use self::PermissionPriv::*; use super::foreign_access_skipping::IdempotentForeignAccess; +use super::wildcard::WildcardAccessLevel; impl PartialOrd for PermissionPriv { /// PermissionPriv is ordered by the reflexive transitive closure of @@ -372,6 +373,23 @@ impl Permission { pub fn strongest_idempotent_foreign_access(&self, prot: bool) -> IdempotentForeignAccess { self.inner.strongest_idempotent_foreign_access(prot) } + + /// Returns the strongest access allowed from a child to this node without + /// causing UB (only considers possible transitions to this permission). + pub fn strongest_allowed_child_access(&self, protected: bool) -> WildcardAccessLevel { + match self.inner { + // Everything except disabled can be accessed by read access. + Disabled => WildcardAccessLevel::None, + // Frozen references cannot be written to by a child. + Frozen => WildcardAccessLevel::Read, + // If the `conflicted` flag is set, then there was a foreign read + // during the function call that is still ongoing (still `protected`), + // this is UB (`noalias` violation). + ReservedFrz { conflicted: true } if protected => WildcardAccessLevel::Read, + // Everything else allows writes. + _ => WildcardAccessLevel::Write, + } + } } impl PermTransition { @@ -772,4 +790,32 @@ mod propagation_optimization_checks { ); } } + + /// Checks that `strongest_allowed_child_access` correctly + /// represents which transitions are possible. + #[test] + fn strongest_allowed_child_access() { + for (permission, protected) in <(Permission, bool)>::exhaustive() { + let strongest_child_access = permission.strongest_allowed_child_access(protected); + + let is_read_valid = Permission::perform_access( + AccessKind::Read, + AccessRelatedness::LocalAccess, + permission, + protected, + ) + .is_some(); + + let is_write_valid = Permission::perform_access( + AccessKind::Write, + AccessRelatedness::LocalAccess, + permission, + protected, + ) + .is_some(); + + assert_eq!(is_read_valid, strongest_child_access >= WildcardAccessLevel::Read); + assert_eq!(is_write_valid, strongest_child_access >= WildcardAccessLevel::Write); + } + } } diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index e337fe05e1..e4c30a5379 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -18,9 +18,11 @@ use rustc_data_structures::fx::FxHashSet; use rustc_span::Span; use smallvec::SmallVec; +use super::diagnostics::AccessCause; +use super::wildcard::WildcardState; use crate::borrow_tracker::tree_borrows::Permission; use crate::borrow_tracker::tree_borrows::diagnostics::{ - self, NodeDebugInfo, TbError, TransitionError, + self, NodeDebugInfo, TbError, TransitionError, no_valid_exposed_references_error, }; use crate::borrow_tracker::tree_borrows::foreign_access_skipping::IdempotentForeignAccess; use crate::borrow_tracker::tree_borrows::perms::PermTransition; @@ -30,7 +32,7 @@ use crate::*; mod tests; -/// Data for a single *location*. +/// Data for a reference at single *location*. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(super) struct LocationState { /// A location is "accessed" when it is child-accessed for the first time (and the initial @@ -81,6 +83,47 @@ impl LocationState { self.permission } + fn perform_transition( + &mut self, + idx: UniIndex, + nodes: &mut UniValMap, + wildcard_accesses: &mut UniValMap, + access_kind: AccessKind, + access_cause: AccessCause, + access_range: Option, + relatedness: AccessRelatedness, + span: Span, + location_range: Range, + protected: bool, + ) -> Result<(), TransitionError> { + // Call this function now (i.e. only if we know `relatedness`), which + // ensures it is only called when `skip_if_known_noop` returns + // `Recurse`, due to the contract of `traverse_this_parents_children_other`. + self.record_new_access(access_kind, relatedness); + + let transition = self.perform_access(access_kind, relatedness, protected)?; + if !transition.is_noop() { + let node = nodes.get_mut(idx).unwrap(); + // Record the event as part of the history. + node.debug_info.history.push(diagnostics::Event { + transition, + is_foreign: relatedness.is_foreign(), + access_cause, + access_range, + transition_range: location_range, + span, + }); + + // We need to update the wildcard state, if the permission + // of an exposed pointer changes. + if node.is_exposed { + let access_type = self.permission.strongest_allowed_child_access(protected); + WildcardState::update_exposure(idx, access_type, nodes, wildcard_accesses); + } + } + Ok(()) + } + /// 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 accessed, @@ -211,30 +254,44 @@ impl fmt::Display for LocationState { Ok(()) } } - +/// The state of the full tree for a particular location: for all nodes, the local permissions +/// of that node, and the tracking for wildcard accesses. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LocationTree { + /// Maps a tag to a perm, with possible lazy initialization. + /// + /// NOTE: not all tags registered in `Tree::nodes` are necessarily in all + /// ranges of `perms`, because `perms` is in part lazily initialized. + /// Just because `nodes.get(key)` is `Some(_)` does not mean you can safely + /// `unwrap` any `perm.get(key)`. + /// + /// We do uphold the fact that `keys(perms)` is a subset of `keys(nodes)` + pub perms: UniValMap, + /// Maps a tag and a location to its wildcard access tracking information, + /// with possible lazy initialization. + /// + /// If this allocation doesn't have any exposed nodes, then this map doesn't get + /// initialized. This way we only need to allocate the map if we need it. + /// + /// NOTE: same guarantees on entry initialization as for `perms`. + pub wildcard_accesses: UniValMap, +} /// Tree structure with both parents and children since we want to be /// able to traverse the tree efficiently in both directions. #[derive(Clone, Debug)] pub struct Tree { /// Mapping from tags to keys. The key obtained can then be used in - /// any of the `UniValMap` relative to this allocation, i.e. both the - /// `nodes` and `rperms` of the same `Tree`. + /// any of the `UniValMap` relative to this allocation, i.e. + /// `nodes`, `LocationTree::perms` and `LocationTree::wildcard_accesses` + /// of the same `Tree`. /// The parent-child relationship in `Node` is encoded in terms of these same /// keys, so traversing the entire tree needs exactly one access to /// `tag_mapping`. pub(super) tag_mapping: UniKeyMap, /// All nodes of this tree. pub(super) nodes: UniValMap, - /// Maps a tag and a location to a perm, with possible lazy - /// initialization. - /// - /// NOTE: not all tags registered in `nodes` are necessarily in all - /// ranges of `rperms`, because `rperms` is in part lazily initialized. - /// Just because `nodes.get(key)` is `Some(_)` does not mean you can safely - /// `unwrap` any `perm.get(key)`. - /// - /// We do uphold the fact that `keys(perms)` is a subset of `keys(nodes)` - pub(super) rperms: DedupRangeMap>, + /// Associates with each location its state and wildcard access tracking. + pub(super) locations: DedupRangeMap, /// The index of the root node. pub(super) root: UniIndex, } @@ -260,7 +317,9 @@ pub(super) struct Node { /// in cases where there is no location state yet. See `foreign_access_skipping.rs`, /// and `LocationState::idempotent_foreign_access` for more information default_initial_idempotent_foreign_access: IdempotentForeignAccess, - /// Some extra information useful only for debugging purposes + /// Weather a wildcard access could happen through this node. + pub is_exposed: bool, + /// Some extra information useful only for debugging purposes. pub debug_info: NodeDebugInfo, } @@ -273,7 +332,7 @@ struct NodeAppArgs<'visit> { /// The node map of this tree. nodes: &'visit mut UniValMap, /// The permissions map of this tree. - perms: &'visit mut UniValMap, + loc: &'visit mut LocationTree, } /// Data given to the error handler struct ErrHandlerArgs<'node, InErr> { @@ -293,7 +352,7 @@ struct ErrHandlerArgs<'node, InErr> { struct TreeVisitor<'tree> { tag_mapping: &'tree UniKeyMap, nodes: &'tree mut UniValMap, - perms: &'tree mut UniValMap, + loc: &'tree mut LocationTree, } /// Whether to continue exploring the children recursively or not. @@ -350,7 +409,7 @@ where idx: UniIndex, rel_pos: AccessRelatedness, ) -> ContinueTraversal { - let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, perms: this.perms }; + let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc }; (self.f_continue)(&args) } @@ -360,14 +419,15 @@ where idx: UniIndex, rel_pos: AccessRelatedness, ) -> Result<(), OutErr> { - (self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, perms: this.perms }) - .map_err(|error_kind| { + (self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc }).map_err( + |error_kind| { (self.err_builder)(ErrHandlerArgs { error_kind, conflicting_info: &this.nodes.get(idx).unwrap().debug_info, accessed_info: &this.nodes.get(self.initial).unwrap().debug_info, }) - }) + }, + ) } fn go_upwards_from_accessed( @@ -577,6 +637,7 @@ impl Tree { default_initial_perm: root_default_perm, // The root may never be skipped, all accesses will be local. default_initial_idempotent_foreign_access: IdempotentForeignAccess::None, + is_exposed: false, debug_info, }, ); @@ -595,9 +656,10 @@ impl Tree { IdempotentForeignAccess::None, ), ); - DedupRangeMap::new(size, perms) + let wildcard_accesses = UniValMap::default(); + DedupRangeMap::new(size, LocationTree { perms, wildcard_accesses }) }; - Self { root: root_idx, nodes, rperms, tag_mapping } + Self { root: root_idx, nodes, locations: rperms, tag_mapping } } } @@ -633,11 +695,13 @@ impl<'tcx> Tree { children: SmallVec::default(), default_initial_perm: outside_perm, default_initial_idempotent_foreign_access: default_strongest_idempotent, + is_exposed: false, debug_info: NodeDebugInfo::new(new_tag, outside_perm, span), }, ); + let parent_node = self.nodes.get_mut(parent_idx).unwrap(); // Register new_tag as a child of parent_tag - self.nodes.get_mut(parent_idx).unwrap().children.push(idx); + parent_node.children.push(idx); // We need to know the weakest SIFA for `update_idempotent_foreign_access_after_retag`. let mut min_sifa = default_strongest_idempotent; @@ -651,11 +715,19 @@ impl<'tcx> Tree { ); min_sifa = cmp::min(min_sifa, perm.idempotent_foreign_access); - for (_perms_range, perms) in self - .rperms + for (_range, loc) in self + .locations .iter_mut(Size::from_bytes(start) + base_offset, Size::from_bytes(end - start)) { - perms.insert(idx, perm); + loc.perms.insert(idx, perm); + } + } + + // We need to ensure the consistency of the wildcard access tracking data structure. + // For this, we insert the correct entry for this tag based on its parent, if it exists. + for (_range, loc) in self.locations.iter_mut_all() { + if let Some(parent_access) = loc.wildcard_accesses.get(parent_idx) { + loc.wildcard_accesses.insert(idx, parent_access.for_new_child()); } } @@ -689,9 +761,9 @@ impl<'tcx> Tree { // as the default SIFA for not-yet-initialized locations. // Record whether we did any change; if not, the invariant is restored and we can stop the traversal. let mut any_change = false; - for (_, map) in self.rperms.iter_mut_all() { + for (_range, loc) in self.locations.iter_mut_all() { // Check if this node has a state for this location (or range of locations). - if let Some(perm) = map.get_mut(current) { + if let Some(perm) = loc.perms.get_mut(current) { // Update the per-location SIFA, recording if it changed. any_change |= perm.idempotent_foreign_access.ensure_no_stronger_than(strongest_allowed); @@ -720,28 +792,37 @@ impl<'tcx> Tree { /// - the absence of Strong Protectors anywhere in the allocation pub fn dealloc( &mut self, - tag: BorTag, + prov: ProvenanceExtra, access_range: AllocRange, global: &GlobalState, alloc_id: AllocId, // diagnostics span: Span, // diagnostics ) -> InterpResult<'tcx> { self.perform_access( - tag, + prov, Some((access_range, AccessKind::Write, diagnostics::AccessCause::Dealloc)), global, alloc_id, span, )?; - for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size) { - TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms } + + // Check if this breaks any strong protector. + // (Weak protectors are already handled by `perform_access`.) + for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) { + // The order in which we check if any nodes are invalidated only + // matters to diagnostics, so we use the root as a default tag. + let start_tag = match prov { + ProvenanceExtra::Concrete(tag) => tag, + ProvenanceExtra::Wildcard => self.nodes.get(self.root).unwrap().tag, + }; + TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, loc } .traverse_this_parents_children_other( - tag, - // visit all children, skipping none + start_tag, + // Visit all children, skipping none. |_| ContinueTraversal::Recurse, |args: NodeAppArgs<'_>| -> Result<(), TransitionError> { let node = args.nodes.get(args.idx).unwrap(); - let perm = args.perms.entry(args.idx); + let perm = args.loc.perms.entry(args.idx); let perm = perm.get().copied().unwrap_or_else(|| node.default_location_state()); @@ -764,9 +845,9 @@ impl<'tcx> Tree { conflicting_info, access_cause: diagnostics::AccessCause::Dealloc, alloc_id, - error_offset: perms_range.start, + error_offset: loc_range.start, error_kind, - accessed_info, + accessed_info: Some(accessed_info), } .build() }, @@ -795,12 +876,15 @@ impl<'tcx> Tree { /// - recording the history. pub fn perform_access( &mut self, - tag: BorTag, + prov: ProvenanceExtra, access_range_and_kind: Option<(AllocRange, AccessKind, diagnostics::AccessCause)>, global: &GlobalState, alloc_id: AllocId, // diagnostics span: Span, // diagnostics ) -> InterpResult<'tcx> { + let ProvenanceExtra::Concrete(tag) = prov else { + return self.perform_wildcard_access(access_range_and_kind, global, alloc_id, span); + }; use std::ops::Range; // Performs the per-node work: // - insert the permission if it does not exist @@ -814,7 +898,7 @@ impl<'tcx> Tree { // the `RangeMap` on which we are currently working). let node_skipper = |access_kind: AccessKind, args: &NodeAppArgs<'_>| -> ContinueTraversal { let node = args.nodes.get(args.idx).unwrap(); - let perm = args.perms.get(args.idx); + let perm = args.loc.perms.get(args.idx); let old_state = perm.copied().unwrap_or_else(|| node.default_location_state()); old_state.skip_if_known_noop(access_kind, args.rel_pos) @@ -825,29 +909,23 @@ impl<'tcx> Tree { args: NodeAppArgs<'_>| -> Result<(), TransitionError> { let node = args.nodes.get_mut(args.idx).unwrap(); - let mut perm = args.perms.entry(args.idx); - - let old_state = perm.or_insert(node.default_location_state()); + let mut perm = args.loc.perms.entry(args.idx); - // Call this function now, which ensures it is only called when - // `skip_if_known_noop` returns `Recurse`, due to the contract of - // `traverse_this_parents_children_other`. - old_state.record_new_access(access_kind, args.rel_pos); + let state = perm.or_insert(node.default_location_state()); let protected = global.borrow().protected_tags.contains_key(&node.tag); - let transition = old_state.perform_access(access_kind, args.rel_pos, protected)?; - // Record the event as part of the history - if !transition.is_noop() { - node.debug_info.history.push(diagnostics::Event { - transition, - is_foreign: args.rel_pos.is_foreign(), - access_cause, - access_range: access_range_and_kind.map(|x| x.0), - transition_range: perms_range, - span, - }); - } - Ok(()) + state.perform_transition( + args.idx, + args.nodes, + &mut args.loc.wildcard_accesses, + access_kind, + access_cause, + /* access_range */ access_range_and_kind.map(|x| x.0), + args.rel_pos, + span, + perms_range, + protected, + ) }; // Error handler in case `node_app` goes wrong. @@ -863,7 +941,7 @@ impl<'tcx> Tree { alloc_id, error_offset: perms_range.start, error_kind, - accessed_info, + accessed_info: Some(accessed_info), } .build() }; @@ -871,14 +949,13 @@ impl<'tcx> Tree { if let Some((access_range, access_kind, access_cause)) = access_range_and_kind { // Default branch: this is a "normal" access through a known range. // We iterate over affected locations and traverse the tree for each of them. - for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size) - { - TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms } + for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) { + TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, loc } .traverse_this_parents_children_other( tag, |args| node_skipper(access_kind, args), - |args| node_app(perms_range.clone(), access_kind, access_cause, args), - |args| err_handler(perms_range.clone(), access_cause, args), + |args| node_app(loc_range.clone(), access_kind, access_cause, args), + |args| err_handler(loc_range.clone(), access_cause, args), )?; } } else { @@ -891,21 +968,21 @@ impl<'tcx> Tree { // See the test case `returned_mut_is_usable` from // `tests/pass/tree_borrows/tree-borrows.rs` for an example of // why this is important. - for (perms_range, perms) in self.rperms.iter_mut_all() { + for (loc_range, loc) in self.locations.iter_mut_all() { let idx = self.tag_mapping.get(&tag).unwrap(); // Only visit accessed permissions - if let Some(p) = perms.get(idx) + if let Some(p) = loc.perms.get(idx) && let Some(access_kind) = p.permission.protector_end_access() && p.accessed { let access_cause = diagnostics::AccessCause::FnExit(access_kind); - TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms } + TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, loc } .traverse_nonchildren( - tag, - |args| node_skipper(access_kind, args), - |args| node_app(perms_range.clone(), access_kind, access_cause, args), - |args| err_handler(perms_range.clone(), access_cause, args), - )?; + tag, + |args| node_skipper(access_kind, args), + |args| node_app(loc_range.clone(), access_kind, access_cause, args), + |args| err_handler(loc_range.clone(), access_cause, args), + )?; } } } @@ -921,7 +998,7 @@ impl Tree { // merge some adjacent ranges that were made equal by the removal of some // tags (this does not necessarily mean that they have identical internal representations, // see the `PartialEq` impl for `UniValMap`) - self.rperms.merge_adjacent_thorough(); + self.locations.merge_adjacent_thorough(); } /// Checks if a node is useless and should be GC'ed. @@ -953,10 +1030,14 @@ impl Tree { let child = self.nodes.get(child_idx).unwrap(); // Check that for that one child, `can_be_replaced_by_child` holds for the permission // on all locations. - for (_, data) in self.rperms.iter_all() { - let parent_perm = - data.get(idx).map(|x| x.permission).unwrap_or_else(|| node.default_initial_perm); - let child_perm = data + for (_range, loc) in self.locations.iter_all() { + let parent_perm = loc + .perms + .get(idx) + .map(|x| x.permission) + .unwrap_or_else(|| node.default_initial_perm); + let child_perm = loc + .perms .get(child_idx) .map(|x| x.permission) .unwrap_or_else(|| child.default_initial_perm); @@ -980,8 +1061,9 @@ impl Tree { // before we can safely apply `UniKeyMap::remove` to truly remove // this tag from the `tag_mapping`. let node = self.nodes.remove(this).unwrap(); - for (_perms_range, perms) in self.rperms.iter_mut_all() { - perms.remove(this); + for (_range, loc) in self.locations.iter_mut_all() { + loc.perms.remove(this); + loc.wildcard_accesses.remove(this); } self.tag_mapping.remove(&node.tag); } @@ -1058,6 +1140,121 @@ impl Tree { } } +/// Methods for wildcard accesses. +impl<'tcx> Tree { + /// Analogous to `perform_access`, but we do not know from which exposed + /// reference the access happens. + pub fn perform_wildcard_access( + &mut self, + access_range_and_kind: Option<(AllocRange, AccessKind, diagnostics::AccessCause)>, + global: &GlobalState, + alloc_id: AllocId, // diagnostics + span: Span, // diagnostics + ) -> InterpResult<'tcx> { + #[cfg(feature = "expensive-consistency-checks")] + self.verify_wildcard_consistency(global); + + if let Some((access_range, access_kind, access_cause)) = access_range_and_kind { + for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) { + let root_tag = self.nodes.get(self.root).unwrap().tag; + // This does a traversal starting from the root through the tree updating + // the permissions of each node. + // The difference to `perform_access` is, that we take the access + // relatedness from the wildcard tracking state of the node instead of + // from the visitor itself. + TreeVisitor { loc, nodes: &mut self.nodes, tag_mapping: &self.tag_mapping } + .traverse_this_parents_children_other( + root_tag, + |args: &NodeAppArgs<'_>| -> ContinueTraversal { + let node = args.nodes.get(args.idx).unwrap(); + let perm = args.loc.perms.get(args.idx); + let wildcard_state = args + .loc + .wildcard_accesses + .get(args.idx) + .cloned() + .unwrap_or_default(); + + let old_state = + perm.copied().unwrap_or_else(|| node.default_location_state()); + // If we know where, relative to this node, the wildcard access occurs, + // then check if we can skip the entire subtree because the access might not + // change any permissions here anyway. + if let Some(relatedness) = + wildcard_state.access_relatedness(access_kind) + && let Some(relatedness) = relatedness.to_relatedness() + { + old_state.skip_if_known_noop(access_kind, relatedness) + } else { + ContinueTraversal::Recurse + } + }, + |args| { + let node = args.nodes.get_mut(args.idx).unwrap(); + let mut entry = args.loc.perms.entry(args.idx); + let perm = entry.or_insert(node.default_location_state()); + + let protected = global.borrow().protected_tags.contains_key(&node.tag); + + let Some(wildcard_relatedness) = args + .loc + .wildcard_accesses + .get(args.idx) + .and_then(|s| s.access_relatedness(access_kind)) + else { + // There doesn't exist a valid exposed reference for this access to + // happen through. + // If this fails for one id, then it fails for all ids. + assert_eq!(self.root, args.idx); + return Err(no_valid_exposed_references_error( + alloc_id, + loc_range.start, + access_cause, + )); + }; + + let Some(relatedness) = wildcard_relatedness.to_relatedness() else { + // If the access type is Either, then we do not apply any transition + // to this node, but we still update each of its children. + return Ok(()); + }; + perm.perform_transition( + args.idx, + args.nodes, + &mut args.loc.wildcard_accesses, + access_kind, + access_cause, + Some(access_range), + relatedness, + span, + loc_range.clone(), + protected, + ) + .map_err(|trans| { + let node = args.nodes.get(args.idx).unwrap(); + TbError { + conflicting_info: &node.debug_info, + access_cause, + alloc_id, + error_offset: loc_range.start, + error_kind: trans, + accessed_info: None, + } + .build() + }) + }, + |err| err.error_kind, + )?; + } + } else { + // This is for the special access when a protector gets released. + // Wildcard pointers are never protected, so this is unreachable. + unreachable!() + }; + interp_ok(()) + } +} + impl Node { pub fn default_location_state(&self) -> LocationState { LocationState::new_non_accessed( @@ -1071,7 +1268,15 @@ impl VisitProvenance for Tree { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { // To ensure that the root never gets removed, we visit it // (the `root` node of `Tree` is not an `Option<_>`) - visit(None, Some(self.nodes.get(self.root).unwrap().tag)) + visit(None, Some(self.nodes.get(self.root).unwrap().tag)); + + // We also need to keep around any exposed tags through which + // an access could still happen. + for (_id, node) in self.nodes.iter() { + if node.is_exposed { + visit(None, Some(node.tag)) + } + } } } diff --git a/src/borrow_tracker/tree_borrows/unimap.rs b/src/borrow_tracker/tree_borrows/unimap.rs index ad0a565dfd..6400a62f44 100644 --- a/src/borrow_tracker/tree_borrows/unimap.rs +++ b/src/borrow_tracker/tree_borrows/unimap.rs @@ -12,6 +12,7 @@ #![allow(dead_code)] +use std::fmt::Debug; use std::hash::Hash; use std::mem; @@ -20,10 +21,15 @@ use rustc_data_structures::fx::FxHashMap; use crate::helpers::ToUsize; /// Intermediate key between a UniKeyMap and a UniValMap. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct UniIndex { idx: u32, } +impl Debug for UniIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.idx.fmt(f) + } +} /// From K to UniIndex #[derive(Debug, Clone, Default)] @@ -201,6 +207,19 @@ impl UniValMap { mem::swap(&mut res, &mut self.data[idx.idx.to_usize()]); res } + + /// Returns true if the map is empty. + pub fn is_empty(&self) -> bool { + self.data.iter().all(|v| v.is_none()) + } + + /// Iterates over all key-value pairs in the map. + pub fn iter(&self) -> impl Iterator { + self.data + .iter() + .enumerate() + .filter_map(|(i, v)| v.as_ref().map(|r| (UniIndex { idx: i.try_into().unwrap() }, r))) + } } /// An access to a single value of the map. diff --git a/src/borrow_tracker/tree_borrows/wildcard.rs b/src/borrow_tracker/tree_borrows/wildcard.rs new file mode 100644 index 0000000000..4a3664aea4 --- /dev/null +++ b/src/borrow_tracker/tree_borrows/wildcard.rs @@ -0,0 +1,517 @@ +use std::cmp::max; +use std::fmt::Debug; + +use super::Tree; +use super::tree::{AccessRelatedness, Node}; +use super::unimap::{UniIndex, UniValMap}; +use crate::BorTag; +use crate::borrow_tracker::AccessKind; +#[cfg(feature = "expensive-consistency-checks")] +use crate::borrow_tracker::GlobalState; + +/// Represents the maximum access level that is possible. +/// +/// Note that we derive Ord and PartialOrd, so the order in which variants are listed below matters: +/// None < Read < Write. Do not change that order. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub enum WildcardAccessLevel { + #[default] + None, + Read, + Write, +} + +/// Where the access happened relative to the current node. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum WildcardAccessRelatedness { + /// The access definitively happened through a local node. + LocalAccess, + /// The access definitively happened through a foreign node. + ForeignAccess, + /// We do not know if the access is foreign or local. + EitherAccess, +} +impl WildcardAccessRelatedness { + pub fn to_relatedness(self) -> Option { + match self { + Self::LocalAccess => Some(AccessRelatedness::LocalAccess), + Self::ForeignAccess => Some(AccessRelatedness::ForeignAccess), + Self::EitherAccess => None, + } + } +} + +/// State per location per node keeping track of where relative to this +/// node exposed nodes are and what access permissions they have. +/// +/// Designed to be completely determined by its parent, siblings and +/// direct children's max_local_access/max_foreign_access. +#[derive(Clone, Default, PartialEq, Eq)] +pub struct WildcardState { + /// How many of this node's direct children have `max_local_access()==Write`. + child_writes: u16, + /// How many of this node's direct children have `max_local_access()>=Read`. + child_reads: u16, + /// The maximum access level that could happen from an exposed node + /// that is foreign to this node. + /// + /// This is calculated as the `max()` of the parent's `max_foreign_access`, + /// `exposed_as` and the siblings' `max_local_access()`. + max_foreign_access: WildcardAccessLevel, + /// At what access level this node itself is exposed. + exposed_as: WildcardAccessLevel, +} +impl Debug for WildcardState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WildcardState") + .field("child_r/w", &(self.child_reads, self.child_writes)) + .field("foreign", &self.max_foreign_access) + .field("exposed_as", &self.exposed_as) + .finish() + } +} +impl WildcardState { + /// The maximum access level that could happen from an exposed + /// node that is local to this node. + fn max_local_access(&self) -> WildcardAccessLevel { + use WildcardAccessLevel::*; + max( + self.exposed_as, + if self.child_writes > 0 { + Write + } else if self.child_reads > 0 { + Read + } else { + None + }, + ) + } + + /// From where relative to the node with this wildcard info a read or write access could happen. + pub fn access_relatedness(&self, kind: AccessKind) -> Option { + match kind { + AccessKind::Read => self.read_access_relatedness(), + AccessKind::Write => self.write_access_relatedness(), + } + } + + /// From where relative to the node with this wildcard info a read access could happen. + fn read_access_relatedness(&self) -> Option { + let has_foreign = self.max_foreign_access >= WildcardAccessLevel::Read; + let has_local = self.max_local_access() >= WildcardAccessLevel::Read; + use WildcardAccessRelatedness as E; + match (has_foreign, has_local) { + (true, true) => Some(E::EitherAccess), + (true, false) => Some(E::ForeignAccess), + (false, true) => Some(E::LocalAccess), + (false, false) => None, + } + } + + /// From where relative to the node with this wildcard info a write access could happen. + fn write_access_relatedness(&self) -> Option { + let has_foreign = self.max_foreign_access == WildcardAccessLevel::Write; + let has_local = self.max_local_access() == WildcardAccessLevel::Write; + use WildcardAccessRelatedness as E; + match (has_foreign, has_local) { + (true, true) => Some(E::EitherAccess), + (true, false) => Some(E::ForeignAccess), + (false, true) => Some(E::LocalAccess), + (false, false) => None, + } + } + + /// Gets the access tracking information for a new child node of a parent with this + /// wildcard info. + /// The new node doesn't have any child reads/writes, but calculates `max_foreign_access` + /// from its parent. + pub fn for_new_child(&self) -> Self { + Self { + max_foreign_access: max(self.max_foreign_access, self.max_local_access()), + ..Default::default() + } + } + + /// Pushes the nodes of `children` onto the stack who's `max_foreign_access` + /// needs to be updated. + /// + /// * `children`: A list of nodes with the same parent. `children` doesn't + /// necessarily have to contain all children of parent, but can just be + /// a subset. + /// + /// * `child_reads`, `child_writes`: How many of `children` have `max_local_access()` + /// of at least `read`/`write` + /// + /// * `new_foreign_access`, `old_foreign_access`: + /// The max possible access level that is foreign to all `children` + /// (i.e., it is not local to *any* of them). + /// This can be calculated as the max of the parent's `exposed_as()`, `max_foreign_access` + /// and of all `max_local_access()` of any nodes with the same parent that are + /// not listed in `children`. + /// + /// This access level changed from `old` to `new`, which is why we need to + /// update `children`. + fn push_relevant_children( + stack: &mut Vec<(UniIndex, WildcardAccessLevel)>, + new_foreign_access: WildcardAccessLevel, + old_foreign_access: WildcardAccessLevel, + child_reads: u16, + child_writes: u16, + children: impl Iterator, + + wildcard_accesses: &UniValMap, + ) { + use WildcardAccessLevel::*; + + // Nothing changed so we don't need to update anything. + if new_foreign_access == old_foreign_access { + return; + } + + // We need to consider that the children's `max_local_access()` affect each + // other's `max_foreign_access`, but do not affect their own `max_foreign_access`. + + // The new `max_foreign_acces` for children with `max_local_access()==Write`. + let write_foreign_access = max( + new_foreign_access, + if child_writes > 1 { + // There exists at least one more child with exposed write access. + // This means that a foreign write through that node is possible. + Write + } else if child_reads > 1 { + // There exists at least one more child with exposed read access, + // but no other with write access. + // This means that a foreign read but no write through that node + // is possible. + Read + } else { + // There are no other nodes with read or write access. + // This means no foreign writes through other children are possible. + None + }, + ); + + // The new `max_foreign_acces` for children with `max_local_access()==Read`. + let read_foreign_access = max( + new_foreign_access, + if child_writes > 0 { + // There exists at least one child with write access (and it's not this one). + Write + } else if child_reads > 1 { + // There exists at least one more child with exposed read access, + // but no other with write access. + Read + } else { + // There are no other nodes with read or write access, + None + }, + ); + + // The new `max_foreign_acces` for children with `max_local_access()==None`. + let none_foreign_access = max( + new_foreign_access, + if child_writes > 0 { + // There exists at least one child with write access (and it's not this one). + Write + } else if child_reads > 0 { + // There exists at least one child with read access (and it's not this one), + // but none with write access. + Read + } else { + // No children are exposed as read or write. + None + }, + ); + + stack.extend(children.filter_map(|child| { + let state = wildcard_accesses.get(child).cloned().unwrap_or_default(); + + let new_foreign_access = match state.max_local_access() { + Write => write_foreign_access, + Read => read_foreign_access, + None => none_foreign_access, + }; + + if new_foreign_access != state.max_foreign_access { + Some((child, new_foreign_access)) + } else { + Option::None + } + })); + } + + /// Update the tracking information of a tree, to reflect that the node specified by `id` is + /// now exposed with `new_exposed_as`. + /// + /// Propagates the Willard access information over the tree. This needs to be called every + /// time the access level of an exposed node changes, to keep the state in sync with + /// the rest of the tree. + pub fn update_exposure( + id: UniIndex, + new_exposed_as: WildcardAccessLevel, + nodes: &UniValMap, + wildcard_accesses: &mut UniValMap, + ) { + let mut entry = wildcard_accesses.entry(id); + let src_state = entry.or_insert(Default::default()); + let old_exposed_as = src_state.exposed_as; + + // If the exposure doesn't change, then we don't need to update anything. + if old_exposed_as == new_exposed_as { + return; + } + + let src_old_local_access = src_state.max_local_access(); + + src_state.exposed_as = new_exposed_as; + + let src_new_local_access = src_state.max_local_access(); + + // Stack of nodes for which the max_foreign_access field needs to be updated. + // Will be filled with the children of this node and its parents children before + // we begin downwards traversal. + let mut stack: Vec<(UniIndex, WildcardAccessLevel)> = Vec::new(); + + // Add the direct children of this node to the stack. + { + let node = nodes.get(id).unwrap(); + Self::push_relevant_children( + &mut stack, + // new_foreign_access + max(src_state.max_foreign_access, new_exposed_as), + // old_foreign_access + max(src_state.max_foreign_access, old_exposed_as), + // Consider all children. + src_state.child_reads, + src_state.child_writes, + node.children.iter().copied(), + wildcard_accesses, + ); + } + // We need to propagate the tracking info up the tree, for this we traverse + // up the parents. + // We can skip propagating info to the parent and siblings of a node if its + // access didn't change. + { + // The child from which we came. + let mut child = id; + // This is the `max_local_access()` of the child we came from, before + // this update... + let mut old_child_access = src_old_local_access; + // and after this update. + let mut new_child_access = src_new_local_access; + while let Some(parent_id) = nodes.get(child).unwrap().parent { + let parent_node = nodes.get(parent_id).unwrap(); + let mut entry = wildcard_accesses.entry(parent_id); + let parent_state = entry.or_insert(Default::default()); + + let old_parent_local_access = parent_state.max_local_access(); + use WildcardAccessLevel::*; + // Updating this node's tracking state for its children. + match (old_child_access, new_child_access) { + (None | Read, Write) => parent_state.child_writes += 1, + (Write, None | Read) => parent_state.child_writes -= 1, + _ => {} + } + match (old_child_access, new_child_access) { + (None, Read | Write) => parent_state.child_reads += 1, + (Read | Write, None) => parent_state.child_reads -= 1, + _ => {} + } + + let new_parent_local_access = parent_state.max_local_access(); + + { + // We need to update the `max_foreign_access` of `child`'s + // siblings. For this we can reuse the `push_relevant_children` + // function. + // + // We pass it just the siblings without child itself. Since + // `child`'s `max_local_access()` is foreign to all of its + // siblings we can pass it as part of the foreign access. + + let parent_access = + max(parent_state.exposed_as, parent_state.max_foreign_access); + // This is how many of `child`'s siblings have read/write local access. + // If `child` itself has access, then we need to subtract its access from the count. + let sibling_reads = + parent_state.child_reads - if new_child_access >= Read { 1 } else { 0 }; + let sibling_writes = + parent_state.child_writes - if new_child_access >= Write { 1 } else { 0 }; + Self::push_relevant_children( + &mut stack, + // new_foreign_access + max(parent_access, new_child_access), + // old_foreign_access + max(parent_access, old_child_access), + // Consider only siblings of child. + sibling_reads, + sibling_writes, + parent_node.children.iter().copied().filter(|id| child != *id), + wildcard_accesses, + ); + } + if old_parent_local_access == new_parent_local_access { + // We didn't change `max_local_access()` for parent, so we don't need to propagate further upwards. + break; + } + + old_child_access = old_parent_local_access; + new_child_access = new_parent_local_access; + child = parent_id; + } + } + // Traverses down the tree to update max_foreign_access fields of children and cousins who need to be updated. + while let Some((id, new_access)) = stack.pop() { + let node = nodes.get(id).unwrap(); + let mut entry = wildcard_accesses.entry(id); + let state = entry.or_insert(Default::default()); + + let old_access = state.max_foreign_access; + state.max_foreign_access = new_access; + + Self::push_relevant_children( + &mut stack, + // new_foreign_access + max(state.exposed_as, new_access), + // old_foreign_access + max(state.exposed_as, old_access), + // Consider all children. + state.child_reads, + state.child_writes, + node.children.iter().copied(), + wildcard_accesses, + ); + } + } +} + +impl Tree { + /// Marks the tag as exposed & updates the wildcard tracking data structure + /// to represent its access level. + pub fn expose_tag(&mut self, tag: BorTag, protected: bool) { + let id = self.tag_mapping.get(&tag).unwrap(); + let node = self.nodes.get_mut(id).unwrap(); + node.is_exposed = true; + let node = self.nodes.get(id).unwrap(); + + // When the first tag gets exposed then we initialize the + // wildcard state for every node and location in the tree. + for (_, loc) in self.locations.iter_mut_all() { + let perm = loc + .perms + .get(id) + .map(|p| p.permission()) + .unwrap_or_else(|| node.default_location_state().permission()); + + let access_type = perm.strongest_allowed_child_access(protected); + WildcardState::update_exposure( + id, + access_type, + &self.nodes, + &mut loc.wildcard_accesses, + ); + } + } + /// This updates the wildcard tracking data structure to reflect the release of + /// the protector on `tag`. + pub(super) fn release_protector_wildcard(&mut self, tag: BorTag) { + let idx = self.tag_mapping.get(&tag).unwrap(); + + // We check if the node is already exposed, as we don't want to expose any + // nodes which aren't already exposed. + + if self.nodes.get(idx).unwrap().is_exposed { + // Updates the exposure to the new permission on every location. + self.expose_tag(tag, /* protected */ false); + } + } +} + +#[cfg(feature = "expensive-consistency-checks")] +impl Tree { + /// Checks that the wildcard tracking data structure is internally consistent and + /// has the correct `exposed_as` values. + pub fn verify_wildcard_consistency(&self, global: &GlobalState) { + let protected_tags = &global.borrow().protected_tags; + for (_, loc) in self.locations.iter_all() { + let wildcard_accesses = &loc.wildcard_accesses; + let perms = &loc.perms; + // Checks if accesses is empty. + if wildcard_accesses.is_empty() { + return; + } + for (id, node) in self.nodes.iter() { + let state = wildcard_accesses.get(id).unwrap(); + + let expected_exposed_as = if node.is_exposed { + let perm = perms.get(id).unwrap(); + + perm.permission() + .strongest_allowed_child_access(protected_tags.contains_key(&node.tag)) + } else { + WildcardAccessLevel::None + }; + + // The foreign wildcard accesses possible at a node are determined by which + // accesses can originate from their siblings, their parent, and from above + // their parent. + let expected_max_foreign_access = if let Some(parent) = node.parent { + let parent_node = self.nodes.get(parent).unwrap(); + let parent_state = wildcard_accesses.get(parent).unwrap(); + + let max_sibling_access = parent_node + .children + .iter() + .copied() + .filter(|child| *child != id) + .map(|child| { + let state = wildcard_accesses.get(child).unwrap(); + state.max_local_access() + }) + .fold(WildcardAccessLevel::None, max); + + max_sibling_access + .max(parent_state.max_foreign_access) + .max(parent_state.exposed_as) + } else { + WildcardAccessLevel::None + }; + + // Count how many children can be the source of wildcard reads or writes + // (either directly, or via their children). + let child_accesses = node.children.iter().copied().map(|child| { + let state = wildcard_accesses.get(child).unwrap(); + state.max_local_access() + }); + let expected_child_reads = + child_accesses.clone().filter(|a| *a >= WildcardAccessLevel::Read).count(); + let expected_child_writes = + child_accesses.filter(|a| *a >= WildcardAccessLevel::Write).count(); + + assert_eq!( + expected_exposed_as, state.exposed_as, + "tag {:?} (id:{id:?}) should be exposed as {expected_exposed_as:?} but is exposed as {:?}", + node.tag, state.exposed_as + ); + assert_eq!( + expected_max_foreign_access, state.max_foreign_access, + "expected {:?}'s (id:{id:?}) max_foreign_access to be {:?} instead of {:?}", + node.tag, expected_max_foreign_access, state.max_foreign_access + ); + let child_reads: usize = state.child_reads.into(); + assert_eq!( + expected_child_reads, child_reads, + "expected {:?}'s (id:{id:?}) child_reads to be {} instead of {}", + node.tag, expected_child_reads, child_reads + ); + let child_writes: usize = state.child_writes.into(); + assert_eq!( + expected_child_writes, child_writes, + "expected {:?}'s (id:{id:?}) child_writes to be {} instead of {}", + node.tag, expected_child_writes, child_writes + ); + } + } + } +} diff --git a/tests/fail/tree_borrows/wildcard/dealloc.rs b/tests/fail/tree_borrows/wildcard/dealloc.rs new file mode 100644 index 0000000000..c314a96a44 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/dealloc.rs @@ -0,0 +1,21 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks that deallocation through a wildcard ref fails, +/// if all exposed references are disabled. +pub fn main() { + use std::alloc::Layout; + let x = unsafe { std::alloc::alloc_zeroed(Layout::new::()) as *mut u32 }; + + let ref1 = unsafe { &mut *x }; + let ref2 = unsafe { &mut *x }; + + let int = ref1 as *mut u32 as usize; + let wild = int as *mut u32; + // Disables ref1 and therefore also wild. + *ref2 = 14; + + // Tries to dealloc through a wildcard reference even though all exposed + // references are disabled. + + unsafe { std::alloc::dealloc(wild as *mut u8, Layout::new::()) }; //~ ERROR: /deallocation through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/dealloc.stderr b/tests/fail/tree_borrows/wildcard/dealloc.stderr new file mode 100644 index 0000000000..b6d28aabbe --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/dealloc.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: deallocation through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/dealloc.rs:LL:CC + | +LL | unsafe { std::alloc::dealloc(wild as *mut u8, Layout::new::()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: there are no exposed tags who may perform this access here + +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/fail/tree_borrows/wildcard/gc.rs b/tests/fail/tree_borrows/wildcard/gc.rs new file mode 100644 index 0000000000..b46f0670be --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/gc.rs @@ -0,0 +1,20 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +#[path = "../../../utils/mod.rs"] +mod utils; + +/// Checks that the garbage collector doesn't remove any exposed tags. +fn main() { + let mut _x: u32 = 4; + let int = { + let y = &_x; + y as *const u32 as usize + }; + // If y wasn't exposed, this would gc it. + utils::run_provenance_gc(); + // This should disable y. + _x = 5; + let wild = int as *const u32; + + let _fail = unsafe { *wild }; //~ ERROR: /read access through at .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/gc.stderr b/tests/fail/tree_borrows/wildcard/gc.stderr new file mode 100644 index 0000000000..f85d53d5f9 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/gc.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/gc.rs:LL:CC + | +LL | let _fail = unsafe { *wild }; + | ^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: there are no exposed tags who may perform this access here + +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/fail/tree_borrows/wildcard/multi_exposed_child.rs b/tests/fail/tree_borrows/wildcard/multi_exposed_child.rs new file mode 100644 index 0000000000..2187fca16c --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_child.rs @@ -0,0 +1,64 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks that disabling an exposed reference correctly narrows the +/// possible locations a wildcard access could happen from. +/// Also checks that an access is treated as foreign, if all exposed +/// (non-disabled) references are ancestors. +pub fn main() { + let mut x: u32 = 42; + + let ref1 = &mut x; + let int1 = ref1 as *mut u32 as usize; + + let ref2 = &mut *ref1; + + let ref3 = &mut *ref2; + let _int3 = ref3 as *mut u32 as usize; + + // Write through ref3 so that all references are active. + *ref3 = 43; + + let wild = int1 as *mut u32; + + // ┌────────────┐ + // │ │ + // │ ref1(Act)* │ + // │ │ + // └──────┬─────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref2(Act) │ + // │ │ + // └──────┬─────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Act)* │ + // │ │ + // └────────────┘ + + // Writes through either ref1 or ref3, which is either a child or foreign + // access to ref2. + unsafe { wild.write(42) }; + + // Reading from ref2 still works, since the previous access could have been + // through its child. + // This also freezes ref3. + let _x = *ref2; + + // We can still write through wild, as there is still the exposed ref1 with + // write permissions under proper exposed provenance, this would be UB as the + // only tag wild can assume to not invalidate ref2 is ref3, which we just + // invalidated. + // + // This disables ref2, ref3. + unsafe { wild.write(43) }; + + // Fails because ref2 is disabled. + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/multi_exposed_child.stderr b/tests/fail/tree_borrows/wildcard/multi_exposed_child.stderr new file mode 100644 index 0000000000..3d450cf784 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_child.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/multi_exposed_child.rs:LL:CC + | +LL | let _fail = *ref2; + | ^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/multi_exposed_child.rs:LL:CC + | +LL | let ref2 = &mut *ref1; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/multi_exposed_child.rs:LL:CC + | +LL | unsafe { wild.write(43) }; + | ^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs b/tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs new file mode 100644 index 0000000000..39c6f315e1 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks if we correctly determine the correct exposed reference a write +/// access could happen through, +/// if there are also exposed reference through which only a read could happen. +pub fn main() { + let mut x: u32 = 42; + + let ref1 = &mut x; + let int1 = ref1 as *mut u32 as usize; + + let ref2 = &mut *ref1; + + let ref3 = &*ref2; + let _int3 = ref3 as *const u32 as usize; + + let wild = int1 as *mut u32; + + // ┌────────────┐ + // │ │ + // │ ref1(Res)* │ + // │ │ + // └──────┬─────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref2(Res) │ + // │ │ + // └──────┬─────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Frz)* │ + // │ │ + // └────────────┘ + + // Writes through ref1 as we cannot write through ref3 since it's frozen. + // Disables ref2, ref3. + unsafe { wild.write(42) }; + + // ref2 is disabled. + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.stderr b/tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.stderr new file mode 100644 index 0000000000..402529cbc3 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs:LL:CC + | +LL | let _fail = *ref2; + | ^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs:LL:CC + | +LL | let ref2 = &mut *ref1; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs:LL:CC + | +LL | unsafe { wild.write(42) }; + | ^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/fail/tree_borrows/wildcard/multi_exposed_siblings_disable.rs b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_disable.rs new file mode 100644 index 0000000000..2622e9a62a --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_disable.rs @@ -0,0 +1,38 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks with multiple exposed nodes, that if they are all disabled +/// then no wildcard accesses are possible. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + let ref3 = unsafe { &mut *ptr_base }; + + // Both references get exposed. + let int1 = ref1 as *mut u32 as usize; + let _int2 = ref2 as *mut u32 as usize; + + let wild = int1 as *mut u32; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├──────────────┬───────────────────┐ + // │ │ │ │ + // └──────┬─────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌────────────┐ ┌───────────┐ + // │ │ │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res)* │ │ ref3(Res) │ + // │ │ │ │ │ │ + // └────────────┘ └────────────┘ └───────────┘ + + // Disables ref1,ref2. + *ref3 = 13; + + // Both exposed references are disabled so this fails. + let _fail = unsafe { *wild }; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_disable.stderr b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_disable.stderr new file mode 100644 index 0000000000..04661b9c22 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_disable.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_disable.rs:LL:CC + | +LL | let _fail = unsafe { *wild }; + | ^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: there are no exposed tags who may perform this access here + +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/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs new file mode 100644 index 0000000000..c889f57552 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs @@ -0,0 +1,38 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks that if for a node all exposed references are foreign, +/// that a wildcard access gets treated as foreign to it. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + let ref3 = unsafe { &mut *ptr_base }; + + // Both references get exposed. + let int1 = ref1 as *mut u32 as usize; + let _int2 = ref2 as *mut u32 as usize; + + let wild = int1 as *mut u32; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├──────────────┬───────────────────┐ + // │ │ │ │ + // └──────┬─────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌────────────┐ ┌───────────┐ + // │ │ │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res)* │ │ ref3(Res) │ + // │ │ │ │ │ │ + // └────────────┘ └────────────┘ └───────────┘ + + // Disables ref3 as both exposed pointers are foreign to it. + unsafe { wild.write(13) }; + + // Fails because ref3 is disabled. + let _fail = *ref3; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.stderr b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.stderr new file mode 100644 index 0000000000..85c836b4b3 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs:LL:CC + | +LL | let _fail = *ref3; + | ^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs:LL:CC + | +LL | let ref3 = unsafe { &mut *ptr_base }; + | ^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs:LL:CC + | +LL | unsafe { wild.write(13) }; + | ^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs new file mode 100644 index 0000000000..067d56e776 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs @@ -0,0 +1,51 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks if a local access gets correctly triggered, if we know that +/// all exposed references are local to a node. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + // Both references get exposed. + let int1 = ref1 as *mut u32 as usize; + let _int2 = ref2 as *mut u32 as usize; + + let wild = int1 as *mut u32; + + // Activates ptr_base. + unsafe { wild.write(41) }; + + // + // ┌─────────────┐ + // │ │ + // │ x (Act) │ + // │ │ + // └──────┬──────┘ + // │ + // │ + // ▼ + // ┌────────────────┐ + // │ │ + // │ ptr_base (Act) ├──────────┐ + // │ │ │ + // └──────┬─────────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res)* │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // We read from x causing a foreign access to ptr_base, freezing it + // (as the previous wildcard access has made it active). + let _y = x; + + // While both exposed references are still enabled for writes, any write + // through them would cause UB at ptr_base. + unsafe { wild.write(0) }; //~ ERROR: /write access through at .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.stderr b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.stderr new file mode 100644 index 0000000000..ca4a2533f7 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC + | +LL | unsafe { wild.write(0) }; + | ^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Frozen which forbids this child write access +help: the conflicting tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC + | +LL | let ptr_base = &mut x as *mut u32; + | ^^^^^^ +help: the conflicting tag later transitioned to Unique due to a child write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC + | +LL | unsafe { wild.write(41) }; + | ^^^^^^^^^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the conflicting tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC + | +LL | let _y = x; + | ^ + = help: this transition corresponds to a loss of write permissions + +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/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs new file mode 100644 index 0000000000..fb0b43f20d --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs @@ -0,0 +1,38 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks if we correctly determine the correct exposed reference a write +/// access could happen through, if there are also exposed reference +/// through which only a read access could happen. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &*ptr_base }; + + // Both references get exposed. + let int1 = ref1 as *mut u32 as usize; + let _int2 = ref2 as *const u32 as usize; + + let wild = int1 as *mut u32; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├──────────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref1(Res)* │ │ ref2(Frz)* │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // Disables ref2 as the only write could happen through ref1. + unsafe { wild.write(13) }; + + // Fails because ref2 is disabled. + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.stderr b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.stderr new file mode 100644 index 0000000000..a07febaa84 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs:LL:CC + | +LL | let _fail = *ref2; + | ^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Frozen + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs:LL:CC + | +LL | let ref2 = unsafe { &*ptr_base }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs:LL:CC + | +LL | unsafe { wild.write(13) }; + | ^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + +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/fail/tree_borrows/wildcard/protector_conflicted.rs b/tests/fail/tree_borrows/wildcard/protector_conflicted.rs new file mode 100644 index 0000000000..620cf34d2a --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/protector_conflicted.rs @@ -0,0 +1,26 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks that wildcard accesses correctly infers the allowed permissions +/// on protected conflicted pointers. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + let protect = |arg: &mut u32| { + // Expose arg. + let int = arg as *mut u32 as usize; + let wild = int as *mut u32; + + // Does a foreign read to arg marking it as conflicted and making child + // writes UB while it's protected. + let _x = *ref2; + + // The only exposed reference (arg) doesn't allow child writes, so this is UB. + unsafe { *wild = 4 }; //~ ERROR: /write access through at .* is forbidden/ + }; + + protect(ref1); +} diff --git a/tests/fail/tree_borrows/wildcard/protector_conflicted.stderr b/tests/fail/tree_borrows/wildcard/protector_conflicted.stderr new file mode 100644 index 0000000000..5667b08042 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/protector_conflicted.stderr @@ -0,0 +1,21 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/protector_conflicted.rs:LL:CC + | +LL | unsafe { *wild = 4 }; + | ^^^^^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: there are no exposed tags who may perform this access here + = note: BACKTRACE: + = note: inside closure at tests/fail/tree_borrows/wildcard/protector_conflicted.rs:LL:CC +note: inside `main` + --> tests/fail/tree_borrows/wildcard/protector_conflicted.rs:LL:CC + | +LL | protect(ref1); + | ^^^^^^^^^^^^^ + +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/fail/tree_borrows/wildcard/single_exposed_disable.rs b/tests/fail/tree_borrows/wildcard/single_exposed_disable.rs new file mode 100644 index 0000000000..1f2236a70f --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/single_exposed_disable.rs @@ -0,0 +1,35 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks that with only one exposed reference, that if this reference gets +/// disabled no wildcard accesses are possible. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + let int1 = ref1 as *mut u32 as usize; + let wild = int1 as *mut u32; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├───────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌───────────┐ + // │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res) │ + // │ │ │ │ + // └────────────┘ └───────────┘ + + // Disables ref1. + *ref2 = 13; + + // Tries to do a wildcard access through the only exposed reference ref1, + // which is disabled. + let _fail = unsafe { *wild }; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/single_exposed_disable.stderr b/tests/fail/tree_borrows/wildcard/single_exposed_disable.stderr new file mode 100644 index 0000000000..a199403cf1 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/single_exposed_disable.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/single_exposed_disable.rs:LL:CC + | +LL | let _fail = unsafe { *wild }; + | ^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: there are no exposed tags who may perform this access here + +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/fail/tree_borrows/wildcard/single_exposed_foreign.rs b/tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs new file mode 100644 index 0000000000..d8f2ab16ee --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs @@ -0,0 +1,34 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks that with only one exposed reference, wildcard accesses +/// correctly cause foreign accesses. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + let int1 = ref1 as *mut u32 as usize; + let wild = int1 as *mut u32; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├───────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌───────────┐ + // │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res) │ + // │ │ │ │ + // └────────────┘ └───────────┘ + + // Write through the wildcard to the only exposed reference ref1, + // disabling ref2. + unsafe { wild.write(13) }; + + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/single_exposed_foreign.stderr b/tests/fail/tree_borrows/wildcard/single_exposed_foreign.stderr new file mode 100644 index 0000000000..98d22598d6 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/single_exposed_foreign.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs:LL:CC + | +LL | let _fail = *ref2; + | ^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs:LL:CC + | +LL | let ref2 = unsafe { &mut *ptr_base }; + | ^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs:LL:CC + | +LL | unsafe { wild.write(13) }; + | ^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/fail/tree_borrows/wildcard/single_exposed_local.rs b/tests/fail/tree_borrows/wildcard/single_exposed_local.rs new file mode 100644 index 0000000000..d7cb3495e0 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/single_exposed_local.rs @@ -0,0 +1,22 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +// Checks if a local access gets correctly triggered during wildcard access, +// if we know that the only exposed reference is local to the node. +pub fn main() { + let mut x: u32 = 0; + + let ref1 = &mut x; + + let int = ref1 as *mut u32 as usize; + let wild = int as *mut u32; + + // Activates ref1. + unsafe { wild.write(41) }; + + // Reads from x causing a foreign read on ref1, freezing it + // (because it was active). + let _y = x; + + // The only exposed reference (ref1) is frozen, so wildcard writes are UB. + unsafe { wild.write(0) }; //~ ERROR: /write access through at .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/single_exposed_local.stderr b/tests/fail/tree_borrows/wildcard/single_exposed_local.stderr new file mode 100644 index 0000000000..dd09533042 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/single_exposed_local.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/single_exposed_local.rs:LL:CC + | +LL | unsafe { wild.write(0) }; + | ^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: there are no exposed tags who may perform this access here + +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/fail/tree_borrows/wildcard/single_exposed_only_ro.rs b/tests/fail/tree_borrows/wildcard/single_exposed_only_ro.rs new file mode 100644 index 0000000000..c8e75636a0 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/single_exposed_only_ro.rs @@ -0,0 +1,11 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// If we have only exposed read-only pointers, doing a write through a +/// wildcard ptr should fail. +fn main() { + let mut x = 0; + let _fool = &mut x as *mut i32; // this would have fooled the old untagged pointer logic + let addr = (&x as *const i32).expose_provenance(); + let ptr = std::ptr::with_exposed_provenance_mut::(addr); + unsafe { *ptr = 0 }; //~ ERROR: /write access through at .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/single_exposed_only_ro.stderr b/tests/fail/tree_borrows/wildcard/single_exposed_only_ro.stderr new file mode 100644 index 0000000000..e175f97b92 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/single_exposed_only_ro.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/single_exposed_only_ro.rs:LL:CC + | +LL | unsafe { *ptr = 0 }; + | ^^^^^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: there are no exposed tags who may perform this access here + +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/stacked_borrows/int-to-ptr.rs b/tests/pass/both_borrows/int-to-ptr.rs similarity index 97% rename from tests/pass/stacked_borrows/int-to-ptr.rs rename to tests/pass/both_borrows/int-to-ptr.rs index 8a05fca3f3..830feed7e2 100644 --- a/tests/pass/stacked_borrows/int-to-ptr.rs +++ b/tests/pass/both_borrows/int-to-ptr.rs @@ -1,4 +1,6 @@ +//@revisions: stack tree //@compile-flags: -Zmiri-permissive-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows use std::ptr; // Just to make sure that casting a ref to raw, to int and back to raw diff --git a/tests/pass/stacked_borrows/issue-miri-2389.rs b/tests/pass/both_borrows/issue-miri-2389.rs similarity index 86% rename from tests/pass/stacked_borrows/issue-miri-2389.rs rename to tests/pass/both_borrows/issue-miri-2389.rs index 469122095e..f9d87b7dea 100644 --- a/tests/pass/stacked_borrows/issue-miri-2389.rs +++ b/tests/pass/both_borrows/issue-miri-2389.rs @@ -1,3 +1,6 @@ +//@revisions: stack tree +//@compile-flags: -Zmiri-permissive-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows use std::cell::Cell; fn main() { diff --git a/tests/pass/stacked_borrows/issue-miri-2389.stderr b/tests/pass/stacked_borrows/issue-miri-2389.stderr deleted file mode 100644 index 363f8a20f8..0000000000 --- a/tests/pass/stacked_borrows/issue-miri-2389.stderr +++ /dev/null @@ -1,12 +0,0 @@ -warning: integer-to-pointer cast - --> tests/pass/stacked_borrows/issue-miri-2389.rs:LL:CC - | -LL | let wildcard = &root0 as *const Cell as usize as *const Cell; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast - | - = help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program - = help: see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation - = help: to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead - = help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics - = help: alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning - diff --git a/tests/pass/tree_borrows/wildcard/undetected_ub.rs b/tests/pass/tree_borrows/wildcard/undetected_ub.rs new file mode 100644 index 0000000000..5e53f30186 --- /dev/null +++ b/tests/pass/tree_borrows/wildcard/undetected_ub.rs @@ -0,0 +1,144 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance +// NOTE: This file documents UB that is not detected by wildcard provenance. + +pub fn main() { + uncertain_provenance(); + protected_exposed(); + protected_wildcard(); +} + +/// Currently, if we do not know for a tag if an access is local or foreign, +/// then we do not do any state transitions on that tag. However, to implement +/// proper provenance we would have to instead pick the correct transition +/// non-deterministically. +/// +/// This test contains such UB that will not be detectable with wildcard provenance, +/// but would be detectable if we implemented this behavior correctly. +pub fn uncertain_provenance() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + // We create 2 mutable references, each with a unique tag. + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + // Both references get exposed. + let int1 = ref1 as *mut u32 as usize; + let _int2 = ref2 as *mut u32 as usize; + //ref1 : Reserved + //ref2 : Reserved + + // We need to pick the "correct" tag for wild from the exposed tags. + let wild = int1 as *mut u32; + // wild=ref1 wild=ref2 + //ref1 : Reserved Reserved + //ref2 : Reserved Reserved + + // We write to wild, disabling the other tag. + unsafe { wild.write(13) }; + // wild=ref1 wild=ref2 + //ref1 : Unique Disabled + //ref2 : Disabled Unique + + // We access both references, even though one of them should be + // disabled under proper exposed provenance. + // This is UB, however, wildcard provenance cannot detect this. + assert_eq!(*ref1, 13); + // wild=ref1 wild=ref2 + //ref1 : Unique UB + //ref2 : Disabled Frozen + assert_eq!(*ref2, 13); + // wild=ref1 wild=ref2 + //ref1 : Frozen UB + //ref2 : UB Frozen +} + +/// If a reference is protected, then all foreign writes to it cause UB. +/// This effectively means any write needs to happen through a child of +/// the protected reference. +/// With this information we could further narrow the possible candidates +/// for a wildcard write. +/// However, currently tree borrows doesn't do this, so this test has UB +/// that isn't detected. +pub fn protected_exposed() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + let _int2 = ref2 as *mut u32 as usize; + + fn protect(ref3: &mut u32) { + let int3 = ref3 as *mut u32 as usize; + // ┌────────────┐ + // │ │ + // │ ptr_base ├──────────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref1(Res) │ │ ref2(Res)* │ + // │ │ │ │ + // └──────┬─────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // Since ref3 is protected, we could know that every write from outside it will be UB. + // This means we know that the access is through ref3, disabling ref2. + let wild = int3 as *mut u32; + unsafe { wild.write(13) } + } + protect(ref1); + + // ref2 is disabled, so this read causes UB, but we currently don't protect this. + let _fail = *ref2; +} + +/// Currently, we do not assign protectors to wildcard references. +/// This test has UB because it does a foreign write to a protected reference. +/// However, that reference is a wildcard, so this doesn't get detected. +#[allow(unused_variables)] +pub fn protected_wildcard() { + let mut x: u32 = 32; + let ref1 = &mut x; + let ref2 = &mut *ref1; + + let int = ref2 as *mut u32 as usize; + let wild = int as *mut u32; + let wild_ref = unsafe { &mut *wild }; + + let mut protect = |arg: &mut u32| { + // arg is a protected pointer with wildcard provenance. + + // ┌────────────┐ + // │ │ + // │ ref1(Res) │ + // │ │ + // └──────┬─────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref2(Res)* │ + // │ │ + // └────────────┘ + + // Writes to ref1, disabling ref2, i.e. disabling all exposed references. + // Since a wildcard reference is protected, this is UB. But we currently don't detect this. + *ref1 = 13; + }; + + // We pass a pointer with wildcard provenance to the function. + protect(wild_ref); +} diff --git a/tests/pass/tree_borrows/wildcard/wildcard.rs b/tests/pass/tree_borrows/wildcard/wildcard.rs new file mode 100644 index 0000000000..2b8b10196e --- /dev/null +++ b/tests/pass/tree_borrows/wildcard/wildcard.rs @@ -0,0 +1,177 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +pub fn main() { + multiple_exposed_siblings(); + multiple_exposed_child(); + dealloc(); + protector(); + protector_conflicted_release(); + returned_mut_is_usable(); +} + +// Checks that an access through a wildcard reference +// doesn't disable any exposed references. +// It tests this with exposed references that are siblings of each other. +pub fn multiple_exposed_siblings() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + // Both references get exposed. + let int1 = ref1 as *mut u32 as usize; + let _int2 = ref2 as *mut u32 as usize; + + let wild = int1 as *mut u32; + + // graph TD + // ptr_base --> ref1(Res)* & ref2(Res)* + // + // ┌────────────┐ + // │ │ + // │ ptr_base ├────────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res)* │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // Writes through either of the two exposed references. + // We do not know which so we cannot disable the other. + unsafe { wild.write(13) }; + + // Reading through either of these references should be valid. + assert_eq!(*ref2, 13); +} + +// Checks that an access through a wildcard reference +// doesn't disable any exposed references. +// It tests this with exposed references where one is the ancestor of the other. +pub fn multiple_exposed_child() { + let mut x: u32 = 42; + + let ref1 = &mut x; + let int1 = ref1 as *mut u32 as usize; + + let ref2 = &mut *ref1; + + let ref3 = &mut *ref2; + let _int3 = ref3 as *mut u32 as usize; + + let wild = int1 as *mut u32; + + // graph TD + // ref1(Res)* --> ref2(Res) --> ref3(Res)* + // + // ┌────────────┐ + // │ │ + // │ ref1(Res)* │ + // │ │ + // └──────┬─────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref2(Res) │ + // │ │ + // └──────┬─────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // This writes either through ref1 or ref3, which is either a child or foreign access to ref2. + unsafe { wild.write(42) }; + + // Reading from ref2 still works, since the previous access could have been through its child. + // This also freezes ref3. + let _x = *ref2; + + // We can still write through wild, as there is still the exposed ref1 with write permissions. + unsafe { wild.write(43) }; +} + +/// Checks that we can deallocate through a wildcard reference. +fn dealloc() { + use std::alloc::Layout; + let x = unsafe { std::alloc::alloc_zeroed(Layout::new::()) as *mut u32 }; + let ref1 = unsafe { &mut *x }; + let int = ref1 as *mut u32 as usize; + let wild = int as *mut u32; + + unsafe { std::alloc::dealloc(wild as *mut u8, Layout::new::()) }; +} + +/// Checks that we can pass a wildcard reference to a function. +fn protector() { + fn protect(arg: &mut u32) { + *arg = 4; + } + let mut x: u32 = 32; + let ref1 = &mut x; + let int = ref1 as *mut u32 as usize; + let wild = int as *mut u32; + let wild_ref = unsafe { &mut *wild }; + + protect(wild_ref); + + assert_eq!(*ref1, 4); +} + +/// Checks whether we correctly handle the protector being released on +/// a conflicted exposed reference. +fn protector_conflicted_release() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + let ref2 = unsafe { &mut *ptr_base }; + + let protect = |arg: &mut u32| { + // Expose arg. + let int = arg as *mut u32 as usize; + let wild = int as *mut u32; + + // Do a foreign read to arg marking it as conflicted and making child_writes UB while its protected. + let _x = *ref2; + + return wild; + }; + + let wild = protect(ref1); + + // The protector on arg got released so writes through arg should work again. + unsafe { *wild = 4 }; +} + +/// Analogous to same test in `../tree-borrows.rs` but with a protected wildcard reference. +fn returned_mut_is_usable() { + // NOTE: Currently we ignore protectors on wildcard references. + fn reborrow(x: &mut u8) -> &mut u8 { + let y = &mut *x; + // Activate the reference so that it is vulnerable to foreign reads. + *y = *y; + y + // An implicit read through `x` is inserted here. + } + let mut x: u8 = 0; + let ref1 = &mut x; + let int = ref1 as *mut u8 as usize; + let wild = int as *mut u8; + let wild_ref = unsafe { &mut *wild }; + + let y = reborrow(wild_ref); + + *y = 1; +}