Skip to content

Commit 8d6fdaa

Browse files
committed
make the tests pass
(and some formatting)
1 parent c0f7118 commit 8d6fdaa

File tree

7 files changed

+201
-160
lines changed

7 files changed

+201
-160
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,7 @@ to Miri failing to detect cases of undefined behavior in a program.
358358
for pointer-to-int and int-to-pointer casts, respectively. This will
359359
necessarily miss some bugs as those semantics are not efficiently
360360
implementable in a sanitizer, but it will only miss bugs that concerns
361-
memory/pointers which is subject to these operations. Also note that this flag
362-
is currently incompatible with Stacked Borrows, so you will have to also pass
363-
`-Zmiri-disable-stacked-borrows` to use this.
361+
memory/pointers which is subject to these operations.
364362
* `-Zmiri-symbolic-alignment-check` makes the alignment check more strict. By
365363
default, alignment is checked by casting the pointer to an integer, and making
366364
sure that is a multiple of the alignment. This can lead to cases where a

src/intptrcast.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,11 @@ impl<'mir, 'tcx> GlobalStateInner {
102102
}
103103

104104
pub fn expose_ptr(ecx: &mut MiriEvalContext<'mir, 'tcx>, alloc_id: AllocId, sb: SbTag) {
105-
trace!("Exposing allocation id {:?}", alloc_id);
106-
107105
let global_state = ecx.machine.intptrcast.get_mut();
108106
// In legacy and strict mode, we don't need this, so we can save some cycles
109107
// by not tracking it.
110108
if global_state.provenance_mode == ProvenanceMode::Permissive {
109+
trace!("Exposing allocation id {alloc_id:?}");
111110
global_state.exposed.insert(alloc_id);
112111
if ecx.machine.stacked_borrows.is_some() {
113112
ecx.expose_tag(alloc_id, sb);

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#![feature(let_else)]
77
#![feature(io_error_more)]
88
#![feature(yeet_expr)]
9+
#![feature(is_some_with)]
910
#![warn(rust_2018_idioms)]
1011
#![allow(
1112
clippy::collapsible_else_if,

src/stacked_borrows.rs

Lines changed: 122 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,10 @@ pub struct Stack {
104104
/// * Above a `SharedReadOnly` there can only be more `SharedReadOnly`.
105105
/// * Except for `Untagged`, no tag occurs in the stack more than once.
106106
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`.
110111
unknown_bottom: Option<PtrId>,
111112
}
112113

@@ -289,72 +290,72 @@ impl Permission {
289290
impl<'tcx> Stack {
290291
/// Find the item granting the given kind of access to the given tag, and return where
291292
/// 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.
294296
fn find_granting(
295297
&self,
296298
access: AccessKind,
297299
tag: Option<SbTag>,
298300
exposed_tags: &FxHashSet<SbTag>,
299301
) -> 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+
};
314322

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));
317335
}
318336

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(()) }
332345
}
333346

334347
/// Find the first write-incompatible item above the given one --
335348
/// 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;
344351
match perm {
345352
Permission::SharedReadOnly => bug!("Cannot use SharedReadOnly for writing"),
346353
Permission::Disabled => bug!("Cannot use Disabled for anything"),
347354
// 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,
354356
Permission::SharedReadWrite => {
355357
// 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;
358359
while let Some(item) = self.borrows.get(idx) {
359360
if item.perm == Permission::SharedReadWrite {
360361
// Go on.
@@ -440,52 +441,57 @@ impl<'tcx> Stack {
440441
alloc_history.access_error(access, tag, alloc_id, alloc_range, offset, self)
441442
})?;
442443

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+
443454
// Step 2: Remove incompatible items above them. Make sure we do not remove protected
444455
// 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);
452485
Stack::check_protector(
453-
&item,
486+
item,
454487
Some((tag, alloc_range, offset, access)),
455488
global,
456489
alloc_history,
457490
)?;
491+
item.perm = Permission::Disabled;
458492
alloc_history.log_invalidation(item.tag, alloc_range, current_span);
459493
}
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-
}
485494
}
486-
} else {
487-
self.borrows.clear();
488-
self.unknown_bottom = Some(global.next_ptr_id);
489495
}
490496

491497
// Done.
@@ -502,7 +508,7 @@ impl<'tcx> Stack {
502508
alloc_history: &mut AllocHistory,
503509
exposed_tags: &FxHashSet<SbTag>,
504510
) -> InterpResult<'tcx> {
505-
// Step 1: Find granting item.
511+
// Step 1: Make sure there is a granting item.
506512
self.find_granting(AccessKind::Write, tag, exposed_tags).map_err(|_| {
507513
err_sb_ub(format!(
508514
"no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack",
@@ -556,17 +562,20 @@ impl<'tcx> Stack {
556562
"this case only makes sense for stack-like accesses"
557563
);
558564

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");
567569
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)
570579
} else {
571580
// A "safe" reborrow for a pointer that actually expects some aliasing guarantees.
572581
// Here, creating a reference actually counts as an access.
@@ -587,9 +596,11 @@ impl<'tcx> Stack {
587596
// This ensures U1 and F1.
588597
self.borrows.len()
589598
};
599+
590600
// 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.
591602
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)
593604
{
594605
// Optimization applies, done.
595606
trace!("reborrow: avoiding adding redundant item {:?}", new);
@@ -648,7 +659,7 @@ impl<'tcx> Stacks {
648659
) -> InterpResult<'tcx>,
649660
) -> InterpResult<'tcx> {
650661
let stacks = self.stacks.get_mut();
651-
let history = &mut *self.history.borrow_mut();
662+
let history = &mut *self.history.get_mut();
652663
let exposed_tags = self.exposed_tags.get_mut();
653664
for (offset, stack) in stacks.iter_mut(range.start, range.size) {
654665
f(offset, stack, history, exposed_tags)?;
@@ -1083,10 +1094,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
10831094

10841095
// Function pointers and dead objects don't have an alloc_extra so we ignore them.
10851096
// 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!
10861098
// FIXME: this catches `InterpError`, which we should not usually do.
10871099
// 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+
}
10901111
}
10911112
}
10921113
}

tests/fail/stacked_borrows/exposed_only_ro.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ fn main() {
88
let _fool = &mut x as *mut i32; // this would have fooled the old untagged pointer logic
99
let addr = (&x as *const i32).expose_addr();
1010
let ptr = std::ptr::from_exposed_addr_mut::<i32>(addr);
11-
unsafe { ptr.write(0) }; //~ ERROR: borrow stack
11+
unsafe { *ptr = 0 }; //~ ERROR: borrow stack
1212
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error: Undefined Behavior: attempting a write access using "<wildcard>" at ALLOC[0x0], but no exposed tags are valid in the borrow stack for this location
2+
--> $DIR/exposed_only_ro.rs:LL:CC
3+
|
4+
LL | unsafe { *ptr = 0 };
5+
| ^^^^^^^^
6+
| |
7+
| attempting a write access using "<wildcard>" at ALLOC[0x0], but no exposed tags are valid in the borrow stack for this location
8+
| this error occurs as part of an access at ALLOC[0x0..0x4]
9+
|
10+
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
11+
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
12+
13+
= note: inside `main` at $DIR/exposed_only_ro.rs:LL:CC
14+
15+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
16+
17+
error: aborting due to previous error
18+

0 commit comments

Comments
 (0)