Skip to content

Commit eb3ff3c

Browse files
committed
TB: tree traversal
1 parent cd954db commit eb3ff3c

File tree

1 file changed

+339
-0
lines changed
  • src/tools/miri/src/borrow_tracker/tree_borrows

1 file changed

+339
-0
lines changed

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

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,151 @@ pub(super) struct Node {
115115
pub debug_info: NodeDebugInfo,
116116
}
117117

118+
/// Data given to the transition function
119+
struct NodeAppArgs<'node> {
120+
/// Node on which the transition is currently being applied
121+
node: &'node Node,
122+
/// Mutable access to its permissions
123+
perm: UniEntry<'node, LocationState>,
124+
/// Relative position of the access
125+
rel_pos: AccessRelatedness,
126+
}
127+
/// Data given to the error handler
128+
struct ErrHandlerArgs<'node, InErr> {
129+
/// Kind of error that occurred
130+
error_kind: InErr,
131+
/// Tag that triggered the error (not the tag that was accessed,
132+
/// rather the parent tag that had insufficient permissions or the
133+
/// non-parent tag that had a protector).
134+
faulty_tag: &'node NodeDebugInfo,
135+
}
136+
/// Internal contents of `Tree` with the minimum of mutable access for
137+
/// the purposes of the tree traversal functions: the permissions (`perms`) can be
138+
/// updated but not the tree structure (`tag_mapping` and `nodes`)
139+
struct TreeVisitor<'tree> {
140+
tag_mapping: &'tree UniKeyMap<BorTag>,
141+
nodes: &'tree UniValMap<Node>,
142+
perms: &'tree mut UniValMap<LocationState>,
143+
}
144+
145+
/// Whether to continue exploring the children recursively or not.
146+
enum ContinueTraversal {
147+
Recurse,
148+
SkipChildren,
149+
}
150+
151+
impl<'tree> TreeVisitor<'tree> {
152+
// Applies `f_propagate` to every vertex of the tree top-down in the following order: first
153+
// all ancestors of `start`, then `start` itself, then children of `start`, then the rest.
154+
// This ensures that errors are triggered in the following order
155+
// - first invalid accesses with insufficient permissions, closest to the root first,
156+
// - then protector violations, closest to `start` first.
157+
//
158+
// `f_propagate` should follow the following format: for a given `Node` it updates its
159+
// `Permission` depending on the position relative to `start` (given by an
160+
// `AccessRelatedness`).
161+
// It outputs whether the tree traversal for this subree should continue or not.
162+
fn traverse_parents_this_children_others<InnErr, OutErr>(
163+
mut self,
164+
start: BorTag,
165+
f_propagate: impl Fn(NodeAppArgs<'_>) -> Result<ContinueTraversal, InnErr>,
166+
err_builder: impl Fn(ErrHandlerArgs<'_, InnErr>) -> OutErr,
167+
) -> Result<(), OutErr>
168+
where {
169+
struct TreeVisitAux<NodeApp, ErrHandler> {
170+
f_propagate: NodeApp,
171+
err_builder: ErrHandler,
172+
stack: Vec<(UniIndex, AccessRelatedness)>,
173+
}
174+
impl<NodeApp, InnErr, OutErr, ErrHandler> TreeVisitAux<NodeApp, ErrHandler>
175+
where
176+
NodeApp: Fn(NodeAppArgs<'_>) -> Result<ContinueTraversal, InnErr>,
177+
ErrHandler: Fn(ErrHandlerArgs<'_, InnErr>) -> OutErr,
178+
{
179+
fn pop(&mut self) -> Option<(UniIndex, AccessRelatedness)> {
180+
self.stack.pop()
181+
}
182+
183+
/// Apply the function to the current `tag`, and push its children
184+
/// to the stack of future tags to visit.
185+
fn exec_and_visit(
186+
&mut self,
187+
this: &mut TreeVisitor<'_>,
188+
tag: UniIndex,
189+
exclude: Option<UniIndex>,
190+
rel_pos: AccessRelatedness,
191+
) -> Result<(), OutErr> {
192+
// 1. apply the propagation function
193+
let node = this.nodes.get(tag).unwrap();
194+
let recurse =
195+
(self.f_propagate)(NodeAppArgs { node, perm: this.perms.entry(tag), rel_pos })
196+
.map_err(|error_kind| {
197+
(self.err_builder)(ErrHandlerArgs {
198+
error_kind,
199+
faulty_tag: &node.debug_info,
200+
})
201+
})?;
202+
// 2. add the children to the stack for future traversal
203+
if matches!(recurse, ContinueTraversal::Recurse) {
204+
let child_rel = rel_pos.for_child();
205+
for &child in node.children.iter() {
206+
// some child might be excluded from here and handled separately
207+
if Some(child) != exclude {
208+
self.stack.push((child, child_rel));
209+
}
210+
}
211+
}
212+
Ok(())
213+
}
214+
}
215+
216+
let start_idx = self.tag_mapping.get(&start).unwrap();
217+
let mut stack = TreeVisitAux { f_propagate, err_builder, stack: Vec::new() };
218+
{
219+
let mut path_ascend = Vec::new();
220+
// First climb to the root while recording the path
221+
let mut curr = start_idx;
222+
while let Some(ancestor) = self.nodes.get(curr).unwrap().parent {
223+
path_ascend.push((ancestor, curr));
224+
curr = ancestor;
225+
}
226+
// Then descend:
227+
// - execute f_propagate on each node
228+
// - record children in visit
229+
while let Some((ancestor, next_in_path)) = path_ascend.pop() {
230+
// Explore ancestors in descending order.
231+
// `next_in_path` is excluded from the recursion because it
232+
// will be the `ancestor` of the next iteration.
233+
// It also needs a different `AccessRelatedness` than the other
234+
// children of `ancestor`.
235+
stack.exec_and_visit(
236+
&mut self,
237+
ancestor,
238+
Some(next_in_path),
239+
AccessRelatedness::StrictChildAccess,
240+
)?;
241+
}
242+
};
243+
// All (potentially zero) ancestors have been explored, call f_propagate on start
244+
stack.exec_and_visit(&mut self, start_idx, None, AccessRelatedness::This)?;
245+
// up to this point we have never popped from `stack`, hence if the
246+
// path to the root is `root = p(n) <- p(n-1)... <- p(1) <- p(0) = start`
247+
// then now `stack` contains
248+
// `[<children(p(n)) except p(n-1)> ... <children(p(1)) except p(0)> <children(p(0))>]`,
249+
// all of which are for now unexplored.
250+
// This is the starting point of a standard DFS which will thus
251+
// explore all non-ancestors of `start` in the following order:
252+
// - all descendants of `start`;
253+
// - then the unexplored descendants of `parent(start)`;
254+
// ...
255+
// - until finally the unexplored descendants of `root`.
256+
while let Some((tag, rel_pos)) = stack.pop() {
257+
stack.exec_and_visit(&mut self, tag, None, rel_pos)?;
258+
}
259+
Ok(())
260+
}
261+
}
262+
118263
impl Tree {
119264
/// Create a new tree, with only a root pointer.
120265
pub fn new(root_tag: BorTag, size: Size) -> Self {
@@ -177,6 +322,200 @@ impl<'tcx> Tree {
177322
Ok(())
178323
}
179324

325+
/// Deallocation requires
326+
/// - a pointer that permits write accesses
327+
/// - the absence of Strong Protectors anywhere in the allocation
328+
pub fn dealloc(
329+
&mut self,
330+
tag: BorTag,
331+
range: AllocRange,
332+
global: &GlobalState,
333+
) -> InterpResult<'tcx> {
334+
self.perform_access(AccessKind::Write, tag, range, global)?;
335+
let access_info = &self.nodes.get(self.tag_mapping.get(&tag).unwrap()).unwrap().debug_info;
336+
for (_range, perms) in self.rperms.iter_mut(range.start, range.size) {
337+
TreeVisitor { nodes: &self.nodes, tag_mapping: &self.tag_mapping, perms }
338+
.traverse_parents_this_children_others(
339+
tag,
340+
|args: NodeAppArgs<'_>| -> Result<ContinueTraversal, TransitionError> {
341+
let NodeAppArgs { node, .. } = args;
342+
if global.borrow().protected_tags.get(&node.tag)
343+
== Some(&ProtectorKind::StrongProtector)
344+
{
345+
Err(TransitionError::ProtectedDealloc)
346+
} else {
347+
Ok(ContinueTraversal::Recurse)
348+
}
349+
},
350+
|args: ErrHandlerArgs<'_, TransitionError>| -> InterpErrorInfo<'tcx> {
351+
let ErrHandlerArgs { error_kind, faulty_tag } = args;
352+
TbError {
353+
faulty_tag,
354+
access_kind: AccessKind::Write,
355+
error_kind,
356+
tag_of_access: access_info,
357+
}
358+
.build()
359+
},
360+
)?;
361+
}
362+
Ok(())
363+
}
364+
365+
/// Maps the following propagation procedure to each range:
366+
/// - initialize if needed;
367+
/// - compute new state after transition;
368+
/// - check that there is no protector that would forbid this;
369+
/// - record this specific location as accessed.
370+
pub fn perform_access(
371+
&mut self,
372+
access_kind: AccessKind,
373+
tag: BorTag,
374+
range: AllocRange,
375+
global: &GlobalState,
376+
) -> InterpResult<'tcx> {
377+
let access_info = &self.nodes.get(self.tag_mapping.get(&tag).unwrap()).unwrap().debug_info;
378+
for (_range, perms) in self.rperms.iter_mut(range.start, range.size) {
379+
TreeVisitor { nodes: &self.nodes, tag_mapping: &self.tag_mapping, perms }
380+
.traverse_parents_this_children_others(
381+
tag,
382+
|args: NodeAppArgs<'_>| -> Result<ContinueTraversal, TransitionError> {
383+
let NodeAppArgs { node, mut perm, rel_pos } = args;
384+
385+
let old_state =
386+
perm.or_insert_with(|| LocationState::new(node.default_initial_perm));
387+
388+
// Optimize the tree traversal.
389+
// The optimization here consists of observing thanks to the tests
390+
// `foreign_read_is_noop_after_write` and `all_transitions_idempotent`
391+
// that if we apply twice in a row the effects of a foreign access
392+
// we can skip some branches.
393+
// "two foreign accesses in a row" occurs when `perm.latest_foreign_access` is `Some(_)`
394+
// AND the `rel_pos` of the current access corresponds to a foreign access.
395+
if rel_pos.is_foreign() {
396+
let new_access_noop =
397+
match (old_state.latest_foreign_access, access_kind) {
398+
// Previously applied transition makes the new one a guaranteed
399+
// noop in the two following cases:
400+
// (1) justified by `foreign_read_is_noop_after_write`
401+
(Some(AccessKind::Write), AccessKind::Read) => true,
402+
// (2) justified by `all_transitions_idempotent`
403+
(Some(old), new) if old == new => true,
404+
// In all other cases there has been a recent enough
405+
// child access that the effects of the new foreign access
406+
// need to be applied to this subtree.
407+
_ => false,
408+
};
409+
if new_access_noop {
410+
// Abort traversal if the new transition is indeed guaranteed
411+
// to be noop.
412+
return Ok(ContinueTraversal::SkipChildren);
413+
} else {
414+
// Otherwise propagate this time, and also record the
415+
// access that just occurred so that we can skip the propagation
416+
// next time.
417+
old_state.latest_foreign_access = Some(access_kind);
418+
}
419+
} else {
420+
// A child access occurred, this breaks the streak of "two foreign
421+
// accesses in a row" and we reset this field.
422+
old_state.latest_foreign_access = None;
423+
}
424+
425+
let old_perm = old_state.permission;
426+
let protected = global.borrow().protected_tags.contains_key(&node.tag);
427+
let new_perm =
428+
Permission::perform_access(access_kind, rel_pos, old_perm, protected)
429+
.ok_or(TransitionError::ChildAccessForbidden(old_perm))?;
430+
if protected
431+
// Can't trigger Protector on uninitialized locations
432+
&& old_state.initialized
433+
&& !old_perm.protector_allows_transition(new_perm)
434+
{
435+
return Err(TransitionError::ProtectedTransition(old_perm, new_perm));
436+
}
437+
old_state.permission = new_perm;
438+
old_state.initialized |= !rel_pos.is_foreign();
439+
Ok(ContinueTraversal::Recurse)
440+
},
441+
|args: ErrHandlerArgs<'_, TransitionError>| -> InterpErrorInfo<'tcx> {
442+
let ErrHandlerArgs { error_kind, faulty_tag } = args;
443+
TbError { faulty_tag, access_kind, error_kind, tag_of_access: access_info }
444+
.build()
445+
},
446+
)?;
447+
}
448+
Ok(())
449+
}
450+
}
451+
452+
/// Integration with the BorTag garbage collector
453+
impl Tree {
454+
pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
455+
assert!(self.keep_only_needed(self.root, live_tags)); // root can't be removed
456+
}
457+
458+
/// Traverses the entire tree looking for useless tags.
459+
/// Returns true iff the tag it was called on is still live or has live children,
460+
/// and removes from the tree all tags that have no live children.
461+
///
462+
/// NOTE: This leaves in the middle of the tree tags that are unreachable but have
463+
/// reachable children. There is a potential for compacting the tree by reassigning
464+
/// children of dead tags to the nearest live parent, but it must be done with care
465+
/// not to remove UB.
466+
///
467+
/// Example: Consider the tree `root - parent - child`, with `parent: Frozen` and
468+
/// `child: Reserved`. This tree can exist. If we blindly delete `parent` and reassign
469+
/// `child` to be a direct child of `root` then Writes to `child` are now permitted
470+
/// whereas they were not when `parent` was still there.
471+
fn keep_only_needed(&mut self, idx: UniIndex, live: &FxHashSet<BorTag>) -> bool {
472+
let node = self.nodes.get(idx).unwrap();
473+
// FIXME: this function does a lot of cloning, a 2-pass approach is possibly
474+
// more efficient. It could consist of
475+
// 1. traverse the Tree, collect all useless tags in a Vec
476+
// 2. traverse the Vec, remove all tags previously selected
477+
// Bench it.
478+
let children: SmallVec<_> = node
479+
.children
480+
.clone()
481+
.into_iter()
482+
.filter(|child| self.keep_only_needed(*child, live))
483+
.collect();
484+
let no_children = children.is_empty();
485+
let node = self.nodes.get_mut(idx).unwrap();
486+
node.children = children;
487+
if !live.contains(&node.tag) && no_children {
488+
// All of the children and this node are unreachable, delete this tag
489+
// from the tree (the children have already been deleted by recursive
490+
// calls).
491+
// Due to the API of UniMap we must absolutely call
492+
// `UniValMap::remove` for the key of this tag on *all* maps that used it
493+
// (which are `self.nodes` and every range of `self.rperms`)
494+
// before we can safely apply `UniValMap::forget` to truly remove
495+
// the tag from the mapping.
496+
let tag = node.tag;
497+
self.nodes.remove(idx);
498+
for perms in self.rperms.iter_mut_all() {
499+
perms.remove(idx);
500+
}
501+
self.tag_mapping.remove(&tag);
502+
// The tag has been deleted, inform the caller
503+
false
504+
} else {
505+
// The tag is still live or has live children, it must be kept
506+
true
507+
}
508+
}
509+
}
510+
511+
impl VisitTags for Tree {
512+
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
513+
// To ensure that the root never gets removed, we visit it
514+
// (the `root` node of `Tree` is not an `Option<_>`)
515+
visit(self.nodes.get(self.root).unwrap().tag)
516+
}
517+
}
518+
180519
/// Relative position of the access
181520
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182521
pub enum AccessRelatedness {

0 commit comments

Comments
 (0)