blob: b5bf16d3d3665de1f3040337d77564774ba4dd31 [file] [log] [blame]
use rustc_middle::{
mir::{Mutability, RetagKind},
ty::{
self,
layout::{HasParamEnv, HasTyCtxt},
Ty,
},
};
use rustc_span::def_id::DefId;
use rustc_target::abi::{Abi, Size};
use crate::*;
use crate::{
borrow_tracker::{GlobalState, GlobalStateInner, ProtectorKind},
concurrency::data_race::NaReadType,
};
pub mod diagnostics;
mod perms;
mod tree;
mod unimap;
#[cfg(test)]
mod exhaustive;
use perms::Permission;
pub use tree::Tree;
pub type AllocState = Tree;
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: &MiriMachine<'_, 'tcx>,
) -> Self {
let tag = state.root_ptr_tag(id, machine); // Fresh tag for the root
let span = machine.current_span();
Tree::new(tag, size, span)
}
/// 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();
let span = machine.current_span();
self.perform_access(
access_kind,
tag,
Some(range),
global,
alloc_id,
span,
diagnostics::AccessCause::Explicit(access_kind),
)
}
/// Check that this pointer has permission to deallocate this range.
pub fn before_memory_deallocation(
&mut self,
alloc_id: AllocId,
prov: ProvenanceExtra,
size: Size,
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();
let span = machine.current_span();
self.dealloc(tag, alloc_range(Size::ZERO, size), global, alloc_id, span)
}
pub fn expose_tag(&mut self, _tag: BorTag) {
// TODO
}
/// A tag just lost its protector.
///
/// This emits a special kind of access that is only applied
/// to initialized locations, as a protection against other
/// tags not having been made aware of the existence of this
/// protector.
pub fn release_protector(
&mut self,
machine: &MiriMachine<'_, 'tcx>,
global: &GlobalState,
tag: BorTag,
alloc_id: AllocId, // diagnostics
) -> InterpResult<'tcx> {
let span = machine.current_span();
self.perform_access(
AccessKind::Read,
tag,
None, // no specified range because it occurs on the entire allocation
global,
alloc_id,
span,
diagnostics::AccessCause::FnExit,
)
}
}
/// Policy for a new borrow.
#[derive(Debug, Clone, Copy)]
struct NewPermission {
/// Optionally ignore the actual size to do a zero-size reborrow.
/// If this is set then `dereferenceable` is not enforced.
zero_size: 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_reserved(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,
};
let protector = (kind == RetagKind::FnEntry).then_some(ProtectorKind::StrongProtector);
Some(Self { zero_size: false, initial_state, protector })
}
/// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
/// These pointers allow deallocation so need a different kind of protector not handled
/// by `from_ref_ty`.
fn from_unique_ty(
ty: Ty<'tcx>,
kind: RetagKind,
cx: &crate::MiriInterpCx<'_, 'tcx>,
zero_size: bool,
) -> Option<Self> {
let pointee = ty.builtin_deref(true).unwrap();
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 {
zero_size,
initial_state: Permission::new_reserved(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 provenance that should be used henceforth.
fn tb_reborrow(
&mut self,
place: &MPlaceTy<'tcx, Provenance>, // parent tag extracted from here
ptr_size: Size,
new_perm: NewPermission,
new_tag: BorTag,
) -> InterpResult<'tcx, Option<Provenance>> {
let this = self.eval_context_mut();
// Make sure the new permission makes sense as the initial permission of a fresh tag.
assert!(new_perm.initial_state.is_initial());
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
this.check_ptr_access(place.ptr(), ptr_size, CheckInAllocMsg::InboundsTest)?;
// 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!("initial state {} (pointee type {ty})", new_perm.initial_state);
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(())
};
trace!("Reborrow of size {:?}", ptr_size);
let (alloc_id, base_offset, parent_prov) = match this.ptr_try_get_alloc_id(place.ptr()) {
Ok(data) => {
// Unlike SB, we *do* a proper retag for size 0 if can identify the allocation.
// After all, the pointer may be lazily initialized outside this initial range.
data
}
Err(_) => {
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
// 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)?;
// Keep original provenance.
return Ok(place.ptr().provenance);
}
};
log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
let orig_tag = match parent_prov {
ProvenanceExtra::Wildcard => return Ok(place.ptr().provenance), // TODO: handle wildcard pointers
ProvenanceExtra::Concrete(tag) => tag,
};
trace!(
"reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
new_tag,
orig_tag,
place.layout.ty,
Pointer::new(alloc_id, base_offset),
ptr_size.bytes()
);
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((alloc_id, 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_kind = this.get_alloc_info(alloc_id).2;
if !matches!(alloc_kind, AllocKind::LiveData) {
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
// There's not actually any bytes here where accesses could even be tracked.
// Just produce the new provenance, nothing else to do.
return Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }));
}
let span = this.machine.current_span();
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();
// All reborrows incur a (possibly zero-sized) read access to the parent
tree_borrows.perform_access(
AccessKind::Read,
orig_tag,
Some(range),
this.machine.borrow_tracker.as_ref().unwrap(),
alloc_id,
this.machine.current_span(),
diagnostics::AccessCause::Reborrow,
)?;
// Record the parent-child pair in the tree.
tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?;
drop(tree_borrows);
// Also inform the data race model (but only if any bytes are actually affected).
if range.size.bytes() > 0 {
if let Some(data_race) = alloc_extra.data_race.as_ref() {
data_race.read(
alloc_id,
range,
NaReadType::Retag,
Some(place.layout.ty),
&this.machine,
)?;
}
}
Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
}
fn tb_retag_place(
&mut self,
place: &MPlaceTy<'tcx, Provenance>,
new_perm: NewPermission,
) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
let this = self.eval_context_mut();
// Determine the size of the reborrow.
// For most types this is the entire size of the place, however
// - when `extern type` is involved we use the size of the known prefix,
// - if the pointer is not reborrowed (raw pointer) or if `zero_size` is set
// then we override the size to do a zero-length reborrow.
let reborrow_size = match new_perm {
NewPermission { zero_size: false, .. } =>
this.size_and_align_of_mplace(place)?
.map(|(size, _)| size)
.unwrap_or(place.layout.size),
_ => Size::from_bytes(0),
};
trace!("Creating new permission: {:?} with size {:?}", new_perm, reborrow_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 new_prov = this.tb_reborrow(place, reborrow_size, new_perm, new_tag)?;
// Adjust place.
// (If the closure gets called, that means the old provenance was `Some`, and hence the new
// one must also be `Some`.)
Ok(place.clone().map_provenance(|_| new_prov.unwrap()))
}
/// Retags an individual pointer, returning the retagged version.
fn tb_retag_reference(
&mut self,
val: &ImmTy<'tcx, Provenance>,
new_perm: NewPermission,
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
let this = self.eval_context_mut();
let place = this.ref_to_mplace(val)?;
let new_place = this.tb_retag_place(&place, new_perm)?;
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 = match val.layout.ty.kind() {
&ty::Ref(_, pointee, mutability) =>
NewPermission::from_ref_ty(pointee, mutability, kind, this),
_ => None,
};
if let Some(new_perm) = new_perm {
this.tb_retag_reference(val, new_perm)
} else {
Ok(val.clone())
}
}
/// 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 options = this.machine.borrow_tracker.as_mut().unwrap().get_mut();
let retag_fields = options.retag_fields;
let unique_did =
options.unique_is_unique.then(|| this.tcx.lang_items().ptr_unique()).flatten();
let mut visitor = RetagVisitor { ecx: this, kind, retag_fields, unique_did };
return visitor.visit_value(place);
// The actual visitor.
struct RetagVisitor<'ecx, 'mir, 'tcx> {
ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
kind: RetagKind,
retag_fields: RetagFields,
unique_did: Option<DefId>,
}
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> {
if let Some(new_perm) = new_perm {
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> ValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>>
for RetagVisitor<'ecx, 'mir, 'tcx>
{
type V = PlaceTy<'tcx, Provenance>;
#[inline(always)]
fn ecx(&self) -> &MiriInterpCx<'mir, 'tcx> {
self.ecx
}
/// Regardless of how `Unique` is handled, Boxes are always reborrowed.
/// When `Unique` is also reborrowed, then it behaves exactly like `Box`
/// except for the fact that `Box` has a non-zero-sized reborrow.
fn visit_box(
&mut self,
box_ty: Ty<'tcx>,
place: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
// Only boxes for the global allocator get any special treatment.
if box_ty.is_box_global(*self.ecx.tcx) {
let new_perm = NewPermission::from_unique_ty(
place.layout.ty,
self.kind,
self.ecx,
/* zero_size */ false,
);
self.retag_ptr_inplace(place, new_perm)?;
}
Ok(())
}
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)?;
}
ty::Adt(adt, _) if self.unique_did == Some(adt.did()) => {
let place = inner_ptr_of_unique(self.ecx, place)?;
let new_perm = NewPermission::from_unique_ty(
place.layout.ty,
self.kind,
self.ecx,
/* zero_size */ true,
);
self.retag_ptr_inplace(&place, new_perm)?;
}
_ => {
// 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(())
}
}
}
/// Protect a place so that it cannot be used any more for the duration of the current function
/// call.
///
/// This is used to ensure soundness of in-place function argument/return passing.
fn tb_protect_place(
&mut self,
place: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
let this = self.eval_context_mut();
// Retag it. With protection! That is the entire point.
let new_perm = NewPermission {
initial_state: Permission::new_reserved(
place.layout.ty.is_freeze(this.tcx(), this.param_env()),
),
zero_size: false,
protector: Some(ProtectorKind::StrongProtector),
};
this.tb_retag_place(place, new_perm)
}
/// 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!("Tree 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)
}
}
/// Takes a place for a `Unique` and turns it into a place with the inner raw pointer.
/// I.e. input is what you get from the visitor upon encountering an `adt` that is `Unique`,
/// and output can be used by `retag_ptr_inplace`.
fn inner_ptr_of_unique<'tcx>(
ecx: &MiriInterpCx<'_, 'tcx>,
place: &PlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx, PlaceTy<'tcx, Provenance>> {
// Follows the same layout as `interpret/visitor.rs:walk_value` for `Box` in
// `rustc_const_eval`, just with one fewer layer.
// Here we have a `Unique(NonNull(*mut), PhantomData)`
assert_eq!(place.layout.fields.count(), 2, "Unique must have exactly 2 fields");
let (nonnull, phantom) = (ecx.project_field(place, 0)?, ecx.project_field(place, 1)?);
assert!(
phantom.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()),
"2nd field of `Unique` should be `PhantomData` but is `{:?}`",
phantom.layout.ty,
);
// Now down to `NonNull(*mut)`
assert_eq!(nonnull.layout.fields.count(), 1, "NonNull must have exactly 1 field");
let ptr = ecx.project_field(&nonnull, 0)?;
// Finally a plain `*mut`
Ok(ptr)
}