miri/borrow_tracker/tree_borrows/
tree.rs

1//! In this file we handle the "Tree" part of Tree Borrows, i.e. all tree
2//! traversal functions, optimizations to trim branches, and keeping track of
3//! the relative position of the access to each node being updated. This of course
4//! also includes the definition of the tree structure.
5//!
6//! Functions here manipulate permissions but are oblivious to them: as
7//! the internals of `Permission` are private, the update process is a black
8//! box. All we need to know here are
9//! - the fact that updates depend only on the old state, the status of protectors,
10//!   and the relative position of the access;
11//! - idempotency properties asserted in `perms.rs` (for optimizations)
12
13use std::ops::Range;
14use std::{cmp, fmt, mem};
15
16use rustc_abi::Size;
17use rustc_data_structures::fx::FxHashSet;
18use rustc_span::Span;
19use smallvec::SmallVec;
20
21use super::diagnostics::{
22    AccessCause, DiagnosticInfo, NodeDebugInfo, TbError, TransitionError,
23    no_valid_exposed_references_error,
24};
25use super::foreign_access_skipping::IdempotentForeignAccess;
26use super::perms::{PermTransition, Permission};
27use super::tree_visitor::{ChildrenVisitMode, ContinueTraversal, NodeAppArgs, TreeVisitor};
28use super::unimap::{UniIndex, UniKeyMap, UniValMap};
29use super::wildcard::WildcardState;
30use crate::borrow_tracker::{AccessKind, GlobalState, ProtectorKind};
31use crate::*;
32
33mod tests;
34
35/// Data for a reference at single *location*.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub(super) struct LocationState {
38    /// A location is "accessed" when it is child-accessed for the first time (and the initial
39    /// retag initializes the location for the range covered by the type), and it then stays
40    /// accessed forever.
41    /// For accessed locations, "permission" is the current permission. However, for
42    /// non-accessed locations, we still need to track the "future initial permission": this will
43    /// start out to be `default_initial_perm`, but foreign accesses need to be taken into account.
44    /// Crucially however, while transitions to `Disabled` would usually be UB if this location is
45    /// protected, that is *not* the case for non-accessed locations. Instead we just have a latent
46    /// "future initial permission" of `Disabled`, causing UB only if an access is ever actually
47    /// performed.
48    /// Note that the tree root is also always accessed, as if the allocation was a write access.
49    accessed: bool,
50    /// This pointer's current permission / future initial permission.
51    permission: Permission,
52    /// See `foreign_access_skipping.rs`.
53    /// Stores an idempotent foreign access for this location and its children.
54    /// For correctness, this must not be too strong, and the recorded idempotent foreign access
55    /// of all children must be at least as strong as this. For performance, it should be as strong as possible.
56    idempotent_foreign_access: IdempotentForeignAccess,
57}
58
59impl LocationState {
60    /// Constructs a new initial state. It has neither been accessed, nor been subjected
61    /// to any foreign access yet.
62    /// The permission is not allowed to be `Unique`.
63    /// `sifa` is the (strongest) idempotent foreign access, see `foreign_access_skipping.rs`
64    pub fn new_non_accessed(permission: Permission, sifa: IdempotentForeignAccess) -> Self {
65        assert!(permission.is_initial() || permission.is_disabled());
66        Self { permission, accessed: false, idempotent_foreign_access: sifa }
67    }
68
69    /// Constructs a new initial state. It has not yet been subjected
70    /// to any foreign access. However, it is already marked as having been accessed.
71    /// `sifa` is the (strongest) idempotent foreign access, see `foreign_access_skipping.rs`
72    pub fn new_accessed(permission: Permission, sifa: IdempotentForeignAccess) -> Self {
73        Self { permission, accessed: true, idempotent_foreign_access: sifa }
74    }
75
76    /// Check if the location has been accessed, i.e. if it has
77    /// ever been accessed through a child pointer.
78    pub fn accessed(&self) -> bool {
79        self.accessed
80    }
81
82    pub fn permission(&self) -> Permission {
83        self.permission
84    }
85
86    /// Performs an access on this index and updates node,
87    /// perm and wildcard_state to reflect the transition.
88    fn perform_transition(
89        &mut self,
90        idx: UniIndex,
91        nodes: &mut UniValMap<Node>,
92        wildcard_accesses: &mut UniValMap<WildcardState>,
93        access_kind: AccessKind,
94        relatedness: AccessRelatedness,
95        protected: bool,
96        diagnostics: &DiagnosticInfo,
97    ) -> Result<(), TransitionError> {
98        // Call this function now (i.e. only if we know `relatedness`), which
99        // ensures it is only called when `skip_if_known_noop` returns
100        // `Recurse`, due to the contract of `traverse_this_parents_children_other`.
101        self.record_new_access(access_kind, relatedness);
102
103        let transition = self.perform_access(access_kind, relatedness, protected)?;
104        if !transition.is_noop() {
105            let node = nodes.get_mut(idx).unwrap();
106            // Record the event as part of the history.
107            node.debug_info
108                .history
109                .push(diagnostics.create_event(transition, relatedness.is_foreign()));
110
111            // We need to update the wildcard state, if the permission
112            // of an exposed pointer changes.
113            if node.is_exposed {
114                let access_type = self.permission.strongest_allowed_child_access(protected);
115                WildcardState::update_exposure(idx, access_type, nodes, wildcard_accesses);
116            }
117        }
118        Ok(())
119    }
120
121    /// Apply the effect of an access to one location, including
122    /// - applying `Permission::perform_access` to the inner `Permission`,
123    /// - emitting protector UB if the location is accessed,
124    /// - updating the accessed status (child accesses produce accessed locations).
125    fn perform_access(
126        &mut self,
127        access_kind: AccessKind,
128        rel_pos: AccessRelatedness,
129        protected: bool,
130    ) -> Result<PermTransition, TransitionError> {
131        let old_perm = self.permission;
132        let transition = Permission::perform_access(access_kind, rel_pos, old_perm, protected)
133            .ok_or(TransitionError::ChildAccessForbidden(old_perm))?;
134        self.accessed |= !rel_pos.is_foreign();
135        self.permission = transition.applied(old_perm).unwrap();
136        // Why do only accessed locations cause protector errors?
137        // Consider two mutable references `x`, `y` into disjoint parts of
138        // the same allocation. A priori, these may actually both be used to
139        // access the entire allocation, as long as only reads occur. However,
140        // a write to `y` needs to somehow record that `x` can no longer be used
141        // on that location at all. For these non-accessed locations (i.e., locations
142        // that haven't been accessed with `x` yet), we track the "future initial state":
143        // it defaults to whatever the initial state of the tag is,
144        // but the access to `y` moves that "future initial state" of `x` to `Disabled`.
145        // However, usually a `Reserved -> Disabled` transition would be UB due to the protector!
146        // So clearly protectors shouldn't fire for such "future initial state" transitions.
147        //
148        // See the test `two_mut_protected_same_alloc` in `tests/pass/tree_borrows/tree-borrows.rs`
149        // for an example of safe code that would be UB if we forgot to check `self.accessed`.
150        if protected && self.accessed && transition.produces_disabled() {
151            return Err(TransitionError::ProtectedDisabled(old_perm));
152        }
153        Ok(transition)
154    }
155
156    /// Like `perform_access`, but ignores the concrete error cause and also uses state-passing
157    /// rather than a mutable reference. As such, it returns `Some(x)` if the transition succeeded,
158    /// or `None` if there was an error.
159    #[cfg(test)]
160    fn perform_access_no_fluff(
161        mut self,
162        access_kind: AccessKind,
163        rel_pos: AccessRelatedness,
164        protected: bool,
165    ) -> Option<Self> {
166        match self.perform_access(access_kind, rel_pos, protected) {
167            Ok(_) => Some(self),
168            Err(_) => None,
169        }
170    }
171
172    /// Tree traversal optimizations. See `foreign_access_skipping.rs`.
173    /// This checks if such a foreign access can be skipped.
174    fn skip_if_known_noop(
175        &self,
176        access_kind: AccessKind,
177        rel_pos: AccessRelatedness,
178    ) -> ContinueTraversal {
179        if rel_pos.is_foreign() {
180            let happening_now = IdempotentForeignAccess::from_foreign(access_kind);
181            let mut new_access_noop =
182                self.idempotent_foreign_access.can_skip_foreign_access(happening_now);
183            if self.permission.is_disabled() {
184                // A foreign access to a `Disabled` tag will have almost no observable effect.
185                // It's a theorem that `Disabled` node have no protected accessed children,
186                // and so this foreign access will never trigger any protector.
187                // (Intuition: You're either protected accessed, and thus can't become Disabled
188                // or you're already Disabled protected, but not accessed, and then can't
189                // become accessed since that requires a child access, which Disabled blocks.)
190                // Further, the children will never be able to read or write again, since they
191                // have a `Disabled` parent. So this only affects diagnostics, such that the
192                // blocking write will still be identified directly, just at a different tag.
193                new_access_noop = true;
194            }
195            if self.permission.is_frozen() && access_kind == AccessKind::Read {
196                // A foreign read to a `Frozen` tag will have almost no observable effect.
197                // It's a theorem that `Frozen` nodes have no `Unique` children, so all children
198                // already survive foreign reads. Foreign reads in general have almost no
199                // effect, the only further thing they could do is make protected `Reserved`
200                // nodes become conflicted, i.e. make them reject child writes for the further
201                // duration of their protector. But such a child write is already rejected
202                // because this node is frozen. So this only affects diagnostics, but the
203                // blocking read will still be identified directly, just at a different tag.
204                new_access_noop = true;
205            }
206            if new_access_noop {
207                // Abort traversal if the new access is indeed guaranteed
208                // to be noop.
209                // No need to update `self.idempotent_foreign_access`,
210                // the type of the current streak among nonempty read-only
211                // or nonempty with at least one write has not changed.
212                ContinueTraversal::SkipSelfAndChildren
213            } else {
214                // Otherwise propagate this time, and also record the
215                // access that just occurred so that we can skip the propagation
216                // next time.
217                ContinueTraversal::Recurse
218            }
219        } else {
220            // A child access occurred, this breaks the streak of foreign
221            // accesses in a row and the sequence since the previous child access
222            // is now empty.
223            ContinueTraversal::Recurse
224        }
225    }
226
227    /// Records a new access, so that future access can potentially be skipped
228    /// by `skip_if_known_noop`. This must be called on child accesses, and otherwise
229    /// shoud be called on foreign accesses for increased performance. It should not be called
230    /// when `skip_if_known_noop` indicated skipping, since it then is a no-op.
231    /// See `foreign_access_skipping.rs`
232    fn record_new_access(&mut self, access_kind: AccessKind, rel_pos: AccessRelatedness) {
233        debug_assert!(matches!(
234            self.skip_if_known_noop(access_kind, rel_pos),
235            ContinueTraversal::Recurse
236        ));
237        self.idempotent_foreign_access
238            .record_new(IdempotentForeignAccess::from_acc_and_rel(access_kind, rel_pos));
239    }
240}
241
242impl fmt::Display for LocationState {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        write!(f, "{}", self.permission)?;
245        if !self.accessed {
246            write!(f, "?")?;
247        }
248        Ok(())
249    }
250}
251/// The state of the full tree for a particular location: for all nodes, the local permissions
252/// of that node, and the tracking for wildcard accesses.
253#[derive(Clone, Debug, PartialEq, Eq)]
254pub struct LocationTree {
255    /// Maps a tag to a perm, with possible lazy initialization.
256    ///
257    /// NOTE: not all tags registered in `Tree::nodes` are necessarily in all
258    /// ranges of `perms`, because `perms` is in part lazily initialized.
259    /// Just because `nodes.get(key)` is `Some(_)` does not mean you can safely
260    /// `unwrap` any `perm.get(key)`.
261    ///
262    /// We do uphold the fact that `keys(perms)` is a subset of `keys(nodes)`
263    pub perms: UniValMap<LocationState>,
264    /// Maps a tag and a location to its wildcard access tracking information,
265    /// with possible lazy initialization.
266    ///
267    /// If this allocation doesn't have any exposed nodes, then this map doesn't get
268    /// initialized. This way we only need to allocate the map if we need it.
269    ///
270    /// NOTE: same guarantees on entry initialization as for `perms`.
271    pub wildcard_accesses: UniValMap<WildcardState>,
272}
273/// Tree structure with both parents and children since we want to be
274/// able to traverse the tree efficiently in both directions.
275#[derive(Clone, Debug)]
276pub struct Tree {
277    /// Mapping from tags to keys. The key obtained can then be used in
278    /// any of the `UniValMap` relative to this allocation, i.e.
279    /// `nodes`, `LocationTree::perms` and `LocationTree::wildcard_accesses`
280    /// of the same `Tree`.
281    /// The parent-child relationship in `Node` is encoded in terms of these same
282    /// keys, so traversing the entire tree needs exactly one access to
283    /// `tag_mapping`.
284    pub(super) tag_mapping: UniKeyMap<BorTag>,
285    /// All nodes of this tree.
286    pub(super) nodes: UniValMap<Node>,
287    /// Associates with each location its state and wildcard access tracking.
288    pub(super) locations: DedupRangeMap<LocationTree>,
289    /// Contains both the root of the main tree as well as the roots of the wildcard subtrees.
290    ///
291    /// If we reborrow a reference which has wildcard provenance, then we do not know where in
292    /// the tree to attach them. Instead we create a new additional tree for this allocation
293    /// with this new reference as a root. We call this additional tree a wildcard subtree.
294    ///
295    /// The actual structure should be a single tree but with wildcard provenance we approximate
296    /// this with this ordered set of trees. Each wildcard subtree is the direct child of *some* exposed
297    /// tag (that is smaller than the root), but we do not know which. This also means that it can only be the
298    /// child of a tree that comes before it in the vec ensuring we don't have any cycles in our
299    /// approximated tree.
300    ///
301    /// Sorted according to `BorTag` from low to high. This also means the main root is `root[0]`.
302    ///
303    /// Has array size 2 because that still ensures the minimum size for SmallVec.
304    pub(super) roots: SmallVec<[UniIndex; 2]>,
305}
306
307/// A node in the borrow tree. Each node is uniquely identified by a tag via
308/// the `nodes` map of `Tree`.
309#[derive(Clone, Debug)]
310pub(super) struct Node {
311    /// The tag of this node.
312    pub tag: BorTag,
313    /// All tags except the root have a parent tag.
314    pub parent: Option<UniIndex>,
315    /// If the pointer was reborrowed, it has children.
316    // FIXME: bench to compare this to FxHashSet and to other SmallVec sizes
317    pub children: SmallVec<[UniIndex; 4]>,
318    /// Either `Reserved`,  `Frozen`, or `Disabled`, it is the permission this tag will
319    /// lazily be initialized to on the first access.
320    /// It is only ever `Disabled` for a tree root, since the root is initialized to `Unique` by
321    /// its own separate mechanism.
322    default_initial_perm: Permission,
323    /// The default initial (strongest) idempotent foreign access.
324    /// This participates in the invariant for `LocationState::idempotent_foreign_access`
325    /// in cases where there is no location state yet. See `foreign_access_skipping.rs`,
326    /// and `LocationState::idempotent_foreign_access` for more information
327    default_initial_idempotent_foreign_access: IdempotentForeignAccess,
328    /// Whether a wildcard access could happen through this node.
329    pub is_exposed: bool,
330    /// Some extra information useful only for debugging purposes.
331    pub debug_info: NodeDebugInfo,
332}
333
334impl Tree {
335    /// Create a new tree, with only a root pointer.
336    pub fn new(root_tag: BorTag, size: Size, span: Span) -> Self {
337        // The root has `Disabled` as the default permission,
338        // so that any access out of bounds is invalid.
339        let root_default_perm = Permission::new_disabled();
340        let mut tag_mapping = UniKeyMap::default();
341        let root_idx = tag_mapping.insert(root_tag);
342        let nodes = {
343            let mut nodes = UniValMap::<Node>::default();
344            let mut debug_info = NodeDebugInfo::new(root_tag, root_default_perm, span);
345            // name the root so that all allocations contain one named pointer
346            debug_info.add_name("root of the allocation");
347            nodes.insert(
348                root_idx,
349                Node {
350                    tag: root_tag,
351                    parent: None,
352                    children: SmallVec::default(),
353                    default_initial_perm: root_default_perm,
354                    // The root may never be skipped, all accesses will be local.
355                    default_initial_idempotent_foreign_access: IdempotentForeignAccess::None,
356                    is_exposed: false,
357                    debug_info,
358                },
359            );
360            nodes
361        };
362        let locations = {
363            let mut perms = UniValMap::default();
364            // We manually set it to `Unique` on all in-bounds positions.
365            // We also ensure that it is accessed, so that no `Unique` but
366            // not yet accessed nodes exist. Essentially, we pretend there
367            // was a write that initialized these to `Unique`.
368            perms.insert(
369                root_idx,
370                LocationState::new_accessed(
371                    Permission::new_unique(),
372                    IdempotentForeignAccess::None,
373                ),
374            );
375            let wildcard_accesses = UniValMap::default();
376            DedupRangeMap::new(size, LocationTree { perms, wildcard_accesses })
377        };
378        Self { roots: SmallVec::from_slice(&[root_idx]), nodes, locations, tag_mapping }
379    }
380}
381
382impl<'tcx> Tree {
383    /// Insert a new tag in the tree.
384    ///
385    /// `inside_perm` defines the initial permissions for a block of memory starting at
386    /// `base_offset`. These may nor may not be already marked as "accessed".
387    /// `outside_perm` defines the initial permission for the rest of the allocation.
388    /// These are definitely not "accessed".
389    pub(super) fn new_child(
390        &mut self,
391        base_offset: Size,
392        parent_prov: ProvenanceExtra,
393        new_tag: BorTag,
394        inside_perms: DedupRangeMap<LocationState>,
395        outside_perm: Permission,
396        protected: bool,
397        span: Span,
398    ) -> InterpResult<'tcx> {
399        let idx = self.tag_mapping.insert(new_tag);
400        let parent_idx = match parent_prov {
401            ProvenanceExtra::Concrete(parent_tag) =>
402                Some(self.tag_mapping.get(&parent_tag).unwrap()),
403            ProvenanceExtra::Wildcard => None,
404        };
405        assert!(outside_perm.is_initial());
406
407        let default_strongest_idempotent =
408            outside_perm.strongest_idempotent_foreign_access(protected);
409        // Create the node
410        self.nodes.insert(
411            idx,
412            Node {
413                tag: new_tag,
414                parent: parent_idx,
415                children: SmallVec::default(),
416                default_initial_perm: outside_perm,
417                default_initial_idempotent_foreign_access: default_strongest_idempotent,
418                is_exposed: false,
419                debug_info: NodeDebugInfo::new(new_tag, outside_perm, span),
420            },
421        );
422        if let Some(parent_idx) = parent_idx {
423            let parent_node = self.nodes.get_mut(parent_idx).unwrap();
424            // Register new_tag as a child of parent_tag
425            parent_node.children.push(idx);
426        } else {
427            // If the parent had wildcard provenance, then register the idx
428            // as a new wildcard root.
429            // This preserves the orderedness of `roots` because a newly created
430            // tag is greater than all previous tags.
431            self.roots.push(idx);
432        }
433
434        // We need to know the weakest SIFA for `update_idempotent_foreign_access_after_retag`.
435        let mut min_sifa = default_strongest_idempotent;
436        for (Range { start, end }, &perm) in
437            inside_perms.iter(Size::from_bytes(0), inside_perms.size())
438        {
439            assert!(perm.permission.is_initial());
440            assert_eq!(
441                perm.idempotent_foreign_access,
442                perm.permission.strongest_idempotent_foreign_access(protected)
443            );
444
445            min_sifa = cmp::min(min_sifa, perm.idempotent_foreign_access);
446            for (_range, loc) in self
447                .locations
448                .iter_mut(Size::from_bytes(start) + base_offset, Size::from_bytes(end - start))
449            {
450                loc.perms.insert(idx, perm);
451            }
452        }
453
454        // We need to ensure the consistency of the wildcard access tracking data structure.
455        // For this, we insert the correct entry for this tag based on its parent, if it exists.
456        // If we are inserting a new wildcard root (with Wildcard as parent_prov) then we insert
457        // the special wildcard root initial state instead.
458        for (_range, loc) in self.locations.iter_mut_all() {
459            if let Some(parent_idx) = parent_idx {
460                if let Some(parent_access) = loc.wildcard_accesses.get(parent_idx) {
461                    loc.wildcard_accesses.insert(idx, parent_access.for_new_child());
462                }
463            } else {
464                loc.wildcard_accesses.insert(idx, WildcardState::for_wildcard_root());
465            }
466        }
467        // If the parent is a wildcard pointer, then it doesn't track SIFA and doesn't need to be updated.
468        if let Some(parent_idx) = parent_idx {
469            // Inserting the new perms might have broken the SIFA invariant (see
470            // `foreign_access_skipping.rs`) if the SIFA we inserted is weaker than that of some parent.
471            // We now weaken the recorded SIFA for our parents, until the invariant is restored. We
472            // could weaken them all to `None`, but it is more efficient to compute the SIFA for the new
473            // permission statically, and use that. For this we need the *minimum* SIFA (`None` needs
474            // more fixup than `Write`).
475            self.update_idempotent_foreign_access_after_retag(parent_idx, min_sifa);
476        }
477
478        interp_ok(())
479    }
480
481    /// Restores the SIFA "children are stronger"/"parents are weaker" invariant after a retag:
482    /// reduce the SIFA of `current` and its parents to be no stronger than `strongest_allowed`.
483    /// See `foreign_access_skipping.rs` and [`Tree::new_child`].
484    fn update_idempotent_foreign_access_after_retag(
485        &mut self,
486        mut current: UniIndex,
487        strongest_allowed: IdempotentForeignAccess,
488    ) {
489        if strongest_allowed == IdempotentForeignAccess::Write {
490            // Nothing is stronger than `Write`.
491            return;
492        }
493        // We walk the tree upwards, until the invariant is restored
494        loop {
495            let current_node = self.nodes.get_mut(current).unwrap();
496            // Call `ensure_no_stronger_than` on all SIFAs for this node: the per-location SIFA, as well
497            // as the default SIFA for not-yet-initialized locations.
498            // Record whether we did any change; if not, the invariant is restored and we can stop the traversal.
499            let mut any_change = false;
500            for (_range, loc) in self.locations.iter_mut_all() {
501                // Check if this node has a state for this location (or range of locations).
502                if let Some(perm) = loc.perms.get_mut(current) {
503                    // Update the per-location SIFA, recording if it changed.
504                    any_change |=
505                        perm.idempotent_foreign_access.ensure_no_stronger_than(strongest_allowed);
506                }
507            }
508            // Now update `default_initial_idempotent_foreign_access`, which stores the default SIFA for not-yet-initialized locations.
509            any_change |= current_node
510                .default_initial_idempotent_foreign_access
511                .ensure_no_stronger_than(strongest_allowed);
512
513            if any_change {
514                let Some(next) = self.nodes.get(current).unwrap().parent else {
515                    // We have arrived at the root.
516                    break;
517                };
518                current = next;
519                continue;
520            } else {
521                break;
522            }
523        }
524    }
525
526    /// Deallocation requires
527    /// - a pointer that permits write accesses
528    /// - the absence of Strong Protectors anywhere in the allocation
529    pub fn dealloc(
530        &mut self,
531        prov: ProvenanceExtra,
532        access_range: AllocRange,
533        global: &GlobalState,
534        alloc_id: AllocId, // diagnostics
535        span: Span,        // diagnostics
536    ) -> InterpResult<'tcx> {
537        self.perform_access(
538            prov,
539            access_range,
540            AccessKind::Write,
541            AccessCause::Dealloc,
542            global,
543            alloc_id,
544            span,
545        )?;
546
547        let start_idx = match prov {
548            ProvenanceExtra::Concrete(tag) => Some(self.tag_mapping.get(&tag).unwrap()),
549            ProvenanceExtra::Wildcard => None,
550        };
551
552        // Check if this breaks any strong protector.
553        // (Weak protectors are already handled by `perform_access`.)
554        for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) {
555            let diagnostics = DiagnosticInfo {
556                alloc_id,
557                span,
558                transition_range: loc_range,
559                access_range: Some(access_range),
560                access_cause: AccessCause::Dealloc,
561            };
562            // Checks the tree containing `idx` for strong protector violations.
563            // It does this in traversal order.
564            let mut check_tree = |idx| {
565                TreeVisitor { nodes: &mut self.nodes, data: loc }
566                    .traverse_this_parents_children_other(
567                        idx,
568                        // Visit all children, skipping none.
569                        |_| ContinueTraversal::Recurse,
570                        |args: NodeAppArgs<'_, _>| {
571                            let node = args.nodes.get(args.idx).unwrap();
572
573                            let perm = args
574                                .data
575                                .perms
576                                .get(args.idx)
577                                .copied()
578                                .unwrap_or_else(|| node.default_location_state());
579                            if global.borrow().protected_tags.get(&node.tag)
580                                == Some(&ProtectorKind::StrongProtector)
581                                // Don't check for protector if it is a Cell (see `unsafe_cell_deallocate` in `interior_mutability.rs`).
582                                // Related to https://github.com/rust-lang/rust/issues/55005.
583                                && !perm.permission.is_cell()
584                                // Only trigger UB if the accessed bit is set, i.e. if the protector is actually protecting this offset. See #4579.
585                                && perm.accessed
586                            {
587                                Err(TbError {
588                                    error_kind: TransitionError::ProtectedDealloc,
589                                    access_info: &diagnostics,
590                                    conflicting_node_info: &node.debug_info,
591                                    accessed_node_info: start_idx
592                                        .map(|idx| &args.nodes.get(idx).unwrap().debug_info),
593                                }
594                                .build())
595                            } else {
596                                Ok(())
597                            }
598                        },
599                    )
600            };
601            // If we have a start index we first check its subtree in traversal order.
602            // This results in us showing the error of the closest node instead of an
603            // arbitrary one.
604            let accessed_root = start_idx.map(&mut check_tree).transpose()?;
605            // Afterwards we check all other trees.
606            // We iterate over the list in reverse order to ensure that we do not visit
607            // a parent before its child.
608            for &root in self.roots.iter().rev() {
609                if Some(root) == accessed_root {
610                    continue;
611                }
612                check_tree(root)?;
613            }
614        }
615        interp_ok(())
616    }
617
618    /// Map the per-node and per-location `LocationState::perform_access`
619    /// to each location of the first component of `access_range_and_kind`,
620    /// on every tag of the allocation.
621    ///
622    /// `LocationState::perform_access` will take care of raising transition
623    /// errors and updating the `accessed` status of each location,
624    /// this traversal adds to that:
625    /// - inserting into the map locations that do not exist yet,
626    /// - trimming the traversal,
627    /// - recording the history.
628    pub fn perform_access(
629        &mut self,
630        prov: ProvenanceExtra,
631        access_range: AllocRange,
632        access_kind: AccessKind,
633        access_cause: AccessCause, // diagnostics
634        global: &GlobalState,
635        alloc_id: AllocId, // diagnostics
636        span: Span,        // diagnostics
637    ) -> InterpResult<'tcx> {
638        #[cfg(feature = "expensive-consistency-checks")]
639        if self.roots.len() > 1 || matches!(prov, ProvenanceExtra::Wildcard) {
640            self.verify_wildcard_consistency(global);
641        }
642
643        let source_idx = match prov {
644            ProvenanceExtra::Concrete(tag) => Some(self.tag_mapping.get(&tag).unwrap()),
645            ProvenanceExtra::Wildcard => None,
646        };
647        // We iterate over affected locations and traverse the tree for each of them.
648        for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) {
649            let diagnostics = DiagnosticInfo {
650                access_cause,
651                access_range: Some(access_range),
652                alloc_id,
653                span,
654                transition_range: loc_range,
655            };
656            loc.perform_access(
657                self.roots.iter().copied(),
658                &mut self.nodes,
659                source_idx,
660                access_kind,
661                global,
662                ChildrenVisitMode::VisitChildrenOfAccessed,
663                &diagnostics,
664            )?;
665        }
666        interp_ok(())
667    }
668    /// This is the special access that is applied on protector release:
669    /// - the access will be applied only to accessed locations of the allocation,
670    /// - it will not be visible to children,
671    /// - it will be recorded as a `FnExit` diagnostic access
672    /// - and it will be a read except if the location is `Unique`, i.e. has been written to,
673    ///   in which case it will be a write.
674    /// - otherwise identical to `Tree::perform_access`
675    pub fn perform_protector_end_access(
676        &mut self,
677        tag: BorTag,
678        global: &GlobalState,
679        alloc_id: AllocId, // diagnostics
680        span: Span,        // diagnostics
681    ) -> InterpResult<'tcx> {
682        #[cfg(feature = "expensive-consistency-checks")]
683        if self.roots.len() > 1 {
684            self.verify_wildcard_consistency(global);
685        }
686
687        let source_idx = self.tag_mapping.get(&tag).unwrap();
688
689        // This is a special access through the entire allocation.
690        // It actually only affects `accessed` locations, so we need
691        // to filter on those before initiating the traversal.
692        //
693        // In addition this implicit access should not be visible to children,
694        // thus the use of `traverse_nonchildren`.
695        // See the test case `returned_mut_is_usable` from
696        // `tests/pass/tree_borrows/tree-borrows.rs` for an example of
697        // why this is important.
698        for (loc_range, loc) in self.locations.iter_mut_all() {
699            // Only visit accessed permissions
700            if let Some(p) = loc.perms.get(source_idx)
701                && let Some(access_kind) = p.permission.protector_end_access()
702                && p.accessed
703            {
704                let diagnostics = DiagnosticInfo {
705                    access_cause: AccessCause::FnExit(access_kind),
706                    access_range: None,
707                    alloc_id,
708                    span,
709                    transition_range: loc_range,
710                };
711                loc.perform_access(
712                    self.roots.iter().copied(),
713                    &mut self.nodes,
714                    Some(source_idx),
715                    access_kind,
716                    global,
717                    ChildrenVisitMode::SkipChildrenOfAccessed,
718                    &diagnostics,
719                )?;
720            }
721        }
722        interp_ok(())
723    }
724}
725
726/// Integration with the BorTag garbage collector
727impl Tree {
728    pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
729        for i in 0..(self.roots.len()) {
730            self.remove_useless_children(self.roots[i], live_tags);
731        }
732        // Right after the GC runs is a good moment to check if we can
733        // merge some adjacent ranges that were made equal by the removal of some
734        // tags (this does not necessarily mean that they have identical internal representations,
735        // see the `PartialEq` impl for `UniValMap`)
736        self.locations.merge_adjacent_thorough();
737    }
738
739    /// Checks if a node is useless and should be GC'ed.
740    /// A node is useless if it has no children and also the tag is no longer live.
741    fn is_useless(&self, idx: UniIndex, live: &FxHashSet<BorTag>) -> bool {
742        let node = self.nodes.get(idx).unwrap();
743        node.children.is_empty() && !live.contains(&node.tag)
744    }
745
746    /// Checks whether a node can be replaced by its only child.
747    /// If so, returns the index of said only child.
748    /// If not, returns none.
749    fn can_be_replaced_by_single_child(
750        &self,
751        idx: UniIndex,
752        live: &FxHashSet<BorTag>,
753    ) -> Option<UniIndex> {
754        let node = self.nodes.get(idx).unwrap();
755
756        let [child_idx] = node.children[..] else { return None };
757
758        // We never want to replace the root node, as it is also kept in `root_ptr_tags`.
759        if live.contains(&node.tag) || node.parent.is_none() {
760            return None;
761        }
762        // Since protected nodes are never GC'd (see `borrow_tracker::FrameExtra::visit_provenance`),
763        // we know that `node` is not protected because otherwise `live` would
764        // have contained `node.tag`.
765        let child = self.nodes.get(child_idx).unwrap();
766        // Check that for that one child, `can_be_replaced_by_child` holds for the permission
767        // on all locations.
768        for (_range, loc) in self.locations.iter_all() {
769            let parent_perm = loc
770                .perms
771                .get(idx)
772                .map(|x| x.permission)
773                .unwrap_or_else(|| node.default_initial_perm);
774            let child_perm = loc
775                .perms
776                .get(child_idx)
777                .map(|x| x.permission)
778                .unwrap_or_else(|| child.default_initial_perm);
779            if !parent_perm.can_be_replaced_by_child(child_perm) {
780                return None;
781            }
782        }
783
784        Some(child_idx)
785    }
786
787    /// Properly removes a node.
788    /// The node to be removed should not otherwise be usable. It also
789    /// should have no children, but this is not checked, so that nodes
790    /// whose children were rotated somewhere else can be deleted without
791    /// having to first modify them to clear that array.
792    fn remove_useless_node(&mut self, this: UniIndex) {
793        // Due to the API of UniMap we must make sure to call
794        // `UniValMap::remove` for the key of this node on *all* maps that used it
795        // (which are `self.nodes` and every range of `self.rperms`)
796        // before we can safely apply `UniKeyMap::remove` to truly remove
797        // this tag from the `tag_mapping`.
798        let node = self.nodes.remove(this).unwrap();
799        for (_range, loc) in self.locations.iter_mut_all() {
800            loc.perms.remove(this);
801            loc.wildcard_accesses.remove(this);
802        }
803        self.tag_mapping.remove(&node.tag);
804    }
805
806    /// Traverses the entire tree looking for useless tags.
807    /// Removes from the tree all useless child nodes of root.
808    /// It will not delete the root itself.
809    ///
810    /// NOTE: This leaves in the middle of the tree tags that are unreachable but have
811    /// reachable children. There is a potential for compacting the tree by reassigning
812    /// children of dead tags to the nearest live parent, but it must be done with care
813    /// not to remove UB.
814    ///
815    /// Example: Consider the tree `root - parent - child`, with `parent: Frozen` and
816    /// `child: Reserved`. This tree can exist. If we blindly delete `parent` and reassign
817    /// `child` to be a direct child of `root` then Writes to `child` are now permitted
818    /// whereas they were not when `parent` was still there.
819    fn remove_useless_children(&mut self, root: UniIndex, live: &FxHashSet<BorTag>) {
820        // To avoid stack overflows, we roll our own stack.
821        // Each element in the stack consists of the current tag, and the number of the
822        // next child to be processed.
823
824        // The other functions are written using the `TreeVisitorStack`, but that does not work here
825        // since we need to 1) do a post-traversal and 2) remove nodes from the tree.
826        // Since we do a post-traversal (by deleting nodes only after handling all children),
827        // we also need to be a bit smarter than "pop node, push all children."
828        let mut stack = vec![(root, 0)];
829        while let Some((tag, nth_child)) = stack.last_mut() {
830            let node = self.nodes.get(*tag).unwrap();
831            if *nth_child < node.children.len() {
832                // Visit the child by pushing it to the stack.
833                // Also increase `nth_child` so that when we come back to the `tag` node, we
834                // look at the next child.
835                let next_child = node.children[*nth_child];
836                *nth_child += 1;
837                stack.push((next_child, 0));
838                continue;
839            } else {
840                // We have processed all children of `node`, so now it is time to process `node` itself.
841                // First, get the current children of `node`. To appease the borrow checker,
842                // we have to temporarily move the list out of the node, and then put the
843                // list of remaining children back in.
844                let mut children_of_node =
845                    mem::take(&mut self.nodes.get_mut(*tag).unwrap().children);
846                // Remove all useless children.
847                children_of_node.retain_mut(|idx| {
848                    if self.is_useless(*idx, live) {
849                        // Delete `idx` node everywhere else.
850                        self.remove_useless_node(*idx);
851                        // And delete it from children_of_node.
852                        false
853                    } else {
854                        if let Some(nextchild) = self.can_be_replaced_by_single_child(*idx, live) {
855                            // `nextchild` is our grandchild, and will become our direct child.
856                            // Delete the in-between node, `idx`.
857                            self.remove_useless_node(*idx);
858                            // Set the new child's parent.
859                            self.nodes.get_mut(nextchild).unwrap().parent = Some(*tag);
860                            // Save the new child in children_of_node.
861                            *idx = nextchild;
862                        }
863                        // retain it
864                        true
865                    }
866                });
867                // Put back the now-filtered vector.
868                self.nodes.get_mut(*tag).unwrap().children = children_of_node;
869
870                // We are done, the parent can continue.
871                stack.pop();
872                continue;
873            }
874        }
875    }
876}
877
878impl<'tcx> LocationTree {
879    /// Performs an access on this location.
880    /// * `access_source`: The index, if any, where the access came from.
881    /// * `visit_children`: Whether to skip updating the children of `access_source`.
882    fn perform_access(
883        &mut self,
884        roots: impl Iterator<Item = UniIndex>,
885        nodes: &mut UniValMap<Node>,
886        access_source: Option<UniIndex>,
887        access_kind: AccessKind,
888        global: &GlobalState,
889        visit_children: ChildrenVisitMode,
890        diagnostics: &DiagnosticInfo,
891    ) -> InterpResult<'tcx> {
892        let accessed_root = if let Some(idx) = access_source {
893            Some(self.perform_normal_access(
894                idx,
895                nodes,
896                access_kind,
897                global,
898                visit_children,
899                diagnostics,
900            )?)
901        } else {
902            // `SkipChildrenOfAccessed` only gets set on protector release, which only
903            // occurs on a known node.
904            assert!(matches!(visit_children, ChildrenVisitMode::VisitChildrenOfAccessed));
905            None
906        };
907
908        let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag);
909        if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed) {
910            // FIXME: approximate which roots could be children of the accessed node and only skip them instead of all other trees.
911            return interp_ok(());
912        }
913        for root in roots {
914            // We don't perform a wildcard access on the tree we already performed a
915            // normal access on.
916            if Some(root) == accessed_root {
917                continue;
918            }
919            // The choice of `max_local_tag` requires some thought.
920            // This can only be a local access for nodes that are a parent of the accessed node
921            // and are therefore smaller, so the accessed node itself is a valid choice for `max_local_tag`.
922            // However, using `accessed_root` is better since that will be smaller. It is still a valid choice
923            // because for nodes *in other trees*, if they are a parent of the accessed node then they
924            // are a parent of `accessed_root`.
925            //
926            // As a consequence of this, since the root of the main tree is the smallest tag in the entire
927            // allocation, if the access occurred in the main tree then other subtrees will only see foreign accesses.
928            self.perform_wildcard_access(
929                root,
930                access_source,
931                /*max_local_tag*/ accessed_root_tag,
932                nodes,
933                access_kind,
934                global,
935                diagnostics,
936            )?;
937        }
938        interp_ok(())
939    }
940
941    /// Performs a normal access on the tree containing `access_source`.
942    ///
943    /// Returns the root index of this tree.
944    /// * `access_source`: The index of the tag being accessed.
945    /// * `visit_children`: Whether to skip the children of `access_source`
946    ///   during the access. Used for protector end access.
947    fn perform_normal_access(
948        &mut self,
949        access_source: UniIndex,
950        nodes: &mut UniValMap<Node>,
951        access_kind: AccessKind,
952        global: &GlobalState,
953        visit_children: ChildrenVisitMode,
954        diagnostics: &DiagnosticInfo,
955    ) -> InterpResult<'tcx, UniIndex> {
956        // Performs the per-node work:
957        // - insert the permission if it does not exist
958        // - perform the access
959        // - record the transition
960        // to which some optimizations are added:
961        // - skip the traversal of the children in some cases
962        // - do not record noop transitions
963        //
964        // `loc_range` is only for diagnostics (it is the range of
965        // the `RangeMap` on which we are currently working).
966        let node_skipper = |args: &NodeAppArgs<'_, LocationTree>| -> ContinueTraversal {
967            let node = args.nodes.get(args.idx).unwrap();
968            let perm = args.data.perms.get(args.idx);
969
970            let old_state = perm.copied().unwrap_or_else(|| node.default_location_state());
971            old_state.skip_if_known_noop(access_kind, args.rel_pos)
972        };
973        let node_app = |args: NodeAppArgs<'_, LocationTree>| {
974            let node = args.nodes.get_mut(args.idx).unwrap();
975            let mut perm = args.data.perms.entry(args.idx);
976
977            let state = perm.or_insert(node.default_location_state());
978
979            let protected = global.borrow().protected_tags.contains_key(&node.tag);
980            state
981                .perform_transition(
982                    args.idx,
983                    args.nodes,
984                    &mut args.data.wildcard_accesses,
985                    access_kind,
986                    args.rel_pos,
987                    protected,
988                    diagnostics,
989                )
990                .map_err(|error_kind| {
991                    TbError {
992                        error_kind,
993                        access_info: diagnostics,
994                        conflicting_node_info: &args.nodes.get(args.idx).unwrap().debug_info,
995                        accessed_node_info: Some(
996                            &args.nodes.get(access_source).unwrap().debug_info,
997                        ),
998                    }
999                    .build()
1000                })
1001        };
1002
1003        let visitor = TreeVisitor { nodes, data: self };
1004        match visit_children {
1005            ChildrenVisitMode::VisitChildrenOfAccessed =>
1006                visitor.traverse_this_parents_children_other(access_source, node_skipper, node_app),
1007            ChildrenVisitMode::SkipChildrenOfAccessed =>
1008                visitor.traverse_nonchildren(access_source, node_skipper, node_app),
1009        }
1010        .into()
1011    }
1012
1013    /// Performs a wildcard access on the tree with root `root`. Takes the `access_relatedness`
1014    /// for each node from the `WildcardState` datastructure.
1015    /// * `root`: Root of the tree being accessed.
1016    /// * `access_source`: the index of the accessed tag, if any.
1017    ///   This is only used for printing the correct tag on errors.
1018    /// * `max_local_tag`: The access can only be local for nodes whose tag is
1019    ///   at most `max_local_tag`.
1020    fn perform_wildcard_access(
1021        &mut self,
1022        root: UniIndex,
1023        access_source: Option<UniIndex>,
1024        max_local_tag: Option<BorTag>,
1025        nodes: &mut UniValMap<Node>,
1026        access_kind: AccessKind,
1027        global: &GlobalState,
1028        diagnostics: &DiagnosticInfo,
1029    ) -> InterpResult<'tcx> {
1030        let get_relatedness = |idx: UniIndex, node: &Node, loc: &LocationTree| {
1031            let wildcard_state = loc.wildcard_accesses.get(idx).cloned().unwrap_or_default();
1032            // If the tag is larger than `max_local_tag` then the access can only be foreign.
1033            let only_foreign = max_local_tag.is_some_and(|max_local_tag| max_local_tag < node.tag);
1034            wildcard_state.access_relatedness(access_kind, only_foreign)
1035        };
1036
1037        // This does a traversal across the tree updating children before their parents. The
1038        // difference to `perform_normal_access` is that we take the access relatedness from
1039        // the wildcard tracking state of the node instead of from the visitor itself.
1040        //
1041        // Unlike for a normal access, the iteration order is important for improving the
1042        // accuracy of wildcard accesses if `max_local_tag` is `Some`: processing the effects of this
1043        // access further down the tree can cause exposed nodes to lose permissions, thus updating
1044        // the wildcard data structure, which will be taken into account when processing the parent
1045        // nodes. Also see the test `cross_tree_update_older_invalid_exposed2.rs`
1046        // (Doing accesses in the opposite order cannot help with precision but the reasons are complicated;
1047        // see <https://github.com/rust-lang/miri/pull/4707#discussion_r2581661123>.)
1048        //
1049        // Note, however, that this is an approximation: there can be situations where a node is
1050        // marked as having an exposed foreign node, but actually that foreign node cannot be
1051        // the source of the access due to `max_local_tag`. The wildcard tracking cannot know
1052        // about `max_local_tag` so we will incorrectly assume that this might be a foreign access.
1053        TreeVisitor { data: self, nodes }.traverse_children_this(
1054            root,
1055            |args| -> ContinueTraversal {
1056                let node = args.nodes.get(args.idx).unwrap();
1057                let perm = args.data.perms.get(args.idx);
1058
1059                let old_state = perm.copied().unwrap_or_else(|| node.default_location_state());
1060                // If we know where, relative to this node, the wildcard access occurs,
1061                // then check if we can skip the entire subtree.
1062                if let Some(relatedness) = get_relatedness(args.idx, node, args.data)
1063                    && let Some(relatedness) = relatedness.to_relatedness()
1064                {
1065                    // We can use the usual SIFA machinery to skip nodes.
1066                    old_state.skip_if_known_noop(access_kind, relatedness)
1067                } else {
1068                    ContinueTraversal::Recurse
1069                }
1070            },
1071            |args| {
1072                let node = args.nodes.get_mut(args.idx).unwrap();
1073
1074                let protected = global.borrow().protected_tags.contains_key(&node.tag);
1075
1076                let Some(wildcard_relatedness) = get_relatedness(args.idx, node, args.data) else {
1077                    // There doesn't exist a valid exposed reference for this access to
1078                    // happen through.
1079                    // This can only happen if `root` is the main root: We set
1080                    // `max_foreign_access==Write` on all wildcard roots, so at least a foreign access
1081                    // is always possible on all nodes in a wildcard subtree.
1082                    return Err(no_valid_exposed_references_error(diagnostics));
1083                };
1084
1085                let Some(relatedness) = wildcard_relatedness.to_relatedness() else {
1086                    // If the access type is Either, then we do not apply any transition
1087                    // to this node, but we still update each of its children.
1088                    // This is an imprecision! In the future, maybe we can still do some sort
1089                    // of best-effort update here.
1090                    return Ok(());
1091                };
1092
1093                let mut entry = args.data.perms.entry(args.idx);
1094                let perm = entry.or_insert(node.default_location_state());
1095                // We know the exact relatedness, so we can actually do precise checks.
1096                perm.perform_transition(
1097                    args.idx,
1098                    args.nodes,
1099                    &mut args.data.wildcard_accesses,
1100                    access_kind,
1101                    relatedness,
1102                    protected,
1103                    diagnostics,
1104                )
1105                .map_err(|trans| {
1106                    let node = args.nodes.get(args.idx).unwrap();
1107                    TbError {
1108                        error_kind: trans,
1109                        access_info: diagnostics,
1110                        conflicting_node_info: &node.debug_info,
1111                        accessed_node_info: access_source
1112                            .map(|idx| &args.nodes.get(idx).unwrap().debug_info),
1113                    }
1114                    .build()
1115                })
1116            },
1117        )?;
1118        interp_ok(())
1119    }
1120}
1121
1122impl Node {
1123    pub fn default_location_state(&self) -> LocationState {
1124        LocationState::new_non_accessed(
1125            self.default_initial_perm,
1126            self.default_initial_idempotent_foreign_access,
1127        )
1128    }
1129}
1130
1131impl VisitProvenance for Tree {
1132    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
1133        // To ensure that the roots never get removed, we visit them.
1134        // FIXME: it should be possible to GC wildcard tree roots.
1135        for id in self.roots.iter().copied() {
1136            visit(None, Some(self.nodes.get(id).unwrap().tag));
1137        }
1138        // We also need to keep around any exposed tags through which
1139        // an access could still happen.
1140        for (_id, node) in self.nodes.iter() {
1141            if node.is_exposed {
1142                visit(None, Some(node.tag))
1143            }
1144        }
1145    }
1146}
1147
1148/// Relative position of the access
1149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1150pub enum AccessRelatedness {
1151    /// The access happened either through the node itself or one of
1152    /// its transitive children.
1153    LocalAccess,
1154    /// The access happened through this nodes ancestor or through
1155    /// a sibling/cousin/uncle/etc.
1156    ForeignAccess,
1157}
1158
1159impl AccessRelatedness {
1160    /// Check that access is either Ancestor or Distant, i.e. not
1161    /// a transitive child (initial pointer included).
1162    pub fn is_foreign(self) -> bool {
1163        matches!(self, AccessRelatedness::ForeignAccess)
1164    }
1165}