mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 14:55:26 +00:00
TB: Reborrow policy and connection to the main machine
This commit is contained in:
parent
7d4e8b9bc0
commit
0afab595b4
@ -11,6 +11,7 @@ use rustc_target::abi::Size;
|
|||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
pub mod stacked_borrows;
|
pub mod stacked_borrows;
|
||||||
|
pub mod tree_borrows;
|
||||||
|
|
||||||
pub type CallId = NonZeroU64;
|
pub type CallId = NonZeroU64;
|
||||||
|
|
||||||
@ -230,8 +231,10 @@ impl GlobalStateInner {
|
|||||||
/// Which borrow tracking method to use
|
/// Which borrow tracking method to use
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum BorrowTrackerMethod {
|
pub enum BorrowTrackerMethod {
|
||||||
/// Stacked Borrows, as implemented in borrow_tracker/stacked
|
/// Stacked Borrows, as implemented in borrow_tracker/stacked_borrows
|
||||||
StackedBorrows,
|
StackedBorrows,
|
||||||
|
/// Tree borrows, as implemented in borrow_tracker/tree_borrows
|
||||||
|
TreeBorrows,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BorrowTrackerMethod {
|
impl BorrowTrackerMethod {
|
||||||
@ -258,6 +261,10 @@ impl GlobalStateInner {
|
|||||||
AllocState::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
|
AllocState::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
|
||||||
id, alloc_size, self, kind, machine,
|
id, alloc_size, self, kind, machine,
|
||||||
)))),
|
)))),
|
||||||
|
BorrowTrackerMethod::TreeBorrows =>
|
||||||
|
AllocState::TreeBorrows(Box::new(RefCell::new(Tree::new_allocation(
|
||||||
|
id, alloc_size, self, kind, machine,
|
||||||
|
)))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,6 +280,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
|||||||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||||
match method {
|
match method {
|
||||||
BorrowTrackerMethod::StackedBorrows => this.sb_retag_ptr_value(kind, val),
|
BorrowTrackerMethod::StackedBorrows => this.sb_retag_ptr_value(kind, val),
|
||||||
|
BorrowTrackerMethod::TreeBorrows => this.tb_retag_ptr_value(kind, val),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +293,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
|||||||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||||
match method {
|
match method {
|
||||||
BorrowTrackerMethod::StackedBorrows => this.sb_retag_place_contents(kind, place),
|
BorrowTrackerMethod::StackedBorrows => this.sb_retag_place_contents(kind, place),
|
||||||
|
BorrowTrackerMethod::TreeBorrows => this.tb_retag_place_contents(kind, place),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,6 +302,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
|||||||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||||
match method {
|
match method {
|
||||||
BorrowTrackerMethod::StackedBorrows => this.sb_retag_return_place(),
|
BorrowTrackerMethod::StackedBorrows => this.sb_retag_return_place(),
|
||||||
|
BorrowTrackerMethod::TreeBorrows => this.tb_retag_return_place(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,6 +311,34 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
|||||||
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||||
match method {
|
match method {
|
||||||
BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag),
|
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<Option<Provenance>>,
|
||||||
|
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.sess.warn("Stacked Borrows does not support named pointers; `miri_pointer_name` is a no-op");
|
||||||
|
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 method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
|
||||||
|
match method {
|
||||||
|
BorrowTrackerMethod::StackedBorrows => this.print_stacks(alloc_id),
|
||||||
|
BorrowTrackerMethod::TreeBorrows => this.print_tree(alloc_id, show_unnamed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,6 +348,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
|||||||
pub enum AllocState {
|
pub enum AllocState {
|
||||||
/// Data corresponding to Stacked Borrows
|
/// Data corresponding to Stacked Borrows
|
||||||
StackedBorrows(Box<RefCell<stacked_borrows::AllocState>>),
|
StackedBorrows(Box<RefCell<stacked_borrows::AllocState>>),
|
||||||
|
/// Data corresponding to Tree Borrows
|
||||||
|
TreeBorrows(Box<RefCell<tree_borrows::AllocState>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl machine::AllocExtra {
|
impl machine::AllocExtra {
|
||||||
@ -328,6 +368,14 @@ impl machine::AllocExtra {
|
|||||||
_ => panic!("expected Stacked Borrows borrow tracking, got something else"),
|
_ => 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 {
|
impl AllocState {
|
||||||
@ -341,6 +389,14 @@ impl AllocState {
|
|||||||
match self {
|
match self {
|
||||||
AllocState::StackedBorrows(sb) =>
|
AllocState::StackedBorrows(sb) =>
|
||||||
sb.borrow_mut().before_memory_read(alloc_id, prov_extra, range, machine),
|
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,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,6 +410,14 @@ impl AllocState {
|
|||||||
match self {
|
match self {
|
||||||
AllocState::StackedBorrows(sb) =>
|
AllocState::StackedBorrows(sb) =>
|
||||||
sb.get_mut().before_memory_write(alloc_id, prov_extra, range, machine),
|
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,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,12 +431,15 @@ impl AllocState {
|
|||||||
match self {
|
match self {
|
||||||
AllocState::StackedBorrows(sb) =>
|
AllocState::StackedBorrows(sb) =>
|
||||||
sb.get_mut().before_memory_deallocation(alloc_id, prov_extra, range, machine),
|
sb.get_mut().before_memory_deallocation(alloc_id, prov_extra, range, machine),
|
||||||
|
AllocState::TreeBorrows(tb) =>
|
||||||
|
tb.get_mut().before_memory_deallocation(alloc_id, prov_extra, range, machine),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>) {
|
pub fn remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>) {
|
||||||
match self {
|
match self {
|
||||||
AllocState::StackedBorrows(sb) => sb.borrow_mut().remove_unreachable_tags(tags),
|
AllocState::StackedBorrows(sb) => sb.borrow_mut().remove_unreachable_tags(tags),
|
||||||
|
AllocState::TreeBorrows(tb) => tb.borrow_mut().remove_unreachable_tags(tags),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -381,6 +448,7 @@ impl VisitTags for AllocState {
|
|||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||||
match self {
|
match self {
|
||||||
AllocState::StackedBorrows(sb) => sb.visit_tags(visit),
|
AllocState::StackedBorrows(sb) => sb.visit_tags(visit),
|
||||||
|
AllocState::TreeBorrows(tb) => tb.visit_tags(visit),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
539
src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
Normal file
539
src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
use log::trace;
|
||||||
|
|
||||||
|
use rustc_target::abi::{Abi, Size};
|
||||||
|
|
||||||
|
use crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind, RetagFields};
|
||||||
|
use rustc_middle::{
|
||||||
|
mir::{Mutability, RetagKind},
|
||||||
|
ty::{
|
||||||
|
self,
|
||||||
|
layout::{HasParamEnv, LayoutOf},
|
||||||
|
Ty,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
mod diagnostics;
|
||||||
|
mod perms;
|
||||||
|
mod tree;
|
||||||
|
mod unimap;
|
||||||
|
use perms::Permission;
|
||||||
|
pub use tree::Tree;
|
||||||
|
|
||||||
|
pub type AllocState = Tree;
|
||||||
|
|
||||||
|
pub fn err_tb_ub<'tcx>(msg: String) -> InterpError<'tcx> {
|
||||||
|
err_machine_stop!(TerminationInfo::TreeBorrowsUb { msg })
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tcx> Tree {
|
||||||
|
/// Create a new allocation, i.e. a new tree
|
||||||
|
pub fn new_allocation(
|
||||||
|
id: AllocId,
|
||||||
|
size: Size,
|
||||||
|
state: &mut GlobalStateInner,
|
||||||
|
_kind: MemoryKind<machine::MiriMemoryKind>,
|
||||||
|
machine: &MiriMachine<'_, 'tcx>,
|
||||||
|
) -> Self {
|
||||||
|
let tag = state.base_ptr_tag(id, machine); // Fresh tag for the root
|
||||||
|
Tree::new(tag, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that an access on the entire range is permitted, and update
|
||||||
|
/// the tree.
|
||||||
|
pub fn before_memory_access(
|
||||||
|
&mut self,
|
||||||
|
access_kind: AccessKind,
|
||||||
|
alloc_id: AllocId,
|
||||||
|
prov: ProvenanceExtra,
|
||||||
|
range: AllocRange,
|
||||||
|
machine: &MiriMachine<'_, 'tcx>,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
|
trace!(
|
||||||
|
"{} with tag {:?}: {:?}, size {}",
|
||||||
|
access_kind,
|
||||||
|
prov,
|
||||||
|
Pointer::new(alloc_id, range.start),
|
||||||
|
range.size.bytes(),
|
||||||
|
);
|
||||||
|
// TODO: for now we bail out on wildcard pointers. Eventually we should
|
||||||
|
// handle them as much as we can.
|
||||||
|
let tag = match prov {
|
||||||
|
ProvenanceExtra::Concrete(tag) => tag,
|
||||||
|
ProvenanceExtra::Wildcard => return Ok(()),
|
||||||
|
};
|
||||||
|
let global = machine.borrow_tracker.as_ref().unwrap();
|
||||||
|
self.perform_access(access_kind, tag, range, global)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that this pointer has permission to deallocate this range.
|
||||||
|
pub fn before_memory_deallocation(
|
||||||
|
&mut self,
|
||||||
|
_alloc_id: AllocId,
|
||||||
|
prov: ProvenanceExtra,
|
||||||
|
range: AllocRange,
|
||||||
|
machine: &MiriMachine<'_, 'tcx>,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
|
// TODO: for now we bail out on wildcard pointers. Eventually we should
|
||||||
|
// handle them as much as we can.
|
||||||
|
let tag = match prov {
|
||||||
|
ProvenanceExtra::Concrete(tag) => tag,
|
||||||
|
ProvenanceExtra::Wildcard => return Ok(()),
|
||||||
|
};
|
||||||
|
let global = machine.borrow_tracker.as_ref().unwrap();
|
||||||
|
self.dealloc(tag, range, global)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expose_tag(&mut self, _tag: BorTag) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Policy for a new borrow.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct NewPermission {
|
||||||
|
/// Whether this borrow requires a read access on its parent.
|
||||||
|
/// `perform_read_access` is `true` for all pointers marked `dereferenceable`.
|
||||||
|
perform_read_access: bool,
|
||||||
|
/// Which permission should the pointer start with.
|
||||||
|
initial_state: Permission,
|
||||||
|
/// Whether this pointer is part of the arguments of a function call.
|
||||||
|
/// `protector` is `Some(_)` for all pointers marked `noalias`.
|
||||||
|
protector: Option<ProtectorKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tcx> NewPermission {
|
||||||
|
/// Determine NewPermission of the reference from the type of the pointee.
|
||||||
|
fn from_ref_ty(
|
||||||
|
pointee: Ty<'tcx>,
|
||||||
|
mutability: Mutability,
|
||||||
|
kind: RetagKind,
|
||||||
|
cx: &crate::MiriInterpCx<'_, 'tcx>,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.param_env());
|
||||||
|
let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.param_env());
|
||||||
|
let initial_state = match mutability {
|
||||||
|
Mutability::Mut if ty_is_unpin => Permission::new_unique_2phase(ty_is_freeze),
|
||||||
|
Mutability::Not if ty_is_freeze => Permission::new_frozen(),
|
||||||
|
// Raw pointers never enter this function so they are not handled.
|
||||||
|
// However raw pointers are not the only pointers that take the parent
|
||||||
|
// tag, this also happens for `!Unpin` `&mut`s and interior mutable
|
||||||
|
// `&`s, which are excluded above.
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
// This field happens to be redundant since right now we always do a read,
|
||||||
|
// but it could be useful in the future.
|
||||||
|
let perform_read_access = true;
|
||||||
|
|
||||||
|
let protector = (kind == RetagKind::FnEntry).then_some(ProtectorKind::StrongProtector);
|
||||||
|
Some(Self { perform_read_access, initial_state, protector })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boxes are not handled by `from_ref_ty`, they need special behavior
|
||||||
|
// implemented here.
|
||||||
|
fn from_box_ty(
|
||||||
|
ty: Ty<'tcx>,
|
||||||
|
kind: RetagKind,
|
||||||
|
cx: &crate::MiriInterpCx<'_, 'tcx>,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let pointee = ty.builtin_deref(true).unwrap().ty;
|
||||||
|
pointee.is_unpin(*cx.tcx, cx.param_env()).then_some(()).map(|()| {
|
||||||
|
// Regular `Unpin` box, give it `noalias` but only a weak protector
|
||||||
|
// because it is valid to deallocate it within the function.
|
||||||
|
let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env());
|
||||||
|
Self {
|
||||||
|
perform_read_access: true,
|
||||||
|
initial_state: Permission::new_unique_2phase(ty_is_freeze),
|
||||||
|
protector: (kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retagging/reborrowing.
|
||||||
|
/// Policy on which permission to grant to each pointer should be left to
|
||||||
|
/// the implementation of NewPermission.
|
||||||
|
impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx>
|
||||||
|
for crate::MiriInterpCx<'mir, 'tcx>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
|
/// Returns the `AllocId` the reborrow was done in, if there is some actual
|
||||||
|
/// memory associated with this pointer. Returns `None` if there is no actual
|
||||||
|
/// memory allocated. Also checks that the reborrow of size `ptr_size` is
|
||||||
|
/// within bounds of the allocation.
|
||||||
|
///
|
||||||
|
/// Also returns the tag that the pointer should get, which is essentially
|
||||||
|
/// `if new_perm.is_some() { new_tag } else { parent_tag }` along with
|
||||||
|
/// some logging (always) and fake reads (if `new_perm` is
|
||||||
|
/// `Some(NewPermission { perform_read_access: true }`).
|
||||||
|
fn tb_reborrow(
|
||||||
|
&mut self,
|
||||||
|
place: &MPlaceTy<'tcx, Provenance>, // parent tag extracted from here
|
||||||
|
ptr_size: Size,
|
||||||
|
new_perm: Option<NewPermission>,
|
||||||
|
new_tag: BorTag,
|
||||||
|
) -> InterpResult<'tcx, Option<(AllocId, BorTag)>> {
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
|
||||||
|
// It is crucial that this gets called on all code paths, to ensure we track tag creation.
|
||||||
|
let log_creation = |this: &MiriInterpCx<'mir, 'tcx>,
|
||||||
|
loc: Option<(AllocId, Size, ProvenanceExtra)>| // alloc_id, base_offset, orig_tag
|
||||||
|
-> InterpResult<'tcx> {
|
||||||
|
let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
|
||||||
|
let ty = place.layout.ty;
|
||||||
|
if global.tracked_pointer_tags.contains(&new_tag) {
|
||||||
|
let kind_str = format!("{new_perm:?} (pointee type {ty})");
|
||||||
|
this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
|
||||||
|
new_tag.inner(),
|
||||||
|
Some(kind_str),
|
||||||
|
loc.map(|(alloc_id, base_offset, orig_tag)| (alloc_id, alloc_range(base_offset, ptr_size), orig_tag)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
drop(global); // don't hold that reference any longer than we have to
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let (alloc_id, base_offset, parent_prov) = if ptr_size > Size::ZERO {
|
||||||
|
this.ptr_get_alloc_id(place.ptr)?
|
||||||
|
} else {
|
||||||
|
match this.ptr_try_get_alloc_id(place.ptr) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(_) => {
|
||||||
|
// This pointer doesn't come with an AllocId, so there's no
|
||||||
|
// memory to do retagging in.
|
||||||
|
trace!(
|
||||||
|
"reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
|
||||||
|
new_tag,
|
||||||
|
place.ptr,
|
||||||
|
place.layout.ty,
|
||||||
|
);
|
||||||
|
log_creation(this, None)?;
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let orig_tag = match parent_prov {
|
||||||
|
ProvenanceExtra::Wildcard => return Ok(None), // TODO: handle wildcard pointers
|
||||||
|
ProvenanceExtra::Concrete(tag) => tag,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Protection against trying to get a reference to a vtable:
|
||||||
|
// vtables do not have an alloc_extra so the call to
|
||||||
|
// `get_alloc_extra` that follows fails.
|
||||||
|
let (alloc_size, _align, alloc_kind) = this.get_alloc_info(alloc_id);
|
||||||
|
if ptr_size == Size::ZERO && !matches!(alloc_kind, AllocKind::LiveData) {
|
||||||
|
return Ok(Some((alloc_id, orig_tag)));
|
||||||
|
}
|
||||||
|
|
||||||
|
log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
|
||||||
|
|
||||||
|
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
|
||||||
|
if base_offset + ptr_size > alloc_size {
|
||||||
|
throw_ub!(PointerOutOfBounds {
|
||||||
|
alloc_id,
|
||||||
|
alloc_size,
|
||||||
|
ptr_offset: this.target_usize_to_isize(base_offset.bytes()),
|
||||||
|
ptr_size,
|
||||||
|
msg: CheckInAllocMsg::InboundsTest
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
|
||||||
|
new_tag,
|
||||||
|
orig_tag,
|
||||||
|
place.layout.ty,
|
||||||
|
Pointer::new(alloc_id, base_offset),
|
||||||
|
ptr_size.bytes()
|
||||||
|
);
|
||||||
|
|
||||||
|
let Some(new_perm) = new_perm else { return Ok(Some((alloc_id, orig_tag))); };
|
||||||
|
|
||||||
|
if let Some(protect) = new_perm.protector {
|
||||||
|
// We register the protection in two different places.
|
||||||
|
// This makes creating a protector slower, but checking whether a tag
|
||||||
|
// is protected faster.
|
||||||
|
this.frame_mut().extra.borrow_tracker.as_mut().unwrap().protected_tags.push(new_tag);
|
||||||
|
this.machine
|
||||||
|
.borrow_tracker
|
||||||
|
.as_mut()
|
||||||
|
.expect("We should have borrow tracking data")
|
||||||
|
.get_mut()
|
||||||
|
.protected_tags
|
||||||
|
.insert(new_tag, protect);
|
||||||
|
}
|
||||||
|
|
||||||
|
let alloc_extra = this.get_alloc_extra(alloc_id)?;
|
||||||
|
let range = alloc_range(base_offset, ptr_size);
|
||||||
|
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
|
||||||
|
|
||||||
|
if new_perm.perform_read_access {
|
||||||
|
// Count this reborrow as a read access
|
||||||
|
let global = &this.machine.borrow_tracker.as_ref().unwrap();
|
||||||
|
tree_borrows.perform_access(AccessKind::Read, orig_tag, range, global)?;
|
||||||
|
if let Some(data_race) = alloc_extra.data_race.as_ref() {
|
||||||
|
data_race.read(alloc_id, range, &this.machine)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the parent-child pair in the tree.
|
||||||
|
tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range)?;
|
||||||
|
Ok(Some((alloc_id, new_tag)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retags an indidual pointer, returning the retagged version.
|
||||||
|
fn tb_retag_reference(
|
||||||
|
&mut self,
|
||||||
|
val: &ImmTy<'tcx, Provenance>,
|
||||||
|
new_perm: Option<NewPermission>,
|
||||||
|
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
// We want a place for where the ptr *points to*, so we get one.
|
||||||
|
let place = this.ref_to_mplace(val)?;
|
||||||
|
|
||||||
|
// Get a lower bound of the size of this place.
|
||||||
|
// (When `extern type` are involved, use the size of the known prefix.)
|
||||||
|
let size = this
|
||||||
|
.size_and_align_of_mplace(&place)?
|
||||||
|
.map(|(size, _)| size)
|
||||||
|
.unwrap_or(place.layout.size);
|
||||||
|
|
||||||
|
// This new tag is not guaranteed to actually be used.
|
||||||
|
//
|
||||||
|
// If you run out of tags, consider the following optimization: adjust `tb_reborrow`
|
||||||
|
// so that rather than taking as input a fresh tag and deciding whether it uses this
|
||||||
|
// one or the parent it instead just returns whether a new tag should be created.
|
||||||
|
// This will avoid creating tags than end up never being used.
|
||||||
|
let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
|
||||||
|
|
||||||
|
// Compute the actual reborrow.
|
||||||
|
let reborrowed = this.tb_reborrow(&place, size, new_perm, new_tag)?;
|
||||||
|
|
||||||
|
// Adjust pointer.
|
||||||
|
let new_place = place.map_provenance(|p| {
|
||||||
|
p.map(|prov| {
|
||||||
|
match reborrowed {
|
||||||
|
Some((alloc_id, actual_tag)) => {
|
||||||
|
// If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one.
|
||||||
|
// Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation.
|
||||||
|
Provenance::Concrete { alloc_id, tag: actual_tag }
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Looks like this has to stay a wildcard pointer.
|
||||||
|
assert!(matches!(prov, Provenance::Wildcard));
|
||||||
|
Provenance::Wildcard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return new pointer.
|
||||||
|
Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
|
||||||
|
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
|
/// Retag a pointer. References are passed to `from_ref_ty` and
|
||||||
|
/// raw pointers are never reborrowed.
|
||||||
|
fn tb_retag_ptr_value(
|
||||||
|
&mut self,
|
||||||
|
kind: RetagKind,
|
||||||
|
val: &ImmTy<'tcx, Provenance>,
|
||||||
|
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
let new_perm = if let &ty::Ref(_, pointee, mutability) = val.layout.ty.kind() {
|
||||||
|
NewPermission::from_ref_ty(pointee, mutability, kind, this)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
this.tb_retag_reference(val, new_perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retag all pointers that are stored in this place.
|
||||||
|
fn tb_retag_place_contents(
|
||||||
|
&mut self,
|
||||||
|
kind: RetagKind,
|
||||||
|
place: &PlaceTy<'tcx, Provenance>,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields;
|
||||||
|
let mut visitor = RetagVisitor { ecx: this, kind, retag_fields };
|
||||||
|
return visitor.visit_value(place);
|
||||||
|
|
||||||
|
// The actual visitor.
|
||||||
|
struct RetagVisitor<'ecx, 'mir, 'tcx> {
|
||||||
|
ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
|
||||||
|
kind: RetagKind,
|
||||||
|
retag_fields: RetagFields,
|
||||||
|
}
|
||||||
|
impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
|
||||||
|
#[inline(always)] // yes this helps in our benchmarks
|
||||||
|
fn retag_ptr_inplace(
|
||||||
|
&mut self,
|
||||||
|
place: &PlaceTy<'tcx, Provenance>,
|
||||||
|
new_perm: Option<NewPermission>,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
|
let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
|
||||||
|
let val = self.ecx.tb_retag_reference(&val, new_perm)?;
|
||||||
|
self.ecx.write_immediate(*val, place)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'ecx, 'mir, 'tcx> MutValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>>
|
||||||
|
for RetagVisitor<'ecx, 'mir, 'tcx>
|
||||||
|
{
|
||||||
|
type V = PlaceTy<'tcx, Provenance>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn ecx(&mut self) -> &mut MiriInterpCx<'mir, 'tcx> {
|
||||||
|
self.ecx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
|
||||||
|
let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx);
|
||||||
|
self.retag_ptr_inplace(place, new_perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
|
||||||
|
// If this place is smaller than a pointer, we know that it can't contain any
|
||||||
|
// pointers we need to retag, so we can stop recursion early.
|
||||||
|
// This optimization is crucial for ZSTs, because they can contain way more fields
|
||||||
|
// than we can ever visit.
|
||||||
|
if place.layout.is_sized() && place.layout.size < self.ecx.pointer_size() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the type of this value to see what to do with it (retag, or recurse).
|
||||||
|
match place.layout.ty.kind() {
|
||||||
|
&ty::Ref(_, pointee, mutability) => {
|
||||||
|
let new_perm =
|
||||||
|
NewPermission::from_ref_ty(pointee, mutability, self.kind, self.ecx);
|
||||||
|
self.retag_ptr_inplace(place, new_perm)?;
|
||||||
|
}
|
||||||
|
ty::RawPtr(_) => {
|
||||||
|
// We definitely do *not* want to recurse into raw pointers -- wide raw
|
||||||
|
// pointers have fields, and for dyn Trait pointees those can have reference
|
||||||
|
// type!
|
||||||
|
// We also do not want to reborrow them.
|
||||||
|
}
|
||||||
|
ty::Adt(adt, _) if adt.is_box() => {
|
||||||
|
// Recurse for boxes, they require some tricky handling and will end up in `visit_box` above.
|
||||||
|
// (Yes this means we technically also recursively retag the allocator itself
|
||||||
|
// even if field retagging is not enabled. *shrug*)
|
||||||
|
self.walk_value(place)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Not a reference/pointer/box. Only recurse if configured appropriately.
|
||||||
|
let recurse = match self.retag_fields {
|
||||||
|
RetagFields::No => false,
|
||||||
|
RetagFields::Yes => true,
|
||||||
|
RetagFields::OnlyScalar => {
|
||||||
|
// Matching `ArgAbi::new` at the time of writing, only fields of
|
||||||
|
// `Scalar` and `ScalarPair` ABI are considered.
|
||||||
|
matches!(place.layout.abi, Abi::Scalar(..) | Abi::ScalarPair(..))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if recurse {
|
||||||
|
self.walk_value(place)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// After a stack frame got pushed, retag the return place so that we are sure
|
||||||
|
/// it does not alias with anything.
|
||||||
|
///
|
||||||
|
/// This is a HACK because there is nothing in MIR that would make the retag
|
||||||
|
/// explicit. Also see <https://github.com/rust-lang/rust/issues/71117>.
|
||||||
|
fn tb_retag_return_place(&mut self) -> InterpResult<'tcx> {
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
//this.debug_hint_location();
|
||||||
|
let return_place = &this.frame().return_place;
|
||||||
|
if return_place.layout.is_zst() {
|
||||||
|
// There may not be any memory here, nothing to do.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// We need this to be in-memory to use tagged pointers.
|
||||||
|
let return_place = this.force_allocation(&return_place.clone())?;
|
||||||
|
|
||||||
|
// We have to turn the place into a pointer to use the existing code.
|
||||||
|
// (The pointer type does not matter, so we use a raw pointer.)
|
||||||
|
let ptr_layout = this.layout_of(this.tcx.mk_mut_ptr(return_place.layout.ty))?;
|
||||||
|
let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout);
|
||||||
|
// Reborrow it. With protection! That is part of the point.
|
||||||
|
// FIXME: do we truly want a 2phase borrow here?
|
||||||
|
let new_perm = Some(NewPermission {
|
||||||
|
initial_state: Permission::new_unique_2phase(/*freeze*/ false),
|
||||||
|
perform_read_access: true,
|
||||||
|
protector: Some(ProtectorKind::StrongProtector),
|
||||||
|
});
|
||||||
|
let val = this.tb_retag_reference(&val, new_perm)?;
|
||||||
|
// And use reborrowed pointer for return place.
|
||||||
|
let return_place = this.ref_to_mplace(&val)?;
|
||||||
|
this.frame_mut().return_place = return_place.into();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark the given tag as exposed. It was found on a pointer with the given AllocId.
|
||||||
|
fn tb_expose_tag(&mut self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
|
||||||
|
// Function pointers and dead objects don't have an alloc_extra so we ignore them.
|
||||||
|
// This is okay because accessing them is UB anyway, no need for any Tree Borrows checks.
|
||||||
|
// NOT using `get_alloc_extra_mut` since this might be a read-only allocation!
|
||||||
|
let (_size, _align, kind) = this.get_alloc_info(alloc_id);
|
||||||
|
match kind {
|
||||||
|
AllocKind::LiveData => {
|
||||||
|
// This should have alloc_extra data, but `get_alloc_extra` can still fail
|
||||||
|
// if converting this alloc_id from a global to a local one
|
||||||
|
// uncovers a non-supported `extern static`.
|
||||||
|
let alloc_extra = this.get_alloc_extra(alloc_id)?;
|
||||||
|
trace!("Stacked Borrows tag {tag:?} exposed in {alloc_id:?}");
|
||||||
|
alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag);
|
||||||
|
}
|
||||||
|
AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
|
||||||
|
// No tree borrows on these allocations.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display the tree.
|
||||||
|
fn print_tree(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
let alloc_extra = this.get_alloc_extra(alloc_id)?;
|
||||||
|
let tree_borrows = alloc_extra.borrow_tracker_tb().borrow();
|
||||||
|
let borrow_tracker = &this.machine.borrow_tracker.as_ref().unwrap().borrow();
|
||||||
|
tree_borrows.print_tree(&borrow_tracker.protected_tags, show_unnamed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give a name to the pointer, usually the name it has in the source code (for debugging).
|
||||||
|
/// The name given is `name` and the pointer that receives it is the `nth_parent`
|
||||||
|
/// of `ptr` (with 0 representing `ptr` itself)
|
||||||
|
fn tb_give_pointer_debug_name(
|
||||||
|
&mut self,
|
||||||
|
ptr: Pointer<Option<Provenance>>,
|
||||||
|
nth_parent: u8,
|
||||||
|
name: &str,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
let (tag, alloc_id) = match ptr.provenance {
|
||||||
|
Some(Provenance::Concrete { tag, alloc_id }) => (tag, alloc_id),
|
||||||
|
_ => {
|
||||||
|
eprintln!("Can't give the name {name} to Wildcard pointer");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let alloc_extra = this.get_alloc_extra(alloc_id)?;
|
||||||
|
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
|
||||||
|
tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user