diff --git a/src/borrow_tracker/tree_borrows/mod.rs b/src/borrow_tracker/tree_borrows/mod.rs index e1da12282c..018421ad10 100644 --- a/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/borrow_tracker/tree_borrows/mod.rs @@ -13,6 +13,7 @@ pub mod diagnostics; mod foreign_access_skipping; mod perms; mod tree; +mod tree_visitor; mod unimap; mod wildcard; diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index 7fc9fa786e..ca68dc8910 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -18,15 +18,15 @@ use rustc_data_structures::fx::FxHashSet; use rustc_span::Span; use smallvec::SmallVec; -use super::diagnostics::AccessCause; -use super::wildcard::WildcardState; -use crate::borrow_tracker::tree_borrows::Permission; -use crate::borrow_tracker::tree_borrows::diagnostics::{ - self, NodeDebugInfo, TbError, TransitionError, no_valid_exposed_references_error, +use super::Permission; +use super::diagnostics::{ + self, AccessCause, NodeDebugInfo, TbError, TransitionError, no_valid_exposed_references_error, }; -use crate::borrow_tracker::tree_borrows::foreign_access_skipping::IdempotentForeignAccess; -use crate::borrow_tracker::tree_borrows::perms::PermTransition; -use crate::borrow_tracker::tree_borrows::unimap::{UniIndex, UniKeyMap, UniValMap}; +use super::foreign_access_skipping::IdempotentForeignAccess; +use super::perms::PermTransition; +use super::tree_visitor::{ChildrenVisitMode, ContinueTraversal, NodeAppArgs, TreeVisitor}; +use super::unimap::{UniIndex, UniKeyMap, UniValMap}; +use super::wildcard::WildcardState; use crate::borrow_tracker::{AccessKind, GlobalState, ProtectorKind}; use crate::*; @@ -339,290 +339,6 @@ pub(super) struct Node { pub debug_info: NodeDebugInfo, } -/// Data given to the transition function -struct NodeAppArgs<'visit> { - /// The index of the current node. - idx: UniIndex, - /// Relative position of the access. - rel_pos: AccessRelatedness, - /// The node map of this tree. - nodes: &'visit mut UniValMap, - /// The permissions map of this tree. - loc: &'visit mut LocationTree, -} -/// Internal contents of `Tree` with the minimum of mutable access for -/// For soundness do not modify the children or parent indexes of nodes -/// during traversal. -struct TreeVisitor<'tree> { - nodes: &'tree mut UniValMap, - loc: &'tree mut LocationTree, -} - -/// Whether to continue exploring the children recursively or not. -#[derive(Debug)] -enum ContinueTraversal { - Recurse, - SkipSelfAndChildren, -} - -#[derive(Clone, Copy, Debug)] -pub enum ChildrenVisitMode { - VisitChildrenOfAccessed, - SkipChildrenOfAccessed, -} - -enum RecursionState { - BeforeChildren, - AfterChildren, -} - -/// Stack of nodes left to explore in a tree traversal. -/// See the docs of `traverse_this_parents_children_other` for details on the -/// traversal order. -struct TreeVisitorStack { - /// Function describing whether to continue at a tag. - /// This is only invoked for foreign accesses. - f_continue: NodeContinue, - /// Function to apply to each tag. - f_propagate: NodeApp, - /// Mutable state of the visit: the tags left to handle. - /// Every tag pushed should eventually be handled, - /// and the precise order is relevant for diagnostics. - /// Since the traversal is piecewise bottom-up, we need to - /// remember whether we're here initially, or after visiting all children. - /// The last element indicates this. - /// This is just an artifact of how you hand-roll recursion, - /// it does not have a deeper meaning otherwise. - stack: Vec<(UniIndex, AccessRelatedness, RecursionState)>, -} - -impl TreeVisitorStack -where - NodeContinue: Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - NodeApp: FnMut(NodeAppArgs<'_>) -> Result<(), Err>, -{ - fn should_continue_at( - &self, - this: &mut TreeVisitor<'_>, - idx: UniIndex, - rel_pos: AccessRelatedness, - ) -> ContinueTraversal { - let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc }; - (self.f_continue)(&args) - } - - fn propagate_at( - &mut self, - this: &mut TreeVisitor<'_>, - idx: UniIndex, - rel_pos: AccessRelatedness, - ) -> Result<(), Err> { - (self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc }) - } - - /// Returns the root of this tree. - fn go_upwards_from_accessed( - &mut self, - this: &mut TreeVisitor<'_>, - accessed_node: UniIndex, - visit_children: ChildrenVisitMode, - ) -> Result { - // We want to visit the accessed node's children first. - // However, we will below walk up our parents and push their children (our cousins) - // onto the stack. To ensure correct iteration order, this method thus finishes - // by reversing the stack. This only works if the stack is empty initially. - assert!(self.stack.is_empty()); - // First, handle accessed node. A bunch of things need to - // be handled differently here compared to the further parents - // of `accesssed_node`. - { - self.propagate_at(this, accessed_node, AccessRelatedness::LocalAccess)?; - if matches!(visit_children, ChildrenVisitMode::VisitChildrenOfAccessed) { - let accessed_node = this.nodes.get(accessed_node).unwrap(); - // We `rev()` here because we reverse the entire stack later. - for &child in accessed_node.children.iter().rev() { - self.stack.push(( - child, - AccessRelatedness::ForeignAccess, - RecursionState::BeforeChildren, - )); - } - } - } - // Then, handle the accessed node's parents. Here, we need to - // make sure we only mark the "cousin" subtrees for later visitation, - // not the subtree that contains the accessed node. - let mut last_node = accessed_node; - while let Some(current) = this.nodes.get(last_node).unwrap().parent { - self.propagate_at(this, current, AccessRelatedness::LocalAccess)?; - let node = this.nodes.get(current).unwrap(); - // We `rev()` here because we reverse the entire stack later. - for &child in node.children.iter().rev() { - if last_node == child { - continue; - } - self.stack.push(( - child, - AccessRelatedness::ForeignAccess, - RecursionState::BeforeChildren, - )); - } - last_node = current; - } - // Reverse the stack, as discussed above. - self.stack.reverse(); - Ok(last_node) - } - - fn finish_foreign_accesses(&mut self, this: &mut TreeVisitor<'_>) -> Result<(), Err> { - while let Some((idx, rel_pos, step)) = self.stack.last_mut() { - let idx = *idx; - let rel_pos = *rel_pos; - match *step { - // How to do bottom-up traversal, 101: Before you handle a node, you handle all children. - // For this, you must first find the children, which is what this code here does. - RecursionState::BeforeChildren => { - // Next time we come back will be when all the children are handled. - *step = RecursionState::AfterChildren; - // Now push the children, except if we are told to skip this subtree. - let handle_children = self.should_continue_at(this, idx, rel_pos); - match handle_children { - ContinueTraversal::Recurse => { - let node = this.nodes.get(idx).unwrap(); - for &child in node.children.iter() { - self.stack.push((child, rel_pos, RecursionState::BeforeChildren)); - } - } - ContinueTraversal::SkipSelfAndChildren => { - // skip self - self.stack.pop(); - continue; - } - } - } - // All the children are handled, let's actually visit this node - RecursionState::AfterChildren => { - self.stack.pop(); - self.propagate_at(this, idx, rel_pos)?; - } - } - } - Ok(()) - } - - fn new(f_continue: NodeContinue, f_propagate: NodeApp) -> Self { - Self { f_continue, f_propagate, stack: Vec::new() } - } -} - -impl<'tree> TreeVisitor<'tree> { - /// Applies `f_propagate` to every vertex of the tree in a piecewise bottom-up way: First, visit - /// all ancestors of `start_idx` (starting with `start_idx` itself), then children of `start_idx`, then the rest, - /// going bottom-up in each of these two "pieces" / sections. - /// This ensures that errors are triggered in the following order - /// - first invalid accesses with insufficient permissions, closest to the accessed node first, - /// - then protector violations, bottom-up, starting with the children of the accessed node, and then - /// going upwards and outwards. - /// - /// The following graphic visualizes it, with numbers indicating visitation order and `start_idx` being - /// the node that is visited first ("1"): - /// - /// ```text - /// 3 - /// /| - /// / | - /// 9 2 - /// | |\ - /// | | \ - /// 8 1 7 - /// / \ - /// 4 6 - /// | - /// 5 - /// ``` - /// - /// `f_propagate` should follow the following format: for a given `Node` it updates its - /// `Permission` depending on the position relative to `start_idx` (given by an - /// `AccessRelatedness`). - /// `f_continue` is called earlier on foreign nodes, and describes whether to even start - /// visiting the subtree at that node. If it e.g. returns `SkipSelfAndChildren` on node 6 - /// above, then nodes 5 _and_ 6 would not be visited by `f_propagate`. It is not used for - /// notes having a child access (nodes 1, 2, 3). - /// - /// Finally, remember that the iteration order is not relevant for UB, it only affects - /// diagnostics. It also affects tree traversal optimizations built on top of this, so - /// those need to be reviewed carefully as well whenever this changes. - /// - /// Returns the index of the root of the accessed tree. - fn traverse_this_parents_children_other( - mut self, - start_idx: UniIndex, - f_continue: impl Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - f_propagate: impl FnMut(NodeAppArgs<'_>) -> Result<(), Err>, - ) -> Result { - let mut stack = TreeVisitorStack::new(f_continue, f_propagate); - // Visits the accessed node itself, and all its parents, i.e. all nodes - // undergoing a child access. Also pushes the children and the other - // cousin nodes (i.e. all nodes undergoing a foreign access) to the stack - // to be processed later. - let root = stack.go_upwards_from_accessed( - &mut self, - start_idx, - ChildrenVisitMode::VisitChildrenOfAccessed, - )?; - // Now visit all the foreign nodes we remembered earlier. - // For this we go bottom-up, but also allow f_continue to skip entire - // subtrees from being visited if it would be a NOP. - stack.finish_foreign_accesses(&mut self)?; - Ok(root) - } - - /// Like `traverse_this_parents_children_other`, but skips the children of `start_idx`. - /// - /// Returns the index of the root of the accessed tree. - fn traverse_nonchildren( - mut self, - start_idx: UniIndex, - f_continue: impl Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - f_propagate: impl FnMut(NodeAppArgs<'_>) -> Result<(), Err>, - ) -> Result { - let mut stack = TreeVisitorStack::new(f_continue, f_propagate); - // Visits the accessed node itself, and all its parents, i.e. all nodes - // undergoing a child access. Also pushes the other cousin nodes to the - // stack, but not the children of the accessed node. - let root = stack.go_upwards_from_accessed( - &mut self, - start_idx, - ChildrenVisitMode::SkipChildrenOfAccessed, - )?; - // Now visit all the foreign nodes we remembered earlier. - // For this we go bottom-up, but also allow f_continue to skip entire - // subtrees from being visited if it would be a NOP. - stack.finish_foreign_accesses(&mut self)?; - Ok(root) - } - - /// Traverses all children of `start_idx` including `start_idx` itself. - /// Uses `f_continue` to filter out subtrees and then processes each node - /// with `f_propagate` so that the children get processed before their - /// parents. - fn traverse_children_this( - mut self, - start_idx: UniIndex, - f_continue: impl Fn(&NodeAppArgs<'_>) -> ContinueTraversal, - f_propagate: impl FnMut(NodeAppArgs<'_>) -> Result<(), Err>, - ) -> Result<(), Err> { - let mut stack = TreeVisitorStack::new(f_continue, f_propagate); - - stack.stack.push(( - start_idx, - AccessRelatedness::ForeignAccess, - RecursionState::BeforeChildren, - )); - stack.finish_foreign_accesses(&mut self) - } -} - impl Tree { /// Create a new tree, with only a root pointer. pub fn new(root_tag: BorTag, size: Size, span: Span) -> Self { @@ -845,42 +561,43 @@ impl<'tcx> Tree { // Checks the tree containing `idx` for strong protector violations. // It does this in traversal order. let mut check_tree = |idx| { - TreeVisitor { nodes: &mut self.nodes, loc }.traverse_this_parents_children_other( - idx, - // Visit all children, skipping none. - |_| ContinueTraversal::Recurse, - |args: NodeAppArgs<'_>| { - let node = args.nodes.get(args.idx).unwrap(); - - let perm = args - .loc - .perms - .get(args.idx) - .copied() - .unwrap_or_else(|| node.default_location_state()); - if global.borrow().protected_tags.get(&node.tag) - == Some(&ProtectorKind::StrongProtector) - // Don't check for protector if it is a Cell (see `unsafe_cell_deallocate` in `interior_mutability.rs`). - // Related to https://github.com/rust-lang/rust/issues/55005. - && !perm.permission.is_cell() - // Only trigger UB if the accessed bit is set, i.e. if the protector is actually protecting this offset. See #4579. - && perm.accessed - { - Err(TbError { - conflicting_info: &node.debug_info, - access_cause: diagnostics::AccessCause::Dealloc, - alloc_id, - error_offset: loc_range.start, - error_kind: TransitionError::ProtectedDealloc, - accessed_info: start_idx - .map(|idx| &args.nodes.get(idx).unwrap().debug_info), + TreeVisitor { nodes: &mut self.nodes, data: loc } + .traverse_this_parents_children_other( + idx, + // Visit all children, skipping none. + |_| ContinueTraversal::Recurse, + |args: NodeAppArgs<'_, _>| { + let node = args.nodes.get(args.idx).unwrap(); + + let perm = args + .data + .perms + .get(args.idx) + .copied() + .unwrap_or_else(|| node.default_location_state()); + if global.borrow().protected_tags.get(&node.tag) + == Some(&ProtectorKind::StrongProtector) + // Don't check for protector if it is a Cell (see `unsafe_cell_deallocate` in `interior_mutability.rs`). + // Related to https://github.com/rust-lang/rust/issues/55005. + && !perm.permission.is_cell() + // Only trigger UB if the accessed bit is set, i.e. if the protector is actually protecting this offset. See #4579. + && perm.accessed + { + Err(TbError { + conflicting_info: &node.debug_info, + access_cause: diagnostics::AccessCause::Dealloc, + alloc_id, + error_offset: loc_range.start, + error_kind: TransitionError::ProtectedDealloc, + accessed_info: start_idx + .map(|idx| &args.nodes.get(idx).unwrap().debug_info), + } + .build()) + } else { + Ok(()) } - .build()) - } else { - Ok(()) - } - }, - ) + }, + ) }; // If we have a start index we first check its subtree in traversal order. // This results in us showing the error of the closest node instead of an @@ -1250,16 +967,16 @@ impl<'tcx> LocationTree { // // `loc_range` is only for diagnostics (it is the range of // the `RangeMap` on which we are currently working). - let node_skipper = |args: &NodeAppArgs<'_>| -> ContinueTraversal { + let node_skipper = |args: &NodeAppArgs<'_, LocationTree>| -> ContinueTraversal { let node = args.nodes.get(args.idx).unwrap(); - let perm = args.loc.perms.get(args.idx); + let perm = args.data.perms.get(args.idx); let old_state = perm.copied().unwrap_or_else(|| node.default_location_state()); old_state.skip_if_known_noop(access_kind, args.rel_pos) }; - let node_app = |args: NodeAppArgs<'_>| { + let node_app = |args: NodeAppArgs<'_, LocationTree>| { let node = args.nodes.get_mut(args.idx).unwrap(); - let mut perm = args.loc.perms.entry(args.idx); + let mut perm = args.data.perms.entry(args.idx); let state = perm.or_insert(node.default_location_state()); @@ -1268,7 +985,7 @@ impl<'tcx> LocationTree { .perform_transition( args.idx, args.nodes, - &mut args.loc.wildcard_accesses, + &mut args.data.wildcard_accesses, access_kind, access_cause, access_range, @@ -1290,7 +1007,7 @@ impl<'tcx> LocationTree { }) }; - let visitor = TreeVisitor { nodes, loc: self }; + let visitor = TreeVisitor { nodes, data: self }; match visit_children { ChildrenVisitMode::VisitChildrenOfAccessed => visitor.traverse_this_parents_children_other(access_source, node_skipper, node_app), @@ -1344,16 +1061,16 @@ impl<'tcx> LocationTree { // marked as having an exposed foreign node, but actually that foreign node cannot be // the source of the access due to `max_local_tag`. The wildcard tracking cannot know // about `max_local_tag` so we will incorrectly assume that this might be a foreign access. - TreeVisitor { loc: self, nodes }.traverse_children_this( + TreeVisitor { data: self, nodes }.traverse_children_this( root, |args| -> ContinueTraversal { let node = args.nodes.get(args.idx).unwrap(); - let perm = args.loc.perms.get(args.idx); + let perm = args.data.perms.get(args.idx); let old_state = perm.copied().unwrap_or_else(|| node.default_location_state()); // If we know where, relative to this node, the wildcard access occurs, // then check if we can skip the entire subtree. - if let Some(relatedness) = get_relatedness(args.idx, node, args.loc) + if let Some(relatedness) = get_relatedness(args.idx, node, args.data) && let Some(relatedness) = relatedness.to_relatedness() { // We can use the usual SIFA machinery to skip nodes. @@ -1367,7 +1084,7 @@ impl<'tcx> LocationTree { let protected = global.borrow().protected_tags.contains_key(&node.tag); - let Some(wildcard_relatedness) = get_relatedness(args.idx, node, args.loc) else { + let Some(wildcard_relatedness) = get_relatedness(args.idx, node, args.data) else { // There doesn't exist a valid exposed reference for this access to // happen through. // This can only happen if `root` is the main root: We set @@ -1388,13 +1105,13 @@ impl<'tcx> LocationTree { return Ok(()); }; - let mut entry = args.loc.perms.entry(args.idx); + let mut entry = args.data.perms.entry(args.idx); let perm = entry.or_insert(node.default_location_state()); // We know the exact relatedness, so we can actually do precise checks. perm.perform_transition( args.idx, args.nodes, - &mut args.loc.wildcard_accesses, + &mut args.data.wildcard_accesses, access_kind, access_cause, access_range, diff --git a/src/borrow_tracker/tree_borrows/tree_visitor.rs b/src/borrow_tracker/tree_borrows/tree_visitor.rs new file mode 100644 index 0000000000..b1ceeecf57 --- /dev/null +++ b/src/borrow_tracker/tree_borrows/tree_visitor.rs @@ -0,0 +1,289 @@ +use std::marker::PhantomData; + +use super::tree::{AccessRelatedness, Node}; +use super::unimap::{UniIndex, UniValMap}; + +/// Data given to the transition function +pub struct NodeAppArgs<'visit, T> { + /// The index of the current node. + pub idx: UniIndex, + /// Relative position of the access. + pub rel_pos: AccessRelatedness, + /// The node map of this tree. + pub nodes: &'visit mut UniValMap, + /// Additional data we want to be able to modify in f_propagate and read in f_continue. + pub data: &'visit mut T, +} +/// Internal contents of `Tree` with the minimum of mutable access for +/// For soundness do not modify the children or parent indexes of nodes +/// during traversal. +pub struct TreeVisitor<'tree, T> { + pub nodes: &'tree mut UniValMap, + pub data: &'tree mut T, +} + +/// Whether to continue exploring the children recursively or not. +#[derive(Debug)] +pub enum ContinueTraversal { + Recurse, + SkipSelfAndChildren, +} + +#[derive(Clone, Copy, Debug)] +pub enum ChildrenVisitMode { + VisitChildrenOfAccessed, + SkipChildrenOfAccessed, +} + +enum RecursionState { + BeforeChildren, + AfterChildren, +} + +/// Stack of nodes left to explore in a tree traversal. +/// See the docs of `traverse_this_parents_children_other` for details on the +/// traversal order. +struct TreeVisitorStack { + /// Function describing whether to continue at a tag. + /// This is only invoked for foreign accesses. + f_continue: NodeContinue, + /// Function to apply to each tag. + f_propagate: NodeApp, + /// Mutable state of the visit: the tags left to handle. + /// Every tag pushed should eventually be handled, + /// and the precise order is relevant for diagnostics. + /// Since the traversal is piecewise bottom-up, we need to + /// remember whether we're here initially, or after visiting all children. + /// The last element indicates this. + /// This is just an artifact of how you hand-roll recursion, + /// it does not have a deeper meaning otherwise. + stack: Vec<(UniIndex, AccessRelatedness, RecursionState)>, + phantom: PhantomData, +} + +impl TreeVisitorStack +where + NodeContinue: Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal, + NodeApp: FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>, +{ + fn should_continue_at( + &self, + this: &mut TreeVisitor<'_, T>, + idx: UniIndex, + rel_pos: AccessRelatedness, + ) -> ContinueTraversal { + let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, data: this.data }; + (self.f_continue)(&args) + } + + fn propagate_at( + &mut self, + this: &mut TreeVisitor<'_, T>, + idx: UniIndex, + rel_pos: AccessRelatedness, + ) -> Result<(), Err> { + (self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, data: this.data }) + } + + /// Returns the root of this tree. + fn go_upwards_from_accessed( + &mut self, + this: &mut TreeVisitor<'_, T>, + accessed_node: UniIndex, + visit_children: ChildrenVisitMode, + ) -> Result { + // We want to visit the accessed node's children first. + // However, we will below walk up our parents and push their children (our cousins) + // onto the stack. To ensure correct iteration order, this method thus finishes + // by reversing the stack. This only works if the stack is empty initially. + assert!(self.stack.is_empty()); + // First, handle accessed node. A bunch of things need to + // be handled differently here compared to the further parents + // of `accesssed_node`. + { + self.propagate_at(this, accessed_node, AccessRelatedness::LocalAccess)?; + if matches!(visit_children, ChildrenVisitMode::VisitChildrenOfAccessed) { + let accessed_node = this.nodes.get(accessed_node).unwrap(); + // We `rev()` here because we reverse the entire stack later. + for &child in accessed_node.children.iter().rev() { + self.stack.push(( + child, + AccessRelatedness::ForeignAccess, + RecursionState::BeforeChildren, + )); + } + } + } + // Then, handle the accessed node's parents. Here, we need to + // make sure we only mark the "cousin" subtrees for later visitation, + // not the subtree that contains the accessed node. + let mut last_node = accessed_node; + while let Some(current) = this.nodes.get(last_node).unwrap().parent { + self.propagate_at(this, current, AccessRelatedness::LocalAccess)?; + let node = this.nodes.get(current).unwrap(); + // We `rev()` here because we reverse the entire stack later. + for &child in node.children.iter().rev() { + if last_node == child { + continue; + } + self.stack.push(( + child, + AccessRelatedness::ForeignAccess, + RecursionState::BeforeChildren, + )); + } + last_node = current; + } + // Reverse the stack, as discussed above. + self.stack.reverse(); + Ok(last_node) + } + + fn finish_foreign_accesses(&mut self, this: &mut TreeVisitor<'_, T>) -> Result<(), Err> { + while let Some((idx, rel_pos, step)) = self.stack.last_mut() { + let idx = *idx; + let rel_pos = *rel_pos; + match *step { + // How to do bottom-up traversal, 101: Before you handle a node, you handle all children. + // For this, you must first find the children, which is what this code here does. + RecursionState::BeforeChildren => { + // Next time we come back will be when all the children are handled. + *step = RecursionState::AfterChildren; + // Now push the children, except if we are told to skip this subtree. + let handle_children = self.should_continue_at(this, idx, rel_pos); + match handle_children { + ContinueTraversal::Recurse => { + let node = this.nodes.get(idx).unwrap(); + for &child in node.children.iter() { + self.stack.push((child, rel_pos, RecursionState::BeforeChildren)); + } + } + ContinueTraversal::SkipSelfAndChildren => { + // skip self + self.stack.pop(); + continue; + } + } + } + // All the children are handled, let's actually visit this node + RecursionState::AfterChildren => { + self.stack.pop(); + self.propagate_at(this, idx, rel_pos)?; + } + } + } + Ok(()) + } + + fn new(f_continue: NodeContinue, f_propagate: NodeApp) -> Self { + Self { f_continue, f_propagate, stack: Vec::new(), phantom: PhantomData } + } +} + +impl<'tree, T> TreeVisitor<'tree, T> { + /// Applies `f_propagate` to every vertex of the tree in a piecewise bottom-up way: First, visit + /// all ancestors of `start_idx` (starting with `start_idx` itself), then children of `start_idx`, then the rest, + /// going bottom-up in each of these two "pieces" / sections. + /// This ensures that errors are triggered in the following order + /// - first invalid accesses with insufficient permissions, closest to the accessed node first, + /// - then protector violations, bottom-up, starting with the children of the accessed node, and then + /// going upwards and outwards. + /// + /// The following graphic visualizes it, with numbers indicating visitation order and `start_idx` being + /// the node that is visited first ("1"): + /// + /// ```text + /// 3 + /// /| + /// / | + /// 9 2 + /// | |\ + /// | | \ + /// 8 1 7 + /// / \ + /// 4 6 + /// | + /// 5 + /// ``` + /// + /// `f_propagate` should follow the following format: for a given `Node` it updates its + /// `Permission` depending on the position relative to `start_idx` (given by an + /// `AccessRelatedness`). + /// `f_continue` is called earlier on foreign nodes, and describes whether to even start + /// visiting the subtree at that node. If it e.g. returns `SkipSelfAndChildren` on node 6 + /// above, then nodes 5 _and_ 6 would not be visited by `f_propagate`. It is not used for + /// notes having a child access (nodes 1, 2, 3). + /// + /// Finally, remember that the iteration order is not relevant for UB, it only affects + /// diagnostics. It also affects tree traversal optimizations built on top of this, so + /// those need to be reviewed carefully as well whenever this changes. + /// + /// Returns the index of the root of the accessed tree. + pub fn traverse_this_parents_children_other( + mut self, + start_idx: UniIndex, + f_continue: impl Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal, + f_propagate: impl FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>, + ) -> Result { + let mut stack = TreeVisitorStack::new(f_continue, f_propagate); + // Visits the accessed node itself, and all its parents, i.e. all nodes + // undergoing a child access. Also pushes the children and the other + // cousin nodes (i.e. all nodes undergoing a foreign access) to the stack + // to be processed later. + let root = stack.go_upwards_from_accessed( + &mut self, + start_idx, + ChildrenVisitMode::VisitChildrenOfAccessed, + )?; + // Now visit all the foreign nodes we remembered earlier. + // For this we go bottom-up, but also allow f_continue to skip entire + // subtrees from being visited if it would be a NOP. + stack.finish_foreign_accesses(&mut self)?; + Ok(root) + } + + /// Like `traverse_this_parents_children_other`, but skips the children of `start_idx`. + /// + /// Returns the index of the root of the accessed tree. + pub fn traverse_nonchildren( + mut self, + start_idx: UniIndex, + f_continue: impl Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal, + f_propagate: impl FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>, + ) -> Result { + let mut stack = TreeVisitorStack::new(f_continue, f_propagate); + // Visits the accessed node itself, and all its parents, i.e. all nodes + // undergoing a child access. Also pushes the other cousin nodes to the + // stack, but not the children of the accessed node. + let root = stack.go_upwards_from_accessed( + &mut self, + start_idx, + ChildrenVisitMode::SkipChildrenOfAccessed, + )?; + // Now visit all the foreign nodes we remembered earlier. + // For this we go bottom-up, but also allow f_continue to skip entire + // subtrees from being visited if it would be a NOP. + stack.finish_foreign_accesses(&mut self)?; + Ok(root) + } + + /// Traverses all children of `start_idx` including `start_idx` itself. + /// Uses `f_continue` to filter out subtrees and then processes each node + /// with `f_propagate` so that the children get processed before their + /// parents. + pub fn traverse_children_this( + mut self, + start_idx: UniIndex, + f_continue: impl Fn(&NodeAppArgs<'_, T>) -> ContinueTraversal, + f_propagate: impl FnMut(NodeAppArgs<'_, T>) -> Result<(), Err>, + ) -> Result<(), Err> { + let mut stack = TreeVisitorStack::new(f_continue, f_propagate); + + stack.stack.push(( + start_idx, + AccessRelatedness::ForeignAccess, + RecursionState::BeforeChildren, + )); + stack.finish_foreign_accesses(&mut self) + } +}