@@ -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+
118263impl 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 ) ]
182521pub enum AccessRelatedness {
0 commit comments