Auto merge of #2030 - saethlin:track-alloc-history, r=oli-obk

Print spans where tags are created and invalidated

5225225 called this "automatic tag tracking" and I think that may be a reasonable description, but I would like to kill tag tracking as a primary use of Miri if possible. Tag tracking isn't always possible; for example if the UB is only detected with isolation off and the failing tag is made unstable by removing isolation. (also it's bad UX to run the tool twice)

This is just one of the things we can do with https://github.com/rust-lang/miri/pull/2024

The memory usage of this is _shockingly_ low, I think because the memory usage of Miri is driven by allocations where each byte ends up with its own very large stack. The memory usage in this change is linear with the number of tags, not tags * bytes. If memory usage gets out of control we can cap the number of events we save per allocation, from experience we tend to only use the most recent few in diagnostics but of course there's no guarantee of that so if we can manage to keep everything that would be best.

In many cases now I can tell exactly what these codebases are doing wrong just from the new outputs here, which I think is extremely cool.

New helps generated with plain old `cargo miri test` on `rust-argon2` v1.0.0:
```
test argon2::tests::single_thread_verification_multi_lane_hash ... error: Undefined Behavior: trying to reborrow <1485898> for Unique permission at alloc110523[0x0], but that tag does not exist in the borrow stack for this location
   --> /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/mem/manually_drop.rs:89:9
    |
89  |         slot.value
    |         ^^^^^^^^^^
    |         |
    |         trying to reborrow <1485898> for Unique permission at alloc110523[0x0], but that tag does not exist in the borrow stack for this location
    |         this error occurs as part of a reborrow at alloc110523[0x0..0x20]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <1485898> was created by a retag at offsets [0x0..0x20]
   --> src/memory.rs:42:13
    |
42  |             vec.push(unsafe { &mut (*ptr) });
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: <1485898> was later invalidated at offsets [0x0..0x20]
   --> src/memory.rs:42:31
    |
42  |             vec.push(unsafe { &mut (*ptr) });
    |                               ^^^^^^^^^^^
```

And with `-Zmiri-tag-raw-pointers` on `slab` v0.4.5
```
error: Undefined Behavior: trying to reborrow <2915> for Unique permission at alloc1418[0x0], but that tag does not exist in the borrow stack for this location
   --> /tmp/slab-0.4.5/src/lib.rs:835:16
    |
835 |         match (&mut *ptr1, &mut *ptr2) {
    |                ^^^^^^^^^^
    |                |
    |                trying to reborrow <2915> for Unique permission at alloc1418[0x0], but that tag does not exist in the borrow stack for this location
    |                this error occurs as part of a reborrow at alloc1418[0x0..0x10]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <2915> was created by a retag at offsets [0x0..0x10]
   --> /tmp/slab-0.4.5/src/lib.rs:833:20
    |
833 |         let ptr1 = self.entries.get_unchecked_mut(key1) as *mut Entry<T>;
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: <2915> was later invalidated at offsets [0x0..0x20]
   --> /tmp/slab-0.4.5/src/lib.rs:834:20
    |
834 |         let ptr2 = self.entries.get_unchecked_mut(key2) as *mut Entry<T>;
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

And without raw pointer tagging, `cargo miri test` on `half` v1.8.2
```
error: Undefined Behavior: trying to reborrow <untagged> for Unique permission at alloc1340[0x0], but that tag only grants SharedReadOnly permission for this location
   --> /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/slice/raw.rs:141:9
    |
141 |         &mut *ptr::slice_from_raw_parts_mut(data, len)
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |         |
    |         trying to reborrow <untagged> for Unique permission at alloc1340[0x0], but that tag only grants SharedReadOnly permission for this location
    |         this error occurs as part of a reborrow at alloc1340[0x0..0x6]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: tag was most recently created at offsets [0x0..0x6]
   --> /tmp/half-1.8.2/src/slice.rs:309:22
    |
309 |         let length = self.len();
    |                      ^^^^^^^^^^
help: this tag was also created here at offsets [0x0..0x6]
   --> /tmp/half-1.8.2/src/slice.rs:308:23
    |
308 |         let pointer = self.as_ptr() as *mut u16;
    |                       ^^^^^^^^^^^^^
```
The second suggestion is close to guesswork, but from experience it tends to be correct (as in, it tends to locate the pointer the user wanted) more often that it doesn't.
This commit is contained in:
bors 2022-05-14 19:27:04 +00:00
commit 98c8c8f9b5
6 changed files with 503 additions and 124 deletions

View file

@ -7,7 +7,8 @@ use log::trace;
use rustc_middle::ty;
use rustc_span::{source_map::DUMMY_SP, Span, SpanData, Symbol};
use crate::stacked_borrows::{AccessKind, SbTag};
use crate::helpers::HexRange;
use crate::stacked_borrows::{diagnostics::TagHistory, AccessKind, SbTag};
use crate::*;
/// Details of premature program termination.
@ -19,6 +20,7 @@ pub enum TerminationInfo {
msg: String,
help: Option<String>,
url: String,
history: Option<TagHistory>,
},
Deadlock,
MultipleSymbolDefinitions {
@ -155,12 +157,46 @@ pub fn report_error<'tcx, 'mir>(
(None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation;")),
(None, format!("or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning")),
],
ExperimentalUb { url, help, .. } => {
ExperimentalUb { url, help, history, .. } => {
msg.extend(help.clone());
vec![
let mut helps = vec![
(None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental")),
(None, format!("see {} for further information", url))
]
(None, format!("see {} for further information", url)),
];
match history {
Some(TagHistory::Tagged {tag, created: (created_range, created_span), invalidated, protected }) => {
let msg = format!("{:?} was created by a retag at offsets {}", tag, HexRange(*created_range));
helps.push((Some(created_span.clone()), msg));
if let Some((invalidated_range, invalidated_span)) = invalidated {
let msg = format!("{:?} was later invalidated at offsets {}", tag, HexRange(*invalidated_range));
helps.push((Some(invalidated_span.clone()), msg));
}
if let Some((protecting_tag, protecting_tag_span, protection_span)) = protected {
helps.push((Some(protecting_tag_span.clone()), format!("{:?} was protected due to {:?} which was created here", tag, protecting_tag)));
helps.push((Some(protection_span.clone()), "this protector is live for this call".to_string()));
}
}
Some(TagHistory::Untagged{ recently_created, recently_invalidated, matching_created, protected }) => {
if let Some((range, span)) = recently_created {
let msg = format!("tag was most recently created at offsets {}", HexRange(*range));
helps.push((Some(span.clone()), msg));
}
if let Some((range, span)) = recently_invalidated {
let msg = format!("tag was later invalidated at offsets {}", HexRange(*range));
helps.push((Some(span.clone()), msg));
}
if let Some((range, span)) = matching_created {
let msg = format!("this tag was also created here at offsets {}", HexRange(*range));
helps.push((Some(span.clone()), msg));
}
if let Some((protecting_tag, protecting_tag_span, protection_span)) = protected {
helps.push((Some(protecting_tag_span.clone()), format!("{:?} was protected due to a tag which was created here", protecting_tag)));
helps.push((Some(protection_span.clone()), "this protector is live for this call".to_string()));
}
}
None => {}
}
helps
}
MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
vec![

View file

@ -2,6 +2,7 @@ pub mod convert;
use std::mem;
use std::num::NonZeroUsize;
use std::rc::Rc;
use std::time::Duration;
use log::trace;
@ -816,7 +817,7 @@ pub fn isolation_abort_error(name: &str) -> InterpResult<'static> {
/// Retrieve the list of local crates that should have been passed by cargo-miri in
/// MIRI_LOCAL_CRATES and turn them into `CrateNum`s.
pub fn get_local_crates(tcx: &TyCtxt<'_>) -> Vec<CrateNum> {
pub fn get_local_crates(tcx: &TyCtxt<'_>) -> Rc<[CrateNum]> {
// Convert the local crate names from the passed-in config into CrateNums so that they can
// be looked up quickly during execution
let local_crate_names = std::env::var("MIRI_LOCAL_CRATES")
@ -830,5 +831,14 @@ pub fn get_local_crates(tcx: &TyCtxt<'_>) -> Vec<CrateNum> {
local_crates.push(crate_num);
}
}
local_crates
Rc::from(local_crates.as_slice())
}
/// Formats an AllocRange like [0x1..0x3], for use in diagnostics.
pub struct HexRange(pub AllocRange);
impl std::fmt::Display for HexRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{:#x}..{:#x}]", self.0.start.bytes(), self.0.end().bytes())
}
}

View file

@ -6,6 +6,7 @@ use std::cell::RefCell;
use std::collections::HashSet;
use std::fmt;
use std::num::NonZeroU64;
use std::rc::Rc;
use std::time::Instant;
use rand::rngs::StdRng;
@ -273,7 +274,7 @@ pub struct Evaluator<'mir, 'tcx> {
pub(crate) backtrace_style: BacktraceStyle,
/// Crates which are considered local for the purposes of error reporting.
pub(crate) local_crates: Vec<CrateNum>,
pub(crate) local_crates: Rc<[CrateNum]>,
/// Mapping extern static names to their base pointer.
extern_statics: FxHashMap<Symbol, Pointer<Tag>>,
@ -569,7 +570,14 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
let kind = kind.expect("we set our STATIC_KIND so this cannot be None");
let alloc = alloc.into_owned();
let stacks = if let Some(stacked_borrows) = &ecx.machine.stacked_borrows {
Some(Stacks::new_allocation(id, alloc.size(), stacked_borrows, kind))
Some(Stacks::new_allocation(
id,
alloc.size(),
stacked_borrows,
kind,
&ecx.machine.threads,
ecx.machine.local_crates.clone(),
))
} else {
None
};
@ -634,6 +642,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
tag,
range,
machine.stacked_borrows.as_ref().unwrap(),
&machine.threads,
)
} else {
Ok(())
@ -656,7 +665,8 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
alloc_id,
tag,
range,
machine.stacked_borrows.as_mut().unwrap(),
machine.stacked_borrows.as_ref().unwrap(),
&machine.threads,
)
} else {
Ok(())
@ -682,7 +692,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
alloc_id,
tag,
range,
machine.stacked_borrows.as_mut().unwrap(),
machine.stacked_borrows.as_ref().unwrap(),
)
} else {
Ok(())

View file

@ -5,6 +5,7 @@ use log::trace;
use std::cell::RefCell;
use std::fmt;
use std::num::NonZeroU64;
use std::rc::Rc;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::Mutability;
@ -13,12 +14,18 @@ use rustc_middle::ty::{
self,
layout::{HasParamEnv, LayoutOf},
};
use rustc_span::def_id::CrateNum;
use rustc_span::DUMMY_SP;
use rustc_target::abi::Size;
use std::collections::HashSet;
use crate::*;
pub mod diagnostics;
use diagnostics::AllocHistory;
use diagnostics::TagHistory;
pub type PtrId = NonZeroU64;
pub type CallId = NonZeroU64;
pub type AllocExtra = Stacks;
@ -90,6 +97,8 @@ pub struct Stack {
pub struct Stacks {
// Even reading memory can have effects on the stack, so we need a `RefCell` here.
stacks: RefCell<RangeMap<Stack>>,
/// Stores past operations on this allocation
history: RefCell<AllocHistory>,
}
/// Extra global state, available to the memory access hooks.
@ -112,6 +121,7 @@ pub struct GlobalStateInner {
/// Whether to track raw pointers.
tag_raw: bool,
}
/// We need interior mutable access to the global state.
pub type GlobalState = RefCell<GlobalStateInner>;
@ -221,13 +231,18 @@ impl GlobalStateInner {
}
/// Error reporting
fn err_sb_ub(msg: String, help: Option<String>) -> InterpError<'static> {
pub fn err_sb_ub(
msg: String,
help: Option<String>,
history: Option<TagHistory>,
) -> InterpError<'static> {
err_machine_stop!(TerminationInfo::ExperimentalUb {
msg,
help,
url: format!(
"https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md"
),
history
})
}
@ -306,33 +321,44 @@ impl<'tcx> Stack {
/// The `provoking_access` argument is only used to produce diagnostics.
/// It is `Some` when we are granting the contained access for said tag, and it is
/// `None` during a deallocation.
/// Within `provoking_access, the `AllocRange` refers the entire operation, and
/// the `Size` refers to the specific location in the `AllocRange` that we are
/// currently checking.
fn check_protector(
item: &Item,
provoking_access: Option<(SbTag, AccessKind)>,
provoking_access: Option<(SbTag, AllocRange, Size, AccessKind)>, // just for debug printing and error messages
global: &GlobalStateInner,
alloc_history: &mut AllocHistory,
) -> InterpResult<'tcx> {
if let SbTag::Tagged(id) = item.tag {
if global.tracked_pointer_tags.contains(&id) {
register_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(
*item,
provoking_access,
provoking_access.map(|(tag, _alloc_range, _size, access)| (tag, access)),
));
}
}
if let Some(call) = item.protector {
if global.is_active(call) {
if let Some((tag, _)) = provoking_access {
if let Some((tag, alloc_range, offset, _access)) = provoking_access {
Err(err_sb_ub(
format!(
"not granting access to tag {:?} because incompatible item is protected: {:?}",
tag, item
),
None,
alloc_history.get_logs_relevant_to(
tag,
alloc_range,
offset,
Some(item.tag),
),
))?
} else {
Err(err_sb_ub(
format!("deallocating while item is protected: {:?}", item),
None,
None,
))?
}
}
@ -348,15 +374,17 @@ impl<'tcx> Stack {
&mut self,
access: AccessKind,
tag: SbTag,
(alloc_id, range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages
global: &GlobalStateInner,
(alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages
global: &mut GlobalStateInner,
threads: &ThreadManager<'_, 'tcx>,
alloc_history: &mut AllocHistory,
) -> InterpResult<'tcx> {
// Two main steps: Find granting item, remove incompatible items above.
// Step 1: Find granting item.
let granting_idx = self
.find_granting(access, tag)
.ok_or_else(|| self.access_error(access, tag, alloc_id, range, offset))?;
let granting_idx = self.find_granting(access, tag).ok_or_else(|| {
alloc_history.access_error(access, tag, alloc_id, alloc_range, offset, self)
})?;
// Step 2: Remove incompatible items above them. Make sure we do not remove protected
// items. Behavior differs for reads and writes.
@ -366,7 +394,13 @@ impl<'tcx> Stack {
let first_incompatible_idx = self.find_first_write_incompatible(granting_idx);
for item in self.borrows.drain(first_incompatible_idx..).rev() {
trace!("access: popping item {:?}", item);
Stack::check_protector(&item, Some((tag, access)), global)?;
Stack::check_protector(
&item,
Some((tag, alloc_range, offset, access)),
global,
alloc_history,
)?;
alloc_history.log_invalidation(item.tag, alloc_range, threads);
}
} else {
// On a read, *disable* all `Unique` above the granting item. This ensures U2 for read accesses.
@ -381,8 +415,14 @@ impl<'tcx> Stack {
let item = &mut self.borrows[idx];
if item.perm == Permission::Unique {
trace!("access: disabling item {:?}", item);
Stack::check_protector(item, Some((tag, access)), global)?;
Stack::check_protector(
item,
Some((tag, alloc_range, offset, access)),
global,
alloc_history,
)?;
item.perm = Permission::Disabled;
alloc_history.log_invalidation(item.tag, alloc_range, threads);
}
}
}
@ -396,20 +436,24 @@ impl<'tcx> Stack {
fn dealloc(
&mut self,
tag: SbTag,
dbg_ptr: Pointer<AllocId>, // just for debug printing and error messages
(alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages
global: &GlobalStateInner,
alloc_history: &mut AllocHistory,
) -> InterpResult<'tcx> {
// Step 1: Find granting item.
self.find_granting(AccessKind::Write, tag).ok_or_else(|| {
err_sb_ub(format!(
"no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack",
tag, dbg_ptr,
), None)
tag, alloc_id,
),
None,
alloc_history.get_logs_relevant_to(tag, alloc_range, offset, None),
)
})?;
// Step 2: Remove all items. Also checks for protectors.
for item in self.borrows.drain(..).rev() {
Stack::check_protector(&item, None, global)?;
Stack::check_protector(&item, None, global, alloc_history)?;
}
Ok(())
@ -426,16 +470,18 @@ impl<'tcx> Stack {
derived_from: SbTag,
new: Item,
(alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages
global: &GlobalStateInner,
global: &mut GlobalStateInner,
threads: &ThreadManager<'_, 'tcx>,
alloc_history: &mut AllocHistory,
) -> InterpResult<'tcx> {
// Figure out which access `perm` corresponds to.
let access =
if new.perm.grants(AccessKind::Write) { AccessKind::Write } else { AccessKind::Read };
// Now we figure out which item grants our parent (`derived_from`) this kind of access.
// We use that to determine where to put the new item.
let granting_idx = self
.find_granting(access, derived_from)
.ok_or_else(|| self.grant_error(derived_from, new, alloc_id, alloc_range, offset))?;
let granting_idx = self.find_granting(access, derived_from).ok_or_else(|| {
alloc_history.grant_error(derived_from, new, alloc_id, alloc_range, offset, self)
})?;
// Compute where to put the new item.
// Either way, we ensure that we insert the new item in a way such that between
@ -454,7 +500,14 @@ impl<'tcx> Stack {
// A "safe" reborrow for a pointer that actually expects some aliasing guarantees.
// Here, creating a reference actually counts as an access.
// This ensures F2b for `Unique`, by removing offending `SharedReadOnly`.
self.access(access, derived_from, (alloc_id, alloc_range, offset), global)?;
self.access(
access,
derived_from,
(alloc_id, alloc_range, offset),
global,
threads,
alloc_history,
)?;
// We insert "as far up as possible": We know only compatible items are remaining
// on top of `derived_from`, and we want the new item at the top so that we
@ -474,94 +527,32 @@ impl<'tcx> Stack {
Ok(())
}
/// Report a descriptive error when `new` could not be granted from `derived_from`.
fn grant_error(
&self,
derived_from: SbTag,
new: Item,
alloc_id: AllocId,
alloc_range: AllocRange,
error_offset: Size,
) -> InterpError<'static> {
let action = format!(
"trying to reborrow {:?} for {:?} permission at {}[{:#x}]",
derived_from,
new.perm,
alloc_id,
error_offset.bytes(),
);
err_sb_ub(
format!("{}{}", action, self.error_cause(derived_from)),
Some(Self::operation_summary("a reborrow", alloc_id, alloc_range)),
)
}
/// Report a descriptive error when `access` is not permitted based on `tag`.
fn access_error(
&self,
access: AccessKind,
tag: SbTag,
alloc_id: AllocId,
alloc_range: AllocRange,
error_offset: Size,
) -> InterpError<'static> {
let action = format!(
"attempting a {} using {:?} at {}[{:#x}]",
access,
tag,
alloc_id,
error_offset.bytes(),
);
err_sb_ub(
format!("{}{}", action, self.error_cause(tag)),
Some(Self::operation_summary("an access", alloc_id, alloc_range)),
)
}
fn operation_summary(
operation: &'static str,
alloc_id: AllocId,
alloc_range: AllocRange,
) -> String {
format!(
"this error occurs as part of {} at {:?}[{:#x}..{:#x}]",
operation,
alloc_id,
alloc_range.start.bytes(),
alloc_range.end().bytes()
)
}
fn error_cause(&self, tag: SbTag) -> &'static str {
if self.borrows.iter().any(|item| item.tag == tag && item.perm != Permission::Disabled) {
", but that tag only grants SharedReadOnly permission for this location"
} else {
", but that tag does not exist in the borrow stack for this location"
}
}
}
// # Stacked Borrows Core End
/// Map per-stack operations to higher-level per-location-range operations.
impl<'tcx> Stacks {
/// Creates new stack with initial tag.
fn new(size: Size, perm: Permission, tag: SbTag) -> Self {
fn new(size: Size, perm: Permission, tag: SbTag, local_crates: Rc<[CrateNum]>) -> Self {
let item = Item { perm, tag, protector: None };
let stack = Stack { borrows: vec![item] };
Stacks { stacks: RefCell::new(RangeMap::new(size, stack)) }
Stacks {
stacks: RefCell::new(RangeMap::new(size, stack)),
history: RefCell::new(AllocHistory::new(local_crates)),
}
}
/// Call `f` on every stack in the range.
fn for_each(
&self,
range: AllocRange,
f: impl Fn(Size, &mut Stack) -> InterpResult<'tcx>,
mut f: impl FnMut(Size, &mut Stack, &mut AllocHistory) -> InterpResult<'tcx>,
) -> InterpResult<'tcx> {
let mut stacks = self.stacks.borrow_mut();
let history = &mut *self.history.borrow_mut();
for (offset, stack) in stacks.iter_mut(range.start, range.size) {
f(offset, stack)?;
f(offset, stack, history)?;
}
Ok(())
}
@ -570,11 +561,12 @@ impl<'tcx> Stacks {
fn for_each_mut(
&mut self,
range: AllocRange,
f: impl Fn(Size, &mut Stack) -> InterpResult<'tcx>,
mut f: impl FnMut(Size, &mut Stack, &mut AllocHistory) -> InterpResult<'tcx>,
) -> InterpResult<'tcx> {
let stacks = self.stacks.get_mut();
let history = &mut *self.history.borrow_mut();
for (offset, stack) in stacks.iter_mut(range.start, range.size) {
f(offset, stack)?;
f(offset, stack, history)?;
}
Ok(())
}
@ -587,6 +579,8 @@ impl Stacks {
size: Size,
state: &GlobalState,
kind: MemoryKind<MiriMemoryKind>,
threads: &ThreadManager<'_, '_>,
local_crates: Rc<[CrateNum]>,
) -> Self {
let mut extra = state.borrow_mut();
let (base_tag, perm) = match kind {
@ -620,7 +614,14 @@ impl Stacks {
(tag, Permission::SharedReadWrite)
}
};
Stacks::new(size, perm, base_tag)
let stacks = Stacks::new(size, perm, base_tag, local_crates);
stacks.history.borrow_mut().log_creation(
None,
base_tag,
alloc_range(Size::ZERO, size),
threads,
);
stacks
}
#[inline(always)]
@ -630,6 +631,7 @@ impl Stacks {
tag: SbTag,
range: AllocRange,
state: &GlobalState,
threads: &ThreadManager<'_, 'tcx>,
) -> InterpResult<'tcx> {
trace!(
"read access with tag {:?}: {:?}, size {}",
@ -637,9 +639,16 @@ impl Stacks {
Pointer::new(alloc_id, range.start),
range.size.bytes()
);
let global = &*state.borrow();
self.for_each(range, move |offset, stack| {
stack.access(AccessKind::Read, tag, (alloc_id, range, offset), global)
let mut state = state.borrow_mut();
self.for_each(range, |offset, stack, history| {
stack.access(
AccessKind::Read,
tag,
(alloc_id, range, offset),
&mut state,
threads,
history,
)
})
}
@ -649,7 +658,8 @@ impl Stacks {
alloc_id: AllocId,
tag: SbTag,
range: AllocRange,
state: &mut GlobalState,
state: &GlobalState,
threads: &ThreadManager<'_, 'tcx>,
) -> InterpResult<'tcx> {
trace!(
"write access with tag {:?}: {:?}, size {}",
@ -657,9 +667,16 @@ impl Stacks {
Pointer::new(alloc_id, range.start),
range.size.bytes()
);
let global = state.get_mut();
self.for_each_mut(range, move |offset, stack| {
stack.access(AccessKind::Write, tag, (alloc_id, range, offset), global)
let mut state = state.borrow_mut();
self.for_each_mut(range, |offset, stack, history| {
stack.access(
AccessKind::Write,
tag,
(alloc_id, range, offset),
&mut state,
threads,
history,
)
})
}
@ -669,13 +686,14 @@ impl Stacks {
alloc_id: AllocId,
tag: SbTag,
range: AllocRange,
state: &mut GlobalState,
state: &GlobalState,
) -> InterpResult<'tcx> {
trace!("deallocation with tag {:?}: {:?}, size {}", tag, alloc_id, range.size.bytes());
let global = state.get_mut();
self.for_each_mut(range, move |offset, stack| {
stack.dealloc(tag, Pointer::new(alloc_id, offset), global)
})
let mut state = state.borrow_mut();
self.for_each_mut(range, |offset, stack, history| {
stack.dealloc(tag, (alloc_id, range, offset), &mut state, history)
})?;
Ok(())
}
}
@ -705,6 +723,22 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
}
let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?;
{
let extra = this.get_alloc_extra(alloc_id)?;
let stacked_borrows =
extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data");
let mut alloc_history = stacked_borrows.history.borrow_mut();
alloc_history.log_creation(
Some(orig_tag),
new_tag,
alloc_range(base_offset, base_offset + size),
&this.machine.threads,
);
if protect {
alloc_history.log_protector(orig_tag, new_tag, &this.machine.threads);
}
}
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
let (alloc_size, _) =
this.get_alloc_size_and_align(alloc_id, AllocCheck::Dereferenceable)?;
@ -753,7 +787,6 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let extra = this.get_alloc_extra(alloc_id)?;
let stacked_borrows =
extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data");
let global = this.machine.stacked_borrows.as_ref().unwrap().borrow();
this.visit_freeze_sensitive(place, size, |mut range, frozen| {
// Adjust range.
range.start += base_offset;
@ -764,8 +797,16 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
Permission::SharedReadWrite
};
let item = Item { perm, tag: new_tag, protector };
stacked_borrows.for_each(range, |offset, stack| {
stack.grant(orig_tag, item, (alloc_id, range, offset), &*global)
let mut global = this.machine.stacked_borrows.as_ref().unwrap().borrow_mut();
stacked_borrows.for_each(range, |offset, stack, history| {
stack.grant(
orig_tag,
item,
(alloc_id, range, offset),
&mut *global,
&this.machine.threads,
history,
)
})
})?;
return Ok(());
@ -774,15 +815,23 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
// Here we can avoid `borrow()` calls because we have mutable references.
// Note that this asserts that the allocation is mutable -- but since we are creating a
// mutable pointer, that seems reasonable.
let (alloc_extra, memory_extra) = this.get_alloc_extra_mut(alloc_id)?;
let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?;
let stacked_borrows =
alloc_extra.stacked_borrows.as_mut().expect("we should have Stacked Borrows data");
let global = memory_extra.stacked_borrows.as_mut().unwrap().get_mut();
let item = Item { perm, tag: new_tag, protector };
let range = alloc_range(base_offset, size);
stacked_borrows.for_each_mut(alloc_range(base_offset, size), |offset, stack| {
stack.grant(orig_tag, item, (alloc_id, range, offset), global)
let mut global = machine.stacked_borrows.as_ref().unwrap().borrow_mut();
stacked_borrows.for_each_mut(range, |offset, stack, history| {
stack.grant(
orig_tag,
item,
(alloc_id, range, offset),
&mut global,
&machine.threads,
history,
)
})?;
Ok(())
}

View file

@ -0,0 +1,274 @@
use smallvec::SmallVec;
use std::rc::Rc;
use rustc_middle::mir::interpret::{AllocId, AllocRange};
use rustc_span::def_id::CrateNum;
use rustc_span::{Span, SpanData};
use rustc_target::abi::Size;
use crate::helpers::HexRange;
use crate::stacked_borrows::{err_sb_ub, AccessKind, Permission};
use crate::Item;
use crate::SbTag;
use crate::Stack;
use crate::ThreadManager;
use rustc_middle::mir::interpret::InterpError;
#[derive(Clone, Debug)]
pub struct AllocHistory {
// The time tags can be compressed down to one bit per event, by just storing a Vec<u8>
// where each bit is set to indicate if the event was a creation or a retag
current_time: usize,
creations: smallvec::SmallVec<[Event; 2]>,
invalidations: smallvec::SmallVec<[Event; 1]>,
protectors: smallvec::SmallVec<[Protection; 1]>,
/// This field is a clone of the `local_crates` field on `Evaluator`.
local_crates: Rc<[CrateNum]>,
}
#[derive(Clone, Debug)]
struct Protection {
orig_tag: SbTag,
tag: SbTag,
span: Span,
}
#[derive(Clone, Debug)]
struct Event {
time: usize,
parent: Option<SbTag>,
tag: SbTag,
range: AllocRange,
span: Span,
}
pub enum TagHistory {
Tagged {
tag: SbTag,
created: (AllocRange, SpanData),
invalidated: Option<(AllocRange, SpanData)>,
protected: Option<(SbTag, SpanData, SpanData)>,
},
Untagged {
recently_created: Option<(AllocRange, SpanData)>,
recently_invalidated: Option<(AllocRange, SpanData)>,
matching_created: Option<(AllocRange, SpanData)>,
protected: Option<(SbTag, SpanData, SpanData)>,
},
}
impl AllocHistory {
pub fn new(local_crates: Rc<[CrateNum]>) -> Self {
Self {
current_time: 0,
creations: SmallVec::new(),
invalidations: SmallVec::new(),
protectors: SmallVec::new(),
local_crates,
}
}
fn current_span(&self, threads: &ThreadManager<'_, '_>) -> Span {
threads
.active_thread_stack()
.into_iter()
.rev()
.find(|frame| {
let def_id = frame.instance.def_id();
def_id.is_local() || self.local_crates.contains(&def_id.krate)
})
.map(|frame| frame.current_span())
.unwrap_or(rustc_span::DUMMY_SP)
}
pub fn log_creation(
&mut self,
parent: Option<SbTag>,
tag: SbTag,
range: AllocRange,
threads: &ThreadManager<'_, '_>,
) {
let span = self.current_span(threads);
self.creations.push(Event { parent, tag, range, span, time: self.current_time });
self.current_time += 1;
}
pub fn log_invalidation(
&mut self,
tag: SbTag,
range: AllocRange,
threads: &ThreadManager<'_, '_>,
) {
let span = self.current_span(threads);
self.invalidations.push(Event { parent: None, tag, range, span, time: self.current_time });
self.current_time += 1;
}
pub fn log_protector(&mut self, orig_tag: SbTag, tag: SbTag, threads: &ThreadManager<'_, '_>) {
let span = self.current_span(threads);
self.protectors.push(Protection { orig_tag, tag, span });
self.current_time += 1;
}
pub fn get_logs_relevant_to(
&self,
tag: SbTag,
alloc_range: AllocRange,
offset: Size,
protector_tag: Option<SbTag>,
) -> Option<TagHistory> {
let protected = protector_tag
.and_then(|protector| {
self.protectors.iter().find_map(|protection| {
if protection.tag == protector {
Some((protection.orig_tag, protection.span.data()))
} else {
None
}
})
})
.and_then(|(tag, call_span)| {
self.creations.iter().rev().find_map(|event| {
if event.tag == tag {
Some((event.parent?, event.span.data(), call_span))
} else {
None
}
})
});
if let SbTag::Tagged(_) = tag {
let get_matching = |events: &[Event]| {
events.iter().rev().find_map(|event| {
if event.tag == tag { Some((event.range, event.span.data())) } else { None }
})
};
Some(TagHistory::Tagged {
tag,
created: get_matching(&self.creations)?,
invalidated: get_matching(&self.invalidations),
protected,
})
} else {
let mut created_time = 0;
// Find the most recently created tag that satsfies this offset
let recently_created = self.creations.iter().rev().find_map(|event| {
if event.tag == tag && offset >= event.range.start && offset < event.range.end() {
created_time = event.time;
Some((event.range, event.span.data()))
} else {
None
}
});
// Find a different recently created tag that satisfies this whole operation, predates
// the recently created tag, and has a different span.
// We're trying to make a guess at which span the user wanted to provide the tag that
// they're using.
let matching_created = recently_created.and_then(|(_created_range, created_span)| {
self.creations.iter().rev().find_map(|event| {
if event.tag == tag
&& alloc_range.start >= event.range.start
&& alloc_range.end() <= event.range.end()
&& event.span.data() != created_span
&& event.time != created_time
{
Some((event.range, event.span.data()))
} else {
None
}
})
});
// Find the most recent invalidation of this tag which post-dates the creation
let recently_invalidated = recently_created.and_then(|_| {
self.invalidations
.iter()
.rev()
.take_while(|event| event.time > created_time)
.find_map(|event| {
if event.tag == tag
&& offset >= event.range.start
&& offset < event.range.end()
{
Some((event.range, event.span.data()))
} else {
None
}
})
});
Some(TagHistory::Untagged {
recently_created,
matching_created,
recently_invalidated,
protected,
})
}
}
/// Report a descriptive error when `new` could not be granted from `derived_from`.
pub fn grant_error(
&self,
derived_from: SbTag,
new: Item,
alloc_id: AllocId,
alloc_range: AllocRange,
error_offset: Size,
stack: &Stack,
) -> InterpError<'static> {
let action = format!(
"trying to reborrow {:?} for {:?} permission at {}[{:#x}]",
derived_from,
new.perm,
alloc_id,
error_offset.bytes(),
);
err_sb_ub(
format!("{}{}", action, error_cause(stack, derived_from)),
Some(operation_summary("a reborrow", alloc_id, alloc_range)),
self.get_logs_relevant_to(derived_from, alloc_range, error_offset, None),
)
}
/// Report a descriptive error when `access` is not permitted based on `tag`.
pub fn access_error(
&self,
access: AccessKind,
tag: SbTag,
alloc_id: AllocId,
alloc_range: AllocRange,
error_offset: Size,
stack: &Stack,
) -> InterpError<'static> {
let action = format!(
"attempting a {} using {:?} at {}[{:#x}]",
access,
tag,
alloc_id,
error_offset.bytes(),
);
err_sb_ub(
format!("{}{}", action, error_cause(stack, tag)),
Some(operation_summary("an access", alloc_id, alloc_range)),
self.get_logs_relevant_to(tag, alloc_range, error_offset, None),
)
}
}
fn operation_summary(
operation: &'static str,
alloc_id: AllocId,
alloc_range: AllocRange,
) -> String {
format!("this error occurs as part of {} at {:?}{}", operation, alloc_id, HexRange(alloc_range))
}
fn error_cause(stack: &Stack, tag: SbTag) -> &'static str {
if stack.borrows.iter().any(|item| item.tag == tag && item.perm != Permission::Disabled) {
", but that tag only grants SharedReadOnly permission for this location"
} else {
", but that tag does not exist in the borrow stack for this location"
}
}

View file

@ -263,7 +263,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
}
/// Borrow the stack of the active thread.
fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Tag, FrameData<'tcx>>] {
pub fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Tag, FrameData<'tcx>>] {
&self.threads[self.active_thread].stack
}