mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 06:44:35 +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::*;
|
||||
pub mod stacked_borrows;
|
||||
pub mod tree_borrows;
|
||||
|
||||
pub type CallId = NonZeroU64;
|
||||
|
||||
@ -230,8 +231,10 @@ impl GlobalStateInner {
|
||||
/// Which borrow tracking method to use
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum BorrowTrackerMethod {
|
||||
/// Stacked Borrows, as implemented in borrow_tracker/stacked
|
||||
/// Stacked Borrows, as implemented in borrow_tracker/stacked_borrows
|
||||
StackedBorrows,
|
||||
/// Tree borrows, as implemented in borrow_tracker/tree_borrows
|
||||
TreeBorrows,
|
||||
}
|
||||
|
||||
impl BorrowTrackerMethod {
|
||||
@ -258,6 +261,10 @@ impl GlobalStateInner {
|
||||
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,
|
||||
)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
match method {
|
||||
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;
|
||||
match method {
|
||||
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;
|
||||
match method {
|
||||
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;
|
||||
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<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 {
|
||||
/// Data corresponding to Stacked Borrows
|
||||
StackedBorrows(Box<RefCell<stacked_borrows::AllocState>>),
|
||||
/// Data corresponding to Tree Borrows
|
||||
TreeBorrows(Box<RefCell<tree_borrows::AllocState>>),
|
||||
}
|
||||
|
||||
impl machine::AllocExtra {
|
||||
@ -328,6 +368,14 @@ impl machine::AllocExtra {
|
||||
_ => 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 {
|
||||
@ -341,6 +389,14 @@ impl AllocState {
|
||||
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,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,6 +410,14 @@ impl AllocState {
|
||||
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,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -367,12 +431,15 @@ impl AllocState {
|
||||
match self {
|
||||
AllocState::StackedBorrows(sb) =>
|
||||
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>) {
|
||||
match self {
|
||||
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)) {
|
||||
match self {
|
||||
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