extract common borrow tracking logic
This commit is contained in:
parent
4a12a13b58
commit
2d42d265ea
365
src/tools/miri/src/borrow_tracker/mod.rs
Normal file
365
src/tools/miri/src/borrow_tracker/mod.rs
Normal file
@ -0,0 +1,365 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
use log::trace;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_middle::mir::RetagKind;
|
||||
use rustc_target::abi::Size;
|
||||
|
||||
use crate::*;
|
||||
pub mod stacked_borrows;
|
||||
use stacked_borrows::diagnostics::RetagCause;
|
||||
|
||||
pub type CallId = NonZeroU64;
|
||||
|
||||
/// Tracking pointer provenance
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct BorTag(NonZeroU64);
|
||||
|
||||
impl BorTag {
|
||||
pub fn new(i: u64) -> Option<Self> {
|
||||
NonZeroU64::new(i).map(BorTag)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> u64 {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> NonZeroU64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn succ(self) -> Option<Self> {
|
||||
self.0.checked_add(1).map(Self)
|
||||
}
|
||||
|
||||
/// The minimum representable tag
|
||||
pub fn one() -> Self {
|
||||
Self::new(1).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for BorTag {
|
||||
/// The default to be used when borrow tracking is disabled
|
||||
fn default() -> Self {
|
||||
Self::one()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BorTag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<{}>", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-frame data for borrow tracking
|
||||
#[derive(Debug)]
|
||||
pub struct FrameExtra {
|
||||
/// The ID of the call this frame corresponds to.
|
||||
pub call_id: CallId,
|
||||
|
||||
/// If this frame is protecting any tags, they are listed here. We use this list to do
|
||||
/// incremental updates of the global list of protected tags stored in the
|
||||
/// `stacked_borrows::GlobalState` upon function return, and if we attempt to pop a protected
|
||||
/// tag, to identify which call is responsible for protecting the tag.
|
||||
/// See `Stack::item_popped` for more explanation.
|
||||
///
|
||||
/// This will contain one tag per reference passed to the function, so
|
||||
/// a size of 2 is enough for the vast majority of functions.
|
||||
pub protected_tags: SmallVec<[BorTag; 2]>,
|
||||
}
|
||||
|
||||
impl VisitTags for FrameExtra {
|
||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
||||
// `protected_tags` are fine to GC.
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra global state, available to the memory access hooks.
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalStateInner {
|
||||
/// Borrow tracker method currently in use.
|
||||
pub borrow_tracker_method: BorrowTrackerMethod,
|
||||
/// Next unused pointer ID (tag).
|
||||
pub next_ptr_tag: BorTag,
|
||||
/// Table storing the "base" tag for each allocation.
|
||||
/// The base tag is the one used for the initial pointer.
|
||||
/// We need this in a separate table to handle cyclic statics.
|
||||
pub base_ptr_tags: FxHashMap<AllocId, BorTag>,
|
||||
/// Next unused call ID (for protectors).
|
||||
pub next_call_id: CallId,
|
||||
/// All currently protected tags.
|
||||
/// An item is protected if its tag is in this set, *and* it has the "protected" bit set.
|
||||
/// We add tags to this when they are created with a protector in `reborrow`, and
|
||||
/// we remove tags from this when the call which is protecting them returns, in
|
||||
/// `GlobalStateInner::end_call`. See `Stack::item_popped` for more details.
|
||||
pub protected_tags: FxHashMap<BorTag, ProtectorKind>,
|
||||
/// The pointer ids to trace
|
||||
pub tracked_pointer_tags: FxHashSet<BorTag>,
|
||||
/// The call ids to trace
|
||||
pub tracked_call_ids: FxHashSet<CallId>,
|
||||
/// Whether to recurse into datatypes when searching for pointers to retag.
|
||||
pub retag_fields: RetagFields,
|
||||
}
|
||||
|
||||
impl VisitTags for GlobalStateInner {
|
||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
||||
// The only candidate is base_ptr_tags, and that does not need visiting since we don't ever
|
||||
// GC the bottommost tag.
|
||||
}
|
||||
}
|
||||
|
||||
/// We need interior mutable access to the global state.
|
||||
pub type GlobalState = RefCell<GlobalStateInner>;
|
||||
|
||||
/// Indicates which kind of access is being performed.
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
|
||||
pub enum AccessKind {
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
impl fmt::Display for AccessKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
AccessKind::Read => write!(f, "read access"),
|
||||
AccessKind::Write => write!(f, "write access"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Policy on whether to recurse into fields to retag
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum RetagFields {
|
||||
/// Don't retag any fields.
|
||||
No,
|
||||
/// Retag all fields.
|
||||
Yes,
|
||||
/// Only retag fields of types with Scalar and ScalarPair layout,
|
||||
/// to match the LLVM `noalias` we generate.
|
||||
OnlyScalar,
|
||||
}
|
||||
|
||||
/// The flavor of the protector.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ProtectorKind {
|
||||
/// Protected against aliasing violations from other pointers.
|
||||
///
|
||||
/// Items protected like this cause UB when they are invalidated, *but* the pointer itself may
|
||||
/// still be used to issue a deallocation.
|
||||
///
|
||||
/// This is required for LLVM IR pointers that are `noalias` but *not* `dereferenceable`.
|
||||
WeakProtector,
|
||||
|
||||
/// Protected against any kind of invalidation.
|
||||
///
|
||||
/// Items protected like this cause UB when they are invalidated or the memory is deallocated.
|
||||
/// This is strictly stronger protection than `WeakProtector`.
|
||||
///
|
||||
/// This is required for LLVM IR pointers that are `dereferenceable` (and also allows `noalias`).
|
||||
StrongProtector,
|
||||
}
|
||||
|
||||
/// Utilities for initialization and ID generation
|
||||
impl GlobalStateInner {
|
||||
pub fn new(
|
||||
borrow_tracker_method: BorrowTrackerMethod,
|
||||
tracked_pointer_tags: FxHashSet<BorTag>,
|
||||
tracked_call_ids: FxHashSet<CallId>,
|
||||
retag_fields: RetagFields,
|
||||
) -> Self {
|
||||
GlobalStateInner {
|
||||
borrow_tracker_method,
|
||||
next_ptr_tag: BorTag::one(),
|
||||
base_ptr_tags: FxHashMap::default(),
|
||||
next_call_id: NonZeroU64::new(1).unwrap(),
|
||||
protected_tags: FxHashMap::default(),
|
||||
tracked_pointer_tags,
|
||||
tracked_call_ids,
|
||||
retag_fields,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a new pointer tag. Remember to also check track_pointer_tags and log its creation!
|
||||
pub fn new_ptr(&mut self) -> BorTag {
|
||||
let id = self.next_ptr_tag;
|
||||
self.next_ptr_tag = id.succ().unwrap();
|
||||
id
|
||||
}
|
||||
|
||||
pub fn new_frame(&mut self, machine: &MiriMachine<'_, '_>) -> FrameExtra {
|
||||
let call_id = self.next_call_id;
|
||||
trace!("new_frame: Assigning call ID {}", call_id);
|
||||
if self.tracked_call_ids.contains(&call_id) {
|
||||
machine.emit_diagnostic(NonHaltingDiagnostic::CreatedCallId(call_id));
|
||||
}
|
||||
self.next_call_id = NonZeroU64::new(call_id.get() + 1).unwrap();
|
||||
FrameExtra { call_id, protected_tags: SmallVec::new() }
|
||||
}
|
||||
|
||||
pub fn end_call(&mut self, frame: &machine::FrameData<'_>) {
|
||||
for tag in &frame
|
||||
.borrow_tracker
|
||||
.as_ref()
|
||||
.expect("we should have borrow tracking data")
|
||||
.protected_tags
|
||||
{
|
||||
self.protected_tags.remove(tag);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_, '_>) -> BorTag {
|
||||
self.base_ptr_tags.get(&id).copied().unwrap_or_else(|| {
|
||||
let tag = self.new_ptr();
|
||||
if self.tracked_pointer_tags.contains(&tag) {
|
||||
machine.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
|
||||
tag.inner(),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
}
|
||||
trace!("New allocation {:?} has base tag {:?}", id, tag);
|
||||
self.base_ptr_tags.try_insert(id, tag).unwrap();
|
||||
tag
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Which borrow tracking method to use
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum BorrowTrackerMethod {
|
||||
/// Stacked Borrows, as implemented in borrow_tracker/stacked
|
||||
StackedBorrows,
|
||||
}
|
||||
|
||||
impl BorrowTrackerMethod {
|
||||
pub fn instanciate_global_state(self, config: &MiriConfig) -> GlobalState {
|
||||
RefCell::new(GlobalStateInner::new(
|
||||
self,
|
||||
config.tracked_pointer_tags.clone(),
|
||||
config.tracked_call_ids.clone(),
|
||||
config.retag_fields,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalStateInner {
|
||||
pub fn new_allocation(
|
||||
&mut self,
|
||||
id: AllocId,
|
||||
alloc_size: Size,
|
||||
kind: MemoryKind<machine::MiriMemoryKind>,
|
||||
machine: &MiriMachine<'_, '_>,
|
||||
) -> AllocExtra {
|
||||
match self.borrow_tracker_method {
|
||||
BorrowTrackerMethod::StackedBorrows =>
|
||||
AllocExtra::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
|
||||
id, alloc_size, self, kind, machine,
|
||||
)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
fn retag(&mut self, kind: RetagKind, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||
match method {
|
||||
BorrowTrackerMethod::StackedBorrows => this.sb_retag(kind, place),
|
||||
}
|
||||
}
|
||||
|
||||
fn retag_return_place(&mut self) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||
match method {
|
||||
BorrowTrackerMethod::StackedBorrows => this.sb_retag_return_place(),
|
||||
}
|
||||
}
|
||||
|
||||
fn expose_tag(&mut self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||
match method {
|
||||
BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra per-allocation data for borrow tracking
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AllocExtra {
|
||||
/// Data corresponding to Stacked Borrows
|
||||
StackedBorrows(Box<RefCell<stacked_borrows::AllocExtra>>),
|
||||
}
|
||||
|
||||
impl AllocExtra {
|
||||
pub fn assert_sb(&self) -> &RefCell<stacked_borrows::AllocExtra> {
|
||||
match self {
|
||||
AllocExtra::StackedBorrows(ref sb) => sb,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_sb_mut(&mut self) -> &mut RefCell<stacked_borrows::AllocExtra> {
|
||||
match self {
|
||||
AllocExtra::StackedBorrows(ref mut sb) => sb,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn before_memory_read<'tcx>(
|
||||
&self,
|
||||
alloc_id: AllocId,
|
||||
prov_extra: ProvenanceExtra,
|
||||
range: AllocRange,
|
||||
machine: &MiriMachine<'_, 'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
match self {
|
||||
AllocExtra::StackedBorrows(sb) =>
|
||||
sb.borrow_mut().before_memory_read(alloc_id, prov_extra, range, machine),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn before_memory_write<'tcx>(
|
||||
&mut self,
|
||||
alloc_id: AllocId,
|
||||
prov_extra: ProvenanceExtra,
|
||||
range: AllocRange,
|
||||
machine: &mut MiriMachine<'_, 'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
match self {
|
||||
AllocExtra::StackedBorrows(sb) =>
|
||||
sb.get_mut().before_memory_write(alloc_id, prov_extra, range, machine),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn before_memory_deallocation<'tcx>(
|
||||
&mut self,
|
||||
alloc_id: AllocId,
|
||||
prov_extra: ProvenanceExtra,
|
||||
range: AllocRange,
|
||||
machine: &mut MiriMachine<'_, 'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
match self {
|
||||
AllocExtra::StackedBorrows(sb) =>
|
||||
sb.get_mut().before_memory_deallocation(alloc_id, prov_extra, range, machine),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>) {
|
||||
match self {
|
||||
AllocExtra::StackedBorrows(sb) => sb.borrow_mut().remove_unreachable_tags(tags),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for AllocExtra {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
match self {
|
||||
AllocExtra::StackedBorrows(sb) => sb.visit_tags(visit),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user