diff --git a/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/borrow_tracker/tree_borrows/diagnostics.rs index c454bb43a2..a91f35a9dc 100644 --- a/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -488,6 +488,8 @@ struct DisplayFmtPadding { indent_middle: S, /// Indentation for the last child. indent_last: S, + /// Replaces `join_last` for a wildcard root. + wildcard_root: S, } /// How to show whether a location has been accessed /// @@ -561,6 +563,11 @@ impl DisplayFmt { }) .unwrap_or("") } + + /// Print extra text if the tag is exposed. + fn print_exposed(&self, exposed: bool) -> S { + if exposed { " (exposed)" } else { "" } + } } /// Track the indentation of the tree. @@ -607,23 +614,21 @@ fn char_repeat(c: char, n: usize) -> String { struct DisplayRepr { tag: BorTag, name: Option, + exposed: bool, rperm: Vec>, children: Vec, } impl DisplayRepr { - fn from(tree: &Tree, show_unnamed: bool) -> Option { + fn from(tree: &Tree, root: UniIndex, show_unnamed: bool) -> Option { let mut v = Vec::new(); - extraction_aux(tree, tree.root, show_unnamed, &mut v); + extraction_aux(tree, root, show_unnamed, &mut v); let Some(root) = v.pop() else { if show_unnamed { unreachable!( "This allocation contains no tags, not even a root. This should not happen." ); } - eprintln!( - "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags." - ); return None; }; assert!(v.is_empty()); @@ -637,6 +642,7 @@ impl DisplayRepr { ) { let node = tree.nodes.get(idx).unwrap(); let name = node.debug_info.name.clone(); + let exposed = node.is_exposed; let children_sorted = { let mut children = node.children.iter().cloned().collect::>(); children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag); @@ -661,12 +667,13 @@ impl DisplayRepr { for child_idx in children_sorted { extraction_aux(tree, child_idx, show_unnamed, &mut children); } - acc.push(DisplayRepr { tag: node.tag, name, rperm, children }); + acc.push(DisplayRepr { tag: node.tag, name, rperm, children, exposed }); } } } fn print( - &self, + main_root: &Option, + wildcard_subtrees: &[DisplayRepr], fmt: &DisplayFmt, indenter: &mut DisplayIndent, protected_tags: &FxHashMap, @@ -703,15 +710,41 @@ impl DisplayRepr { block.push(s); } // This is the actual work - print_aux( - self, - &range_padding, - fmt, - indenter, - protected_tags, - true, /* root _is_ the last child */ - &mut block, - ); + if let Some(root) = main_root { + print_aux( + root, + &range_padding, + fmt, + indenter, + protected_tags, + true, /* root _is_ the last child */ + false, /* not a wildcard_root*/ + &mut block, + ); + } + for tree in wildcard_subtrees.iter() { + let mut gap_line = String::new(); + gap_line.push_str(fmt.perm.open); + for (i, &pad) in range_padding.iter().enumerate() { + if i > 0 { + gap_line.push_str(fmt.perm.sep); + } + gap_line.push_str(&format!("{}{}", char_repeat(' ', pad), " ")); + } + gap_line.push_str(fmt.perm.close); + block.push(gap_line); + + print_aux( + tree, + &range_padding, + fmt, + indenter, + protected_tags, + true, /* root _is_ the last child */ + true, /* wildcard_root*/ + &mut block, + ); + } // Then it's just prettifying it with a border of dashes. { let wr = &fmt.wrapper; @@ -741,6 +774,7 @@ impl DisplayRepr { indent: &mut DisplayIndent, protected_tags: &FxHashMap, is_last_child: bool, + is_wildcard_root: bool, acc: &mut Vec, ) { let mut line = String::new(); @@ -760,7 +794,9 @@ impl DisplayRepr { indent.write(&mut line); { // padding - line.push_str(if is_last_child { + line.push_str(if is_wildcard_root { + fmt.padding.wildcard_root + } else if is_last_child { fmt.padding.join_last } else { fmt.padding.join_middle @@ -777,12 +813,22 @@ impl DisplayRepr { line.push_str(&fmt.print_tag(tree.tag, &tree.name)); let protector = protected_tags.get(&tree.tag); line.push_str(fmt.print_protector(protector)); + line.push_str(fmt.print_exposed(tree.exposed)); // Push the line to the accumulator then recurse. acc.push(line); let nb_children = tree.children.len(); for (i, child) in tree.children.iter().enumerate() { indent.increment(fmt, is_last_child); - print_aux(child, padding, fmt, indent, protected_tags, i + 1 == nb_children, acc); + print_aux( + child, + padding, + fmt, + indent, + protected_tags, + /* is_last_child */ i + 1 == nb_children, + /* is_wildcard_root */ false, + acc, + ); indent.decrement(fmt); } } @@ -803,6 +849,7 @@ const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt { indent_last: " ", join_haschild: "┬", join_default: "─", + wildcard_root: "*", }, accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" }, }; @@ -816,15 +863,27 @@ impl<'tcx> Tree { ) -> InterpResult<'tcx> { let mut indenter = DisplayIndent::new(); let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::>(); - if let Some(repr) = DisplayRepr::from(self, show_unnamed) { - repr.print( - &DEFAULT_FORMATTER, - &mut indenter, - protected_tags, - ranges, - /* print warning message about tags not shown */ !show_unnamed, + let main_tree = DisplayRepr::from(self, self.roots[0], show_unnamed); + let wildcard_subtrees = self.roots[1..] + .iter() + .filter_map(|root| DisplayRepr::from(self, *root, show_unnamed)) + .collect::>(); + + if main_tree.is_none() && wildcard_subtrees.is_empty() { + eprintln!( + "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags." ); } + + DisplayRepr::print( + &main_tree, + wildcard_subtrees.as_slice(), + &DEFAULT_FORMATTER, + &mut indenter, + protected_tags, + ranges, + /* print warning message about tags not shown */ !show_unnamed, + ); interp_ok(()) } } diff --git a/src/borrow_tracker/tree_borrows/mod.rs b/src/borrow_tracker/tree_borrows/mod.rs index 2a1c98e526..e1da12282c 100644 --- a/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/borrow_tracker/tree_borrows/mod.rs @@ -239,18 +239,14 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(new_prov); } }; + let new_prov = Provenance::Concrete { alloc_id, tag: new_tag }; 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 retagging wildcard pointers - ProvenanceExtra::Concrete(tag) => tag, - }; - trace!( "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}", new_tag, - orig_tag, + parent_prov, place.layout.ty, interpret::Pointer::new(alloc_id, base_offset), ptr_size.bytes() @@ -281,7 +277,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here // There's not actually any bytes here where accesses could even be tracked. // Just produce the new provenance, nothing else to do. - return interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag })); + return interp_ok(Some(new_prov)); } let protected = new_perm.protector.is_some(); @@ -367,11 +363,10 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } } - // Record the parent-child pair in the tree. tree_borrows.new_child( base_offset, - orig_tag, + parent_prov, new_tag, inside_perms, new_perm.outside_perm, @@ -380,7 +375,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; drop(tree_borrows); - interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag })) + interp_ok(Some(new_prov)) } fn tb_retag_place( diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index 07edf20fc4..7fc9fa786e 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -91,11 +91,11 @@ impl LocationState { nodes: &mut UniValMap, wildcard_accesses: &mut UniValMap, access_kind: AccessKind, - access_cause: AccessCause, - access_range: Option, + access_cause: AccessCause, //diagnostics + access_range: Option, //diagnostics relatedness: AccessRelatedness, - span: Span, - location_range: Range, + span: Span, //diagnostics + location_range: Range, //diagnostics protected: bool, ) -> Result<(), TransitionError> { // Call this function now (i.e. only if we know `relatedness`), which @@ -294,8 +294,22 @@ pub struct Tree { pub(super) nodes: UniValMap, /// Associates with each location its state and wildcard access tracking. pub(super) locations: DedupRangeMap, - /// The index of the root node. - pub(super) root: UniIndex, + /// Contains both the root of the main tree as well as the roots of the wildcard subtrees. + /// + /// If we reborrow a reference which has wildcard provenance, then we do not know where in + /// the tree to attach them. Instead we create a new additional tree for this allocation + /// with this new reference as a root. We call this additional tree a wildcard subtree. + /// + /// The actual structure should be a single tree but with wildcard provenance we approximate + /// this with this ordered set of trees. Each wildcard subtree is the direct child of *some* exposed + /// tag (that is smaller than the root), but we do not know which. This also means that it can only be the + /// child of a tree that comes before it in the vec ensuring we don't have any cycles in our + /// approximated tree. + /// + /// Sorted according to `BorTag` from low to high. This also means the main root is `root[0]`. + /// + /// Has array size 2 because that still ensures the minimum size for SmallVec. + pub(super) roots: SmallVec<[UniIndex; 2]>, } /// A node in the borrow tree. Each node is uniquely identified by a tag via @@ -345,12 +359,13 @@ struct TreeVisitor<'tree> { } /// Whether to continue exploring the children recursively or not. +#[derive(Debug)] enum ContinueTraversal { Recurse, SkipSelfAndChildren, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum ChildrenVisitMode { VisitChildrenOfAccessed, SkipChildrenOfAccessed, @@ -384,7 +399,7 @@ struct TreeVisitorStack { impl TreeVisitorStack where NodeContinue: Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - NodeApp: Fn(NodeAppArgs<'_>) -> Result<(), Err>, + NodeApp: FnMut(NodeAppArgs<'_>) -> Result<(), Err>, { fn should_continue_at( &self, @@ -405,12 +420,13 @@ where (self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc }) } + /// Returns the root of this tree. fn go_upwards_from_accessed( &mut self, this: &mut TreeVisitor<'_>, accessed_node: UniIndex, visit_children: ChildrenVisitMode, - ) -> Result<(), Err> { + ) -> Result { // We want to visit the accessed node's children first. // However, we will below walk up our parents and push their children (our cousins) // onto the stack. To ensure correct iteration order, this method thus finishes @@ -455,7 +471,7 @@ where } // Reverse the stack, as discussed above. self.stack.reverse(); - Ok(()) + Ok(last_node) } fn finish_foreign_accesses(&mut self, this: &mut TreeVisitor<'_>) -> Result<(), Err> { @@ -536,18 +552,20 @@ impl<'tree> TreeVisitor<'tree> { /// Finally, remember that the iteration order is not relevant for UB, it only affects /// diagnostics. It also affects tree traversal optimizations built on top of this, so /// those need to be reviewed carefully as well whenever this changes. + /// + /// Returns the index of the root of the accessed tree. fn traverse_this_parents_children_other( mut self, start_idx: UniIndex, f_continue: impl Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - f_propagate: impl Fn(NodeAppArgs<'_>) -> Result<(), Err>, - ) -> Result<(), Err> { + f_propagate: impl FnMut(NodeAppArgs<'_>) -> Result<(), Err>, + ) -> Result { let mut stack = TreeVisitorStack::new(f_continue, f_propagate); // Visits the accessed node itself, and all its parents, i.e. all nodes // undergoing a child access. Also pushes the children and the other // cousin nodes (i.e. all nodes undergoing a foreign access) to the stack // to be processed later. - stack.go_upwards_from_accessed( + let root = stack.go_upwards_from_accessed( &mut self, start_idx, ChildrenVisitMode::VisitChildrenOfAccessed, @@ -555,21 +573,24 @@ impl<'tree> TreeVisitor<'tree> { // Now visit all the foreign nodes we remembered earlier. // For this we go bottom-up, but also allow f_continue to skip entire // subtrees from being visited if it would be a NOP. - stack.finish_foreign_accesses(&mut self) + stack.finish_foreign_accesses(&mut self)?; + Ok(root) } /// Like `traverse_this_parents_children_other`, but skips the children of `start_idx`. + /// + /// Returns the index of the root of the accessed tree. fn traverse_nonchildren( mut self, start_idx: UniIndex, f_continue: impl Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - f_propagate: impl Fn(NodeAppArgs<'_>) -> Result<(), Err>, - ) -> Result<(), Err> { + f_propagate: impl FnMut(NodeAppArgs<'_>) -> Result<(), Err>, + ) -> Result { let mut stack = TreeVisitorStack::new(f_continue, f_propagate); // Visits the accessed node itself, and all its parents, i.e. all nodes // undergoing a child access. Also pushes the other cousin nodes to the // stack, but not the children of the accessed node. - stack.go_upwards_from_accessed( + let root = stack.go_upwards_from_accessed( &mut self, start_idx, ChildrenVisitMode::SkipChildrenOfAccessed, @@ -577,6 +598,27 @@ impl<'tree> TreeVisitor<'tree> { // Now visit all the foreign nodes we remembered earlier. // For this we go bottom-up, but also allow f_continue to skip entire // subtrees from being visited if it would be a NOP. + stack.finish_foreign_accesses(&mut self)?; + Ok(root) + } + + /// Traverses all children of `start_idx` including `start_idx` itself. + /// Uses `f_continue` to filter out subtrees and then processes each node + /// with `f_propagate` so that the children get processed before their + /// parents. + fn traverse_children_this( + mut self, + start_idx: UniIndex, + f_continue: impl Fn(&NodeAppArgs<'_>) -> ContinueTraversal, + f_propagate: impl FnMut(NodeAppArgs<'_>) -> Result<(), Err>, + ) -> Result<(), Err> { + let mut stack = TreeVisitorStack::new(f_continue, f_propagate); + + stack.stack.push(( + start_idx, + AccessRelatedness::ForeignAccess, + RecursionState::BeforeChildren, + )); stack.finish_foreign_accesses(&mut self) } } @@ -625,7 +667,7 @@ impl Tree { let wildcard_accesses = UniValMap::default(); DedupRangeMap::new(size, LocationTree { perms, wildcard_accesses }) }; - Self { root: root_idx, nodes, locations, tag_mapping } + Self { roots: SmallVec::from_slice(&[root_idx]), nodes, locations, tag_mapping } } } @@ -639,7 +681,7 @@ impl<'tcx> Tree { pub(super) fn new_child( &mut self, base_offset: Size, - parent_tag: BorTag, + parent_prov: ProvenanceExtra, new_tag: BorTag, inside_perms: DedupRangeMap, outside_perm: Permission, @@ -647,7 +689,11 @@ impl<'tcx> Tree { span: Span, ) -> InterpResult<'tcx> { let idx = self.tag_mapping.insert(new_tag); - let parent_idx = self.tag_mapping.get(&parent_tag).unwrap(); + let parent_idx = match parent_prov { + ProvenanceExtra::Concrete(parent_tag) => + Some(self.tag_mapping.get(&parent_tag).unwrap()), + ProvenanceExtra::Wildcard => None, + }; assert!(outside_perm.is_initial()); let default_strongest_idempotent = @@ -657,7 +703,7 @@ impl<'tcx> Tree { idx, Node { tag: new_tag, - parent: Some(parent_idx), + parent: parent_idx, children: SmallVec::default(), default_initial_perm: outside_perm, default_initial_idempotent_foreign_access: default_strongest_idempotent, @@ -665,9 +711,17 @@ impl<'tcx> Tree { 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 - parent_node.children.push(idx); + if let Some(parent_idx) = parent_idx { + let parent_node = self.nodes.get_mut(parent_idx).unwrap(); + // Register new_tag as a child of parent_tag + parent_node.children.push(idx); + } else { + // If the parent had wildcard provenance, then register the idx + // as a new wildcard root. + // This preserves the orderedness of `roots` because a newly created + // tag is greater than all previous tags. + self.roots.push(idx); + } // We need to know the weakest SIFA for `update_idempotent_foreign_access_after_retag`. let mut min_sifa = default_strongest_idempotent; @@ -691,19 +745,27 @@ impl<'tcx> Tree { // 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. + // If we are inserting a new wildcard root (with Wildcard as parent_prov) then we insert + // the special wildcard root initial state instead. 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()); + if let Some(parent_idx) = parent_idx { + if let Some(parent_access) = loc.wildcard_accesses.get(parent_idx) { + loc.wildcard_accesses.insert(idx, parent_access.for_new_child()); + } + } else { + loc.wildcard_accesses.insert(idx, WildcardState::for_wildcard_root()); } } - - // Inserting the new perms might have broken the SIFA invariant (see - // `foreign_access_skipping.rs`) if the SIFA we inserted is weaker than that of some parent. - // We now weaken the recorded SIFA for our parents, until the invariant is restored. We - // could weaken them all to `None`, but it is more efficient to compute the SIFA for the new - // permission statically, and use that. For this we need the *minimum* SIFA (`None` needs - // more fixup than `Write`). - self.update_idempotent_foreign_access_after_retag(parent_idx, min_sifa); + // If the parent is a wildcard pointer, then it doesn't track SIFA and doesn't need to be updated. + if let Some(parent_idx) = parent_idx { + // Inserting the new perms might have broken the SIFA invariant (see + // `foreign_access_skipping.rs`) if the SIFA we inserted is weaker than that of some parent. + // We now weaken the recorded SIFA for our parents, until the invariant is restored. We + // could weaken them all to `None`, but it is more efficient to compute the SIFA for the new + // permission statically, and use that. For this we need the *minimum* SIFA (`None` needs + // more fixup than `Write`). + self.update_idempotent_foreign_access_after_retag(parent_idx, min_sifa); + } interp_ok(()) } @@ -772,52 +834,67 @@ impl<'tcx> Tree { span, )?; - // 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_idx = match prov { - ProvenanceExtra::Concrete(tag) => self.tag_mapping.get(&tag).unwrap(), - ProvenanceExtra::Wildcard => self.root, + ProvenanceExtra::Concrete(tag) => Some(self.tag_mapping.get(&tag).unwrap()), + ProvenanceExtra::Wildcard => None, }; // 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) { - TreeVisitor { nodes: &mut self.nodes, loc }.traverse_this_parents_children_other( - start_idx, - // Visit all children, skipping none. - |_| ContinueTraversal::Recurse, - |args: NodeAppArgs<'_>| { - let node = args.nodes.get(args.idx).unwrap(); - let perm = args.loc.perms.entry(args.idx); - - let perm = perm.get().copied().unwrap_or_else(|| node.default_location_state()); - if global.borrow().protected_tags.get(&node.tag) - == Some(&ProtectorKind::StrongProtector) - // Don't check for protector if it is a Cell (see `unsafe_cell_deallocate` in `interior_mutability.rs`). - // Related to https://github.com/rust-lang/rust/issues/55005. - && !perm.permission.is_cell() - // Only trigger UB if the accessed bit is set, i.e. if the protector is actually protecting this offset. See #4579. - && perm.accessed - { - Err(TbError { - conflicting_info: &node.debug_info, - access_cause: diagnostics::AccessCause::Dealloc, - alloc_id, - error_offset: loc_range.start, - error_kind: TransitionError::ProtectedDealloc, - accessed_info: match prov { - ProvenanceExtra::Concrete(_) => - Some(&args.nodes.get(start_idx).unwrap().debug_info), - // We don't know from where the access came during a wildcard access. - ProvenanceExtra::Wildcard => None, - }, + // Checks the tree containing `idx` for strong protector violations. + // It does this in traversal order. + let mut check_tree = |idx| { + TreeVisitor { nodes: &mut self.nodes, loc }.traverse_this_parents_children_other( + idx, + // Visit all children, skipping none. + |_| ContinueTraversal::Recurse, + |args: NodeAppArgs<'_>| { + let node = args.nodes.get(args.idx).unwrap(); + + let perm = args + .loc + .perms + .get(args.idx) + .copied() + .unwrap_or_else(|| node.default_location_state()); + if global.borrow().protected_tags.get(&node.tag) + == Some(&ProtectorKind::StrongProtector) + // Don't check for protector if it is a Cell (see `unsafe_cell_deallocate` in `interior_mutability.rs`). + // Related to https://github.com/rust-lang/rust/issues/55005. + && !perm.permission.is_cell() + // Only trigger UB if the accessed bit is set, i.e. if the protector is actually protecting this offset. See #4579. + && perm.accessed + { + Err(TbError { + conflicting_info: &node.debug_info, + access_cause: diagnostics::AccessCause::Dealloc, + alloc_id, + error_offset: loc_range.start, + error_kind: TransitionError::ProtectedDealloc, + accessed_info: start_idx + .map(|idx| &args.nodes.get(idx).unwrap().debug_info), + } + .build()) + } else { + Ok(()) } - .build()) - } else { - Ok(()) - } - }, - )?; + }, + ) + }; + // If we have a start index we first check its subtree in traversal order. + // This results in us showing the error of the closest node instead of an + // arbitrary one. + let accessed_root = start_idx.map(&mut check_tree).transpose()?; + // Afterwards we check all other trees. + // We iterate over the list in reverse order to ensure that we do not visit + // a parent before its child. + for &root in self.roots.iter().rev() { + if Some(root) == accessed_root { + continue; + } + check_tree(root)?; + } } interp_ok(()) } @@ -849,20 +926,20 @@ impl<'tcx> Tree { span: Span, // diagnostics ) -> InterpResult<'tcx> { #[cfg(feature = "expensive-consistency-checks")] - if matches!(prov, ProvenanceExtra::Wildcard) { + if self.roots.len() > 1 || matches!(prov, ProvenanceExtra::Wildcard) { self.verify_wildcard_consistency(global); } + let source_idx = match prov { ProvenanceExtra::Concrete(tag) => Some(self.tag_mapping.get(&tag).unwrap()), ProvenanceExtra::Wildcard => None, }; - 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 (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) { loc.perform_access( - self.root, + self.roots.iter().copied(), &mut self.nodes, source_idx, loc_range, @@ -898,7 +975,7 @@ impl<'tcx> Tree { { let access_cause = diagnostics::AccessCause::FnExit(access_kind); loc.perform_access( - self.root, + self.roots.iter().copied(), &mut self.nodes, Some(source_idx), loc_range, @@ -920,7 +997,9 @@ impl<'tcx> Tree { /// Integration with the BorTag garbage collector impl Tree { pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet) { - self.remove_useless_children(self.root, live_tags); + for i in 0..(self.roots.len()) { + self.remove_useless_children(self.roots[i], live_tags); + } // Right after the GC runs is a good moment to check if we can // 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, @@ -1073,20 +1152,20 @@ impl<'tcx> LocationTree { /// * `visit_children`: Whether to skip updating the children of `access_source`. fn perform_access( &mut self, - root: UniIndex, + roots: impl Iterator, nodes: &mut UniValMap, access_source: Option, - loc_range: Range, - access_range: Option, + loc_range: Range, // diagnostics + access_range: Option, // diagnostics access_kind: AccessKind, - access_cause: diagnostics::AccessCause, + access_cause: diagnostics::AccessCause, // diagnostics global: &GlobalState, alloc_id: AllocId, // diagnostics span: Span, // diagnostics visit_children: ChildrenVisitMode, ) -> InterpResult<'tcx> { - if let Some(idx) = access_source { - self.perform_normal_access( + let accessed_root = if let Some(idx) = access_source { + Some(self.perform_normal_access( idx, nodes, loc_range.clone(), @@ -1097,13 +1176,38 @@ impl<'tcx> LocationTree { alloc_id, span, visit_children, - ) + )?) } else { - // `SkipChildrenOfAccessed` only gets set on protector release. - // Since a wildcard reference are never protected this assert shouldn't fail. + // `SkipChildrenOfAccessed` only gets set on protector release, which only + // occurs on a known node. assert!(matches!(visit_children, ChildrenVisitMode::VisitChildrenOfAccessed)); + None + }; + + let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag); + if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed) { + // FIXME: approximate which roots could be children of the accessed node and only skip them instead of all other trees. + return interp_ok(()); + } + for root in roots { + // We don't perform a wildcard access on the tree we already performed a + // normal access on. + if Some(root) == accessed_root { + continue; + } + // The choice of `max_local_tag` requires some thought. + // This can only be a local access for nodes that are a parent of the accessed node + // and are therefore smaller, so the accessed node itself is a valid choice for `max_local_tag`. + // However, using `accessed_root` is better since that will be smaller. It is still a valid choice + // because for nodes *in other trees*, if they are a parent of the accessed node then they + // are a parent of `accessed_root`. + // + // As a consequence of this, since the root of the main tree is the smallest tag in the entire + // allocation, if the access occurred in the main tree then other subtrees will only see foreign accesses. self.perform_wildcard_access( root, + access_source, + /*max_local_tag*/ accessed_root_tag, nodes, loc_range.clone(), access_range, @@ -1112,11 +1216,14 @@ impl<'tcx> LocationTree { global, alloc_id, span, - ) + )?; } + interp_ok(()) } /// Performs a normal access on the tree containing `access_source`. + /// + /// Returns the root index of this tree. /// * `access_source`: The index of the tag being accessed. /// * `visit_children`: Whether to skip the children of `access_source` /// during the access. Used for protector end access. @@ -1124,15 +1231,15 @@ impl<'tcx> LocationTree { &mut self, access_source: UniIndex, nodes: &mut UniValMap, - loc_range: Range, - access_range: Option, + loc_range: Range, // diagnostics + access_range: Option, // diagnostics access_kind: AccessKind, - access_cause: diagnostics::AccessCause, + access_cause: diagnostics::AccessCause, // diagnostics global: &GlobalState, alloc_id: AllocId, // diagnostics span: Span, // diagnostics visit_children: ChildrenVisitMode, - ) -> InterpResult<'tcx> { + ) -> InterpResult<'tcx, UniIndex> { // Performs the per-node work: // - insert the permission if it does not exist // - perform the access @@ -1141,7 +1248,7 @@ impl<'tcx> LocationTree { // - skip the traversal of the children in some cases // - do not record noop transitions // - // `perms_range` is only for diagnostics (it is the range of + // `loc_range` is only for diagnostics (it is the range of // the `RangeMap` on which we are currently working). let node_skipper = |args: &NodeAppArgs<'_>| -> ContinueTraversal { let node = args.nodes.get(args.idx).unwrap(); @@ -1150,7 +1257,7 @@ impl<'tcx> LocationTree { let old_state = perm.copied().unwrap_or_else(|| node.default_location_state()); old_state.skip_if_known_noop(access_kind, args.rel_pos) }; - let node_app = |args: NodeAppArgs<'_>| -> Result<(), _> { + let node_app = |args: NodeAppArgs<'_>| { let node = args.nodes.get_mut(args.idx).unwrap(); let mut perm = args.loc.perms.entry(args.idx); @@ -1164,7 +1271,7 @@ impl<'tcx> LocationTree { &mut args.loc.wildcard_accesses, access_kind, access_cause, - /* access_range */ access_range, + access_range, args.rel_pos, span, loc_range.clone(), @@ -1182,6 +1289,7 @@ impl<'tcx> LocationTree { .build() }) }; + let visitor = TreeVisitor { nodes, loc: self }; match visit_children { ChildrenVisitMode::VisitChildrenOfAccessed => @@ -1191,31 +1299,61 @@ impl<'tcx> LocationTree { } .into() } + /// Performs a wildcard access on the tree with root `root`. Takes the `access_relatedness` /// for each node from the `WildcardState` datastructure. /// * `root`: Root of the tree being accessed. + /// * `access_source`: the index of the accessed tag, if any. + /// This is only used for printing the correct tag on errors. + /// * `max_local_tag`: The access can only be local for nodes whose tag is + /// at most `max_local_tag`. fn perform_wildcard_access( &mut self, root: UniIndex, + access_source: Option, + max_local_tag: Option, nodes: &mut UniValMap, - loc_range: Range, - access_range: Option, + loc_range: Range, // diagnostics + access_range: Option, // diagnostics access_kind: AccessKind, - access_cause: diagnostics::AccessCause, + access_cause: diagnostics::AccessCause, // diagnostics global: &GlobalState, alloc_id: AllocId, // diagnostics span: Span, // diagnostics ) -> InterpResult<'tcx> { - let f_continue = - |idx: UniIndex, nodes: &UniValMap, loc: &LocationTree| -> ContinueTraversal { - let node = nodes.get(idx).unwrap(); - let perm = loc.perms.get(idx); - let wildcard_state = loc.wildcard_accesses.get(idx).cloned().unwrap_or_default(); + let get_relatedness = |idx: UniIndex, node: &Node, loc: &LocationTree| { + let wildcard_state = loc.wildcard_accesses.get(idx).cloned().unwrap_or_default(); + // If the tag is larger than `max_local_tag` then the access can only be foreign. + let only_foreign = max_local_tag.is_some_and(|max_local_tag| max_local_tag < node.tag); + wildcard_state.access_relatedness(access_kind, only_foreign) + }; + + // This does a traversal across the tree updating children before their parents. The + // difference to `perform_normal_access` is that we take the access relatedness from + // the wildcard tracking state of the node instead of from the visitor itself. + // + // Unlike for a normal access, the iteration order is important for improving the + // accuracy of wildcard accesses if `max_local_tag` is `Some`: processing the effects of this + // access further down the tree can cause exposed nodes to lose permissions, thus updating + // the wildcard data structure, which will be taken into account when processing the parent + // nodes. Also see the test `cross_tree_update_older_invalid_exposed2.rs` + // (Doing accesses in the opposite order cannot help with precision but the reasons are complicated; + // see .) + // + // Note, however, that this is an approximation: there can be situations where a node is + // marked as having an exposed foreign node, but actually that foreign node cannot be + // the source of the access due to `max_local_tag`. The wildcard tracking cannot know + // about `max_local_tag` so we will incorrectly assume that this might be a foreign access. + TreeVisitor { loc: self, nodes }.traverse_children_this( + root, + |args| -> ContinueTraversal { + let node = args.nodes.get(args.idx).unwrap(); + let perm = args.loc.perms.get(args.idx); 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. - if let Some(relatedness) = wildcard_state.access_relatedness(access_kind) + if let Some(relatedness) = get_relatedness(args.idx, node, args.loc) && let Some(relatedness) = relatedness.to_relatedness() { // We can use the usual SIFA machinery to skip nodes. @@ -1223,78 +1361,64 @@ impl<'tcx> LocationTree { } else { ContinueTraversal::Recurse } - }; - // 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: self, nodes } - .traverse_this_parents_children_other( - root, - |args| f_continue(args.idx, args.nodes, args.loc), - |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 so this. - // Since we always check the root first, this means it should always - // fail on the root. - assert_eq!(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. - // This is an imprecision! In the future, maybe we can still do some sort - // of best-effort update here. - return Ok(()); - }; - // We know the exact relatedness, so we can actually do precise checks. - perm.perform_transition( - args.idx, - args.nodes, - &mut args.loc.wildcard_accesses, - access_kind, + }, + |args| { + let node = args.nodes.get_mut(args.idx).unwrap(); + + let protected = global.borrow().protected_tags.contains_key(&node.tag); + + let Some(wildcard_relatedness) = get_relatedness(args.idx, node, args.loc) else { + // There doesn't exist a valid exposed reference for this access to + // happen through. + // This can only happen if `root` is the main root: We set + // `max_foreign_access==Write` on all wildcard roots, so at least a foreign access + // is always possible on all nodes in a wildcard subtree. + return Err(no_valid_exposed_references_error( + alloc_id, + loc_range.start, access_cause, - 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, - // We don't know from where the access came during a wildcard access. - accessed_info: None, - } - .build() - }) - }, - ) - .into() + )); + }; + + 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. + // This is an imprecision! In the future, maybe we can still do some sort + // of best-effort update here. + return Ok(()); + }; + + let mut entry = args.loc.perms.entry(args.idx); + let perm = entry.or_insert(node.default_location_state()); + // We know the exact relatedness, so we can actually do precise checks. + perm.perform_transition( + args.idx, + args.nodes, + &mut args.loc.wildcard_accesses, + access_kind, + access_cause, + 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: access_source + .map(|idx| &args.nodes.get(idx).unwrap().debug_info), + } + .build() + }) + }, + )?; + interp_ok(()) } } @@ -1309,10 +1433,11 @@ impl Node { 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)); - + // To ensure that the roots never get removed, we visit them. + // FIXME: it should be possible to GC wildcard tree roots. + for id in self.roots.iter().copied() { + visit(None, Some(self.nodes.get(id).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() { diff --git a/src/borrow_tracker/tree_borrows/wildcard.rs b/src/borrow_tracker/tree_borrows/wildcard.rs index 56a85e6f4c..3b55a9e36e 100644 --- a/src/borrow_tracker/tree_borrows/wildcard.rs +++ b/src/borrow_tracker/tree_borrows/wildcard.rs @@ -88,10 +88,26 @@ impl WildcardState { } /// 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 { + /// If `only_foreign` is true then we treat `LocalAccess` as impossible. This means we return + /// `None` if only a `LocalAccess` is possible, and we treat `EitherAccess` as a + /// `ForeignAccess`. + pub fn access_relatedness( + &self, + kind: AccessKind, + only_foreign: bool, + ) -> Option { + let rel = match kind { AccessKind::Read => self.read_access_relatedness(), AccessKind::Write => self.write_access_relatedness(), + }; + if only_foreign { + use WildcardAccessRelatedness as E; + match rel { + Some(E::EitherAccess | E::ForeignAccess) => Some(E::ForeignAccess), + Some(E::LocalAccess) | None => None, + } + } else { + rel } } @@ -131,6 +147,15 @@ impl WildcardState { ..Default::default() } } + /// Crates the initial `WildcardState` for a wildcard root. + /// This has `max_foreign_access==Write` as it actually is the child of *some* exposed node + /// through which we can receive foreign accesses. + /// + /// This is different from the main root which has `max_foreign_access==None`, since there + /// cannot be a foreign access to the root of the allocation. + pub fn for_wildcard_root() -> Self { + Self { max_foreign_access: WildcardAccessLevel::Write, ..Default::default() } + } /// Pushes the nodes of `children` onto the stack who's `max_foreign_access` /// needs to be updated. @@ -435,6 +460,10 @@ 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) { + // We rely on the fact that `roots` is ordered according to tag from low to high. + assert!(self.roots.is_sorted_by_key(|idx| self.nodes.get(*idx).unwrap().tag)); + let main_root_idx = self.roots[0]; + let protected_tags = &global.borrow().protected_tags; for (_, loc) in self.locations.iter_all() { let wildcard_accesses = &loc.wildcard_accesses; @@ -447,7 +476,8 @@ impl Tree { let state = wildcard_accesses.get(id).unwrap(); let expected_exposed_as = if node.is_exposed { - let perm = perms.get(id).unwrap(); + let perm = + perms.get(id).copied().unwrap_or_else(|| node.default_location_state()); perm.permission() .strongest_allowed_child_access(protected_tags.contains_key(&node.tag)) @@ -477,7 +507,16 @@ impl Tree { .max(parent_state.max_foreign_access) .max(parent_state.exposed_as) } else { - WildcardAccessLevel::None + if main_root_idx == id { + // There can never be a foreign access to the root of the allocation. + // So its foreign access level is always `None`. + WildcardAccessLevel::None + } else { + // For wildcard roots any access on a different subtree can be foreign + // to it. So a wildcard root has the maximum possible foreign access + // level. + WildcardAccessLevel::Write + } }; // Count how many children can be the source of wildcard reads or writes diff --git a/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs b/tests/fail/both_borrows/illegal_read_despite_exposed1.rs similarity index 70% rename from tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs rename to tests/fail/both_borrows/illegal_read_despite_exposed1.rs index 76516b7d92..8cd72b589b 100644 --- a/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs +++ b/tests/fail/both_borrows/illegal_read_despite_exposed1.rs @@ -1,4 +1,6 @@ +//@revisions: stack tree //@compile-flags: -Zmiri-permissive-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows fn main() { unsafe { @@ -12,6 +14,8 @@ fn main() { // And we test that it has uniqueness by doing a conflicting write. *exposed_ptr = 0; // Stack: Unknown( at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC | LL | let _val = *root2; | ^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4] @@ -7,12 +7,12 @@ LL | let _val = *root2; = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: was created by a Unique retag at offsets [0x0..0x4] - --> tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC | LL | let root2 = &mut *exposed_ptr; | ^^^^^^^^^^^^^^^^^ help: was later invalidated at offsets [0x0..0x4] by a write access - --> tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC | LL | *exposed_ptr = 0; | ^^^^^^^^^^^^^^^^ diff --git a/tests/fail/both_borrows/illegal_read_despite_exposed1.tree.stderr b/tests/fail/both_borrows/illegal_read_despite_exposed1.tree.stderr new file mode 100644 index 0000000000..e6c9a142c3 --- /dev/null +++ b/tests/fail/both_borrows/illegal_read_despite_exposed1.tree.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC + | +LL | let _val = *root2; + | ^^^^^^ 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/both_borrows/illegal_read_despite_exposed1.rs:LL:CC + | +LL | let root2 = &mut *exposed_ptr; + | ^^^^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/both_borrows/illegal_read_despite_exposed1.rs:LL:CC + | +LL | *exposed_ptr = 0; + | ^^^^^^^^^^^^^^^^ + = 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/stacked_borrows/illegal_read_despite_exposed2.rs b/tests/fail/both_borrows/illegal_read_despite_exposed2.rs similarity index 65% rename from tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs rename to tests/fail/both_borrows/illegal_read_despite_exposed2.rs index 97e0bf40c0..9e8c94031c 100644 --- a/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs +++ b/tests/fail/both_borrows/illegal_read_despite_exposed2.rs @@ -1,4 +1,6 @@ +//@revisions: stack tree //@compile-flags: -Zmiri-permissive-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows fn main() { unsafe { @@ -7,6 +9,8 @@ fn main() { let exposed_ptr = addr as *mut i32; // From the exposed ptr, we get a new unique ptr. let root2 = &mut *exposed_ptr; + // Activate the reference (unnecessary on Stacked Borrows). + *root2 = 42; // let _fool = root2 as *mut _; // this would fool us, since SRW(N+1) remains on the stack // Stack: Unknown( at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs:LL:CC +error: Undefined Behavior: attempting a write access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC | -LL | let _val = *root2; - | ^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4] +LL | *root2 = 3; + | ^^^^^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: was created by a Unique retag at offsets [0x0..0x4] - --> tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC | LL | let root2 = &mut *exposed_ptr; | ^^^^^^^^^^^^^^^^^ help: was later invalidated at offsets [0x0..0x4] by a read access - --> tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs:LL:CC + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC | LL | let _val = *exposed_ptr; | ^^^^^^^^^^^^ diff --git a/tests/fail/both_borrows/illegal_read_despite_exposed2.tree.stderr b/tests/fail/both_borrows/illegal_read_despite_exposed2.tree.stderr new file mode 100644 index 0000000000..17ae8bc513 --- /dev/null +++ b/tests/fail/both_borrows/illegal_read_despite_exposed2.tree.stderr @@ -0,0 +1,31 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC + | +LL | *root2 = 3; + | ^^^^^^^^^^ 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 Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC + | +LL | let root2 = &mut *exposed_ptr; + | ^^^^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Unique due to a child write access at offsets [0x0..0x4] + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC + | +LL | *root2 = 42; + | ^^^^^^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the accessed tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4] + --> tests/fail/both_borrows/illegal_read_despite_exposed2.rs:LL:CC + | +LL | let _val = *exposed_ptr; + | ^^^^^^^^^^^^ + = 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/stacked_borrows/illegal_write_despite_exposed1.rs b/tests/fail/both_borrows/illegal_write_despite_exposed1.rs similarity index 69% rename from tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs rename to tests/fail/both_borrows/illegal_write_despite_exposed1.rs index 0e34c5c98f..a6c67f8998 100644 --- a/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs +++ b/tests/fail/both_borrows/illegal_write_despite_exposed1.rs @@ -1,4 +1,6 @@ +//@revisions: stack tree //@compile-flags: -Zmiri-permissive-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows fn main() { unsafe { @@ -12,6 +14,9 @@ fn main() { // (The write is still fine, using the `root as *mut i32` provenance which got exposed.) *exposed_ptr = 0; // Stack: Unknown( at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC | LL | let _val = *root2; | ^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x4] @@ -7,12 +7,12 @@ LL | let _val = *root2; = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: was created by a SharedReadOnly retag at offsets [0x0..0x4] - --> tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC | LL | let root2 = &*exposed_ptr; | ^^^^^^^^^^^^^ help: was later invalidated at offsets [0x0..0x4] by a write access - --> tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs:LL:CC + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC | LL | *exposed_ptr = 0; | ^^^^^^^^^^^^^^^^ diff --git a/tests/fail/both_borrows/illegal_write_despite_exposed1.tree.stderr b/tests/fail/both_borrows/illegal_write_despite_exposed1.tree.stderr new file mode 100644 index 0000000000..2f6f7eb5e9 --- /dev/null +++ b/tests/fail/both_borrows/illegal_write_despite_exposed1.tree.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC + | +LL | let _val = *root2; + | ^^^^^^ 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/both_borrows/illegal_write_despite_exposed1.rs:LL:CC + | +LL | let root2 = &*exposed_ptr; + | ^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/both_borrows/illegal_write_despite_exposed1.rs:LL:CC + | +LL | *exposed_ptr = 0; + | ^^^^^^^^^^^^^^^^ + = 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/cross_tree_from_main.rs b/tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs new file mode 100644 index 0000000000..40099076f6 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs @@ -0,0 +1,36 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks the case where the access is to the main tree. +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; + + let reb3 = unsafe { &mut *wild }; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├───────────┐ * + // │ │ │ │ + // └──────┬─────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌───────────┐ ┌───────────┐ + // │ │ │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res) │ │ reb3(Res) │ + // │ │ │ │ │ │ + // └────────────┘ └───────────┘ └───────────┘ + + // ref2 is part of the main tree and therefore foreign to all subtrees. + // Therefore, this disables reb3. + *ref2 = 13; + + let _fail = *reb3; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/cross_tree_from_main.stderr b/tests/fail/tree_borrows/wildcard/cross_tree_from_main.stderr new file mode 100644 index 0000000000..bdd75027c1 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_from_main.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs:LL:CC + | +LL | let _fail = *reb3; + | ^^^^^ 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/cross_tree_from_main.rs:LL:CC + | +LL | let reb3 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs:LL:CC + | +LL | *ref2 = 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/cross_tree_update_main.rs b/tests/fail/tree_borrows/wildcard/cross_tree_update_main.rs new file mode 100644 index 0000000000..a21dcfe2ac --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_main.rs @@ -0,0 +1,37 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This tests how main is effected by an access through a subtree. +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; + + let reb = unsafe { &mut *wild }; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├───────────┐ * + // │ │ │ │ + // └──────┬─────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌───────────┐ ┌───────────┐ + // │ │ │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res) │ │ reb(Res) │ + // │ │ │ │ │ │ + // └────────────┘ └───────────┘ └───────────┘ + + // Writes through the reborrowed reference causing a wildcard + // write on the main tree. This disables ref2 as it doesn't + // have any exposed children. + *reb = 13; + + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/cross_tree_update_main.stderr b/tests/fail/tree_borrows/wildcard/cross_tree_update_main.stderr new file mode 100644 index 0000000000..285ffc4763 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_main.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_main.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/cross_tree_update_main.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/cross_tree_update_main.rs:LL:CC + | +LL | *reb = 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/cross_tree_update_main_invalid_exposed.rs b/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.rs new file mode 100644 index 0000000000..641ffb4304 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.rs @@ -0,0 +1,44 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from a subtree performs a +/// wildcard access on all earlier trees, and that local +/// accesses are treated as access errors for tags that are +/// larger than the root of the accessed subtree. +pub fn main() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let ref1 = unsafe { &mut *ptr_base }; + + // Activates ref1. + *ref1 = 4; + + let int1 = ref1 as *mut u32 as usize; + let wild = int1 as *mut u32; + + let ref2 = unsafe { &mut *wild }; + + // Freezes ref1. + let ref3 = unsafe { &mut *ptr_base }; + let _int3 = ref3 as *mut u32 as usize; + + // ┌──────────────┐ + // │ │ + // │ptr_base(Act) ├───────────┐ * + // │ │ │ │ + // └──────┬───────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌─────────────┐ ┌────────────┐ ┌───────────┐ + // │ │ │ │ │ │ + // │ ref1(Frz)* │ │ ref3(Res)* │ │ ref2(Res) │ + // │ │ │ │ │ │ + // └─────────────┘ └────────────┘ └───────────┘ + + // Performs a wildcard access on the main root. However, as there are + // no exposed tags with write permissions and a tag smaller than ref2 + // this access fails. + *ref2 = 13; //~ ERROR: /write access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.stderr b/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.stderr new file mode 100644 index 0000000000..3d91ec4db8 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.stderr @@ -0,0 +1,14 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.rs:LL:CC + | +LL | *ref2 = 13; + | ^^^^^^^^^^ 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 which 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/cross_tree_update_newer.rs b/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs new file mode 100644 index 0000000000..a65508c78b --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from an earlier created subtree +/// is foreign to a later created one. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) ├ │ reb2(Res) ├ + // │ │ │ │ + // └──────┬─────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // This access disables reb2 because ref3 cannot be a child of it + // as reb2 both has a higher tag and doesn't have any exposed children. + *ref3 = 13; + + let _fail = *reb2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.stderr b/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.stderr new file mode 100644 index 0000000000..c1ab12f340 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs:LL:CC + | +LL | let _fail = *reb2; + | ^^^^^ 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/cross_tree_update_newer.rs:LL:CC + | +LL | let reb2 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs:LL:CC + | +LL | *ref3 = 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/cross_tree_update_newer_exposed.rs b/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs new file mode 100644 index 0000000000..666eac1e7e --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs @@ -0,0 +1,48 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from an earlier created subtree +/// is foreign to a later created one. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + let _int2 = reb2 as *mut u32 as usize; + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) │ │ reb2(Res)* │ + // │ │ │ │ + // └──────┬─────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // This access disables reb2 because ref3 cannot be a child of it + // as ref3's root has a lower tag than reb2. + *ref3 = 13; + + let _fail = *reb2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.stderr b/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.stderr new file mode 100644 index 0000000000..e1232c7511 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs:LL:CC + | +LL | let _fail = *reb2; + | ^^^^^ 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/cross_tree_update_newer_exposed.rs:LL:CC + | +LL | let reb2 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs:LL:CC + | +LL | *ref3 = 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/cross_tree_update_older.rs b/tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs new file mode 100644 index 0000000000..35fbc30ff5 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from a newer created subtree +/// performs a wildcard access on all earlier trees. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + + let ref3 = &mut *reb2; + let _int3 = ref3 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) ├ │ reb2(Res) ├ + // │ │ │ │ + // └────────────┘ └──────┬─────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // this access disables reb2 because ref3 cannot be a child of it + // as reb1 does not have any exposed children. + *ref3 = 13; + + let _fail = *reb1; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/cross_tree_update_older.stderr b/tests/fail/tree_borrows/wildcard/cross_tree_update_older.stderr new file mode 100644 index 0000000000..54f041b555 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_older.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs:LL:CC + | +LL | let _fail = *reb1; + | ^^^^^ 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/cross_tree_update_older.rs:LL:CC + | +LL | let reb1 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs:LL:CC + | +LL | *ref3 = 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/cross_tree_update_older_invalid_exposed.rs b/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs new file mode 100644 index 0000000000..0f87bb445f --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs @@ -0,0 +1,53 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from a newer created subtree +/// performs a wildcard access on all earlier trees, and that +/// either accesses are treated as foreign for tags that are +/// larger than the root of the accessed subtree. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + + let ref4 = &mut *reb2; + let _int4 = ref4 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) ├ │ reb2(Res) ├ + // │ │ │ │ + // └──────┬─────┘ └──────┬─────┘ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref3(Res)* │ │ ref4(Res)* │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // This access disables ref3 and reb1 because ref4 cannot be a child of it + // as reb2 has a smaller tag than ref3. + *ref4 = 13; + + // Fails because ref3 is disabled. + let _fail = *ref3; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.stderr b/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.stderr new file mode 100644 index 0000000000..66787ef72f --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.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/cross_tree_update_older_invalid_exposed.rs:LL:CC + | +LL | let ref3 = &mut *reb1; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs:LL:CC + | +LL | *ref4 = 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/cross_tree_update_older_invalid_exposed2.rs b/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs new file mode 100644 index 0000000000..f01ac26dc6 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs @@ -0,0 +1,59 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks how accesses from one subtree affect other subtrees. +/// This test checks that an access from a newer created subtree +/// performs a wildcard access on all earlier trees, and that +/// either accesses are treated as foreign for tags that are +/// larger than the root of the accessed subtree. +/// This tests the special case where these updates get propagated +/// up the tree. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + + let reb2 = unsafe { &mut *wild }; + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + + let ref4 = &mut *reb2; + let _int4 = ref4 as *mut u32 as usize; + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) ├ │ reb2(Res) ├ + // │ │ │ │ + // └──────┬─────┘ └──────┬─────┘ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref3(Res)* │ │ ref4(Res)* │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // This access disables ref3 and reb1 because ref4 cannot be a child of it + // as reb2 has a smaller tag than ref3. + // + // Because of the update order during a wildcard access (child before parent) + // ref3 gets disabled before we update reb1. So reb1 has no exposed children + // with write access at the time it gets updated so it also gets disabled. + *ref4 = 13; + + // Fails because reb1 is disabled. + let _fail = *reb1; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.stderr b/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.stderr new file mode 100644 index 0000000000..644d30cae0 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs:LL:CC + | +LL | let _fail = *reb1; + | ^^^^^ 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/cross_tree_update_older_invalid_exposed2.rs:LL:CC + | +LL | let reb1 = unsafe { &mut *wild }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs:LL:CC + | +LL | *ref4 = 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/protected_wildcard.rs b/tests/fail/tree_borrows/wildcard/protected_wildcard.rs new file mode 100644 index 0000000000..18fe931b9e --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/protected_wildcard.rs @@ -0,0 +1,39 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +/// Checks if we pass a reference derived from a wildcard pointer +/// that it gets correctly protected. +pub fn main() { + let mut x: u32 = 32; + let ref1 = &mut x; + + let ref2 = &mut *ref1; + let int2 = ref2 as *mut u32 as usize; + + let wild = int2 as *mut u32; + let wild_ref = unsafe { &mut *wild }; + + let mut protect = |_arg: &mut u32| { + // _arg is a protected pointer with wildcard parent. + + // ┌────────────┐ + // │ │ + // │ ref1(Res) │ * + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref2(Res)* │ │ _arg(Res) │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // Writes to ref1, causing a foreign write to ref2 and _arg. + // Since _arg is protected this is UB. + *ref1 = 13; //~ ERROR: /write access through .* is forbidden/ + }; + + // We pass a pointer with wildcard provenance to the function. + protect(wild_ref); +} diff --git a/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr b/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr new file mode 100644 index 0000000000..e257a3511f --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC + | +LL | *ref1 = 13; + | ^^^^^^^^^^ 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 foreign to the protected tag (i.e., it is not a child) + = help: this foreign write access would cause the protected tag (currently Reserved) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC + | +LL | let mut protect = |_arg: &mut u32| { + | _______________________^ +... | +LL | | *ref1 = 13; +LL | | }; + | |_____^ +help: the protected tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC + | +LL | let mut protect = |_arg: &mut u32| { + | ^^^^ + = note: BACKTRACE (of the first span): + = note: inside closure at tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC +note: inside `main` + --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC + | +LL | protect(wild_ref); + | ^^^^^^^^^^^^^^^^^ + +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/strongly_protected_wildcard.stderr b/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr index de92cf91e3..6e115b22fe 100644 --- a/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr +++ b/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr @@ -1,4 +1,4 @@ -error: Undefined Behavior: deallocation through at ALLOC[0x0] is forbidden +error: Undefined Behavior: deallocation through at ALLOC[0x0] is forbidden --> RUSTLIB/alloc/src/boxed.rs:LL:CC | LL | self.1.deallocate(From::from(ptr.cast()), layout); @@ -6,8 +6,13 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); | = 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 allocation of the accessed tag also contains the strongly protected tag + = help: the allocation of the accessed tag also contains the strongly protected tag = help: the strongly protected tag disallows deallocations +help: the accessed tag was created here + --> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC + | +LL | drop(unsafe { Box::from_raw(raw as *mut i32) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: the strongly protected tag was created here, in the initial state Reserved --> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC | diff --git a/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs b/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs new file mode 100644 index 0000000000..7ce243abce --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs @@ -0,0 +1,44 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +// Checks if we correctly infer the relatedness of nodes that are +// part of the same wildcard root. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int1 = ref_base as *mut u32 as usize; + let wild = int1 as *mut u32; + + let reb = unsafe { &mut *wild }; + let ptr_reb = reb as *mut u32; + let ref1 = unsafe { &mut *ptr_reb }; + let ref2 = unsafe { &mut *ptr_reb }; + + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * + // │ │ │ + // └──────────────┘ │ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ reb(Res) ├───────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌───────────┐ + // │ │ │ │ + // │ ref1(Res) │ │ ref2(Res) │ + // │ │ │ │ + // └────────────┘ └───────────┘ + + // ref1 is foreign to ref2, so this should disable ref2. + *ref1 = 13; + + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.stderr b/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.stderr new file mode 100644 index 0000000000..4b4f73a73b --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.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/subtree_internal_relatedness.rs:LL:CC + | +LL | let ref2 = unsafe { &mut *ptr_reb }; + | ^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs:LL:CC + | +LL | *ref1 = 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/subtree_internal_relatedness_wildcard.rs b/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs new file mode 100644 index 0000000000..7b115f788a --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs @@ -0,0 +1,49 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +// Checks if we correctly infer the relatedness of nodes that are +// part of the same wildcard root during a wildcard access. +pub fn main() { + let mut x: u32 = 42; + + let ref_base = &mut x; + + let int = ref_base as *mut u32 as usize; + let wild = int as *mut u32; + + let reb = unsafe { &mut *wild }; + let ptr_reb = reb as *mut u32; + let ref1 = unsafe { &mut *ptr_reb }; + let _int1 = ref1 as *mut u32 as usize; + let ref2 = unsafe { &mut *ptr_reb }; + + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * + // │ │ │ + // └──────────────┘ │ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ reb(Res) ├───────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌───────────┐ + // │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res) │ + // │ │ │ │ + // └────────────┘ └───────────┘ + + // Writes either through ref1 or ptr_base. + // This disables ref2 as the access is foreign to it in either case. + unsafe { *wild = 13 }; + + // This is fine because the earlier write could have come from ref1. + let _succ = *ref1; + // ref2 is disabled so this fails. + let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.stderr b/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.stderr new file mode 100644 index 0000000000..e1a8bebe91 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.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/subtree_internal_relatedness_wildcard.rs:LL:CC + | +LL | let ref2 = unsafe { &mut *ptr_reb }; + | ^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs:LL:CC + | +LL | unsafe { *wild = 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/write_to_shr.stderr b/tests/fail/tree_borrows/write_to_shr.stderr deleted file mode 100644 index f57b28cbf4..0000000000 --- a/tests/fail/tree_borrows/write_to_shr.stderr +++ /dev/null @@ -1,26 +0,0 @@ -error: Undefined Behavior: write access through is forbidden - --> $DIR/write_to_shr.rs:LL:CC - | -LL | *xmut = 31; - | ^^^^^^^^^^ write access through is forbidden - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental - = help: the accessed tag is a child of the conflicting tag - = help: the conflicting tag has state Frozen which forbids child write accesses -help: the accessed tag was created here - --> $DIR/write_to_shr.rs:LL:CC - | -LL | let xmut = unsafe { &mut *(xref as *const u64 as *mut u64) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: the conflicting tag was created here, in the initial state Frozen - --> $DIR/write_to_shr.rs:LL:CC - | -LL | let xref = unsafe { &*(x as *mut u64) }; - | ^^^^^^^^^^^^^^^^^ - = note: BACKTRACE (of the first span): - = note: inside `main` at $DIR/write_to_shr.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/tests/pass/tree_borrows/wildcard/formatting.rs b/tests/pass/tree_borrows/wildcard/formatting.rs new file mode 100644 index 0000000000..1f6b797a3d --- /dev/null +++ b/tests/pass/tree_borrows/wildcard/formatting.rs @@ -0,0 +1,36 @@ +// We disable the GC for this test because it would change what is printed. +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance -Zmiri-provenance-gc=0 + +#[path = "../../../utils/mod.rs"] +#[macro_use] +mod utils; + +fn main() { + unsafe { + let x = &0u8; + name!(x); + let xa = &*x; + name!(xa); + let xb = &*x; + name!(xb); + let wild = xb as *const u8 as usize as *const u8; + + let y = &*wild; + name!(y); + let ya = &*y; + name!(ya); + let yb = &*y; + name!(yb); + let _int = ya as *const u8 as usize; + + let z = &*wild; + name!(z); + + let u = &*wild; + name!(u); + let ua = &*u; + name!(ua); + let alloc_id = alloc_id!(x); + print_state!(alloc_id); + } +} diff --git a/tests/pass/tree_borrows/wildcard/formatting.stderr b/tests/pass/tree_borrows/wildcard/formatting.stderr new file mode 100644 index 0000000000..583c845343 --- /dev/null +++ b/tests/pass/tree_borrows/wildcard/formatting.stderr @@ -0,0 +1,17 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act | └─┬── +| Frz | └─┬── +| Frz | ├──── +| Frz | └──── (exposed) +| | +| Frz | *─┬── +| Frz | ├──── (exposed) +| Frz | └──── +| | +| Frz | *──── +| | +| Frz | *─┬── +| Frz | └──── +────────────────────────────────────────────────── diff --git a/tests/pass/tree_borrows/wildcard/reborrow.rs b/tests/pass/tree_borrows/wildcard/reborrow.rs new file mode 100644 index 0000000000..fbb50d5acd --- /dev/null +++ b/tests/pass/tree_borrows/wildcard/reborrow.rs @@ -0,0 +1,224 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance + +pub fn main() { + multiple_exposed_siblings1(); + multiple_exposed_siblings2(); + reborrow3(); + returned_mut_is_usable(); + only_foreign_is_temporary(); +} + +/// Checks that accessing through a reborrowed wildcard doesn't +/// disable any exposed reference. +fn multiple_exposed_siblings1() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + + let ref1 = unsafe { &mut *ptr_base }; + let int1 = ref1 as *mut u32 as usize; + + let ref2 = unsafe { &mut *ptr_base }; + let _int2 = ref2 as *mut u32 as usize; + + let wild = int1 as *mut u32; + + let reb = unsafe { &mut *wild }; + + // ┌────────────┐ + // │ │ + // │ ptr_base ├────────────┐ * + // │ │ │ │ + // └──────┬─────┘ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌────────────┐ ┌────────────┐ + // │ │ │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res)* │ │ reb(Res) │ + // │ │ │ │ │ │ + // └────────────┘ └────────────┘ └────────────┘ + + // Could either have as a parent ref1 or ref2. + // So we can't disable either of them. + *reb = 13; + + // We can still access either ref1 or ref2. + // Although it is actually UB to access both of them. + assert_eq!(*ref2, 13); + assert_eq!(*ref1, 13); +} + +/// Checks that wildcard accesses do not invalidate any exposed +/// nodes through which the access could have happened. +/// It checks this for the case where some reborrowed wildcard +/// pointers are exposed as well. +fn multiple_exposed_siblings2() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let int = ptr_base as usize; + + let wild = int as *mut u32; + + let reb_ptr = unsafe { &mut *wild } as *mut u32; + + let ref1 = unsafe { &mut *reb_ptr }; + let _int1 = ref1 as *mut u32 as usize; + + let ref2 = unsafe { &mut *reb_ptr }; + let _int2 = ref2 as *mut u32 as usize; + + // ┌────────────┐ + // │ │ + // │ ptr_base* │ * + // │ │ │ + // └────────────┘ │ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ reb ├────────────┐ + // │ │ │ + // └──────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ ref1(Res)* │ │ ref2(Res)* │ + // │ │ │ │ + // └────────────┘ └────────────┘ + + // Writes either through ref1, ref2 or ptr_base, which are all exposed. + // Since we don't know which we do not apply any transitions to any of + // the references. + unsafe { wild.write(13) }; + + // We should be able to access either ref1 or ref2. + // Although it is actually UB to access ref1 and ref2 together. + assert_eq!(*ref2, 13); + assert_eq!(*ref1, 13); +} + +/// Checks that accessing a reborrowed wildcard reference doesn't +/// invalidate other reborrowed wildcard references, if they +/// are also exposed. +fn reborrow3() { + let mut x: u32 = 42; + + let ptr_base = &mut x as *mut u32; + let int = ptr_base as usize; + + let wild = int as *mut u32; + + let reb1 = unsafe { &mut *wild }; + let ref2 = &mut *reb1; + let _int = ref2 as *mut u32 as usize; + + let reb3 = unsafe { &mut *wild }; + + // ┌────────────┐ + // │ │ + // │ ptr_base* │ * * + // │ │ │ │ + // └────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Res) | │ reb3(Res) | + // │ │ │ │ + // └──────┬─────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref2(Res)* │ + // │ │ + // └────────────┘ + + // This is the only valid ordering these accesses can happen in. + + // reb3 could be a child of ref2 so we don't disable ref2, reb1. + *reb3 = 1; + // Disables reb3 as it cannot be an ancestor of ref2. + *ref2 = 2; + // Disables ref2 (and reb3 if it wasn't already). + *reb1 = 3; +} + +/// Analogous to same test in `../tree-borrows.rs` but with returning a +/// reborrowed wildcard reference. +fn returned_mut_is_usable() { + let mut x: u32 = 32; + let ref1 = &mut x; + + let y = protect(ref1); + + fn protect(arg: &mut u32) -> &mut u32 { + // Reborrow `arg` through a wildcard. + let int = arg as *mut u32 as usize; + let wild = int as *mut u32; + let ref2 = unsafe { &mut *wild }; + + // Activate the reference so that it is vulnerable to foreign reads. + *ref2 = 42; + + ref2 + // An implicit read through `arg` is inserted here. + } + + *y = 4; +} + +/// When accessing an allocation through a tag that was created from wildcard reference +/// we treat nodes with a larger tag as if the access could only have been foreign to them. +/// This change in access relatedness should not be visible in later accesses. +fn only_foreign_is_temporary() { + let mut x = 0u32; + let wild = &mut x as *mut u32 as usize as *mut u32; + + let reb1 = unsafe { &mut *wild }; + let reb2 = unsafe { &mut *wild }; + let ref3 = &mut *reb1; + let _int = ref3 as *mut u32 as usize; + + let reb4 = unsafe { &mut *wild }; + + // + // + // * * * + // │ │ │ + // │ │ │ + // │ │ │ + // │ │ │ + // ▼ ▼ ▼ + // ┌────────────┐ ┌────────────┐ ┌────────────┐ + // │ │ │ │ │ │ + // │ reb1(Res) │ │ reb2(Res) │ │ reb4(Res) │ + // │ │ │ │ │ │ + // └──────┬─────┘ └────────────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // Performs a foreign read on ref3 and doesn't update reb1. + // This temporarily treats ref3 as if only foreign accesses are possible to + // it. This is because the accessed tag reb2 has a larger tag than ref3. + let _x = *reb2; + // Should not update ref3, reb1 as we don't know if the access is local or foreign. + // This should stop treating ref3 as only foreign because the accessed tag reb4 + // has a larger tag than ref3. + *reb4 = 32; + // The previous write could have been local to ref3, so this access should still work. + *ref3 = 4; +} diff --git a/tests/pass/tree_borrows/wildcard/undetected_ub.rs b/tests/pass/tree_borrows/wildcard/undetected_ub.rs index c34fbcb501..73392543f7 100644 --- a/tests/pass/tree_borrows/wildcard/undetected_ub.rs +++ b/tests/pass/tree_borrows/wildcard/undetected_ub.rs @@ -4,7 +4,7 @@ pub fn main() { uncertain_provenance(); protected_exposed(); - protected_wildcard(); + cross_tree_update_older_invalid_exposed(); } /// Currently, if we do not know for a tag if an access is local or foreign, @@ -101,45 +101,61 @@ pub fn protected_exposed() { } protect(ref1); - // ref2 is disabled, so this read causes UB, but we currently don't protect this. + // ref2 should be disabled, so this read causes UB, but we currently don't detect 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); +/// Checks how accesses from one subtree affect other subtrees. +/// This test shows an example where we don't update a node whose exposed +/// children are greater than `max_local_tag`. +pub fn cross_tree_update_older_invalid_exposed() { + let mut x: [u32; 2] = [42, 43]; + + let ref_base = &mut x; + + let int0 = ref_base as *mut u32 as usize; + let wild = int0 as *mut u32; + + let reb1 = unsafe { &mut *wild }; + *reb1 = 44; + + // We need this reference to be at a different location, + // so that creating it doesn't freeze reb1. + let reb2 = unsafe { &mut *wild.wrapping_add(1) }; + let reb2_ptr = (reb2 as *mut u32).wrapping_sub(1); + + let ref3 = &mut *reb1; + let _int3 = ref3 as *mut u32 as usize; + + // ┌──────────────┐ + // │ │ + // │ptr_base(Res)*│ * * + // │ │ │ │ + // └──────────────┘ │ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌────────────┐ ┌────────────┐ + // │ │ │ │ + // │ reb1(Act) │ │ reb2(Res) │ + // │ │ │ │ + // └──────┬─────┘ └────────────┘ + // │ + // │ + // ▼ + // ┌────────────┐ + // │ │ + // │ ref3(Res)* │ + // │ │ + // └────────────┘ + + // This access doesn't freeze reb1 even though no access could have come from its + // child ref3 (since ref3>reb2). This is because ref3 doesnt get disabled during this + // access. + // + // ref3 doesn't get frozen because it's still reserved. + let _y = unsafe { *reb2_ptr }; + + // reb1 should be frozen so a write should be UB. But we currently don't detect this. + *reb1 = 4; } diff --git a/tests/pass/tree_borrows/wildcard/wildcard.rs b/tests/pass/tree_borrows/wildcard/wildcard.rs index c406bfe01f..01385313dc 100644 --- a/tests/pass/tree_borrows/wildcard/wildcard.rs +++ b/tests/pass/tree_borrows/wildcard/wildcard.rs @@ -151,7 +151,6 @@ fn protector_conflicted_release() { /// 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. diff --git a/tests/utils/macros.rs b/tests/utils/macros.rs index 3f5b9f78ee..25f40ed994 100644 --- a/tests/utils/macros.rs +++ b/tests/utils/macros.rs @@ -9,7 +9,7 @@ /// The id obtained can be passed directly to `print_state!`. macro_rules! alloc_id { ($ptr:expr) => { - $crate::utils::miri_get_alloc_id($ptr as *const u8 as *const ()) + $crate::utils::miri_get_alloc_id($ptr as *const _ as *const ()) }; } @@ -52,6 +52,6 @@ macro_rules! name { }; ($ptr:expr => $nth_parent:expr, $name:expr) => { let name = $name.as_bytes(); - $crate::utils::miri_pointer_name($ptr as *const u8 as *const (), $nth_parent, name); + $crate::utils::miri_pointer_name($ptr as *const _ as *const (), $nth_parent, name); }; }