implement 'delimited' expose tracking so we still detect some UB
This commit is contained in:
parent
8d6fdaa024
commit
4fbb284a99
@ -8,7 +8,7 @@ use rustc_middle::ty;
|
||||
use rustc_span::{source_map::DUMMY_SP, Span, SpanData, Symbol};
|
||||
|
||||
use crate::helpers::HexRange;
|
||||
use crate::stacked_borrows::{diagnostics::TagHistory, AccessKind, SbTag};
|
||||
use crate::stacked_borrows::{diagnostics::TagHistory, AccessKind};
|
||||
use crate::*;
|
||||
|
||||
/// Details of premature program termination.
|
||||
@ -61,9 +61,9 @@ impl MachineStopType for TerminationInfo {}
|
||||
/// Miri specific diagnostics
|
||||
pub enum NonHaltingDiagnostic {
|
||||
CreatedPointerTag(NonZeroU64),
|
||||
/// This `Item` was popped from the borrow stack, either due to a grant of
|
||||
/// `AccessKind` to `SbTag` or a deallocation when the second argument is `None`.
|
||||
PoppedPointerTag(Item, Option<(SbTag, AccessKind)>),
|
||||
/// This `Item` was popped from the borrow stack, either due to an access with the given tag or
|
||||
/// a deallocation when the second argument is `None`.
|
||||
PoppedPointerTag(Item, Option<(SbTagExtra, AccessKind)>),
|
||||
CreatedCallId(CallId),
|
||||
CreatedAlloc(AllocId),
|
||||
FreedAlloc(AllocId),
|
||||
|
@ -142,9 +142,7 @@ impl<'mir, 'tcx> GlobalStateInner {
|
||||
// Determine the allocation this points to at cast time.
|
||||
let alloc_id = Self::alloc_id_from_addr(ecx, addr);
|
||||
Pointer::new(
|
||||
alloc_id.map(|alloc_id| {
|
||||
Tag::Concrete(ConcreteTag { alloc_id, sb: SbTag::Untagged })
|
||||
}),
|
||||
alloc_id.map(|alloc_id| Tag::Concrete { alloc_id, sb: SbTag::Untagged }),
|
||||
Size::from_bytes(addr),
|
||||
)
|
||||
}
|
||||
@ -222,8 +220,8 @@ impl<'mir, 'tcx> GlobalStateInner {
|
||||
) -> Option<(AllocId, Size)> {
|
||||
let (tag, addr) = ptr.into_parts(); // addr is absolute (Tag provenance)
|
||||
|
||||
let alloc_id = if let Tag::Concrete(concrete) = tag {
|
||||
concrete.alloc_id
|
||||
let alloc_id = if let Tag::Concrete { alloc_id, .. } = tag {
|
||||
alloc_id
|
||||
} else {
|
||||
// A wildcard pointer.
|
||||
assert_eq!(ecx.machine.intptrcast.borrow().provenance_mode, ProvenanceMode::Permissive);
|
||||
|
@ -7,6 +7,7 @@
|
||||
#![feature(io_error_more)]
|
||||
#![feature(yeet_expr)]
|
||||
#![feature(is_some_with)]
|
||||
#![feature(nonzero_ops)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
@ -81,15 +82,15 @@ pub use crate::eval::{
|
||||
pub use crate::helpers::{CurrentSpan, EvalContextExt as HelpersEvalContextExt};
|
||||
pub use crate::intptrcast::ProvenanceMode;
|
||||
pub use crate::machine::{
|
||||
AllocExtra, ConcreteTag, Evaluator, FrameData, MiriEvalContext, MiriEvalContextExt,
|
||||
MiriMemoryKind, Tag, NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
|
||||
AllocExtra, Evaluator, FrameData, MiriEvalContext, MiriEvalContextExt, MiriMemoryKind, Tag,
|
||||
NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
|
||||
};
|
||||
pub use crate::mono_hash_map::MonoHashMap;
|
||||
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
|
||||
pub use crate::range_map::RangeMap;
|
||||
pub use crate::stacked_borrows::{
|
||||
CallId, EvalContextExt as StackedBorEvalContextExt, Item, Permission, PtrId, SbTag, Stack,
|
||||
Stacks,
|
||||
CallId, EvalContextExt as StackedBorEvalContextExt, Item, Permission, PtrId, SbTag, SbTagExtra,
|
||||
Stack, Stacks,
|
||||
};
|
||||
pub use crate::sync::{CondvarId, EvalContextExt as SyncEvalContextExt, MutexId, RwLockId};
|
||||
pub use crate::thread::{
|
||||
|
@ -130,17 +130,14 @@ impl fmt::Display for MiriMemoryKind {
|
||||
/// Pointer provenance (tag).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Tag {
|
||||
Concrete(ConcreteTag),
|
||||
Concrete {
|
||||
alloc_id: AllocId,
|
||||
/// Stacked Borrows tag.
|
||||
sb: SbTag,
|
||||
},
|
||||
Wildcard,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ConcreteTag {
|
||||
pub alloc_id: AllocId,
|
||||
/// Stacked Borrows tag.
|
||||
pub sb: SbTag,
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
|
||||
static_assert_size!(Pointer<Tag>, 24);
|
||||
// #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
|
||||
@ -160,15 +157,15 @@ impl Provenance for Tag {
|
||||
write!(f, "0x{:x}", addr.bytes())?;
|
||||
|
||||
match tag {
|
||||
Tag::Concrete(tag) => {
|
||||
Tag::Concrete { alloc_id, sb } => {
|
||||
// Forward `alternate` flag to `alloc_id` printing.
|
||||
if f.alternate() {
|
||||
write!(f, "[{:#?}]", tag.alloc_id)?;
|
||||
write!(f, "[{:#?}]", alloc_id)?;
|
||||
} else {
|
||||
write!(f, "[{:?}]", tag.alloc_id)?;
|
||||
write!(f, "[{:?}]", alloc_id)?;
|
||||
}
|
||||
// Print Stacked Borrows tag.
|
||||
write!(f, "{:?}", tag.sb)?;
|
||||
write!(f, "{:?}", sb)?;
|
||||
}
|
||||
Tag::Wildcard => {
|
||||
write!(f, "[Wildcard]")?;
|
||||
@ -180,7 +177,7 @@ impl Provenance for Tag {
|
||||
|
||||
fn get_alloc_id(self) -> Option<AllocId> {
|
||||
match self {
|
||||
Tag::Concrete(concrete) => Some(concrete.alloc_id),
|
||||
Tag::Concrete { alloc_id, .. } => Some(alloc_id),
|
||||
Tag::Wildcard => None,
|
||||
}
|
||||
}
|
||||
@ -489,8 +486,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
|
||||
type AllocExtra = AllocExtra;
|
||||
|
||||
type PointerTag = Tag;
|
||||
// `None` represents a wildcard pointer.
|
||||
type TagExtra = Option<SbTag>;
|
||||
type TagExtra = SbTagExtra;
|
||||
|
||||
type MemoryMap =
|
||||
MonoHashMap<AllocId, (MemoryKind<MiriMemoryKind>, Allocation<Tag, Self::AllocExtra>)>;
|
||||
@ -683,7 +679,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
|
||||
SbTag::Untagged
|
||||
};
|
||||
Pointer::new(
|
||||
Tag::Concrete(ConcreteTag { alloc_id: ptr.provenance, sb: sb_tag }),
|
||||
Tag::Concrete { alloc_id: ptr.provenance, sb: sb_tag },
|
||||
Size::from_bytes(absolute_addr),
|
||||
)
|
||||
}
|
||||
@ -709,7 +705,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
|
||||
ptr: Pointer<Self::PointerTag>,
|
||||
) -> InterpResult<'tcx> {
|
||||
match ptr.provenance {
|
||||
Tag::Concrete(ConcreteTag { alloc_id, sb }) => {
|
||||
Tag::Concrete { alloc_id, sb } => {
|
||||
intptrcast::GlobalStateInner::expose_ptr(ecx, alloc_id, sb);
|
||||
}
|
||||
Tag::Wildcard => {
|
||||
@ -730,8 +726,8 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
|
||||
|
||||
rel.map(|(alloc_id, size)| {
|
||||
let sb = match ptr.provenance {
|
||||
Tag::Concrete(ConcreteTag { sb, .. }) => Some(sb),
|
||||
Tag::Wildcard => None,
|
||||
Tag::Concrete { sb, .. } => SbTagExtra::Concrete(sb),
|
||||
Tag::Wildcard => SbTagExtra::Wildcard,
|
||||
};
|
||||
(alloc_id, size, sb)
|
||||
})
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
use log::trace;
|
||||
use std::cell::RefCell;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
@ -60,6 +61,32 @@ impl fmt::Debug for SbTag {
|
||||
}
|
||||
}
|
||||
|
||||
/// The "extra" information an SB pointer has over a regular AllocId.
|
||||
/// Newtype for `Option<SbTag>`.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum SbTagExtra {
|
||||
Concrete(SbTag),
|
||||
Wildcard,
|
||||
}
|
||||
|
||||
impl fmt::Debug for SbTagExtra {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SbTagExtra::Concrete(tag) => write!(f, "{tag:?}"),
|
||||
SbTagExtra::Wildcard => write!(f, "<wildcard>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SbTagExtra {
|
||||
fn and_then<T>(self, f: impl FnOnce(SbTag) -> Option<T>) -> Option<T> {
|
||||
match self {
|
||||
SbTagExtra::Concrete(tag) => f(tag),
|
||||
SbTagExtra::Wildcard => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates which permission is granted (by this item to some pointers)
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum Permission {
|
||||
@ -108,6 +135,8 @@ pub struct Stack {
|
||||
/// wildcard pointers are used to access this location. What we do know is that `borrows` are at
|
||||
/// the top of the stack, and below it are arbitrarily many items whose `tag` is either
|
||||
/// `Untagged` or strictly less than `id`.
|
||||
/// When the bottom is unknown, `borrows` always has a `SharedReadOnly` or `Unique` at the bottom;
|
||||
/// we never have the unknown-to-known boundary in an SRW group.
|
||||
unknown_bottom: Option<PtrId>,
|
||||
}
|
||||
|
||||
@ -289,35 +318,37 @@ impl Permission {
|
||||
/// Core per-location operations: access, dealloc, reborrow.
|
||||
impl<'tcx> Stack {
|
||||
/// Find the item granting the given kind of access to the given tag, and return where
|
||||
/// it is on the stack.
|
||||
/// `Ok(None)` indicates it matched the "unknown" part of the stack, or it was a wildcard tag
|
||||
/// and we have no clue what exactly it matched (but it could have matched something)
|
||||
/// it is on the stack. For wildcard tags, the given index is approximate, but if *no*
|
||||
/// index is given it means the match was *not* in the known part of the stack.
|
||||
/// `Ok(None)` indicates it matched the "unknown" part of the stack.
|
||||
/// `Err` indicates it was not found.
|
||||
fn find_granting(
|
||||
&self,
|
||||
access: AccessKind,
|
||||
tag: Option<SbTag>,
|
||||
tag: SbTagExtra,
|
||||
exposed_tags: &FxHashSet<SbTag>,
|
||||
) -> Result<Option<usize>, ()> {
|
||||
let Some(tag) = tag else {
|
||||
let SbTagExtra::Concrete(tag) = tag else {
|
||||
// Handle the wildcard case.
|
||||
// Go search the stack for an exposed tag.
|
||||
let maybe_in_stack = self
|
||||
.borrows
|
||||
.iter()
|
||||
.rev() // search top-to-bottom
|
||||
.find_map(|item| {
|
||||
// If the item fits and *might* be this wildcard, use it.
|
||||
if item.perm.grants(access) && exposed_tags.contains(&item.tag) {
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.is_some();
|
||||
if let Some(idx) =
|
||||
self.borrows
|
||||
.iter()
|
||||
.enumerate() // we also need to know *where* in the stack
|
||||
.rev() // search top-to-bottom
|
||||
.find_map(|(idx, item)| {
|
||||
// If the item fits and *might* be this wildcard, use it.
|
||||
if item.perm.grants(access) && exposed_tags.contains(&item.tag) {
|
||||
Some(idx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
{
|
||||
return Ok(Some(idx));
|
||||
}
|
||||
// If we couldn't find it in the stack, check the unknown bottom.
|
||||
let found = maybe_in_stack || self.unknown_bottom.is_some();
|
||||
return if found { Ok(None) } else { Err(()) };
|
||||
return if self.unknown_bottom.is_some() { Ok(None) } else { Err(()) };
|
||||
};
|
||||
|
||||
if let Some(idx) =
|
||||
@ -351,8 +382,10 @@ impl<'tcx> Stack {
|
||||
match perm {
|
||||
Permission::SharedReadOnly => bug!("Cannot use SharedReadOnly for writing"),
|
||||
Permission::Disabled => bug!("Cannot use Disabled for anything"),
|
||||
// On a write, everything above us is incompatible.
|
||||
Permission::Unique => granting + 1,
|
||||
Permission::Unique => {
|
||||
// On a write, everything above us is incompatible.
|
||||
granting + 1
|
||||
}
|
||||
Permission::SharedReadWrite => {
|
||||
// The SharedReadWrite *just* above us are compatible, to skip those.
|
||||
let mut idx = granting + 1;
|
||||
@ -380,7 +413,7 @@ impl<'tcx> Stack {
|
||||
/// currently checking.
|
||||
fn check_protector(
|
||||
item: &Item,
|
||||
provoking_access: Option<(SbTag, AllocRange, Size, AccessKind)>, // just for debug printing and error messages
|
||||
provoking_access: Option<(SbTagExtra, AllocRange, Size, AccessKind)>, // just for debug printing and error messages
|
||||
global: &GlobalStateInner,
|
||||
alloc_history: &mut AllocHistory,
|
||||
) -> InterpResult<'tcx> {
|
||||
@ -401,12 +434,14 @@ impl<'tcx> Stack {
|
||||
tag, item
|
||||
),
|
||||
None,
|
||||
alloc_history.get_logs_relevant_to(
|
||||
tag,
|
||||
alloc_range,
|
||||
offset,
|
||||
Some(item.tag),
|
||||
),
|
||||
tag.and_then(|tag| {
|
||||
alloc_history.get_logs_relevant_to(
|
||||
tag,
|
||||
alloc_range,
|
||||
offset,
|
||||
Some(item.tag),
|
||||
)
|
||||
}),
|
||||
))?
|
||||
} else {
|
||||
Err(err_sb_ub(
|
||||
@ -427,7 +462,7 @@ impl<'tcx> Stack {
|
||||
fn access(
|
||||
&mut self,
|
||||
access: AccessKind,
|
||||
tag: Option<SbTag>,
|
||||
tag: SbTagExtra,
|
||||
(alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages
|
||||
global: &mut GlobalStateInner,
|
||||
current_span: &mut CurrentSpan<'_, '_, 'tcx>,
|
||||
@ -441,22 +476,22 @@ impl<'tcx> Stack {
|
||||
alloc_history.access_error(access, tag, alloc_id, alloc_range, offset, self)
|
||||
})?;
|
||||
|
||||
let Some(granting_idx) = granting_idx else {
|
||||
// The access used a wildcard pointer or matched the unknown bottom.
|
||||
// Nobody knows what happened, so forget everything.
|
||||
trace!("access: clearing stack due to wildcard");
|
||||
self.borrows.clear();
|
||||
self.unknown_bottom = Some(global.next_ptr_id);
|
||||
return Ok(());
|
||||
};
|
||||
let tag = tag.unwrap(); // only precise tags have precise locations
|
||||
|
||||
// Step 2: Remove incompatible items above them. Make sure we do not remove protected
|
||||
// items. Behavior differs for reads and writes.
|
||||
// In case of wildcards/unknown matches, we remove everything that is *definitely* gone.
|
||||
if access == AccessKind::Write {
|
||||
// Remove everything above the write-compatible items, like a proper stack. This makes sure read-only and unique
|
||||
// pointers become invalid on write accesses (ensures F2a, and ensures U2 for write accesses).
|
||||
let first_incompatible_idx = self.find_first_write_incompatible(granting_idx);
|
||||
let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
|
||||
// The granting_idx *might* be approximate, but any lower idx would remove more
|
||||
// things. Even if this is a Unique and the lower idx is an SRW (which removes
|
||||
// less), there is an SRW group boundary here so strictly more would get removed.
|
||||
self.find_first_write_incompatible(granting_idx)
|
||||
} else {
|
||||
// We are writing to something in the unknown part.
|
||||
// There is a SRW group boundary between the unknown and the known, so everything is incompatible.
|
||||
0
|
||||
};
|
||||
for item in self.borrows.drain(first_incompatible_idx..).rev() {
|
||||
trace!("access: popping item {:?}", item);
|
||||
Stack::check_protector(
|
||||
@ -476,7 +511,13 @@ impl<'tcx> Stack {
|
||||
// This pattern occurs a lot in the standard library: create a raw pointer, then also create a shared
|
||||
// reference and use that.
|
||||
// We *disable* instead of removing `Unique` to avoid "connecting" two neighbouring blocks of SRWs.
|
||||
let first_incompatible_idx = granting_idx + 1;
|
||||
let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
|
||||
// The granting_idx *might* be approximate, but any lower idx would disable more things.
|
||||
granting_idx + 1
|
||||
} else {
|
||||
// We are reading from something in the unknown part. That means *all* `Unique` we know about are dead now.
|
||||
0
|
||||
};
|
||||
for idx in (first_incompatible_idx..self.borrows.len()).rev() {
|
||||
let item = &mut self.borrows[idx];
|
||||
|
||||
@ -494,6 +535,31 @@ impl<'tcx> Stack {
|
||||
}
|
||||
}
|
||||
|
||||
// If this was an approximate action, we now collapse everything into an unknown.
|
||||
if granting_idx.is_none() || matches!(tag, SbTagExtra::Wildcard) {
|
||||
// Compute the upper bound of the items that remain.
|
||||
// (This is why we did all the work above: to reduce the items we have to consider here.)
|
||||
let mut max = NonZeroU64::new(1).unwrap();
|
||||
for item in &self.borrows {
|
||||
// Skip disabled items, they cannot be matched anyway.
|
||||
if !matches!(item.perm, Permission::Disabled) {
|
||||
if let SbTag::Tagged(tag) = item.tag {
|
||||
// We are looking for a strict upper bound, so add 1 to this tag.
|
||||
max = cmp::max(tag.checked_add(1).unwrap(), max);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(unk) = self.unknown_bottom {
|
||||
max = cmp::max(unk, max);
|
||||
}
|
||||
// Use `max` as new strict upper bound for everything.
|
||||
trace!(
|
||||
"access: forgetting stack to upper bound {max} due to wildcard or unknown access"
|
||||
);
|
||||
self.borrows.clear();
|
||||
self.unknown_bottom = Some(max);
|
||||
}
|
||||
|
||||
// Done.
|
||||
Ok(())
|
||||
}
|
||||
@ -502,7 +568,7 @@ impl<'tcx> Stack {
|
||||
/// active protectors at all because we will remove all items.
|
||||
fn dealloc(
|
||||
&mut self,
|
||||
tag: Option<SbTag>,
|
||||
tag: SbTagExtra,
|
||||
(alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages
|
||||
global: &GlobalStateInner,
|
||||
alloc_history: &mut AllocHistory,
|
||||
@ -534,7 +600,7 @@ impl<'tcx> Stack {
|
||||
/// `range` that we are currently checking.
|
||||
fn grant(
|
||||
&mut self,
|
||||
derived_from: Option<SbTag>,
|
||||
derived_from: SbTagExtra,
|
||||
new: Item,
|
||||
(alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages
|
||||
global: &mut GlobalStateInner,
|
||||
@ -562,10 +628,12 @@ impl<'tcx> Stack {
|
||||
"this case only makes sense for stack-like accesses"
|
||||
);
|
||||
|
||||
let Some(granting_idx) = granting_idx else {
|
||||
let (Some(granting_idx), SbTagExtra::Concrete(_)) = (granting_idx, derived_from) else {
|
||||
// The parent is a wildcard pointer or matched the unknown bottom.
|
||||
// Nobody knows what happened, so forget everything.
|
||||
trace!("reborrow: clearing stack due to wildcard");
|
||||
// This is approximate. Nobody knows what happened, so forget everything.
|
||||
// The new thing is SRW anyway, so we cannot push it "on top of the unkown part"
|
||||
// (for all we know, it might join an SRW group inside the unknown).
|
||||
trace!("reborrow: forgetting stack entirely due to SharedReadWrite reborrow from wildcard or unknown");
|
||||
self.borrows.clear();
|
||||
self.unknown_bottom = Some(global.next_ptr_id);
|
||||
return Ok(());
|
||||
@ -723,7 +791,7 @@ impl Stacks {
|
||||
pub fn memory_read<'tcx>(
|
||||
&self,
|
||||
alloc_id: AllocId,
|
||||
tag: Option<SbTag>,
|
||||
tag: SbTagExtra,
|
||||
range: AllocRange,
|
||||
state: &GlobalState,
|
||||
mut current_span: CurrentSpan<'_, '_, 'tcx>,
|
||||
@ -752,7 +820,7 @@ impl Stacks {
|
||||
pub fn memory_written<'tcx>(
|
||||
&mut self,
|
||||
alloc_id: AllocId,
|
||||
tag: Option<SbTag>,
|
||||
tag: SbTagExtra,
|
||||
range: AllocRange,
|
||||
state: &GlobalState,
|
||||
mut current_span: CurrentSpan<'_, '_, 'tcx>,
|
||||
@ -781,7 +849,7 @@ impl Stacks {
|
||||
pub fn memory_deallocated<'tcx>(
|
||||
&mut self,
|
||||
alloc_id: AllocId,
|
||||
tag: Option<SbTag>,
|
||||
tag: SbTagExtra,
|
||||
range: AllocRange,
|
||||
state: &GlobalState,
|
||||
) -> InterpResult<'tcx> {
|
||||
@ -798,6 +866,8 @@ impl Stacks {
|
||||
/// to grant for which references, and when to add protectors.
|
||||
impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
|
||||
trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
|
||||
/// Returns the `AllocId` the reborrow was done in, if some actual borrow stack manipulation
|
||||
/// happened.
|
||||
fn reborrow(
|
||||
&mut self,
|
||||
place: &MPlaceTy<'tcx, Tag>,
|
||||
@ -805,7 +875,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||
kind: RefKind,
|
||||
new_tag: SbTag,
|
||||
protect: bool,
|
||||
) -> InterpResult<'tcx> {
|
||||
) -> InterpResult<'tcx, Option<AllocId>> {
|
||||
let this = self.eval_context_mut();
|
||||
let current_span = &mut this.machine.current_span();
|
||||
|
||||
@ -815,6 +885,10 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||
base_offset,
|
||||
orig_tag|
|
||||
-> InterpResult<'tcx> {
|
||||
let SbTagExtra::Concrete(orig_tag) = orig_tag else {
|
||||
// FIXME: should we log this?
|
||||
return Ok(())
|
||||
};
|
||||
let extra = this.get_alloc_extra(alloc_id)?;
|
||||
let stacked_borrows =
|
||||
extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data");
|
||||
@ -832,6 +906,13 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||
};
|
||||
|
||||
if size == Size::ZERO {
|
||||
trace!(
|
||||
"reborrow of size 0: {} reference {:?} derived from {:?} (pointee {})",
|
||||
kind,
|
||||
new_tag,
|
||||
place.ptr,
|
||||
place.layout.ty,
|
||||
);
|
||||
// Don't update any stacks for a zero-sized access; borrow stacks are per-byte and this
|
||||
// touches no bytes so there is no stack to put this tag in.
|
||||
// However, if the pointer for this operation points at a real allocation we still
|
||||
@ -840,24 +921,14 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||
// Dangling slices are a common case here; it's valid to get their length but with raw
|
||||
// pointer tagging for example all calls to get_unchecked on them are invalid.
|
||||
if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr) {
|
||||
if let Some(orig_tag) = orig_tag {
|
||||
log_creation(this, current_span, alloc_id, base_offset, orig_tag)?;
|
||||
}
|
||||
log_creation(this, current_span, alloc_id, base_offset, orig_tag)?;
|
||||
return Ok(Some(alloc_id));
|
||||
}
|
||||
|
||||
trace!(
|
||||
"reborrow of size 0: {} reference {:?} derived from {:?} (pointee {})",
|
||||
kind,
|
||||
new_tag,
|
||||
place.ptr,
|
||||
place.layout.ty,
|
||||
);
|
||||
return Ok(());
|
||||
// This pointer doesn't come with an AllocId. :shrug:
|
||||
return Ok(None);
|
||||
}
|
||||
let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?;
|
||||
if let Some(orig_tag) = orig_tag {
|
||||
log_creation(this, current_span, alloc_id, base_offset, orig_tag)?;
|
||||
}
|
||||
log_creation(this, current_span, alloc_id, base_offset, orig_tag)?;
|
||||
|
||||
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
|
||||
let (alloc_size, _) =
|
||||
@ -937,7 +1008,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||
)
|
||||
})
|
||||
})?;
|
||||
return Ok(());
|
||||
return Ok(Some(alloc_id));
|
||||
}
|
||||
};
|
||||
// Here we can avoid `borrow()` calls because we have mutable references.
|
||||
@ -962,7 +1033,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
Ok(Some(alloc_id))
|
||||
}
|
||||
|
||||
/// Retags an indidual pointer, returning the retagged version.
|
||||
@ -997,16 +1068,22 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||
};
|
||||
|
||||
// Reborrow.
|
||||
this.reborrow(&place, size, kind, new_tag, protect)?;
|
||||
let alloc_id = this.reborrow(&place, size, kind, new_tag, protect)?;
|
||||
|
||||
// Adjust pointer.
|
||||
let new_place = place.map_provenance(|p| {
|
||||
p.map(|t| {
|
||||
// TODO: Fix this eventually
|
||||
if let Tag::Concrete(t) = t {
|
||||
Tag::Concrete(ConcreteTag { sb: new_tag, ..t })
|
||||
} else {
|
||||
t
|
||||
p.map(|prov| {
|
||||
match alloc_id {
|
||||
Some(alloc_id) => {
|
||||
// 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.
|
||||
Tag::Concrete { alloc_id, sb: new_tag }
|
||||
}
|
||||
None => {
|
||||
// Looks like this has to stay a wildcard pointer.
|
||||
assert!(matches!(prov, Tag::Wildcard));
|
||||
Tag::Wildcard
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -4,12 +4,11 @@ use rustc_middle::mir::interpret::{AllocId, AllocRange};
|
||||
use rustc_span::{Span, SpanData};
|
||||
use rustc_target::abi::Size;
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
use crate::helpers::{CurrentSpan, HexRange};
|
||||
use crate::stacked_borrows::{err_sb_ub, AccessKind, Permission};
|
||||
use crate::Item;
|
||||
use crate::SbTag;
|
||||
use crate::SbTagExtra;
|
||||
use crate::Stack;
|
||||
|
||||
use rustc_middle::mir::interpret::InterpError;
|
||||
@ -199,19 +198,15 @@ impl AllocHistory {
|
||||
/// Report a descriptive error when `new` could not be granted from `derived_from`.
|
||||
pub fn grant_error<'tcx>(
|
||||
&self,
|
||||
derived_from: Option<SbTag>,
|
||||
derived_from: SbTagExtra,
|
||||
new: Item,
|
||||
alloc_id: AllocId,
|
||||
alloc_range: AllocRange,
|
||||
error_offset: Size,
|
||||
stack: &Stack,
|
||||
) -> InterpError<'tcx> {
|
||||
// TODO: Fix this properly
|
||||
let z = &derived_from;
|
||||
let f = if let Some(ref t) = z { t as &dyn Debug } else { &"<wildcard>" as &dyn Debug };
|
||||
let action = format!(
|
||||
"trying to reborrow {:?} for {:?} permission at {}[{:#x}]",
|
||||
f,
|
||||
"trying to reborrow {derived_from:?} for {:?} permission at {}[{:#x}]",
|
||||
new.perm,
|
||||
alloc_id,
|
||||
error_offset.bytes(),
|
||||
@ -229,18 +224,14 @@ impl AllocHistory {
|
||||
pub fn access_error<'tcx>(
|
||||
&self,
|
||||
access: AccessKind,
|
||||
tag: Option<SbTag>,
|
||||
tag: SbTagExtra,
|
||||
alloc_id: AllocId,
|
||||
alloc_range: AllocRange,
|
||||
error_offset: Size,
|
||||
stack: &Stack,
|
||||
) -> InterpError<'tcx> {
|
||||
let z = &tag;
|
||||
let f = if let Some(ref t) = z { t as &dyn Debug } else { &"<wildcard>" as &dyn Debug };
|
||||
let action = format!(
|
||||
"attempting a {} using {:?} at {}[{:#x}]",
|
||||
access,
|
||||
f,
|
||||
"attempting a {access} using {tag:?} at {}[{:#x}]",
|
||||
alloc_id,
|
||||
error_offset.bytes(),
|
||||
);
|
||||
@ -260,8 +251,8 @@ fn operation_summary(
|
||||
format!("this error occurs as part of {} at {:?}{}", operation, alloc_id, HexRange(alloc_range))
|
||||
}
|
||||
|
||||
fn error_cause(stack: &Stack, tag: Option<SbTag>) -> &'static str {
|
||||
if let Some(tag) = tag {
|
||||
fn error_cause(stack: &Stack, tag: SbTagExtra) -> &'static str {
|
||||
if let SbTagExtra::Concrete(tag) = tag {
|
||||
if stack.borrows.iter().any(|item| item.tag == tag && item.perm != Permission::Disabled) {
|
||||
", but that tag only grants SharedReadOnly permission for this location"
|
||||
} else {
|
||||
|
@ -1,10 +1,10 @@
|
||||
error: Undefined Behavior: attempting a write access using "<wildcard>" at ALLOC[0x0], but no exposed tags are valid in the borrow stack for this location
|
||||
error: Undefined Behavior: attempting a write access using <wildcard> at ALLOC[0x0], but no exposed tags are valid in the borrow stack for this location
|
||||
--> $DIR/exposed_only_ro.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { *ptr = 0 };
|
||||
| ^^^^^^^^
|
||||
| |
|
||||
| attempting a write access using "<wildcard>" at ALLOC[0x0], but no exposed tags are valid in the borrow stack for this location
|
||||
| attempting a write access using <wildcard> at ALLOC[0x0], but no exposed tags are valid in the borrow stack for this location
|
||||
| this error occurs as part of an access at ALLOC[0x0..0x4]
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
|
||||
|
17
tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs
Normal file
17
tests/fail/stacked_borrows/illegal_read_despite_exposed1.rs
Normal file
@ -0,0 +1,17 @@
|
||||
// compile-flags: -Zmiri-permissive-provenance
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
let root = &mut 42;
|
||||
let addr = root as *mut i32 as usize;
|
||||
let exposed_ptr = addr as *mut i32;
|
||||
// From the exposed ptr, we get a new unique ptr.
|
||||
let root2 = &mut *exposed_ptr;
|
||||
let _fool = root2 as *mut _; // this would have fooled the old untagged pointer logic
|
||||
// Stack: Unknown(<N), Unique(N), SRW(N+1)
|
||||
// And we test that it has uniqueness by doing a conflicting write.
|
||||
*exposed_ptr = 0;
|
||||
// Stack: Unknown(<N)
|
||||
let _val = *root2; //~ ERROR: borrow stack
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
|
||||
--> $DIR/illegal_read_despite_exposed1.rs:LL:CC
|
||||
|
|
||||
LL | let _val = *root2;
|
||||
| ^^^^^^
|
||||
| |
|
||||
| attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
|
||||
| this error occurs as part of an access at ALLOC[0x0..0x4]
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
|
||||
|
||||
= note: inside `main` at $DIR/illegal_read_despite_exposed1.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to previous error
|
||||
|
18
tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs
Normal file
18
tests/fail/stacked_borrows/illegal_read_despite_exposed2.rs
Normal file
@ -0,0 +1,18 @@
|
||||
// compile-flags: -Zmiri-permissive-provenance
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
let root = &mut 42;
|
||||
let addr = root as *mut i32 as usize;
|
||||
let exposed_ptr = addr as *mut i32;
|
||||
// From the exposed ptr, we get a new unique ptr.
|
||||
let root2 = &mut *exposed_ptr;
|
||||
// let _fool = root2 as *mut _; // this would [fool] us, since SRW(N+1) remains on the stack
|
||||
// Stack: Unknown(<N), Unique(N) [, SRW(N+1)]
|
||||
// And we test that it has uniqueness by doing a conflicting read.
|
||||
let _val = *exposed_ptr;
|
||||
// Stack: Unknown(<N), Disabled(N) [, SRW(N+1)]
|
||||
// collapsed to Unknown(<N) [Unknown(<N+2)]
|
||||
let _val = *root2; //~ ERROR: borrow stack
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
|
||||
--> $DIR/illegal_read_despite_exposed2.rs:LL:CC
|
||||
|
|
||||
LL | let _val = *root2;
|
||||
| ^^^^^^
|
||||
| |
|
||||
| attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
|
||||
| this error occurs as part of an access at ALLOC[0x0..0x4]
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
|
||||
|
||||
= note: inside `main` at $DIR/illegal_read_despite_exposed2.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to previous error
|
||||
|
16
tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs
Normal file
16
tests/fail/stacked_borrows/illegal_write_despite_exposed1.rs
Normal file
@ -0,0 +1,16 @@
|
||||
// compile-flags: -Zmiri-permissive-provenance
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
let root = &mut 42;
|
||||
let addr = root as *mut i32 as usize;
|
||||
let exposed_ptr = addr as *mut i32;
|
||||
// From the exposed ptr, we get a new SRO ptr.
|
||||
let root2 = &*exposed_ptr;
|
||||
// Stack: Unknown(<N), SRO(N), SRO(N+1)
|
||||
// And we test that it is read-only by doing a conflicting write.
|
||||
*exposed_ptr = 0;
|
||||
// Stack: Unknown(<N)
|
||||
let _val = *root2; //~ ERROR: borrow stack
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
|
||||
--> $DIR/illegal_write_despite_exposed1.rs:LL:CC
|
||||
|
|
||||
LL | let _val = *root2;
|
||||
| ^^^^^^
|
||||
| |
|
||||
| attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
|
||||
| this error occurs as part of an access at ALLOC[0x0..0x4]
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
|
||||
|
||||
= note: inside `main` at $DIR/illegal_write_despite_exposed1.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to previous error
|
||||
|
Loading…
x
Reference in New Issue
Block a user