@@ -104,9 +104,10 @@ pub struct Stack {
104
104
/// * Above a `SharedReadOnly` there can only be more `SharedReadOnly`.
105
105
/// * Except for `Untagged`, no tag occurs in the stack more than once.
106
106
borrows : Vec < Item > ,
107
- /// If this is `Some(id)`, then the actual current stack is unknown. What we do know
108
- /// is that `borrows` are at the top of the stack, and below it are arbitrarily many items
109
- /// whose `tag` is either `Untagged` or strictly less than `id`.
107
+ /// If this is `Some(id)`, then the actual current stack is unknown. THis can happen when
108
+ /// wildcard pointers are used to access this location. What we do know is that `borrows` are at
109
+ /// the top of the stack, and below it are arbitrarily many items whose `tag` is either
110
+ /// `Untagged` or strictly less than `id`.
110
111
unknown_bottom : Option < PtrId > ,
111
112
}
112
113
@@ -289,72 +290,72 @@ impl Permission {
289
290
impl < ' tcx > Stack {
290
291
/// Find the item granting the given kind of access to the given tag, and return where
291
292
/// it is on the stack.
292
- // TODO: Doc ok with Some(index) or None if unknown_bottom used
293
- // Err if does not match
293
+ /// `Ok(None)` indicates it matched the "unknown" part of the stack, or it was a wildcard tag
294
+ /// and we have no clue what exactly it matched (but it could have matched something)
295
+ /// `Err` indicates it was not found.
294
296
fn find_granting (
295
297
& self ,
296
298
access : AccessKind ,
297
299
tag : Option < SbTag > ,
298
300
exposed_tags : & FxHashSet < SbTag > ,
299
301
) -> Result < Option < usize > , ( ) > {
300
- let res = self
301
- . borrows
302
- . iter ( )
303
- . enumerate ( ) // we also need to know *where* in the stack
304
- . rev ( ) // search top-to-bottom
305
- // Return permission of first item that grants access.
306
- // We require a permission with the right tag, ensuring U3 and F3.
307
- . find_map ( |( idx, item) | {
308
- match tag {
309
- Some ( tag) if tag == item. tag && item. perm . grants ( access) => Some ( idx) ,
310
- None if exposed_tags. contains ( & item. tag ) => Some ( idx) ,
311
- _ => None ,
312
- }
313
- } ) ;
302
+ let Some ( tag) = tag else {
303
+ // Handle the wildcard case.
304
+ // Go search the stack for an exposed tag.
305
+ let maybe_in_stack = self
306
+ . borrows
307
+ . iter ( )
308
+ . rev ( ) // search top-to-bottom
309
+ . find_map ( |item| {
310
+ // If the item fits and *might* be this wildcard, use it.
311
+ if item. perm . grants ( access) && exposed_tags. contains ( & item. tag ) {
312
+ Some ( ( ) )
313
+ } else {
314
+ None
315
+ }
316
+ } )
317
+ . is_some ( ) ;
318
+ // If we couldn't find it in the stack, check the unknown bottom.
319
+ let found = maybe_in_stack || self . unknown_bottom . is_some ( ) ;
320
+ return if found { Ok ( None ) } else { Err ( ( ) ) } ;
321
+ } ;
314
322
315
- if res. is_some ( ) {
316
- return Ok ( res) ;
323
+ if let Some ( idx) =
324
+ self . borrows
325
+ . iter ( )
326
+ . enumerate ( ) // we also need to know *where* in the stack
327
+ . rev ( ) // search top-to-bottom
328
+ // Return permission of first item that grants access.
329
+ // We require a permission with the right tag, ensuring U3 and F3.
330
+ . find_map ( |( idx, item) | {
331
+ if tag == item. tag && item. perm . grants ( access) { Some ( idx) } else { None }
332
+ } )
333
+ {
334
+ return Ok ( Some ( idx) ) ;
317
335
}
318
336
319
- match self . unknown_bottom {
320
- Some ( id) =>
321
- match tag {
322
- Some ( tag) =>
323
- match tag {
324
- SbTag :: Tagged ( tag_id) if tag_id < id => Ok ( None ) ,
325
- SbTag :: Untagged => Ok ( None ) ,
326
- _ => Err ( ( ) ) ,
327
- } ,
328
- None => Ok ( None ) ,
329
- } ,
330
- None => Err ( ( ) ) ,
331
- }
337
+ // Couldn't find it in the stack; but if there is an unknown bottom it might be there.
338
+ let found = self . unknown_bottom . is_some_and ( |& unknown_limit| {
339
+ match tag {
340
+ SbTag :: Tagged ( tag_id) => tag_id < unknown_limit, // unknown_limit is an upper bound for what can be in the unknown bottom.
341
+ SbTag :: Untagged => true , // yeah whatever
342
+ }
343
+ } ) ;
344
+ if found { Ok ( None ) } else { Err ( ( ) ) }
332
345
}
333
346
334
347
/// Find the first write-incompatible item above the given one --
335
348
/// i.e, find the height to which the stack will be truncated when writing to `granting`.
336
- fn find_first_write_incompatible ( & self , granting : Option < usize > ) -> usize {
337
- let perm = if let Some ( idx) = granting {
338
- self . borrows [ idx] . perm
339
- } else {
340
- // I assume this has to be it?
341
- Permission :: SharedReadWrite
342
- } ;
343
-
349
+ fn find_first_write_incompatible ( & self , granting : usize ) -> usize {
350
+ let perm = self . borrows [ granting] . perm ;
344
351
match perm {
345
352
Permission :: SharedReadOnly => bug ! ( "Cannot use SharedReadOnly for writing" ) ,
346
353
Permission :: Disabled => bug ! ( "Cannot use Disabled for anything" ) ,
347
354
// On a write, everything above us is incompatible.
348
- Permission :: Unique =>
349
- if let Some ( idx) = granting {
350
- idx + 1
351
- } else {
352
- 0
353
- } ,
355
+ Permission :: Unique => granting + 1 ,
354
356
Permission :: SharedReadWrite => {
355
357
// The SharedReadWrite *just* above us are compatible, to skip those.
356
- let mut idx = if let Some ( idx) = granting { idx + 1 } else { 0 } ;
357
-
358
+ let mut idx = granting + 1 ;
358
359
while let Some ( item) = self . borrows . get ( idx) {
359
360
if item. perm == Permission :: SharedReadWrite {
360
361
// Go on.
@@ -440,52 +441,57 @@ impl<'tcx> Stack {
440
441
alloc_history. access_error ( access, tag, alloc_id, alloc_range, offset, self )
441
442
} ) ?;
442
443
444
+ let Some ( granting_idx) = granting_idx else {
445
+ // The access used a wildcard pointer or matched the unknown bottom.
446
+ // Nobody knows what happened, so forget everything.
447
+ trace ! ( "access: clearing stack due to wildcard" ) ;
448
+ self . borrows . clear ( ) ;
449
+ self . unknown_bottom = Some ( global. next_ptr_id ) ;
450
+ return Ok ( ( ) ) ;
451
+ } ;
452
+ let tag = tag. unwrap ( ) ; // only precise tags have precise locations
453
+
443
454
// Step 2: Remove incompatible items above them. Make sure we do not remove protected
444
455
// items. Behavior differs for reads and writes.
445
- if let Some ( tag) = tag {
446
- if access == AccessKind :: Write {
447
- // Remove everything above the write-compatible items, like a proper stack. This makes sure read-only and unique
448
- // pointers become invalid on write accesses (ensures F2a, and ensures U2 for write accesses).
449
- let first_incompatible_idx = self . find_first_write_incompatible ( granting_idx) ;
450
- for item in self . borrows . drain ( first_incompatible_idx..) . rev ( ) {
451
- trace ! ( "access: popping item {:?}" , item) ;
456
+ if access == AccessKind :: Write {
457
+ // Remove everything above the write-compatible items, like a proper stack. This makes sure read-only and unique
458
+ // pointers become invalid on write accesses (ensures F2a, and ensures U2 for write accesses).
459
+ let first_incompatible_idx = self . find_first_write_incompatible ( granting_idx) ;
460
+ for item in self . borrows . drain ( first_incompatible_idx..) . rev ( ) {
461
+ trace ! ( "access: popping item {:?}" , item) ;
462
+ Stack :: check_protector (
463
+ & item,
464
+ Some ( ( tag, alloc_range, offset, access) ) ,
465
+ global,
466
+ alloc_history,
467
+ ) ?;
468
+ alloc_history. log_invalidation ( item. tag , alloc_range, current_span) ;
469
+ }
470
+ } else {
471
+ // On a read, *disable* all `Unique` above the granting item. This ensures U2 for read accesses.
472
+ // The reason this is not following the stack discipline (by removing the first Unique and
473
+ // everything on top of it) is that in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement
474
+ // would pop the `Unique` from the reborrow of the first statement, and subsequently also pop the
475
+ // `SharedReadWrite` for `raw`.
476
+ // This pattern occurs a lot in the standard library: create a raw pointer, then also create a shared
477
+ // reference and use that.
478
+ // We *disable* instead of removing `Unique` to avoid "connecting" two neighbouring blocks of SRWs.
479
+ let first_incompatible_idx = granting_idx + 1 ;
480
+ for idx in ( first_incompatible_idx..self . borrows . len ( ) ) . rev ( ) {
481
+ let item = & mut self . borrows [ idx] ;
482
+
483
+ if item. perm == Permission :: Unique {
484
+ trace ! ( "access: disabling item {:?}" , item) ;
452
485
Stack :: check_protector (
453
- & item,
486
+ item,
454
487
Some ( ( tag, alloc_range, offset, access) ) ,
455
488
global,
456
489
alloc_history,
457
490
) ?;
491
+ item. perm = Permission :: Disabled ;
458
492
alloc_history. log_invalidation ( item. tag , alloc_range, current_span) ;
459
493
}
460
- } else {
461
- let start_idx = if let Some ( idx) = granting_idx { idx + 1 } else { 0 } ;
462
- // On a read, *disable* all `Unique` above the granting item. This ensures U2 for read accesses.
463
- // The reason this is not following the stack discipline (by removing the first Unique and
464
- // everything on top of it) is that in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement
465
- // would pop the `Unique` from the reborrow of the first statement, and subsequently also pop the
466
- // `SharedReadWrite` for `raw`.
467
- // This pattern occurs a lot in the standard library: create a raw pointer, then also create a shared
468
- // reference and use that.
469
- // We *disable* instead of removing `Unique` to avoid "connecting" two neighbouring blocks of SRWs.
470
- for idx in ( start_idx..self . borrows . len ( ) ) . rev ( ) {
471
- let item = & mut self . borrows [ idx] ;
472
-
473
- if item. perm == Permission :: Unique {
474
- trace ! ( "access: disabling item {:?}" , item) ;
475
- Stack :: check_protector (
476
- item,
477
- Some ( ( tag, alloc_range, offset, access) ) ,
478
- global,
479
- alloc_history,
480
- ) ?;
481
- item. perm = Permission :: Disabled ;
482
- alloc_history. log_invalidation ( item. tag , alloc_range, current_span) ;
483
- }
484
- }
485
494
}
486
- } else {
487
- self . borrows . clear ( ) ;
488
- self . unknown_bottom = Some ( global. next_ptr_id ) ;
489
495
}
490
496
491
497
// Done.
@@ -502,7 +508,7 @@ impl<'tcx> Stack {
502
508
alloc_history : & mut AllocHistory ,
503
509
exposed_tags : & FxHashSet < SbTag > ,
504
510
) -> InterpResult < ' tcx > {
505
- // Step 1: Find granting item.
511
+ // Step 1: Make sure there is a granting item.
506
512
self . find_granting ( AccessKind :: Write , tag, exposed_tags) . map_err ( |_| {
507
513
err_sb_ub ( format ! (
508
514
"no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack" ,
@@ -556,17 +562,20 @@ impl<'tcx> Stack {
556
562
"this case only makes sense for stack-like accesses"
557
563
) ;
558
564
559
- if derived_from. is_some ( ) {
560
- // SharedReadWrite can coexist with "existing loans", meaning they don't act like a write
561
- // access. Instead of popping the stack, we insert the item at the place the stack would
562
- // be popped to (i.e., we insert it above all the write-compatible items).
563
- // This ensures F2b by adding the new item below any potentially existing `SharedReadOnly`.
564
- self . find_first_write_incompatible ( granting_idx)
565
- } else {
566
- // TODO: is this correct
565
+ let Some ( granting_idx) = granting_idx else {
566
+ // The parent is a wildcard pointer or matched the unknown bottom.
567
+ // Nobody knows what happened, so forget everything.
568
+ trace ! ( "reborrow: clearing stack due to wildcard" ) ;
567
569
self . borrows . clear ( ) ;
568
- 0
569
- }
570
+ self . unknown_bottom = Some ( global. next_ptr_id ) ;
571
+ return Ok ( ( ) ) ;
572
+ } ;
573
+
574
+ // SharedReadWrite can coexist with "existing loans", meaning they don't act like a write
575
+ // access. Instead of popping the stack, we insert the item at the place the stack would
576
+ // be popped to (i.e., we insert it above all the write-compatible items).
577
+ // This ensures F2b by adding the new item below any potentially existing `SharedReadOnly`.
578
+ self . find_first_write_incompatible ( granting_idx)
570
579
} else {
571
580
// A "safe" reborrow for a pointer that actually expects some aliasing guarantees.
572
581
// Here, creating a reference actually counts as an access.
@@ -587,9 +596,11 @@ impl<'tcx> Stack {
587
596
// This ensures U1 and F1.
588
597
self . borrows . len ( )
589
598
} ;
599
+
590
600
// Put the new item there. As an optimization, deduplicate if it is equal to one of its new neighbors.
601
+ // `new_idx` might be 0 if we just cleared the entire stack.
591
602
if self . borrows . get ( new_idx) == Some ( & new)
592
- || new_idx > 0 && self . borrows . get ( new_idx - 1 ) == Some ( & new)
603
+ || ( new_idx > 0 && self . borrows [ new_idx - 1 ] == new)
593
604
{
594
605
// Optimization applies, done.
595
606
trace ! ( "reborrow: avoiding adding redundant item {:?}" , new) ;
@@ -648,7 +659,7 @@ impl<'tcx> Stacks {
648
659
) -> InterpResult < ' tcx > ,
649
660
) -> InterpResult < ' tcx > {
650
661
let stacks = self . stacks . get_mut ( ) ;
651
- let history = & mut * self . history . borrow_mut ( ) ;
662
+ let history = & mut * self . history . get_mut ( ) ;
652
663
let exposed_tags = self . exposed_tags . get_mut ( ) ;
653
664
for ( offset, stack) in stacks. iter_mut ( range. start , range. size ) {
654
665
f ( offset, stack, history, exposed_tags) ?;
@@ -1083,10 +1094,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
1083
1094
1084
1095
// Function pointers and dead objects don't have an alloc_extra so we ignore them.
1085
1096
// This is okay because accessing them is UB anyway, no need for any Stacked Borrows checks.
1097
+ // NOT using `get_alloc_extra_mut` since this might be a read-only allocation!
1086
1098
// FIXME: this catches `InterpError`, which we should not usually do.
1087
1099
// We might need a proper fallible API from `memory.rs` to avoid this though.
1088
- if let Ok ( ( alloc_extra, _) ) = this. get_alloc_extra_mut ( alloc_id) {
1089
- alloc_extra. stacked_borrows . as_mut ( ) . unwrap ( ) . exposed_tags . get_mut ( ) . insert ( tag) ;
1100
+ match this. get_alloc_extra ( alloc_id) {
1101
+ Ok ( alloc_extra) => {
1102
+ trace ! ( "Stacked Borrows tag {tag:?} exposed in {alloc_id}" ) ;
1103
+ alloc_extra. stacked_borrows . as_ref ( ) . unwrap ( ) . exposed_tags . borrow_mut ( ) . insert ( tag) ;
1104
+ }
1105
+ Err ( err) => {
1106
+ trace ! (
1107
+ "Not exposing Stacked Borrows tag {tag:?} due to error \
1108
+ when accessing {alloc_id}: {err}"
1109
+ ) ;
1110
+ }
1090
1111
}
1091
1112
}
1092
1113
}
0 commit comments