Skip to content

Commit 5be5cec

Browse files
Add explanation to TB's "piecewise bottom-up" traversal
1 parent 2765444 commit 5be5cec

File tree

1 file changed

+111
-44
lines changed
  • src/tools/miri/src/borrow_tracker/tree_borrows

1 file changed

+111
-44
lines changed

src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs

Lines changed: 111 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,20 @@ enum ContinueTraversal {
302302
SkipSelfAndChildren,
303303
}
304304

305+
#[derive(Clone, Copy)]
306+
pub enum ChildrenVisitMode {
307+
VisitChildrenOfAccessed,
308+
SkipChildrenOfAccessed,
309+
}
310+
311+
enum RecursionState {
312+
BeforeChildren,
313+
AfterChildren,
314+
}
315+
305316
/// Stack of nodes left to explore in a tree traversal.
317+
/// See the docs of `traverse_this_parents_children_other` for details on the
318+
/// traversal order.
306319
struct TreeVisitorStack<NodeContinue, NodeApp, ErrHandler> {
307320
/// Identifier of the original access.
308321
initial: UniIndex,
@@ -316,10 +329,12 @@ struct TreeVisitorStack<NodeContinue, NodeApp, ErrHandler> {
316329
/// Mutable state of the visit: the tags left to handle.
317330
/// Every tag pushed should eventually be handled,
318331
/// and the precise order is relevant for diagnostics.
319-
/// Since the traversal is bottom-up, we need to remember
320-
/// whether we're here initially (false) or after visiting all
321-
/// children (true). The bool indicates this.
322-
stack: Vec<(UniIndex, AccessRelatedness, bool)>,
332+
/// Since the traversal is piecewise bottom-up, we need to
333+
/// remember whether we're here initially, or after visiting all children.
334+
/// The last element indicates this.
335+
/// This is just an artifact of how you hand-roll recursion,
336+
/// it does not have a deeper meaning otherwise.
337+
stack: Vec<(UniIndex, AccessRelatedness, RecursionState)>,
323338
}
324339

325340
impl<NodeContinue, NodeApp, InnErr, OutErr, ErrHandler>
@@ -362,64 +377,85 @@ where
362377
&mut self,
363378
this: &mut TreeVisitor<'_>,
364379
accessed_node: UniIndex,
365-
push_children_of_accessed: bool,
380+
visit_children: ChildrenVisitMode,
366381
) -> Result<(), OutErr> {
382+
// We want to visit the accessed node's children first.
383+
// However, we will below walk up our parents and push their children (our cousins)
384+
// onto the stack. To ensure correct iteration order, this method thus finishes
385+
// by reversing the stack. This only works if the stack is empty initially.
367386
assert!(self.stack.is_empty());
368387
// First, handle accessed node. A bunch of things need to
369388
// be handled differently here compared to the further parents
370389
// of `accesssed_node`.
371390
{
372391
self.propagate_at(this, accessed_node, AccessRelatedness::This)?;
373-
if push_children_of_accessed {
392+
if matches!(visit_children, ChildrenVisitMode::VisitChildrenOfAccessed) {
374393
let accessed_node = this.nodes.get(accessed_node).unwrap();
394+
// We `rev()` here because we reverse the entire stack later.
375395
for &child in accessed_node.children.iter().rev() {
376-
self.stack.push((child, AccessRelatedness::AncestorAccess, false));
396+
self.stack.push((
397+
child,
398+
AccessRelatedness::AncestorAccess,
399+
RecursionState::BeforeChildren,
400+
));
377401
}
378402
}
379403
}
380-
// Then, handle the accessed node's parent. Here, we need to
404+
// Then, handle the accessed node's parents. Here, we need to
381405
// make sure we only mark the "cousin" subtrees for later visitation,
382406
// not the subtree that contains the accessed node.
383407
let mut last_node = accessed_node;
384408
while let Some(current) = this.nodes.get(last_node).unwrap().parent {
385409
self.propagate_at(this, current, AccessRelatedness::StrictChildAccess)?;
386410
let node = this.nodes.get(current).unwrap();
411+
// We `rev()` here because we reverse the entire stack later.
387412
for &child in node.children.iter().rev() {
388413
if last_node == child {
389414
continue;
390415
}
391-
self.stack.push((child, AccessRelatedness::DistantAccess, false));
416+
self.stack.push((
417+
child,
418+
AccessRelatedness::DistantAccess,
419+
RecursionState::BeforeChildren,
420+
));
392421
}
393422
last_node = current;
394423
}
424+
// Reverse the stack, as discussed above.
395425
self.stack.reverse();
396426
Ok(())
397427
}
398428

399429
fn finish_foreign_accesses(&mut self, this: &mut TreeVisitor<'_>) -> Result<(), OutErr> {
400-
while let Some((idx, rel_pos, is_final)) = self.stack.last_mut() {
430+
while let Some((idx, rel_pos, step)) = self.stack.last_mut() {
401431
let idx = *idx;
402432
let rel_pos = *rel_pos;
403-
if *is_final {
404-
self.stack.pop();
405-
self.propagate_at(this, idx, rel_pos)?;
406-
} else {
407-
*is_final = true;
408-
let handle_children = self.should_continue_at(this, idx, rel_pos);
409-
match handle_children {
410-
ContinueTraversal::Recurse => {
411-
// add all children, and also leave the node itself
412-
// on the stack so that it can be visited later.
413-
let node = this.nodes.get(idx).unwrap();
414-
for &child in node.children.iter() {
415-
self.stack.push((child, rel_pos, false));
433+
match *step {
434+
// How to do bottom-up traversal, 101: Before you handle a node, you handle all children.
435+
// For this, you must first find the children, which is what this code here does.
436+
RecursionState::BeforeChildren => {
437+
// Next time we come back will be when all the children are handled.
438+
*step = RecursionState::AfterChildren;
439+
// Now push the children, except if we are told to skip this subtree.
440+
let handle_children = self.should_continue_at(this, idx, rel_pos);
441+
match handle_children {
442+
ContinueTraversal::Recurse => {
443+
let node = this.nodes.get(idx).unwrap();
444+
for &child in node.children.iter() {
445+
self.stack.push((child, rel_pos, RecursionState::BeforeChildren));
446+
}
447+
}
448+
ContinueTraversal::SkipSelfAndChildren => {
449+
// skip self
450+
self.stack.pop();
451+
continue;
416452
}
417453
}
418-
ContinueTraversal::SkipSelfAndChildren => {
419-
// skip self
420-
self.stack.pop();
421-
continue;
422-
}
454+
}
455+
// All the children are handled, let's actually visit this node
456+
RecursionState::AfterChildren => {
457+
self.stack.pop();
458+
self.propagate_at(this, idx, rel_pos)?;
423459
}
424460
}
425461
}
@@ -437,19 +473,42 @@ where
437473
}
438474

439475
impl<'tree> TreeVisitor<'tree> {
440-
// Applies `f_propagate` to every vertex of the tree bottom-up in the following order: first
441-
// all ancestors of `start` (starting with `start` itself), then children of `start`, then the rest,
442-
// always going bottom-up.
443-
// This ensures that errors are triggered in the following order
444-
// - first invalid accesses with insufficient permissions, closest to the accessed node first,
445-
// - then protector violations, bottom-up, starting with the children of the accessed node, and then
446-
// going upwards and outwards.
447-
//
448-
// `f_propagate` should follow the following format: for a given `Node` it updates its
449-
// `Permission` depending on the position relative to `start` (given by an
450-
// `AccessRelatedness`).
451-
// `f_continue` is called before on foreign nodes, and describes whether to continue
452-
// with the subtree at that node.
476+
/// Applies `f_propagate` to every vertex of the tree in a piecewise bottom-up way: First, visit
477+
/// all ancestors of `start` (starting with `start` itself), then children of `start`, then the rest,
478+
/// going bottom-up in each of these two "pieces" / sections.
479+
/// This ensures that errors are triggered in the following order
480+
/// - first invalid accesses with insufficient permissions, closest to the accessed node first,
481+
/// - then protector violations, bottom-up, starting with the children of the accessed node, and then
482+
/// going upwards and outwards.
483+
///
484+
/// The following graphic visualizes it, with numbers indicating visitation order and `start` being
485+
/// the node that is visited first ("1"):
486+
///
487+
/// ```text
488+
/// 3
489+
/// /|
490+
/// / |
491+
/// 9 2
492+
/// | |\
493+
/// | | \
494+
/// 8 1 7
495+
/// / \
496+
/// 4 6
497+
/// |
498+
/// 5
499+
/// ```
500+
///
501+
/// `f_propagate` should follow the following format: for a given `Node` it updates its
502+
/// `Permission` depending on the position relative to `start` (given by an
503+
/// `AccessRelatedness`).
504+
/// `f_continue` is called earlier on foreign nodes, and describes whether to even start
505+
/// visiting the subtree at that node. If it e.g. returns `SkipSelfAndChildren` on node 6
506+
/// above, then nodes 5 _and_ 6 would not be visited by `f_propagate`. It is not used for
507+
/// notes having a child access (nodes 1, 2, 3).
508+
///
509+
/// Finally, remember that the iteration order is not relevant for UB, it only affects
510+
/// diagnostics. It also affects tree traversal optimizations built on top of this, so
511+
/// those need to be reviewed carefully as well whenever this changes.
453512
fn traverse_this_parents_children_other<InnErr, OutErr>(
454513
mut self,
455514
start: BorTag,
@@ -463,14 +522,18 @@ impl<'tree> TreeVisitor<'tree> {
463522
// undergoing a child access. Also pushes the children and the other
464523
// cousin nodes (i.e. all nodes undergoing a foreign access) to the stack
465524
// to be processed later.
466-
stack.go_upwards_from_accessed(&mut self, start_idx, true)?;
525+
stack.go_upwards_from_accessed(
526+
&mut self,
527+
start_idx,
528+
ChildrenVisitMode::VisitChildrenOfAccessed,
529+
)?;
467530
// Now visit all the foreign nodes we remembered earlier.
468531
// For this we go bottom-up, but also allow f_continue to skip entire
469532
// subtrees from being visited if it would be a NOP.
470533
stack.finish_foreign_accesses(&mut self)
471534
}
472535

473-
// Like `traverse_this_parents_children_other`, but skips the children of `start`.
536+
/// Like `traverse_this_parents_children_other`, but skips the children of `start`.
474537
fn traverse_nonchildren<InnErr, OutErr>(
475538
mut self,
476539
start: BorTag,
@@ -483,7 +546,11 @@ impl<'tree> TreeVisitor<'tree> {
483546
// Visits the accessed node itself, and all its parents, i.e. all nodes
484547
// undergoing a child access. Also pushes the other cousin nodes to the
485548
// stack, but not the children of the accessed node.
486-
stack.go_upwards_from_accessed(&mut self, start_idx, false)?;
549+
stack.go_upwards_from_accessed(
550+
&mut self,
551+
start_idx,
552+
ChildrenVisitMode::SkipChildrenOfAccessed,
553+
)?;
487554
// Now visit all the foreign nodes we remembered earlier.
488555
// For this we go bottom-up, but also allow f_continue to skip entire
489556
// subtrees from being visited if it would be a NOP.

0 commit comments

Comments
 (0)