Skip to content

Commit add928f

Browse files
committed
[WIP] Stacked Borrows: remove the 'quirk', properly pop items on read
1 parent 8ae194b commit add928f

File tree

7 files changed

+52
-66
lines changed

7 files changed

+52
-66
lines changed

src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,31 @@ enum ItemInvalidationCause {
169169

170170
/// Core per-location operations: access, dealloc, reborrow.
171171
impl<'tcx> Stack {
172-
/// Find the first write-incompatible item above the given one --
173-
/// i.e, find the height to which the stack will be truncated when writing to `granting`.
174-
fn find_first_write_incompatible(&self, granting: usize) -> usize {
175-
// The SharedReadWrite *just* above us are compatible, to skip those.
176-
let mut idx = granting + 1;
172+
/// Find the first incompatible item above the given one --
173+
/// i.e, find the height to which the stack will be truncated when accessing `granting`.
174+
/// `None` means the access used the unknown bottom.
175+
fn find_first_incompatible(&self, access: AccessKind, granting: Option<usize>) -> usize {
176+
let mut idx = match granting {
177+
Some(granting) => {
178+
// Start scanning above the granting item.
179+
// The granting_idx *might* be approximate, but any lower idx would remove more
180+
// things. Even if this is a Unique and the lower idx is an SRW (which removes
181+
// less), there is an SRW group boundary here so strictly more would get removed.
182+
granting + 1
183+
}
184+
None => {
185+
// If the access uses the unknown bottom, scan the entire stack.
186+
0
187+
}
188+
};
177189
while let Some(item) = self.get(idx) {
178-
if item.perm() == Permission::SharedReadWrite {
190+
// SRW is compatible with everything; SRO is compatible with reads.
191+
let compatible = match (item.perm(), access) {
192+
(Permission::SharedReadWrite, _) => true,
193+
(Permission::SharedReadOnly, AccessKind::Read) => true,
194+
_ => false,
195+
};
196+
if compatible {
179197
// Go on.
180198
idx += 1;
181199
} else {
@@ -245,48 +263,15 @@ impl<'tcx> Stack {
245263
self.find_granting(access, tag, exposed_tags).map_err(|()| dcx.access_error(self))?;
246264

247265
// Step 2: Remove incompatible items above them. Make sure we do not remove protected
248-
// items. Behavior differs for reads and writes.
266+
// items. This ensures U2 for all accesses, and F2a on write accesses.
249267
// In case of wildcards/unknown matches, we remove everything that is *definitely* gone.
250-
if access == AccessKind::Write {
251-
// Remove everything above the write-compatible items, like a proper stack. This makes sure read-only and unique
252-
// pointers become invalid on write accesses (ensures F2a, and ensures U2 for write accesses).
253-
let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
254-
// The granting_idx *might* be approximate, but any lower idx would remove more
255-
// things. Even if this is a Unique and the lower idx is an SRW (which removes
256-
// less), there is an SRW group boundary here so strictly more would get removed.
257-
self.find_first_write_incompatible(granting_idx)
258-
} else {
259-
// We are writing to something in the unknown part.
260-
// There is a SRW group boundary between the unknown and the known, so everything is incompatible.
261-
0
262-
};
263-
self.pop_items_after(first_incompatible_idx, |item| {
264-
Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Conflict)?;
265-
dcx.log_invalidation(item.tag());
266-
interp_ok(())
267-
})?;
268-
} else {
269-
// On a read, *disable* all `Unique` above the granting item. This ensures U2 for read accesses.
270-
// The reason this is not following the stack discipline (by removing the first Unique and
271-
// everything on top of it) is that in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement
272-
// would pop the `Unique` from the reborrow of the first statement, and subsequently also pop the
273-
// `SharedReadWrite` for `raw`.
274-
// This pattern occurs a lot in the standard library: create a raw pointer, then also create a shared
275-
// reference and use that.
276-
// We *disable* instead of removing `Unique` to avoid "connecting" two neighbouring blocks of SRWs.
277-
let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
278-
// The granting_idx *might* be approximate, but any lower idx would disable more things.
279-
granting_idx + 1
280-
} else {
281-
// We are reading from something in the unknown part. That means *all* `Unique` we know about are dead now.
282-
0
283-
};
284-
self.disable_uniques_starting_at(first_incompatible_idx, |item| {
285-
Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Conflict)?;
286-
dcx.log_invalidation(item.tag());
287-
interp_ok(())
288-
})?;
289-
}
268+
269+
let first_incompatible_idx = self.find_first_incompatible(access, granting_idx);
270+
self.pop_items_after(first_incompatible_idx, |item| {
271+
Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Conflict)?;
272+
dcx.log_invalidation(item.tag());
273+
interp_ok(())
274+
})?;
290275

291276
// If this was an approximate action, we now collapse everything into an unknown.
292277
if granting_idx.is_none() || matches!(tag, ProvenanceExtra::Wildcard) {
@@ -393,7 +378,7 @@ impl<'tcx> Stack {
393378
// access. Instead of popping the stack, we insert the item at the place the stack would
394379
// be popped to (i.e., we insert it above all the write-compatible items).
395380
// This ensures F2b by adding the new item below any potentially existing `SharedReadOnly`.
396-
self.find_first_write_incompatible(granting_idx)
381+
self.find_first_incompatible(AccessKind::Write, Some(granting_idx))
397382
};
398383

399384
// Put the new item there.

src/tools/miri/tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ help: <TAG> was created by a Unique retag at offsets [0x0..0x4]
1414
|
1515
LL | let mutref = &mut *base;
1616
| ^^^^^^^^^^
17-
help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
17+
help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
1818
--> tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.rs:LL:CC
1919
|
20-
LL | *base = 1;
21-
| ^^^^^^^^^
20+
LL | let _val = *base;
21+
| ^^^^^
2222
= note: BACKTRACE (of the first span):
2323
= note: inside `main` at tests/fail/stacked_borrows/disable_mut_does_not_merge_srw.rs:LL:CC
2424

src/tools/miri/tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ fn main() {
88
// From the exposed ptr, we get a new unique ptr.
99
let root2 = &mut *exposed_ptr;
1010
let _fool = root2 as *mut _; // this would have fooled the old untagged pointer logic
11-
// Stack: Unknown(<N), Unique(N), SRW(N+1)
11+
// Stack: Unknown(<N), Unique(N)
1212
// And we test that it has uniqueness by doing a conflicting write.
1313
*exposed_ptr = 0;
1414
// Stack: Unknown(<N)

src/tools/miri/tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ fn main() {
77
let exposed_ptr = addr as *mut i32;
88
// From the exposed ptr, we get a new SRO ptr.
99
let root2 = &*exposed_ptr;
10-
// Stack: Unknown(<N), SRO(N), SRO(N+1)
10+
// Stack: Unknown(<N), SRO(N)
1111
// And we test that it is read-only by doing a conflicting write.
1212
// (The write is still fine, using the `root as *mut i32` provenance which got exposed.)
1313
*exposed_ptr = 0;

src/tools/miri/tests/fail/stacked_borrows/interior_mut2.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@ fn main() {
1515
// stack: [c: SharedReadWrite, inner_uniq: Unique, inner_shr: SharedReadWrite]
1616

1717
let _val = c.get().read(); // invalidates inner_uniq
18-
// stack: [c: SharedReadWrite, inner_uniq: Disabled, inner_shr: SharedReadWrite]
18+
// stack: [c: SharedReadWrite]
1919

2020
// We have to be careful not to add any raw pointers above inner_uniq in
2121
// the stack, hence the use of unsafe_cell_get.
22-
let _val = *unsafe_cell_get(inner_shr); // this still works
22+
// This used to work, but since we removed the "quirk" it fails here.
23+
let _val = *unsafe_cell_get(inner_shr); //~ ERROR: /retag .* tag does not exist in the borrow stack/
2324

2425
*c.get() = UnsafeCell::new(0); // now inner_shr gets invalidated
2526
// stack: [c: SharedReadWrite]
2627

27-
// now this does not work any more
28-
let _val = *inner_shr.get(); //~ ERROR: /retag .* tag does not exist in the borrow stack/
28+
// this definitely should not work
29+
let _val = *inner_shr.get();
2930
}
3031
}

src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
error: Undefined Behavior: trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
22
--> tests/fail/stacked_borrows/interior_mut2.rs:LL:CC
33
|
4-
LL | let _val = *inner_shr.get();
5-
| ^^^^^^^^^
6-
| |
7-
| trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
8-
| this error occurs as part of retag at ALLOC[0x0..0x4]
4+
LL | let _val = *unsafe_cell_get(inner_shr);
5+
| ^^^^^^^^^
6+
| |
7+
| trying to retag from <TAG> for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
8+
| this error occurs as part of retag at ALLOC[0x0..0x4]
99
|
1010
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
1111
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
@@ -14,11 +14,11 @@ help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
1414
|
1515
LL | let inner_shr = &*inner_uniq;
1616
| ^^^^^^^^^^^^
17-
help: <TAG> was later invalidated at offsets [0x0..0x4] by a write access
17+
help: <TAG> was later invalidated at offsets [0x0..0x4] by a read access
1818
--> tests/fail/stacked_borrows/interior_mut2.rs:LL:CC
1919
|
20-
LL | *c.get() = UnsafeCell::new(0); // now inner_shr gets invalidated
21-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20+
LL | let _val = c.get().read(); // invalidates inner_uniq
21+
| ^^^^^^^^^^^^^^
2222
= note: BACKTRACE (of the first span):
2323
= note: inside `main` at tests/fail/stacked_borrows/interior_mut2.rs:LL:CC
2424

src/tools/miri/tests/pass/stacked_borrows/stack-printing.stdout

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
0..1: [ SharedReadWrite<TAG> ]
33
0..1: [ SharedReadWrite<TAG> ]
44
0..1: [ SharedReadWrite<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> ]
5-
0..1: [ SharedReadWrite<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> SharedReadOnly<TAG> ]
5+
0..1: [ SharedReadWrite<TAG> SharedReadOnly<TAG> ]
66
0..1: [ unknown-bottom(..<TAG>) ]

0 commit comments

Comments
 (0)