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_local_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 /* min_exposed_child */ None, // only matters for protector end access,
665 )?;
666 }
667 interp_ok(())
668 }
669 /// This is the special access that is applied on protector release:
670 /// - the access will be applied only to accessed locations of the allocation,
671 /// - it will not be visible to children,
672 /// - it will be recorded as a `FnExit` diagnostic access
673 /// - and it will be a read except if the location is `Unique`, i.e. has been written to,
674 /// in which case it will be a write.
675 /// - otherwise identical to `Tree::perform_access`
676 pub fn perform_protector_end_access(
677 &mut self,
678 tag: BorTag,
679 global: &GlobalState,
680 alloc_id: AllocId, // diagnostics
681 span: Span, // diagnostics
682 ) -> InterpResult<'tcx> {
683 #[cfg(feature = "expensive-consistency-checks")]
684 if self.roots.len() > 1 {
685 self.verify_wildcard_consistency(global);
686 }
687
688 let source_idx = self.tag_mapping.get(&tag).unwrap();
689
690 let min_exposed_child = if self.roots.len() > 1 {
691 LocationTree::get_min_exposed_child(source_idx, &self.nodes)
692 } else {
693 // There's no point in computing this when there is just one tree.
694 None
695 };
696
697 // This is a special access through the entire allocation.
698 // It actually only affects `accessed` locations, so we need
699 // to filter on those before initiating the traversal.
700 //
701 // In addition this implicit access should not be visible to children,
702 // thus the use of `traverse_nonchildren`.
703 // See the test case `returned_mut_is_usable` from
704 // `tests/pass/tree_borrows/tree-borrows.rs` for an example of
705 // why this is important.
706 for (loc_range, loc) in self.locations.iter_mut_all() {
707 // Only visit accessed permissions
708 if let Some(p) = loc.perms.get(source_idx)
709 && let Some(access_kind) = p.permission.protector_end_access()
710 && p.accessed
711 {
712 let diagnostics = DiagnosticInfo {
713 access_cause: AccessCause::FnExit(access_kind),
714 access_range: None,
715 alloc_id,
716 span,
717 transition_range: loc_range,
718 };
719 loc.perform_access(
720 self.roots.iter().copied(),
721 &mut self.nodes,
722 Some(source_idx),
723 access_kind,
724 global,
725 ChildrenVisitMode::SkipChildrenOfAccessed,
726 &diagnostics,
727 min_exposed_child,
728 )?;
729 }
730 }
731 interp_ok(())
732 }
733}
734
735/// Integration with the BorTag garbage collector
736impl Tree {
737 pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
738 for i in 0..(self.roots.len()) {
739 self.remove_useless_children(self.roots[i], live_tags);
740 }
741 // Right after the GC runs is a good moment to check if we can
742 // merge some adjacent ranges that were made equal by the removal of some
743 // tags (this does not necessarily mean that they have identical internal representations,
744 // see the `PartialEq` impl for `UniValMap`)
745 self.locations.merge_adjacent_thorough();
746 }
747
748 /// Checks if a node is useless and should be GC'ed.
749 /// A node is useless if it has no children and also the tag is no longer live.
750 fn is_useless(&self, idx: UniIndex, live: &FxHashSet<BorTag>) -> bool {
751 let node = self.nodes.get(idx).unwrap();
752 node.children.is_empty() && !live.contains(&node.tag)
753 }
754
755 /// Checks whether a node can be replaced by its only child.
756 /// If so, returns the index of said only child.
757 /// If not, returns none.
758 fn can_be_replaced_by_single_child(
759 &self,
760 idx: UniIndex,
761 live: &FxHashSet<BorTag>,
762 ) -> Option<UniIndex> {
763 let node = self.nodes.get(idx).unwrap();
764
765 let [child_idx] = node.children[..] else { return None };
766
767 // We never want to replace the root node, as it is also kept in `root_ptr_tags`.
768 if live.contains(&node.tag) || node.parent.is_none() {
769 return None;
770 }
771 // Since protected nodes are never GC'd (see `borrow_tracker::FrameExtra::visit_provenance`),
772 // we know that `node` is not protected because otherwise `live` would
773 // have contained `node.tag`.
774 let child = self.nodes.get(child_idx).unwrap();
775 // Check that for that one child, `can_be_replaced_by_child` holds for the permission
776 // on all locations.
777 for (_range, loc) in self.locations.iter_all() {
778 let parent_perm = loc
779 .perms
780 .get(idx)
781 .map(|x| x.permission)
782 .unwrap_or_else(|| node.default_initial_perm);
783 let child_perm = loc
784 .perms
785 .get(child_idx)
786 .map(|x| x.permission)
787 .unwrap_or_else(|| child.default_initial_perm);
788 if !parent_perm.can_be_replaced_by_child(child_perm) {
789 return None;
790 }
791 }
792
793 Some(child_idx)
794 }
795
796 /// Properly removes a node.
797 /// The node to be removed should not otherwise be usable. It also
798 /// should have no children, but this is not checked, so that nodes
799 /// whose children were rotated somewhere else can be deleted without
800 /// having to first modify them to clear that array.
801 fn remove_useless_node(&mut self, this: UniIndex) {
802 // Due to the API of UniMap we must make sure to call
803 // `UniValMap::remove` for the key of this node on *all* maps that used it
804 // (which are `self.nodes` and every range of `self.rperms`)
805 // before we can safely apply `UniKeyMap::remove` to truly remove
806 // this tag from the `tag_mapping`.
807 let node = self.nodes.remove(this).unwrap();
808 for (_range, loc) in self.locations.iter_mut_all() {
809 loc.perms.remove(this);
810 loc.wildcard_accesses.remove(this);
811 }
812 self.tag_mapping.remove(&node.tag);
813 }
814
815 /// Traverses the entire tree looking for useless tags.
816 /// Removes from the tree all useless child nodes of root.
817 /// It will not delete the root itself.
818 ///
819 /// NOTE: This leaves in the middle of the tree tags that are unreachable but have
820 /// reachable children. There is a potential for compacting the tree by reassigning
821 /// children of dead tags to the nearest live parent, but it must be done with care
822 /// not to remove UB.
823 ///
824 /// Example: Consider the tree `root - parent - child`, with `parent: Frozen` and
825 /// `child: Reserved`. This tree can exist. If we blindly delete `parent` and reassign
826 /// `child` to be a direct child of `root` then Writes to `child` are now permitted
827 /// whereas they were not when `parent` was still there.
828 fn remove_useless_children(&mut self, root: UniIndex, live: &FxHashSet<BorTag>) {
829 // To avoid stack overflows, we roll our own stack.
830 // Each element in the stack consists of the current tag, and the number of the
831 // next child to be processed.
832
833 // The other functions are written using the `TreeVisitorStack`, but that does not work here
834 // since we need to 1) do a post-traversal and 2) remove nodes from the tree.
835 // Since we do a post-traversal (by deleting nodes only after handling all children),
836 // we also need to be a bit smarter than "pop node, push all children."
837 let mut stack = vec![(root, 0)];
838 while let Some((tag, nth_child)) = stack.last_mut() {
839 let node = self.nodes.get(*tag).unwrap();
840 if *nth_child < node.children.len() {
841 // Visit the child by pushing it to the stack.
842 // Also increase `nth_child` so that when we come back to the `tag` node, we
843 // look at the next child.
844 let next_child = node.children[*nth_child];
845 *nth_child += 1;
846 stack.push((next_child, 0));
847 continue;
848 } else {
849 // We have processed all children of `node`, so now it is time to process `node` itself.
850 // First, get the current children of `node`. To appease the borrow checker,
851 // we have to temporarily move the list out of the node, and then put the
852 // list of remaining children back in.
853 let mut children_of_node =
854 mem::take(&mut self.nodes.get_mut(*tag).unwrap().children);
855 // Remove all useless children.
856 children_of_node.retain_mut(|idx| {
857 if self.is_useless(*idx, live) {
858 // Delete `idx` node everywhere else.
859 self.remove_useless_node(*idx);
860 // And delete it from children_of_node.
861 false
862 } else {
863 if let Some(nextchild) = self.can_be_replaced_by_single_child(*idx, live) {
864 // `nextchild` is our grandchild, and will become our direct child.
865 // Delete the in-between node, `idx`.
866 self.remove_useless_node(*idx);
867 // Set the new child's parent.
868 self.nodes.get_mut(nextchild).unwrap().parent = Some(*tag);
869 // Save the new child in children_of_node.
870 *idx = nextchild;
871 }
872 // retain it
873 true
874 }
875 });
876 // Put back the now-filtered vector.
877 self.nodes.get_mut(*tag).unwrap().children = children_of_node;
878
879 // We are done, the parent can continue.
880 stack.pop();
881 continue;
882 }
883 }
884 }
885}
886
887impl<'tcx> LocationTree {
888 /// Returns the smallest exposed tag, if any, that is a transitive child of `root`.
889 fn get_min_exposed_child(root: UniIndex, nodes: &UniValMap<Node>) -> Option<BorTag> {
890 // We cannot use the wildcard datastructure to improve this lookup. This is because
891 // the datastructure only tracks enabled nodes and we need to also consider disabled ones.
892 let mut stack = vec![root];
893 let mut min_tag = None;
894 while let Some(idx) = stack.pop() {
895 let node = nodes.get(idx).unwrap();
896 if min_tag.is_some_and(|min| min < node.tag) {
897 // The minimum we found before is bigger than this tag, and therefore
898 // also bigger than all its children, so we can skip this subtree.
899 continue;
900 }
901 stack.extend_from_slice(node.children.as_slice());
902 if node.is_exposed {
903 min_tag = match min_tag {
904 Some(prev) if prev < node.tag => Some(prev),
905 _ => Some(node.tag),
906 };
907 }
908 }
909 min_tag
910 }
911
912 /// Performs an access on this location.
913 /// * `access_source`: The index, if any, where the access came from.
914 /// * `visit_children`: Whether to skip updating the children of `access_source`.
915 /// * `min_exposed_child`: The tag of the smallest exposed (transitive) child of the accessed node.
916 /// This is only used with `visit_children == SkipChildrenOfAccessed`, where we need to skip children
917 /// of the accessed node.
918 fn perform_access(
919 &mut self,
920 roots: impl Iterator<Item = UniIndex>,
921 nodes: &mut UniValMap<Node>,
922 access_source: Option<UniIndex>,
923 access_kind: AccessKind,
924 global: &GlobalState,
925 visit_children: ChildrenVisitMode,
926 diagnostics: &DiagnosticInfo,
927 min_exposed_child: Option<BorTag>,
928 ) -> InterpResult<'tcx> {
929 let accessed_root = if let Some(idx) = access_source {
930 Some(self.perform_normal_access(
931 idx,
932 nodes,
933 access_kind,
934 global,
935 visit_children,
936 diagnostics,
937 )?)
938 } else {
939 // `SkipChildrenOfAccessed` only gets set on protector release, which only
940 // occurs on a known node.
941 assert!(matches!(visit_children, ChildrenVisitMode::VisitChildrenOfAccessed));
942 None
943 };
944
945 let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag);
946 for root in roots {
947 let tag = nodes.get(root).unwrap().tag;
948 // On a protector release access we have to skip the children of the accessed tag.
949 // However, if the tag has exposed children then some of the wildcard subtrees could
950 // also be children of the accessed node and would also need to be skipped. We can
951 // narrow down which wildcard trees might be children by comparing their root tag to the
952 // minimum exposed child of the accessed node. As the parent tag is always smaller
953 // than the child tag this means we only need to skip subtrees with a root tag larger
954 // than `min_exposed_child`. Once we find such a root, we can leave the loop because roots
955 // are sorted by tag.
956 if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed)
957 && let Some(min_exposed_child) = min_exposed_child
958 && tag > min_exposed_child
959 {
960 break;
961 }
962 // We don't perform a wildcard access on the tree we already performed a
963 // normal access on.
964 if Some(root) == accessed_root {
965 continue;
966 }
967 // The choice of `max_local_tag` requires some thought.
968 // This can only be a local access for nodes that are a parent of the accessed node
969 // and are therefore smaller, so the accessed node itself is a valid choice for `max_local_tag`.
970 // However, using `accessed_root` is better since that will be smaller. It is still a valid choice
971 // because for nodes *in other trees*, if they are a parent of the accessed node then they
972 // are a parent of `accessed_root`.
973 //
974 // As a consequence of this, since the root of the main tree is the smallest tag in the entire
975 // allocation, if the access occurred in the main tree then other subtrees will only see foreign accesses.
976 self.perform_wildcard_access(
977 root,
978 access_source,
979 /*max_local_tag*/ accessed_root_tag,
980 nodes,
981 access_kind,
982 global,
983 diagnostics,
984 )?;
985 }
986 interp_ok(())
987 }
988
989 /// Performs a normal access on the tree containing `access_source`.
990 ///
991 /// Returns the root index of this tree.
992 /// * `access_source`: The index of the tag being accessed.
993 /// * `visit_children`: Whether to skip the children of `access_source`
994 /// during the access. Used for protector end access.
995 fn perform_normal_access(
996 &mut self,
997 access_source: UniIndex,
998 nodes: &mut UniValMap<Node>,
999 access_kind: AccessKind,
1000 global: &GlobalState,
1001 visit_children: ChildrenVisitMode,
1002 diagnostics: &DiagnosticInfo,
1003 ) -> InterpResult<'tcx, UniIndex> {
1004 // Performs the per-node work:
1005 // - insert the permission if it does not exist
1006 // - perform the access
1007 // - record the transition
1008 // to which some optimizations are added:
1009 // - skip the traversal of the children in some cases
1010 // - do not record noop transitions
1011 //
1012 // `loc_range` is only for diagnostics (it is the range of
1013 // the `RangeMap` on which we are currently working).
1014 let node_skipper = |args: &NodeAppArgs<'_, LocationTree>| -> ContinueTraversal {
1015 let node = args.nodes.get(args.idx).unwrap();
1016 let perm = args.data.perms.get(args.idx);
1017
1018 let old_state = perm.copied().unwrap_or_else(|| node.default_location_state());
1019 old_state.skip_if_known_noop(access_kind, args.rel_pos)
1020 };
1021 let node_app = |args: NodeAppArgs<'_, LocationTree>| {
1022 let node = args.nodes.get_mut(args.idx).unwrap();
1023 let mut perm = args.data.perms.entry(args.idx);
1024
1025 let state = perm.or_insert(node.default_location_state());
1026
1027 let protected = global.borrow().protected_tags.contains_key(&node.tag);
1028 state
1029 .perform_transition(
1030 args.idx,
1031 args.nodes,
1032 &mut args.data.wildcard_accesses,
1033 access_kind,
1034 args.rel_pos,
1035 protected,
1036 diagnostics,
1037 )
1038 .map_err(|error_kind| {
1039 TbError {
1040 error_kind,
1041 access_info: diagnostics,
1042 conflicting_node_info: &args.nodes.get(args.idx).unwrap().debug_info,
1043 accessed_node_info: Some(
1044 &args.nodes.get(access_source).unwrap().debug_info,
1045 ),
1046 }
1047 .build()
1048 })
1049 };
1050
1051 let visitor = TreeVisitor { nodes, data: self };
1052 match visit_children {
1053 ChildrenVisitMode::VisitChildrenOfAccessed =>
1054 visitor.traverse_this_parents_children_other(access_source, node_skipper, node_app),
1055 ChildrenVisitMode::SkipChildrenOfAccessed =>
1056 visitor.traverse_nonchildren(access_source, node_skipper, node_app),
1057 }
1058 .into()
1059 }
1060
1061 /// Performs a wildcard access on the tree with root `root`. Takes the `access_relatedness`
1062 /// for each node from the `WildcardState` datastructure.
1063 /// * `root`: Root of the tree being accessed.
1064 /// * `access_source`: the index of the accessed tag, if any.
1065 /// This is only used for printing the correct tag on errors.
1066 /// * `max_local_tag`: The access can only be local for nodes whose tag is
1067 /// at most `max_local_tag`.
1068 fn perform_wildcard_access(
1069 &mut self,
1070 root: UniIndex,
1071 access_source: Option<UniIndex>,
1072 max_local_tag: Option<BorTag>,
1073 nodes: &mut UniValMap<Node>,
1074 access_kind: AccessKind,
1075 global: &GlobalState,
1076 diagnostics: &DiagnosticInfo,
1077 ) -> InterpResult<'tcx> {
1078 let get_relatedness = |idx: UniIndex, node: &Node, loc: &LocationTree| {
1079 let wildcard_state = loc.wildcard_accesses.get(idx).cloned().unwrap_or_default();
1080 // If the tag is larger than `max_local_tag` then the access can only be foreign.
1081 let only_foreign = max_local_tag.is_some_and(|max_local_tag| max_local_tag < node.tag);
1082 wildcard_state.access_relatedness(access_kind, only_foreign)
1083 };
1084
1085 // Whether there is an exposed node in this tree that allows this access.
1086 let mut has_valid_exposed = false;
1087
1088 // This does a traversal across the tree updating children before their parents. The
1089 // difference to `perform_normal_access` is that we take the access relatedness from
1090 // the wildcard tracking state of the node instead of from the visitor itself.
1091 //
1092 // Unlike for a normal access, the iteration order is important for improving the
1093 // accuracy of wildcard accesses if `max_local_tag` is `Some`: processing the effects of this
1094 // access further down the tree can cause exposed nodes to lose permissions, thus updating
1095 // the wildcard data structure, which will be taken into account when processing the parent
1096 // nodes. Also see the test `cross_tree_update_older_invalid_exposed2.rs`
1097 // (Doing accesses in the opposite order cannot help with precision but the reasons are complicated;
1098 // see <https://github.com/rust-lang/miri/pull/4707#discussion_r2581661123>.)
1099 //
1100 // Note, however, that this is an approximation: there can be situations where a node is
1101 // marked as having an exposed foreign node, but actually that foreign node cannot be
1102 // the source of the access due to `max_local_tag`. The wildcard tracking cannot know
1103 // about `max_local_tag` so we will incorrectly assume that this might be a foreign access.
1104 TreeVisitor { data: self, nodes }.traverse_children_this(
1105 root,
1106 |args| -> ContinueTraversal {
1107 let node = args.nodes.get(args.idx).unwrap();
1108 let perm = args.data.perms.get(args.idx);
1109
1110 let old_state = perm.copied().unwrap_or_else(|| node.default_location_state());
1111 // If we know where, relative to this node, the wildcard access occurs,
1112 // then check if we can skip the entire subtree.
1113 if let Some(relatedness) = get_relatedness(args.idx, node, args.data)
1114 && let Some(relatedness) = relatedness.to_relatedness()
1115 {
1116 // We can use the usual SIFA machinery to skip nodes.
1117 old_state.skip_if_known_noop(access_kind, relatedness)
1118 } else {
1119 ContinueTraversal::Recurse
1120 }
1121 },
1122 |args| {
1123 let node = args.nodes.get_mut(args.idx).unwrap();
1124
1125 let protected = global.borrow().protected_tags.contains_key(&node.tag);
1126
1127 let Some(wildcard_relatedness) = get_relatedness(args.idx, node, args.data) else {
1128 // There doesn't exist a valid exposed reference for this access to
1129 // happen through.
1130 // This can only happen if `root` is the main root: We set
1131 // `max_foreign_access==Write` on all wildcard roots, so at least a foreign access
1132 // is always possible on all nodes in a wildcard subtree.
1133 return Err(no_valid_exposed_references_error(diagnostics));
1134 };
1135
1136 let mut entry = args.data.perms.entry(args.idx);
1137 let perm = entry.or_insert(node.default_location_state());
1138
1139 // We only count exposed nodes through which an access could happen.
1140 if node.is_exposed
1141 && perm.permission.strongest_allowed_local_access(protected).allows(access_kind)
1142 && max_local_tag.is_none_or(|max_local_tag| max_local_tag >= node.tag)
1143 {
1144 has_valid_exposed = true;
1145 }
1146
1147 let Some(relatedness) = wildcard_relatedness.to_relatedness() else {
1148 // If the access type is Either, then we do not apply any transition
1149 // to this node, but we still update each of its children.
1150 // This is an imprecision! In the future, maybe we can still do some sort
1151 // of best-effort update here.
1152 return Ok(());
1153 };
1154
1155 // We know the exact relatedness, so we can actually do precise checks.
1156 perm.perform_transition(
1157 args.idx,
1158 args.nodes,
1159 &mut args.data.wildcard_accesses,
1160 access_kind,
1161 relatedness,
1162 protected,
1163 diagnostics,
1164 )
1165 .map_err(|trans| {
1166 let node = args.nodes.get(args.idx).unwrap();
1167 TbError {
1168 error_kind: trans,
1169 access_info: diagnostics,
1170 conflicting_node_info: &node.debug_info,
1171 accessed_node_info: access_source
1172 .map(|idx| &args.nodes.get(idx).unwrap().debug_info),
1173 }
1174 .build()
1175 })
1176 },
1177 )?;
1178 // If there is no exposed node in this tree that allows this access, then the
1179 // access *must* be foreign. So we check if the root of this tree would allow this
1180 // as a foreign access, and if not, then we can error.
1181 // In practice, all wildcard trees accept foreign accesses, but the main tree does
1182 // not, so this catches UB when none of the nodes in the main tree allows this access.
1183 if !has_valid_exposed
1184 && self
1185 .wildcard_accesses
1186 .get(root)
1187 .unwrap()
1188 .access_relatedness(access_kind, /* only_foreign */ true)
1189 .is_none()
1190 {
1191 return Err(no_valid_exposed_references_error(diagnostics)).into();
1192 }
1193 interp_ok(())
1194 }
1195}
1196
1197impl Node {
1198 pub fn default_location_state(&self) -> LocationState {
1199 LocationState::new_non_accessed(
1200 self.default_initial_perm,
1201 self.default_initial_idempotent_foreign_access,
1202 )
1203 }
1204}
1205
1206impl VisitProvenance for Tree {
1207 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
1208 // To ensure that the roots never get removed, we visit them.
1209 // FIXME: it should be possible to GC wildcard tree roots.
1210 for id in self.roots.iter().copied() {
1211 visit(None, Some(self.nodes.get(id).unwrap().tag));
1212 }
1213 // We also need to keep around any exposed tags through which
1214 // an access could still happen.
1215 for (_id, node) in self.nodes.iter() {
1216 if node.is_exposed {
1217 visit(None, Some(node.tag))
1218 }
1219 }
1220 }
1221}
1222
1223/// Relative position of the access
1224#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1225pub enum AccessRelatedness {
1226 /// The access happened either through the node itself or one of
1227 /// its transitive children.
1228 LocalAccess,
1229 /// The access happened through this nodes ancestor or through
1230 /// a sibling/cousin/uncle/etc.
1231 ForeignAccess,
1232}
1233
1234impl AccessRelatedness {
1235 /// Check that access is either Ancestor or Distant, i.e. not
1236 /// a transitive child (initial pointer included).
1237 pub fn is_foreign(self) -> bool {
1238 matches!(self, AccessRelatedness::ForeignAccess)
1239 }
1240}