@@ -115,6 +115,151 @@ pub(super) struct Node {
115
115
pub debug_info : NodeDebugInfo ,
116
116
}
117
117
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
+
118
263
impl Tree {
119
264
/// Create a new tree, with only a root pointer.
120
265
pub fn new ( root_tag : BorTag , size : Size ) -> Self {
@@ -177,6 +322,200 @@ impl<'tcx> Tree {
177
322
Ok ( ( ) )
178
323
}
179
324
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
+
180
519
/// Relative position of the access
181
520
#[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
182
521
pub enum AccessRelatedness {
0 commit comments