use std::cell::RefCell;
use std::fmt;
use std::num::NonZero;
use rustc_abi::Size;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_middle::mir::RetagKind;
use smallvec::SmallVec;
use crate::*;
pub mod stacked_borrows;
pub mod tree_borrows;
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct BorTag(NonZero<u64>);
impl BorTag {
pub fn new(i: u64) -> Option<Self> {
NonZero::new(i).map(BorTag)
}
pub fn get(&self) -> u64 {
self.0.get()
}
pub fn inner(&self) -> NonZero<u64> {
self.0
}
pub fn succ(self) -> Option<Self> {
self.0.checked_add(1).map(Self)
}
pub fn one() -> Self {
Self::new(1).unwrap()
}
}
impl std::default::Default for BorTag {
fn default() -> Self {
Self::one()
}
}
impl fmt::Debug for BorTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<{}>", self.0)
}
}
#[derive(Debug)]
pub struct FrameState {
protected_tags: SmallVec<[(AllocId, BorTag); 2]>,
}
impl VisitProvenance for FrameState {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
for (id, tag) in &self.protected_tags {
visit(Some(*id), Some(*tag));
}
}
}
#[derive(Debug)]
pub struct GlobalStateInner {
borrow_tracker_method: BorrowTrackerMethod,
next_ptr_tag: BorTag,
root_ptr_tags: FxHashMap<AllocId, BorTag>,
protected_tags: FxHashMap<BorTag, ProtectorKind>,
tracked_pointer_tags: FxHashSet<BorTag>,
retag_fields: RetagFields,
unique_is_unique: bool,
}
impl VisitProvenance for GlobalStateInner {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
}
}
pub type GlobalState = RefCell<GlobalStateInner>;
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"),
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum RetagFields {
No,
Yes,
OnlyScalar,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ProtectorKind {
WeakProtector,
StrongProtector,
}
impl GlobalStateInner {
pub fn new(
borrow_tracker_method: BorrowTrackerMethod,
tracked_pointer_tags: FxHashSet<BorTag>,
retag_fields: RetagFields,
unique_is_unique: bool,
) -> Self {
GlobalStateInner {
borrow_tracker_method,
next_ptr_tag: BorTag::one(),
root_ptr_tags: FxHashMap::default(),
protected_tags: FxHashMap::default(),
tracked_pointer_tags,
retag_fields,
unique_is_unique,
}
}
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) -> FrameState {
FrameState { protected_tags: SmallVec::new() }
}
fn end_call(&mut self, frame: &machine::FrameExtra<'_>) {
for (_, tag) in &frame
.borrow_tracker
.as_ref()
.expect("we should have borrow tracking data")
.protected_tags
{
self.protected_tags.remove(tag);
}
}
pub fn root_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_>) -> BorTag {
self.root_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 rpot tag {:?}", id, tag);
self.root_ptr_tags.try_insert(id, tag).unwrap();
tag
})
}
pub fn remove_unreachable_allocs(&mut self, allocs: &LiveAllocs<'_, '_>) {
self.root_ptr_tags.retain(|id, _| allocs.is_live(*id));
}
pub fn borrow_tracker_method(&self) -> BorrowTrackerMethod {
self.borrow_tracker_method
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BorrowTrackerMethod {
StackedBorrows,
TreeBorrows,
}
impl BorrowTrackerMethod {
pub fn instantiate_global_state(self, config: &MiriConfig) -> GlobalState {
RefCell::new(GlobalStateInner::new(
self,
config.tracked_pointer_tags.clone(),
config.retag_fields,
config.unique_is_unique,
))
}
}
impl GlobalStateInner {
pub fn new_allocation(
&mut self,
id: AllocId,
alloc_size: Size,
kind: MemoryKind,
machine: &MiriMachine<'_>,
) -> AllocState {
match self.borrow_tracker_method {
BorrowTrackerMethod::StackedBorrows =>
AllocState::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
id, alloc_size, self, kind, machine,
)))),
BorrowTrackerMethod::TreeBorrows =>
AllocState::TreeBorrows(Box::new(RefCell::new(Tree::new_allocation(
id, alloc_size, self, kind, machine,
)))),
}
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn retag_ptr_value(
&mut self,
kind: RetagKind,
val: &ImmTy<'tcx>,
) -> InterpResult<'tcx, ImmTy<'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_ptr_value(kind, val),
BorrowTrackerMethod::TreeBorrows => this.tb_retag_ptr_value(kind, val),
}
}
fn retag_place_contents(
&mut self,
kind: RetagKind,
place: &PlaceTy<'tcx>,
) -> 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_place_contents(kind, place),
BorrowTrackerMethod::TreeBorrows => this.tb_retag_place_contents(kind, place),
}
}
fn protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'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_protect_place(place),
BorrowTrackerMethod::TreeBorrows => this.tb_protect_place(place),
}
}
fn expose_tag(&self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
let this = self.eval_context_ref();
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
match method {
BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag),
BorrowTrackerMethod::TreeBorrows => this.tb_expose_tag(alloc_id, tag),
}
}
fn give_pointer_debug_name(
&mut self,
ptr: Pointer,
nth_parent: u8,
name: &str,
) -> 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.tcx.tcx.dcx().warn("Stacked Borrows does not support named pointers; `miri_pointer_name` is a no-op");
interp_ok(())
}
BorrowTrackerMethod::TreeBorrows =>
this.tb_give_pointer_debug_name(ptr, nth_parent, name),
}
}
fn print_borrow_state(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let Some(borrow_tracker) = &this.machine.borrow_tracker else {
eprintln!("attempted to print borrow state, but no borrow state is being tracked");
return interp_ok(());
};
let method = borrow_tracker.borrow().borrow_tracker_method;
match method {
BorrowTrackerMethod::StackedBorrows => this.print_stacks(alloc_id),
BorrowTrackerMethod::TreeBorrows => this.print_tree(alloc_id, show_unnamed),
}
}
fn on_stack_pop(
&self,
frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>,
) -> InterpResult<'tcx> {
let this = self.eval_context_ref();
let borrow_tracker = this.machine.borrow_tracker.as_ref().unwrap();
for (alloc_id, tag) in &frame
.extra
.borrow_tracker
.as_ref()
.expect("we should have borrow tracking data")
.protected_tags
{
let kind = this.get_alloc_info(*alloc_id).kind;
if matches!(kind, AllocKind::LiveData) {
let alloc_extra = this.get_alloc_extra(*alloc_id)?; let alloc_borrow_tracker = &alloc_extra.borrow_tracker.as_ref().unwrap();
alloc_borrow_tracker.release_protector(
&this.machine,
borrow_tracker,
*tag,
*alloc_id,
)?;
}
}
borrow_tracker.borrow_mut().end_call(&frame.extra);
interp_ok(())
}
}
#[derive(Debug, Clone)]
pub enum AllocState {
StackedBorrows(Box<RefCell<stacked_borrows::AllocState>>),
TreeBorrows(Box<RefCell<tree_borrows::AllocState>>),
}
impl machine::AllocExtra<'_> {
#[track_caller]
pub fn borrow_tracker_sb(&self) -> &RefCell<stacked_borrows::AllocState> {
match self.borrow_tracker {
Some(AllocState::StackedBorrows(ref sb)) => sb,
_ => panic!("expected Stacked Borrows borrow tracking, got something else"),
}
}
#[track_caller]
pub fn borrow_tracker_sb_mut(&mut self) -> &mut RefCell<stacked_borrows::AllocState> {
match self.borrow_tracker {
Some(AllocState::StackedBorrows(ref mut sb)) => sb,
_ => panic!("expected Stacked Borrows borrow tracking, got something else"),
}
}
#[track_caller]
pub fn borrow_tracker_tb(&self) -> &RefCell<tree_borrows::AllocState> {
match self.borrow_tracker {
Some(AllocState::TreeBorrows(ref tb)) => tb,
_ => panic!("expected Tree Borrows borrow tracking, got something else"),
}
}
}
impl AllocState {
pub fn before_memory_read<'tcx>(
&self,
alloc_id: AllocId,
prov_extra: ProvenanceExtra,
range: AllocRange,
machine: &MiriMachine<'tcx>,
) -> InterpResult<'tcx> {
match self {
AllocState::StackedBorrows(sb) =>
sb.borrow_mut().before_memory_read(alloc_id, prov_extra, range, machine),
AllocState::TreeBorrows(tb) =>
tb.borrow_mut().before_memory_access(
AccessKind::Read,
alloc_id,
prov_extra,
range,
machine,
),
}
}
pub fn before_memory_write<'tcx>(
&mut self,
alloc_id: AllocId,
prov_extra: ProvenanceExtra,
range: AllocRange,
machine: &MiriMachine<'tcx>,
) -> InterpResult<'tcx> {
match self {
AllocState::StackedBorrows(sb) =>
sb.get_mut().before_memory_write(alloc_id, prov_extra, range, machine),
AllocState::TreeBorrows(tb) =>
tb.get_mut().before_memory_access(
AccessKind::Write,
alloc_id,
prov_extra,
range,
machine,
),
}
}
pub fn before_memory_deallocation<'tcx>(
&mut self,
alloc_id: AllocId,
prov_extra: ProvenanceExtra,
size: Size,
machine: &MiriMachine<'tcx>,
) -> InterpResult<'tcx> {
match self {
AllocState::StackedBorrows(sb) =>
sb.get_mut().before_memory_deallocation(alloc_id, prov_extra, size, machine),
AllocState::TreeBorrows(tb) =>
tb.get_mut().before_memory_deallocation(alloc_id, prov_extra, size, machine),
}
}
pub fn remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>) {
match self {
AllocState::StackedBorrows(sb) => sb.borrow_mut().remove_unreachable_tags(tags),
AllocState::TreeBorrows(tb) => tb.borrow_mut().remove_unreachable_tags(tags),
}
}
pub fn release_protector<'tcx>(
&self,
machine: &MiriMachine<'tcx>,
global: &GlobalState,
tag: BorTag,
alloc_id: AllocId, ) -> InterpResult<'tcx> {
match self {
AllocState::StackedBorrows(_sb) => interp_ok(()),
AllocState::TreeBorrows(tb) =>
tb.borrow_mut().release_protector(machine, global, tag, alloc_id),
}
}
}
impl VisitProvenance for AllocState {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
match self {
AllocState::StackedBorrows(sb) => sb.visit_provenance(visit),
AllocState::TreeBorrows(tb) => tb.visit_provenance(visit),
}
}
}