Support retagging of wildcard references in tree borrows
diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs
index c454bb4..a91f35a 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs
+++ b/src/tools/miri/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 @@ fn print_protector(&self, protector: Option<&ProtectorKind>) -> &'static str {
             })
             .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<String>,
+    exposed: bool,
     rperm: Vec<Option<LocationState>>,
     children: Vec<DisplayRepr>,
 }
 
 impl DisplayRepr {
-    fn from(tree: &Tree, show_unnamed: bool) -> Option<Self> {
+    fn from(tree: &Tree, root: UniIndex, show_unnamed: bool) -> Option<Self> {
         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 @@ fn extraction_aux(
         ) {
             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::<Vec<_>>();
                 children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag);
@@ -661,12 +667,13 @@ fn extraction_aux(
                 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<DisplayRepr>,
+        wildcard_subtrees: &[DisplayRepr],
         fmt: &DisplayFmt,
         indenter: &mut DisplayIndent,
         protected_tags: &FxHashMap<BorTag, ProtectorKind>,
@@ -703,15 +710,41 @@ fn print(
             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 @@ fn print_aux(
             indent: &mut DisplayIndent,
             protected_tags: &FxHashMap<BorTag, ProtectorKind>,
             is_last_child: bool,
+            is_wildcard_root: bool,
             acc: &mut Vec<String>,
         ) {
             let mut line = String::new();
@@ -760,7 +794,9 @@ fn print_aux(
             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 @@ fn print_aux(
             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 @@ fn print_aux(
         indent_last: "  ",
         join_haschild: "┬",
         join_default: "─",
+        wildcard_root: "*",
     },
     accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" },
 };
@@ -816,15 +863,27 @@ pub fn print_tree(
     ) -> InterpResult<'tcx> {
         let mut indenter = DisplayIndent::new();
         let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
-        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::<Vec<_>>();
+
+        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/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
index 2a1c98e..e1da122 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
@@ -239,18 +239,14 @@ fn tb_reborrow(
                 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 @@ fn tb_reborrow(
             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 @@ fn tb_reborrow(
                 }
             }
         }
-
         // 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 @@ fn tb_reborrow(
         )?;
         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/tools/miri/src/borrow_tracker/tree_borrows/tree.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs
index 07edf20..7fc9fa7 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs
@@ -91,11 +91,11 @@ fn perform_transition(
         nodes: &mut UniValMap<Node>,
         wildcard_accesses: &mut UniValMap<WildcardState>,
         access_kind: AccessKind,
-        access_cause: AccessCause,
-        access_range: Option<AllocRange>,
+        access_cause: AccessCause,        //diagnostics
+        access_range: Option<AllocRange>, //diagnostics
         relatedness: AccessRelatedness,
-        span: Span,
-        location_range: Range<u64>,
+        span: Span,                 //diagnostics
+        location_range: Range<u64>, //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<Node>,
     /// Associates with each location its state and wildcard access tracking.
     pub(super) locations: DedupRangeMap<LocationTree>,
-    /// 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<NodeContinue, NodeApp> {
 impl<NodeContinue, NodeApp, Err> TreeVisitorStack<NodeContinue, NodeApp>
 where
     NodeContinue: Fn(&NodeAppArgs<'_>) -> ContinueTraversal,
-    NodeApp: Fn(NodeAppArgs<'_>) -> Result<(), Err>,
+    NodeApp: FnMut(NodeAppArgs<'_>) -> Result<(), Err>,
 {
     fn should_continue_at(
         &self,
@@ -405,12 +420,13 @@ fn propagate_at(
         (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<UniIndex, Err> {
         // 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 @@ fn go_upwards_from_accessed(
         }
         // 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<Err>(
         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<UniIndex, Err> {
         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 @@ fn traverse_this_parents_children_other<Err>(
         // 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<Err>(
         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<UniIndex, Err> {
         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 @@ fn traverse_nonchildren<Err>(
         // 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<Err>(
+        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 @@ pub fn new(root_tag: BorTag, size: Size, span: Span) -> Self {
             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<LocationState>,
         outside_perm: Permission,
@@ -647,7 +689,11 @@ pub(super) fn new_child(
         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 @@ pub(super) fn new_child(
             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 @@ pub(super) fn new_child(
                 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 @@ pub(super) fn new_child(
 
         // 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 @@ pub fn dealloc(
             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);
+            // 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 = 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,
-                            },
+                        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 @@ pub fn perform_access(
         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 @@ pub fn perform_access(
                 {
                     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 @@ pub fn perform_access(
 /// Integration with the BorTag garbage collector
 impl Tree {
     pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
-        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<Item = UniIndex>,
         nodes: &mut UniValMap<Node>,
         access_source: Option<UniIndex>,
-        loc_range: Range<u64>,
-        access_range: Option<AllocRange>,
+        loc_range: Range<u64>,            // diagnostics
+        access_range: Option<AllocRange>, // 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 @@ fn perform_access(
                 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 @@ fn perform_access(
                 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 @@ fn perform_normal_access(
         &mut self,
         access_source: UniIndex,
         nodes: &mut UniValMap<Node>,
-        loc_range: Range<u64>,
-        access_range: Option<AllocRange>,
+        loc_range: Range<u64>,            // diagnostics
+        access_range: Option<AllocRange>, // 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 @@ fn perform_normal_access(
         // - 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 @@ fn perform_normal_access(
             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 @@ fn perform_normal_access(
                     &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 @@ fn perform_normal_access(
                     .build()
                 })
         };
+
         let visitor = TreeVisitor { nodes, loc: self };
         match visit_children {
             ChildrenVisitMode::VisitChildrenOfAccessed =>
@@ -1191,31 +1299,61 @@ fn perform_normal_access(
         }
         .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<UniIndex>,
+        max_local_tag: Option<BorTag>,
         nodes: &mut UniValMap<Node>,
-        loc_range: Range<u64>,
-        access_range: Option<AllocRange>,
+        loc_range: Range<u64>,            // diagnostics
+        access_range: Option<AllocRange>, // 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<Node>, 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 <https://github.com/rust-lang/miri/pull/4707#discussion_r2581661123>.)
+        //
+        // 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 @@ fn perform_wildcard_access(
                 } 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());
+            },
+            |args| {
+                let node = args.nodes.get_mut(args.idx).unwrap();
 
-                    let protected = global.borrow().protected_tags.contains_key(&node.tag);
+                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,
+                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 @@ pub fn default_location_state(&self) -> LocationState {
 
 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/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs
index 56a85e6..3b55a9e 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs
@@ -88,10 +88,26 @@ fn max_local_access(&self) -> WildcardAccessLevel {
     }
 
     /// 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<WildcardAccessRelatedness> {
-        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<WildcardAccessRelatedness> {
+        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 @@ pub fn for_new_child(&self) -> Self {
             ..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 @@ pub fn verify_wildcard_consistency(&self, global: &GlobalState) {
                 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 @@ pub fn verify_wildcard_consistency(&self, global: &GlobalState) {
                         .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/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.rs
similarity index 70%
rename from src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs
rename to src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.rs
index 76516b7..8cd72b5 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs
+++ b/src/tools/miri/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(<N)
-        let _val = *root2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+        let _val = *root2;
+        //~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/
+        //~[tree]| ERROR: /read access through .* is forbidden/
     }
 }
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.stack.stderr
similarity index 81%
rename from src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.stderr
rename to src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.stack.stderr
index 065e739..dccbe6a 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.stack.stderr
@@ -1,5 +1,5 @@
 error: Undefined Behavior: attempting a read access using <TAG> 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 @@
    = 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: <TAG> 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: <TAG> 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/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.tree.stderr
new file mode 100644
index 0000000..e6c9a14
--- /dev/null
+++ b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed1.tree.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.rs
similarity index 65%
rename from src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs
rename to src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.rs
index 97e0bf4..9e8c940 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs
+++ b/src/tools/miri/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(<N), Unique(N)
         // Stack if _fool existed: Unknown(<N), Unique(N), SRW(N+1)
@@ -15,6 +19,10 @@ fn main() {
         // Stack: Unknown(<N), Disabled(N)
         // collapsed to Unknown(<N)
         // Stack if _fool existed: Unknown(<N), Disabled(N), SRW(N+1); collapsed to Unknown(<N+2) which would not cause an ERROR
-        let _val = *root2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+
+        // Stack borrows would also fail if we replaced this with a read, but tree borrows would let it pass.
+        *root2 = 3;
+        //~[stack]^ ERROR: /write access .* tag does not exist in the borrow stack/
+        //~[tree]| ERROR: /write access through .* is forbidden/
     }
 }
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.stack.stderr
similarity index 60%
rename from src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.stderr
rename to src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.stack.stderr
index 5643226..c598ff8 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed2.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.stack.stderr
@@ -1,18 +1,18 @@
-error: Undefined Behavior: attempting a read access using <TAG> 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 <TAG> 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: <TAG> 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: <TAG> 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/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.tree.stderr
new file mode 100644
index 0000000..17ae8bc
--- /dev/null
+++ b/src/tools/miri/tests/fail/both_borrows/illegal_read_despite_exposed2.tree.stderr
@@ -0,0 +1,31 @@
+error: Undefined Behavior: write access through <TAG> 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 <TAG> has state Frozen which forbids this child write access
+help: the accessed tag <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 <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 <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/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs b/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.rs
similarity index 69%
rename from src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs
rename to src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.rs
index 0e34c5c..a6c67f8 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs
+++ b/src/tools/miri/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(<N)
-        let _val = *root2; //~ ERROR: /read access .* tag does not exist in the borrow stack/
+
+        let _val = *root2;
+        //~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/
+        //~[tree]| ERROR: /read access through .* is forbidden/
     }
 }
diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.stack.stderr
similarity index 81%
rename from src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.stderr
rename to src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.stack.stderr
index 5ed9ab6..7550337 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.stack.stderr
@@ -1,5 +1,5 @@
 error: Undefined Behavior: attempting a read access using <TAG> 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 @@
    = 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: <TAG> 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: <TAG> 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/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.tree.stderr
new file mode 100644
index 0000000..2f6f7eb
--- /dev/null
+++ b/src/tools/miri/tests/fail/both_borrows/illegal_write_despite_exposed1.tree.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.rs
new file mode 100644
index 0000000..4009907
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.stderr
new file mode 100644
index 0000000..bdd7502
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_from_main.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.rs
new file mode 100644
index 0000000..a21dcfe
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.stderr
new file mode 100644
index 0000000..285ffc4
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.rs
new file mode 100644
index 0000000..641ffb4
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.stderr
new file mode 100644
index 0000000..3d91ec4
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_main_invalid_exposed.stderr
@@ -0,0 +1,14 @@
+error: Undefined Behavior: write access through <wildcard> 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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.rs
new file mode 100644
index 0000000..a65508c
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.stderr
new file mode 100644
index 0000000..c1ab12f
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.rs
new file mode 100644
index 0000000..666eac1
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.stderr
new file mode 100644
index 0000000..e1232c7
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_newer_exposed.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.rs
new file mode 100644
index 0000000..35fbc30
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.stderr
new file mode 100644
index 0000000..54f041b
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.rs
new file mode 100644
index 0000000..0f87bb4
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.stderr
new file mode 100644
index 0000000..66787ef
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.rs
new file mode 100644
index 0000000..f01ac26
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.stderr
new file mode 100644
index 0000000..644d30c
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/cross_tree_update_older_invalid_exposed2.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.rs
new file mode 100644
index 0000000..18fe931
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr
new file mode 100644
index 0000000..e257a35
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr
@@ -0,0 +1,37 @@
+error: Undefined Behavior: write access through <TAG> 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 <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
+   = help: this foreign write access would cause the protected tag <TAG> (currently Reserved) to become Disabled
+   = help: protected tags must never be Disabled
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr
index de92cf9..6e115b2 100644
--- a/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.stderr
@@ -1,4 +1,4 @@
-error: Undefined Behavior: deallocation through <wildcard> at ALLOC[0x0] is forbidden
+error: Undefined Behavior: deallocation through <TAG> 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 @@
    |
    = 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 <wildcard> also contains the strongly protected tag <TAG>
+   = help: the allocation of the accessed tag <TAG> also contains the strongly protected tag <TAG>
    = help: the strongly protected tag <TAG> disallows deallocations
+help: the accessed tag <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 <TAG> was created here, in the initial state Reserved
   --> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
    |
diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs
new file mode 100644
index 0000000..7ce243a
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.stderr
new file mode 100644
index 0000000..4b4f73a
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs
new file mode 100644
index 0000000..7b115f7
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.stderr
new file mode 100644
index 0000000..e1a8beb
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: read access through <TAG> 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 <TAG> has state Disabled which forbids this child read access
+help: the accessed tag <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 <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/src/tools/miri/tests/fail/tree_borrows/write_to_shr.stderr b/src/tools/miri/tests/fail/tree_borrows/write_to_shr.stderr
deleted file mode 100644
index f57b28c..0000000
--- a/src/tools/miri/tests/fail/tree_borrows/write_to_shr.stderr
+++ /dev/null
@@ -1,26 +0,0 @@
-error: Undefined Behavior: write access through <TAG> is forbidden
-  --> $DIR/write_to_shr.rs:LL:CC
-   |
-LL |     *xmut = 31;
-   |     ^^^^^^^^^^ write access through <TAG> 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 <TAG> is a child of the conflicting tag <TAG>
-   = help: the conflicting tag <TAG> has state Frozen which forbids child write accesses
-help: the accessed tag <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 <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/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.rs
new file mode 100644
index 0000000..1f6b797
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.stderr b/src/tools/miri/tests/pass/tree_borrows/wildcard/formatting.stderr
new file mode 100644
index 0000000..583c845
--- /dev/null
+++ b/src/tools/miri/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 |    └─┬──<TAG=root of the allocation>
+| Frz |      └─┬──<TAG=x>
+| Frz |        ├────<TAG=xa>
+| Frz |        └────<TAG=xb> (exposed)
+|     |
+| Frz |    *─┬──<TAG=y>
+| Frz |      ├────<TAG=ya> (exposed)
+| Frz |      └────<TAG=yb>
+|     |
+| Frz |    *────<TAG=z>
+|     |
+| Frz |    *─┬──<TAG=u>
+| Frz |      └────<TAG=ua>
+──────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/wildcard/reborrow.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/reborrow.rs
new file mode 100644
index 0000000..fbb50d5
--- /dev/null
+++ b/src/tools/miri/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/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs
index c34fbcb..7339254 100644
--- a/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs
+++ b/src/tools/miri/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 @@ fn protect(ref3: &mut u32) {
     }
     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;
+/// 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 int = ref2 as *mut u32 as usize;
-    let wild = int as *mut u32;
-    let wild_ref = unsafe { &mut *wild };
+    let ref_base = &mut x;
 
-    let mut protect = |arg: &mut u32| {
-        // arg is a protected pointer with wildcard provenance.
+    let int0 = ref_base as *mut u32 as usize;
+    let wild = int0 as *mut u32;
 
-        //    ┌────────────┐
-        //    │            │
-        //    │ ref1(Res)  │
-        //    │            │
-        //    └──────┬─────┘
-        //           │
-        //           │
-        //           ▼
-        //    ┌────────────┐
-        //    │            │
-        //    │ ref2(Res)* │
-        //    │            │
-        //    └────────────┘
+    let reb1 = unsafe { &mut *wild };
+    *reb1 = 44;
 
-        // 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 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);
 
-    // We pass a pointer with wildcard provenance to the function.
-    protect(wild_ref);
+    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/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs
index c406bfe..0138531 100644
--- a/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs
+++ b/src/tools/miri/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/src/tools/miri/tests/utils/macros.rs b/src/tools/miri/tests/utils/macros.rs
index 3f5b9f7..25f40ed 100644
--- a/src/tools/miri/tests/utils/macros.rs
+++ b/src/tools/miri/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);
     };
 }