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