Merge pull request #4630 from royAmmerschuber/pull-request/wildcard-provenance
initial implementation of wildcard provenence for tree borrows
This commit is contained in:
commit
18d2f462ce
43 changed files with 2037 additions and 152 deletions
|
|
@ -72,7 +72,7 @@ harness = false
|
|||
default = ["stack-cache", "native-lib"]
|
||||
genmc = ["dep:genmc-sys"]
|
||||
stack-cache = []
|
||||
stack-cache-consistency-check = ["stack-cache"]
|
||||
expensive-consistency-checks = ["stack-cache"]
|
||||
tracing = ["serde_json"]
|
||||
native-lib = ["dep:libffi", "dep:libloading", "dep:capstone", "dep:ipc-channel", "dep:nix", "dep:serde"]
|
||||
|
||||
|
|
|
|||
|
|
@ -509,7 +509,6 @@ fn main() {
|
|||
Some(BorrowTrackerMethod::TreeBorrows(TreeBorrowsParams {
|
||||
precise_interior_mut: true,
|
||||
}));
|
||||
miri_config.provenance_mode = ProvenanceMode::Strict;
|
||||
} else if arg == "-Zmiri-tree-borrows-no-precise-interior-mut" {
|
||||
match &mut miri_config.borrow_tracker {
|
||||
Some(BorrowTrackerMethod::TreeBorrows(params)) => {
|
||||
|
|
@ -703,17 +702,6 @@ fn main() {
|
|||
rustc_args.push(arg);
|
||||
}
|
||||
}
|
||||
// Tree Borrows implies strict provenance, and is not compatible with native calls.
|
||||
if matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows { .. })) {
|
||||
if miri_config.provenance_mode != ProvenanceMode::Strict {
|
||||
fatal_error!(
|
||||
"Tree Borrows does not support integer-to-pointer casts, and hence requires strict provenance"
|
||||
);
|
||||
}
|
||||
if !miri_config.native_lib.is_empty() {
|
||||
fatal_error!("Tree Borrows is not compatible with calling native functions");
|
||||
}
|
||||
}
|
||||
|
||||
// Native calls and strict provenance are not compatible.
|
||||
if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict {
|
||||
|
|
|
|||
|
|
@ -352,6 +352,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let borrow_tracker = this.machine.borrow_tracker.as_ref().unwrap();
|
||||
// The body of this loop needs `borrow_tracker` immutably
|
||||
// so we can't move this code inside the following `end_call`.
|
||||
|
||||
for (alloc_id, tag) in &frame
|
||||
.extra
|
||||
.borrow_tracker
|
||||
|
|
@ -378,6 +379,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
}
|
||||
borrow_tracker.borrow_mut().end_call(&frame.extra);
|
||||
|
||||
interp_ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ impl<'tcx> Stack {
|
|||
/// Panics if any of the caching mechanisms have broken,
|
||||
/// - The StackCache indices don't refer to the parallel items,
|
||||
/// - There are no Unique items outside of first_unique..last_unique
|
||||
#[cfg(feature = "stack-cache-consistency-check")]
|
||||
#[cfg(feature = "expensive-consistency-checks")]
|
||||
fn verify_cache_consistency(&self) {
|
||||
// Only a full cache needs to be valid. Also see the comments in find_granting_cache
|
||||
// and set_unknown_bottom.
|
||||
|
|
@ -197,7 +197,7 @@ impl<'tcx> Stack {
|
|||
tag: ProvenanceExtra,
|
||||
exposed_tags: &FxHashSet<BorTag>,
|
||||
) -> Result<Option<usize>, ()> {
|
||||
#[cfg(feature = "stack-cache-consistency-check")]
|
||||
#[cfg(feature = "expensive-consistency-checks")]
|
||||
self.verify_cache_consistency();
|
||||
|
||||
let ProvenanceExtra::Concrete(tag) = tag else {
|
||||
|
|
@ -334,7 +334,7 @@ impl<'tcx> Stack {
|
|||
// This primes the cache for the next access, which is almost always the just-added tag.
|
||||
self.cache.add(new_idx, new);
|
||||
|
||||
#[cfg(feature = "stack-cache-consistency-check")]
|
||||
#[cfg(feature = "expensive-consistency-checks")]
|
||||
self.verify_cache_consistency();
|
||||
}
|
||||
|
||||
|
|
@ -417,7 +417,7 @@ impl<'tcx> Stack {
|
|||
self.unique_range.end = self.unique_range.end.min(disable_start);
|
||||
}
|
||||
|
||||
#[cfg(feature = "stack-cache-consistency-check")]
|
||||
#[cfg(feature = "expensive-consistency-checks")]
|
||||
self.verify_cache_consistency();
|
||||
|
||||
interp_ok(())
|
||||
|
|
@ -472,7 +472,7 @@ impl<'tcx> Stack {
|
|||
self.unique_range = 0..0;
|
||||
}
|
||||
|
||||
#[cfg(feature = "stack-cache-consistency-check")]
|
||||
#[cfg(feature = "expensive-consistency-checks")]
|
||||
self.verify_cache_consistency();
|
||||
interp_ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,9 +291,10 @@ pub(super) struct TbError<'node> {
|
|||
pub conflicting_info: &'node NodeDebugInfo,
|
||||
// What kind of access caused this error (read, write, reborrow, deallocation)
|
||||
pub access_cause: AccessCause,
|
||||
/// Which tag the access that caused this error was made through, i.e.
|
||||
/// Which tag, if any, the access that caused this error was made through, i.e.
|
||||
/// which tag was used to read/write/deallocate.
|
||||
pub accessed_info: &'node NodeDebugInfo,
|
||||
/// Not set on wildcard accesses.
|
||||
pub accessed_info: Option<&'node NodeDebugInfo>,
|
||||
}
|
||||
|
||||
impl TbError<'_> {
|
||||
|
|
@ -302,10 +303,20 @@ impl TbError<'_> {
|
|||
use TransitionError::*;
|
||||
let cause = self.access_cause;
|
||||
let accessed = self.accessed_info;
|
||||
let accessed_str =
|
||||
self.accessed_info.map(|v| format!("{v}")).unwrap_or_else(|| "<wildcard>".into());
|
||||
let conflicting = self.conflicting_info;
|
||||
let accessed_is_conflicting = accessed.tag == conflicting.tag;
|
||||
// An access is considered conflicting if it happened through a
|
||||
// different tag than the one who caused UB.
|
||||
// When doing a wildcard access (where `accessed` is `None`) we
|
||||
// do not know which precise tag the accessed happened from,
|
||||
// however we can be certain that it did not come from the
|
||||
// conflicting tag.
|
||||
// This is because the wildcard data structure already removes
|
||||
// all tags through which an access would cause UB.
|
||||
let accessed_is_conflicting = accessed.map(|a| a.tag) == Some(conflicting.tag);
|
||||
let title = format!(
|
||||
"{cause} through {accessed} at {alloc_id:?}[{offset:#x}] is forbidden",
|
||||
"{cause} through {accessed_str} at {alloc_id:?}[{offset:#x}] is forbidden",
|
||||
alloc_id = self.alloc_id,
|
||||
offset = self.error_offset
|
||||
);
|
||||
|
|
@ -316,7 +327,7 @@ impl TbError<'_> {
|
|||
let mut details = Vec::new();
|
||||
if !accessed_is_conflicting {
|
||||
details.push(format!(
|
||||
"the accessed tag {accessed} is a child of the conflicting tag {conflicting}"
|
||||
"the accessed tag {accessed_str} is a child of the conflicting tag {conflicting}"
|
||||
));
|
||||
}
|
||||
let access = cause.print_as_access(/* is_foreign */ false);
|
||||
|
|
@ -330,7 +341,7 @@ impl TbError<'_> {
|
|||
let access = cause.print_as_access(/* is_foreign */ true);
|
||||
let details = vec![
|
||||
format!(
|
||||
"the accessed tag {accessed} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
|
||||
"the accessed tag {accessed_str} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
|
||||
),
|
||||
format!(
|
||||
"this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled"
|
||||
|
|
@ -343,7 +354,7 @@ impl TbError<'_> {
|
|||
let conflicting_tag_name = "strongly protected";
|
||||
let details = vec![
|
||||
format!(
|
||||
"the allocation of the accessed tag {accessed} also contains the {conflicting_tag_name} tag {conflicting}"
|
||||
"the allocation of the accessed tag {accessed_str} also contains the {conflicting_tag_name} tag {conflicting}"
|
||||
),
|
||||
format!("the {conflicting_tag_name} tag {conflicting} disallows deallocations"),
|
||||
];
|
||||
|
|
@ -351,8 +362,10 @@ impl TbError<'_> {
|
|||
}
|
||||
};
|
||||
let mut history = HistoryData::default();
|
||||
if !accessed_is_conflicting {
|
||||
history.extend(self.accessed_info.history.forget(), "accessed", false);
|
||||
if let Some(accessed_info) = self.accessed_info
|
||||
&& !accessed_is_conflicting
|
||||
{
|
||||
history.extend(accessed_info.history.forget(), "accessed", false);
|
||||
}
|
||||
history.extend(
|
||||
self.conflicting_info.history.extract_relevant(self.error_offset, self.error_kind),
|
||||
|
|
@ -363,6 +376,20 @@ impl TbError<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Cannot access this allocation with wildcard provenance, as there are no
|
||||
/// valid exposed references for this access kind.
|
||||
pub fn no_valid_exposed_references_error<'tcx>(
|
||||
alloc_id: AllocId,
|
||||
offset: u64,
|
||||
access_cause: AccessCause,
|
||||
) -> InterpErrorKind<'tcx> {
|
||||
let title =
|
||||
format!("{access_cause} through <wildcard> at {alloc_id:?}[{offset:#x}] is forbidden");
|
||||
let details = vec![format!("there are no exposed tags which may perform this access here")];
|
||||
let history = HistoryData::default();
|
||||
err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
|
||||
}
|
||||
|
||||
type S = &'static str;
|
||||
/// Pretty-printing details
|
||||
///
|
||||
|
|
@ -623,10 +650,10 @@ impl DisplayRepr {
|
|||
} else {
|
||||
// We take this node
|
||||
let rperm = tree
|
||||
.rperms
|
||||
.locations
|
||||
.iter_all()
|
||||
.map(move |(_offset, perms)| {
|
||||
let perm = perms.get(idx);
|
||||
.map(move |(_offset, loc)| {
|
||||
let perm = loc.perms.get(idx);
|
||||
perm.cloned()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -788,7 +815,7 @@ impl<'tcx> Tree {
|
|||
show_unnamed: bool,
|
||||
) -> InterpResult<'tcx> {
|
||||
let mut indenter = DisplayIndent::new();
|
||||
let ranges = self.rperms.iter_all().map(|(range, _perms)| range).collect::<Vec<_>>();
|
||||
let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
|
||||
if let Some(repr) = DisplayRepr::from(self, show_unnamed) {
|
||||
repr.print(
|
||||
&DEFAULT_FORMATTER,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ mod foreign_access_skipping;
|
|||
mod perms;
|
||||
mod tree;
|
||||
mod unimap;
|
||||
mod wildcard;
|
||||
|
||||
#[cfg(test)]
|
||||
mod exhaustive;
|
||||
|
|
@ -54,16 +55,10 @@ impl<'tcx> Tree {
|
|||
interpret::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 interp_ok(()),
|
||||
};
|
||||
let global = machine.borrow_tracker.as_ref().unwrap();
|
||||
let span = machine.current_user_relevant_span();
|
||||
self.perform_access(
|
||||
tag,
|
||||
prov,
|
||||
Some((range, access_kind, diagnostics::AccessCause::Explicit(access_kind))),
|
||||
global,
|
||||
alloc_id,
|
||||
|
|
@ -79,19 +74,9 @@ impl<'tcx> Tree {
|
|||
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 interp_ok(()),
|
||||
};
|
||||
let global = machine.borrow_tracker.as_ref().unwrap();
|
||||
let span = machine.current_user_relevant_span();
|
||||
self.dealloc(tag, alloc_range(Size::ZERO, size), global, alloc_id, span)
|
||||
}
|
||||
|
||||
pub fn expose_tag(&mut self, _tag: BorTag) {
|
||||
// TODO
|
||||
self.dealloc(prov, alloc_range(Size::ZERO, size), global, alloc_id, span)
|
||||
}
|
||||
|
||||
/// A tag just lost its protector.
|
||||
|
|
@ -109,7 +94,11 @@ impl<'tcx> Tree {
|
|||
) -> InterpResult<'tcx> {
|
||||
let span = machine.current_user_relevant_span();
|
||||
// `None` makes it the magic on-protector-end operation
|
||||
self.perform_access(tag, None, global, alloc_id, span)
|
||||
self.perform_access(ProvenanceExtra::Concrete(tag), None, global, alloc_id, span)?;
|
||||
|
||||
self.update_exposure_for_protector_release(tag);
|
||||
|
||||
interp_ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -239,21 +228,22 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
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.
|
||||
let new_prov = place.ptr().provenance;
|
||||
trace!(
|
||||
"reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
|
||||
new_tag,
|
||||
"reborrow of size 0: reusing {:?} (pointee {})",
|
||||
place.ptr(),
|
||||
place.layout.ty,
|
||||
);
|
||||
log_creation(this, None)?;
|
||||
// Keep original provenance.
|
||||
return interp_ok(place.ptr().provenance);
|
||||
return interp_ok(new_prov);
|
||||
}
|
||||
};
|
||||
|
||||
log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
|
||||
|
||||
let orig_tag = match parent_prov {
|
||||
ProvenanceExtra::Wildcard => return interp_ok(place.ptr().provenance), // TODO: handle wildcard pointers
|
||||
ProvenanceExtra::Wildcard => return interp_ok(place.ptr().provenance), // TODO: handle retagging wildcard pointers
|
||||
ProvenanceExtra::Concrete(tag) => tag,
|
||||
};
|
||||
|
||||
|
|
@ -356,7 +346,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
};
|
||||
|
||||
tree_borrows.perform_access(
|
||||
orig_tag,
|
||||
parent_prov,
|
||||
Some((range_in_alloc, AccessKind::Read, diagnostics::AccessCause::Reborrow)),
|
||||
this.machine.borrow_tracker.as_ref().unwrap(),
|
||||
alloc_id,
|
||||
|
|
@ -589,7 +579,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// 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);
|
||||
|
||||
let global = this.machine.borrow_tracker.as_ref().unwrap();
|
||||
let protected_tags = &global.borrow().protected_tags;
|
||||
let protected = protected_tags.contains_key(&tag);
|
||||
alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag, protected);
|
||||
}
|
||||
AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => {
|
||||
// No tree borrows on these allocations.
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ enum PermissionPriv {
|
|||
}
|
||||
use self::PermissionPriv::*;
|
||||
use super::foreign_access_skipping::IdempotentForeignAccess;
|
||||
use super::wildcard::WildcardAccessLevel;
|
||||
|
||||
impl PartialOrd for PermissionPriv {
|
||||
/// PermissionPriv is ordered by the reflexive transitive closure of
|
||||
|
|
@ -372,6 +373,23 @@ impl Permission {
|
|||
pub fn strongest_idempotent_foreign_access(&self, prot: bool) -> IdempotentForeignAccess {
|
||||
self.inner.strongest_idempotent_foreign_access(prot)
|
||||
}
|
||||
|
||||
/// Returns the strongest access allowed from a child to this node without
|
||||
/// causing UB (only considers possible transitions to this permission).
|
||||
pub fn strongest_allowed_child_access(&self, protected: bool) -> WildcardAccessLevel {
|
||||
match self.inner {
|
||||
// Everything except disabled can be accessed by read access.
|
||||
Disabled => WildcardAccessLevel::None,
|
||||
// Frozen references cannot be written to by a child.
|
||||
Frozen => WildcardAccessLevel::Read,
|
||||
// If the `conflicted` flag is set, then there was a foreign read
|
||||
// during the function call that is still ongoing (still `protected`),
|
||||
// this is UB (`noalias` violation).
|
||||
ReservedFrz { conflicted: true } if protected => WildcardAccessLevel::Read,
|
||||
// Everything else allows writes.
|
||||
_ => WildcardAccessLevel::Write,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PermTransition {
|
||||
|
|
@ -772,4 +790,32 @@ mod propagation_optimization_checks {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that `strongest_allowed_child_access` correctly
|
||||
/// represents which transitions are possible.
|
||||
#[test]
|
||||
fn strongest_allowed_child_access() {
|
||||
for (permission, protected) in <(Permission, bool)>::exhaustive() {
|
||||
let strongest_child_access = permission.strongest_allowed_child_access(protected);
|
||||
|
||||
let is_read_valid = Permission::perform_access(
|
||||
AccessKind::Read,
|
||||
AccessRelatedness::LocalAccess,
|
||||
permission,
|
||||
protected,
|
||||
)
|
||||
.is_some();
|
||||
|
||||
let is_write_valid = Permission::perform_access(
|
||||
AccessKind::Write,
|
||||
AccessRelatedness::LocalAccess,
|
||||
permission,
|
||||
protected,
|
||||
)
|
||||
.is_some();
|
||||
|
||||
assert_eq!(is_read_valid, strongest_child_access >= WildcardAccessLevel::Read);
|
||||
assert_eq!(is_write_valid, strongest_child_access >= WildcardAccessLevel::Write);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ use rustc_data_structures::fx::FxHashSet;
|
|||
use rustc_span::Span;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::diagnostics::AccessCause;
|
||||
use super::wildcard::WildcardState;
|
||||
use crate::borrow_tracker::tree_borrows::Permission;
|
||||
use crate::borrow_tracker::tree_borrows::diagnostics::{
|
||||
self, NodeDebugInfo, TbError, TransitionError,
|
||||
self, NodeDebugInfo, TbError, TransitionError, no_valid_exposed_references_error,
|
||||
};
|
||||
use crate::borrow_tracker::tree_borrows::foreign_access_skipping::IdempotentForeignAccess;
|
||||
use crate::borrow_tracker::tree_borrows::perms::PermTransition;
|
||||
|
|
@ -30,7 +32,7 @@ use crate::*;
|
|||
|
||||
mod tests;
|
||||
|
||||
/// Data for a single *location*.
|
||||
/// Data for a reference at single *location*.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub(super) struct LocationState {
|
||||
/// A location is "accessed" when it is child-accessed for the first time (and the initial
|
||||
|
|
@ -81,6 +83,49 @@ impl LocationState {
|
|||
self.permission
|
||||
}
|
||||
|
||||
/// Performs an access on this index and updates node,
|
||||
/// perm and wildcard_state to reflect the transition.
|
||||
fn perform_transition(
|
||||
&mut self,
|
||||
idx: UniIndex,
|
||||
nodes: &mut UniValMap<Node>,
|
||||
wildcard_accesses: &mut UniValMap<WildcardState>,
|
||||
access_kind: AccessKind,
|
||||
access_cause: AccessCause,
|
||||
access_range: Option<AllocRange>,
|
||||
relatedness: AccessRelatedness,
|
||||
span: Span,
|
||||
location_range: Range<u64>,
|
||||
protected: bool,
|
||||
) -> Result<(), TransitionError> {
|
||||
// Call this function now (i.e. only if we know `relatedness`), which
|
||||
// ensures it is only called when `skip_if_known_noop` returns
|
||||
// `Recurse`, due to the contract of `traverse_this_parents_children_other`.
|
||||
self.record_new_access(access_kind, relatedness);
|
||||
|
||||
let transition = self.perform_access(access_kind, relatedness, protected)?;
|
||||
if !transition.is_noop() {
|
||||
let node = nodes.get_mut(idx).unwrap();
|
||||
// Record the event as part of the history.
|
||||
node.debug_info.history.push(diagnostics::Event {
|
||||
transition,
|
||||
is_foreign: relatedness.is_foreign(),
|
||||
access_cause,
|
||||
access_range,
|
||||
transition_range: location_range,
|
||||
span,
|
||||
});
|
||||
|
||||
// We need to update the wildcard state, if the permission
|
||||
// of an exposed pointer changes.
|
||||
if node.is_exposed {
|
||||
let access_type = self.permission.strongest_allowed_child_access(protected);
|
||||
WildcardState::update_exposure(idx, access_type, nodes, wildcard_accesses);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply the effect of an access to one location, including
|
||||
/// - applying `Permission::perform_access` to the inner `Permission`,
|
||||
/// - emitting protector UB if the location is accessed,
|
||||
|
|
@ -211,30 +256,44 @@ impl fmt::Display for LocationState {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of the full tree for a particular location: for all nodes, the local permissions
|
||||
/// of that node, and the tracking for wildcard accesses.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct LocationTree {
|
||||
/// Maps a tag to a perm, with possible lazy initialization.
|
||||
///
|
||||
/// NOTE: not all tags registered in `Tree::nodes` are necessarily in all
|
||||
/// ranges of `perms`, because `perms` is in part lazily initialized.
|
||||
/// Just because `nodes.get(key)` is `Some(_)` does not mean you can safely
|
||||
/// `unwrap` any `perm.get(key)`.
|
||||
///
|
||||
/// We do uphold the fact that `keys(perms)` is a subset of `keys(nodes)`
|
||||
pub perms: UniValMap<LocationState>,
|
||||
/// Maps a tag and a location to its wildcard access tracking information,
|
||||
/// with possible lazy initialization.
|
||||
///
|
||||
/// If this allocation doesn't have any exposed nodes, then this map doesn't get
|
||||
/// initialized. This way we only need to allocate the map if we need it.
|
||||
///
|
||||
/// NOTE: same guarantees on entry initialization as for `perms`.
|
||||
pub wildcard_accesses: UniValMap<WildcardState>,
|
||||
}
|
||||
/// Tree structure with both parents and children since we want to be
|
||||
/// able to traverse the tree efficiently in both directions.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Tree {
|
||||
/// Mapping from tags to keys. The key obtained can then be used in
|
||||
/// any of the `UniValMap` relative to this allocation, i.e. both the
|
||||
/// `nodes` and `rperms` of the same `Tree`.
|
||||
/// any of the `UniValMap` relative to this allocation, i.e.
|
||||
/// `nodes`, `LocationTree::perms` and `LocationTree::wildcard_accesses`
|
||||
/// of the same `Tree`.
|
||||
/// The parent-child relationship in `Node` is encoded in terms of these same
|
||||
/// keys, so traversing the entire tree needs exactly one access to
|
||||
/// `tag_mapping`.
|
||||
pub(super) tag_mapping: UniKeyMap<BorTag>,
|
||||
/// All nodes of this tree.
|
||||
pub(super) nodes: UniValMap<Node>,
|
||||
/// Maps a tag and a location to a perm, with possible lazy
|
||||
/// initialization.
|
||||
///
|
||||
/// NOTE: not all tags registered in `nodes` are necessarily in all
|
||||
/// ranges of `rperms`, because `rperms` is in part lazily initialized.
|
||||
/// Just because `nodes.get(key)` is `Some(_)` does not mean you can safely
|
||||
/// `unwrap` any `perm.get(key)`.
|
||||
///
|
||||
/// We do uphold the fact that `keys(perms)` is a subset of `keys(nodes)`
|
||||
pub(super) rperms: DedupRangeMap<UniValMap<LocationState>>,
|
||||
/// Associates with each location its state and wildcard access tracking.
|
||||
pub(super) locations: DedupRangeMap<LocationTree>,
|
||||
/// The index of the root node.
|
||||
pub(super) root: UniIndex,
|
||||
}
|
||||
|
|
@ -260,7 +319,9 @@ pub(super) struct Node {
|
|||
/// in cases where there is no location state yet. See `foreign_access_skipping.rs`,
|
||||
/// and `LocationState::idempotent_foreign_access` for more information
|
||||
default_initial_idempotent_foreign_access: IdempotentForeignAccess,
|
||||
/// Some extra information useful only for debugging purposes
|
||||
/// Whether a wildcard access could happen through this node.
|
||||
pub is_exposed: bool,
|
||||
/// Some extra information useful only for debugging purposes.
|
||||
pub debug_info: NodeDebugInfo,
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +334,7 @@ struct NodeAppArgs<'visit> {
|
|||
/// The node map of this tree.
|
||||
nodes: &'visit mut UniValMap<Node>,
|
||||
/// The permissions map of this tree.
|
||||
perms: &'visit mut UniValMap<LocationState>,
|
||||
loc: &'visit mut LocationTree,
|
||||
}
|
||||
/// Data given to the error handler
|
||||
struct ErrHandlerArgs<'node, InErr> {
|
||||
|
|
@ -293,7 +354,7 @@ struct ErrHandlerArgs<'node, InErr> {
|
|||
struct TreeVisitor<'tree> {
|
||||
tag_mapping: &'tree UniKeyMap<BorTag>,
|
||||
nodes: &'tree mut UniValMap<Node>,
|
||||
perms: &'tree mut UniValMap<LocationState>,
|
||||
loc: &'tree mut LocationTree,
|
||||
}
|
||||
|
||||
/// Whether to continue exploring the children recursively or not.
|
||||
|
|
@ -350,7 +411,7 @@ where
|
|||
idx: UniIndex,
|
||||
rel_pos: AccessRelatedness,
|
||||
) -> ContinueTraversal {
|
||||
let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, perms: this.perms };
|
||||
let args = NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc };
|
||||
(self.f_continue)(&args)
|
||||
}
|
||||
|
||||
|
|
@ -360,14 +421,15 @@ where
|
|||
idx: UniIndex,
|
||||
rel_pos: AccessRelatedness,
|
||||
) -> Result<(), OutErr> {
|
||||
(self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, perms: this.perms })
|
||||
.map_err(|error_kind| {
|
||||
(self.f_propagate)(NodeAppArgs { idx, rel_pos, nodes: this.nodes, loc: this.loc }).map_err(
|
||||
|error_kind| {
|
||||
(self.err_builder)(ErrHandlerArgs {
|
||||
error_kind,
|
||||
conflicting_info: &this.nodes.get(idx).unwrap().debug_info,
|
||||
accessed_info: &this.nodes.get(self.initial).unwrap().debug_info,
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn go_upwards_from_accessed(
|
||||
|
|
@ -577,6 +639,7 @@ impl Tree {
|
|||
default_initial_perm: root_default_perm,
|
||||
// The root may never be skipped, all accesses will be local.
|
||||
default_initial_idempotent_foreign_access: IdempotentForeignAccess::None,
|
||||
is_exposed: false,
|
||||
debug_info,
|
||||
},
|
||||
);
|
||||
|
|
@ -595,9 +658,10 @@ impl Tree {
|
|||
IdempotentForeignAccess::None,
|
||||
),
|
||||
);
|
||||
DedupRangeMap::new(size, perms)
|
||||
let wildcard_accesses = UniValMap::default();
|
||||
DedupRangeMap::new(size, LocationTree { perms, wildcard_accesses })
|
||||
};
|
||||
Self { root: root_idx, nodes, rperms, tag_mapping }
|
||||
Self { root: root_idx, nodes, locations: rperms, tag_mapping }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -633,11 +697,13 @@ impl<'tcx> Tree {
|
|||
children: SmallVec::default(),
|
||||
default_initial_perm: outside_perm,
|
||||
default_initial_idempotent_foreign_access: default_strongest_idempotent,
|
||||
is_exposed: false,
|
||||
debug_info: NodeDebugInfo::new(new_tag, outside_perm, span),
|
||||
},
|
||||
);
|
||||
let parent_node = self.nodes.get_mut(parent_idx).unwrap();
|
||||
// Register new_tag as a child of parent_tag
|
||||
self.nodes.get_mut(parent_idx).unwrap().children.push(idx);
|
||||
parent_node.children.push(idx);
|
||||
|
||||
// We need to know the weakest SIFA for `update_idempotent_foreign_access_after_retag`.
|
||||
let mut min_sifa = default_strongest_idempotent;
|
||||
|
|
@ -651,11 +717,19 @@ impl<'tcx> Tree {
|
|||
);
|
||||
|
||||
min_sifa = cmp::min(min_sifa, perm.idempotent_foreign_access);
|
||||
for (_perms_range, perms) in self
|
||||
.rperms
|
||||
for (_range, loc) in self
|
||||
.locations
|
||||
.iter_mut(Size::from_bytes(start) + base_offset, Size::from_bytes(end - start))
|
||||
{
|
||||
perms.insert(idx, perm);
|
||||
loc.perms.insert(idx, perm);
|
||||
}
|
||||
}
|
||||
|
||||
// We need to ensure the consistency of the wildcard access tracking data structure.
|
||||
// For this, we insert the correct entry for this tag based on its parent, if it exists.
|
||||
for (_range, loc) in self.locations.iter_mut_all() {
|
||||
if let Some(parent_access) = loc.wildcard_accesses.get(parent_idx) {
|
||||
loc.wildcard_accesses.insert(idx, parent_access.for_new_child());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -689,9 +763,9 @@ impl<'tcx> Tree {
|
|||
// as the default SIFA for not-yet-initialized locations.
|
||||
// Record whether we did any change; if not, the invariant is restored and we can stop the traversal.
|
||||
let mut any_change = false;
|
||||
for (_, map) in self.rperms.iter_mut_all() {
|
||||
for (_range, loc) in self.locations.iter_mut_all() {
|
||||
// Check if this node has a state for this location (or range of locations).
|
||||
if let Some(perm) = map.get_mut(current) {
|
||||
if let Some(perm) = loc.perms.get_mut(current) {
|
||||
// Update the per-location SIFA, recording if it changed.
|
||||
any_change |=
|
||||
perm.idempotent_foreign_access.ensure_no_stronger_than(strongest_allowed);
|
||||
|
|
@ -720,28 +794,37 @@ impl<'tcx> Tree {
|
|||
/// - the absence of Strong Protectors anywhere in the allocation
|
||||
pub fn dealloc(
|
||||
&mut self,
|
||||
tag: BorTag,
|
||||
prov: ProvenanceExtra,
|
||||
access_range: AllocRange,
|
||||
global: &GlobalState,
|
||||
alloc_id: AllocId, // diagnostics
|
||||
span: Span, // diagnostics
|
||||
) -> InterpResult<'tcx> {
|
||||
self.perform_access(
|
||||
tag,
|
||||
prov,
|
||||
Some((access_range, AccessKind::Write, diagnostics::AccessCause::Dealloc)),
|
||||
global,
|
||||
alloc_id,
|
||||
span,
|
||||
)?;
|
||||
for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size) {
|
||||
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
|
||||
|
||||
// Check if this breaks any strong protector.
|
||||
// (Weak protectors are already handled by `perform_access`.)
|
||||
for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) {
|
||||
// The order in which we check if any nodes are invalidated only
|
||||
// matters to diagnostics, so we use the root as a default tag.
|
||||
let start_tag = match prov {
|
||||
ProvenanceExtra::Concrete(tag) => tag,
|
||||
ProvenanceExtra::Wildcard => self.nodes.get(self.root).unwrap().tag,
|
||||
};
|
||||
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, loc }
|
||||
.traverse_this_parents_children_other(
|
||||
tag,
|
||||
// visit all children, skipping none
|
||||
start_tag,
|
||||
// Visit all children, skipping none.
|
||||
|_| ContinueTraversal::Recurse,
|
||||
|args: NodeAppArgs<'_>| -> Result<(), TransitionError> {
|
||||
let node = args.nodes.get(args.idx).unwrap();
|
||||
let perm = args.perms.entry(args.idx);
|
||||
let perm = args.loc.perms.entry(args.idx);
|
||||
|
||||
let perm =
|
||||
perm.get().copied().unwrap_or_else(|| node.default_location_state());
|
||||
|
|
@ -764,9 +847,15 @@ impl<'tcx> Tree {
|
|||
conflicting_info,
|
||||
access_cause: diagnostics::AccessCause::Dealloc,
|
||||
alloc_id,
|
||||
error_offset: perms_range.start,
|
||||
error_offset: loc_range.start,
|
||||
error_kind,
|
||||
accessed_info,
|
||||
accessed_info: match prov {
|
||||
ProvenanceExtra::Concrete(_) => Some(accessed_info),
|
||||
// `accessed_info` contains the info of `start_tag`.
|
||||
// On a wildcard access this is not the info of the accessed tag
|
||||
// (as we don't know the accessed tag).
|
||||
ProvenanceExtra::Wildcard => None,
|
||||
},
|
||||
}
|
||||
.build()
|
||||
},
|
||||
|
|
@ -795,12 +884,15 @@ impl<'tcx> Tree {
|
|||
/// - recording the history.
|
||||
pub fn perform_access(
|
||||
&mut self,
|
||||
tag: BorTag,
|
||||
prov: ProvenanceExtra,
|
||||
access_range_and_kind: Option<(AllocRange, AccessKind, diagnostics::AccessCause)>,
|
||||
global: &GlobalState,
|
||||
alloc_id: AllocId, // diagnostics
|
||||
span: Span, // diagnostics
|
||||
) -> InterpResult<'tcx> {
|
||||
let ProvenanceExtra::Concrete(tag) = prov else {
|
||||
return self.perform_wildcard_access(access_range_and_kind, global, alloc_id, span);
|
||||
};
|
||||
use std::ops::Range;
|
||||
// Performs the per-node work:
|
||||
// - insert the permission if it does not exist
|
||||
|
|
@ -814,7 +906,7 @@ impl<'tcx> Tree {
|
|||
// the `RangeMap` on which we are currently working).
|
||||
let node_skipper = |access_kind: AccessKind, args: &NodeAppArgs<'_>| -> ContinueTraversal {
|
||||
let node = args.nodes.get(args.idx).unwrap();
|
||||
let perm = args.perms.get(args.idx);
|
||||
let perm = args.loc.perms.get(args.idx);
|
||||
|
||||
let old_state = perm.copied().unwrap_or_else(|| node.default_location_state());
|
||||
old_state.skip_if_known_noop(access_kind, args.rel_pos)
|
||||
|
|
@ -825,29 +917,23 @@ impl<'tcx> Tree {
|
|||
args: NodeAppArgs<'_>|
|
||||
-> Result<(), TransitionError> {
|
||||
let node = args.nodes.get_mut(args.idx).unwrap();
|
||||
let mut perm = args.perms.entry(args.idx);
|
||||
let mut perm = args.loc.perms.entry(args.idx);
|
||||
|
||||
let old_state = perm.or_insert(node.default_location_state());
|
||||
|
||||
// Call this function now, which ensures it is only called when
|
||||
// `skip_if_known_noop` returns `Recurse`, due to the contract of
|
||||
// `traverse_this_parents_children_other`.
|
||||
old_state.record_new_access(access_kind, args.rel_pos);
|
||||
let state = perm.or_insert(node.default_location_state());
|
||||
|
||||
let protected = global.borrow().protected_tags.contains_key(&node.tag);
|
||||
let transition = old_state.perform_access(access_kind, args.rel_pos, protected)?;
|
||||
// Record the event as part of the history
|
||||
if !transition.is_noop() {
|
||||
node.debug_info.history.push(diagnostics::Event {
|
||||
transition,
|
||||
is_foreign: args.rel_pos.is_foreign(),
|
||||
access_cause,
|
||||
access_range: access_range_and_kind.map(|x| x.0),
|
||||
transition_range: perms_range,
|
||||
span,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
state.perform_transition(
|
||||
args.idx,
|
||||
args.nodes,
|
||||
&mut args.loc.wildcard_accesses,
|
||||
access_kind,
|
||||
access_cause,
|
||||
/* access_range */ access_range_and_kind.map(|x| x.0),
|
||||
args.rel_pos,
|
||||
span,
|
||||
perms_range,
|
||||
protected,
|
||||
)
|
||||
};
|
||||
|
||||
// Error handler in case `node_app` goes wrong.
|
||||
|
|
@ -863,7 +949,7 @@ impl<'tcx> Tree {
|
|||
alloc_id,
|
||||
error_offset: perms_range.start,
|
||||
error_kind,
|
||||
accessed_info,
|
||||
accessed_info: Some(accessed_info),
|
||||
}
|
||||
.build()
|
||||
};
|
||||
|
|
@ -871,14 +957,13 @@ impl<'tcx> Tree {
|
|||
if let Some((access_range, access_kind, access_cause)) = access_range_and_kind {
|
||||
// Default branch: this is a "normal" access through a known range.
|
||||
// We iterate over affected locations and traverse the tree for each of them.
|
||||
for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size)
|
||||
{
|
||||
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
|
||||
for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) {
|
||||
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, loc }
|
||||
.traverse_this_parents_children_other(
|
||||
tag,
|
||||
|args| node_skipper(access_kind, args),
|
||||
|args| node_app(perms_range.clone(), access_kind, access_cause, args),
|
||||
|args| err_handler(perms_range.clone(), access_cause, args),
|
||||
|args| node_app(loc_range.clone(), access_kind, access_cause, args),
|
||||
|args| err_handler(loc_range.clone(), access_cause, args),
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -891,21 +976,21 @@ impl<'tcx> Tree {
|
|||
// See the test case `returned_mut_is_usable` from
|
||||
// `tests/pass/tree_borrows/tree-borrows.rs` for an example of
|
||||
// why this is important.
|
||||
for (perms_range, perms) in self.rperms.iter_mut_all() {
|
||||
for (loc_range, loc) in self.locations.iter_mut_all() {
|
||||
let idx = self.tag_mapping.get(&tag).unwrap();
|
||||
// Only visit accessed permissions
|
||||
if let Some(p) = perms.get(idx)
|
||||
if let Some(p) = loc.perms.get(idx)
|
||||
&& let Some(access_kind) = p.permission.protector_end_access()
|
||||
&& p.accessed
|
||||
{
|
||||
let access_cause = diagnostics::AccessCause::FnExit(access_kind);
|
||||
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
|
||||
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, loc }
|
||||
.traverse_nonchildren(
|
||||
tag,
|
||||
|args| node_skipper(access_kind, args),
|
||||
|args| node_app(perms_range.clone(), access_kind, access_cause, args),
|
||||
|args| err_handler(perms_range.clone(), access_cause, args),
|
||||
)?;
|
||||
tag,
|
||||
|args| node_skipper(access_kind, args),
|
||||
|args| node_app(loc_range.clone(), access_kind, access_cause, args),
|
||||
|args| err_handler(loc_range.clone(), access_cause, args),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -921,7 +1006,7 @@ impl Tree {
|
|||
// merge some adjacent ranges that were made equal by the removal of some
|
||||
// tags (this does not necessarily mean that they have identical internal representations,
|
||||
// see the `PartialEq` impl for `UniValMap`)
|
||||
self.rperms.merge_adjacent_thorough();
|
||||
self.locations.merge_adjacent_thorough();
|
||||
}
|
||||
|
||||
/// Checks if a node is useless and should be GC'ed.
|
||||
|
|
@ -953,10 +1038,14 @@ impl Tree {
|
|||
let child = self.nodes.get(child_idx).unwrap();
|
||||
// Check that for that one child, `can_be_replaced_by_child` holds for the permission
|
||||
// on all locations.
|
||||
for (_, data) in self.rperms.iter_all() {
|
||||
let parent_perm =
|
||||
data.get(idx).map(|x| x.permission).unwrap_or_else(|| node.default_initial_perm);
|
||||
let child_perm = data
|
||||
for (_range, loc) in self.locations.iter_all() {
|
||||
let parent_perm = loc
|
||||
.perms
|
||||
.get(idx)
|
||||
.map(|x| x.permission)
|
||||
.unwrap_or_else(|| node.default_initial_perm);
|
||||
let child_perm = loc
|
||||
.perms
|
||||
.get(child_idx)
|
||||
.map(|x| x.permission)
|
||||
.unwrap_or_else(|| child.default_initial_perm);
|
||||
|
|
@ -980,8 +1069,9 @@ impl Tree {
|
|||
// before we can safely apply `UniKeyMap::remove` to truly remove
|
||||
// this tag from the `tag_mapping`.
|
||||
let node = self.nodes.remove(this).unwrap();
|
||||
for (_perms_range, perms) in self.rperms.iter_mut_all() {
|
||||
perms.remove(this);
|
||||
for (_range, loc) in self.locations.iter_mut_all() {
|
||||
loc.perms.remove(this);
|
||||
loc.wildcard_accesses.remove(this);
|
||||
}
|
||||
self.tag_mapping.remove(&node.tag);
|
||||
}
|
||||
|
|
@ -1058,6 +1148,126 @@ impl Tree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Methods for wildcard accesses.
|
||||
impl<'tcx> Tree {
|
||||
/// Analogous to `perform_access`, but we do not know from which exposed
|
||||
/// reference the access happens.
|
||||
pub fn perform_wildcard_access(
|
||||
&mut self,
|
||||
access_range_and_kind: Option<(AllocRange, AccessKind, diagnostics::AccessCause)>,
|
||||
global: &GlobalState,
|
||||
alloc_id: AllocId, // diagnostics
|
||||
span: Span, // diagnostics
|
||||
) -> InterpResult<'tcx> {
|
||||
#[cfg(feature = "expensive-consistency-checks")]
|
||||
self.verify_wildcard_consistency(global);
|
||||
|
||||
if let Some((access_range, access_kind, access_cause)) = access_range_and_kind {
|
||||
// This does a traversal starting from the root through the tree updating
|
||||
// the permissions of each node.
|
||||
// The difference to `perform_access` is that we take the access
|
||||
// relatedness from the wildcard tracking state of the node instead of
|
||||
// from the visitor itself.
|
||||
for (loc_range, loc) in self.locations.iter_mut(access_range.start, access_range.size) {
|
||||
let root_tag = self.nodes.get(self.root).unwrap().tag;
|
||||
TreeVisitor { loc, nodes: &mut self.nodes, tag_mapping: &self.tag_mapping }
|
||||
.traverse_this_parents_children_other(
|
||||
root_tag,
|
||||
|args: &NodeAppArgs<'_>| -> ContinueTraversal {
|
||||
let node = args.nodes.get(args.idx).unwrap();
|
||||
let perm = args.loc.perms.get(args.idx);
|
||||
let wildcard_state = args
|
||||
.loc
|
||||
.wildcard_accesses
|
||||
.get(args.idx)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let old_state =
|
||||
perm.copied().unwrap_or_else(|| node.default_location_state());
|
||||
// If we know where, relative to this node, the wildcard access occurs,
|
||||
// then check if we can skip the entire subtree.
|
||||
if let Some(relatedness) =
|
||||
wildcard_state.access_relatedness(access_kind)
|
||||
&& let Some(relatedness) = relatedness.to_relatedness()
|
||||
{
|
||||
// We can use the usual SIFA machinery to skip nodes.
|
||||
old_state.skip_if_known_noop(access_kind, relatedness)
|
||||
} else {
|
||||
ContinueTraversal::Recurse
|
||||
}
|
||||
},
|
||||
|args| {
|
||||
let node = args.nodes.get_mut(args.idx).unwrap();
|
||||
let mut entry = args.loc.perms.entry(args.idx);
|
||||
let perm = entry.or_insert(node.default_location_state());
|
||||
|
||||
let protected = global.borrow().protected_tags.contains_key(&node.tag);
|
||||
|
||||
let Some(wildcard_relatedness) = args
|
||||
.loc
|
||||
.wildcard_accesses
|
||||
.get(args.idx)
|
||||
.and_then(|s| s.access_relatedness(access_kind))
|
||||
else {
|
||||
// There doesn't exist a valid exposed reference for this access to
|
||||
// happen through.
|
||||
// If this fails for one id, then it fails for all ids so this.
|
||||
// Since we always check the root first, this means it should always
|
||||
// fail on the root.
|
||||
assert_eq!(self.root, args.idx);
|
||||
return Err(no_valid_exposed_references_error(
|
||||
alloc_id,
|
||||
loc_range.start,
|
||||
access_cause,
|
||||
));
|
||||
};
|
||||
|
||||
let Some(relatedness) = wildcard_relatedness.to_relatedness() else {
|
||||
// If the access type is Either, then we do not apply any transition
|
||||
// to this node, but we still update each of its children.
|
||||
// This is an imprecision! In the future, maybe we can still do some sort
|
||||
// of best-effort update here.
|
||||
return Ok(());
|
||||
};
|
||||
// We know the exact relatedness, so we can actually do precise checks.
|
||||
perm.perform_transition(
|
||||
args.idx,
|
||||
args.nodes,
|
||||
&mut args.loc.wildcard_accesses,
|
||||
access_kind,
|
||||
access_cause,
|
||||
Some(access_range),
|
||||
relatedness,
|
||||
span,
|
||||
loc_range.clone(),
|
||||
protected,
|
||||
)
|
||||
.map_err(|trans| {
|
||||
let node = args.nodes.get(args.idx).unwrap();
|
||||
TbError {
|
||||
conflicting_info: &node.debug_info,
|
||||
access_cause,
|
||||
alloc_id,
|
||||
error_offset: loc_range.start,
|
||||
error_kind: trans,
|
||||
accessed_info: None,
|
||||
}
|
||||
.build()
|
||||
})
|
||||
},
|
||||
|err| err.error_kind,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
// This is for the special access when a protector gets released.
|
||||
// Wildcard pointers are never protected, so this is unreachable.
|
||||
unreachable!()
|
||||
};
|
||||
interp_ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn default_location_state(&self) -> LocationState {
|
||||
LocationState::new_non_accessed(
|
||||
|
|
@ -1071,7 +1281,15 @@ impl VisitProvenance for Tree {
|
|||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
// To ensure that the root never gets removed, we visit it
|
||||
// (the `root` node of `Tree` is not an `Option<_>`)
|
||||
visit(None, Some(self.nodes.get(self.root).unwrap().tag))
|
||||
visit(None, Some(self.nodes.get(self.root).unwrap().tag));
|
||||
|
||||
// We also need to keep around any exposed tags through which
|
||||
// an access could still happen.
|
||||
for (_id, node) in self.nodes.iter() {
|
||||
if node.is_exposed {
|
||||
visit(None, Some(node.tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::mem;
|
||||
|
||||
|
|
@ -20,10 +21,15 @@ use rustc_data_structures::fx::FxHashMap;
|
|||
use crate::helpers::ToUsize;
|
||||
|
||||
/// Intermediate key between a UniKeyMap and a UniValMap.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct UniIndex {
|
||||
idx: u32,
|
||||
}
|
||||
impl Debug for UniIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.idx.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// From K to UniIndex
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
|
@ -201,6 +207,19 @@ impl<V> UniValMap<V> {
|
|||
mem::swap(&mut res, &mut self.data[idx.idx.to_usize()]);
|
||||
res
|
||||
}
|
||||
|
||||
/// Returns true if the map is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.iter().all(|v| v.is_none())
|
||||
}
|
||||
|
||||
/// Iterates over all key-value pairs in the map.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (UniIndex, &V)> {
|
||||
self.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, v)| v.as_ref().map(|r| (UniIndex { idx: i.try_into().unwrap() }, r)))
|
||||
}
|
||||
}
|
||||
|
||||
/// An access to a single value of the map.
|
||||
|
|
|
|||
519
src/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs
Normal file
519
src/tools/miri/src/borrow_tracker/tree_borrows/wildcard.rs
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
use std::cmp::max;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::Tree;
|
||||
use super::tree::{AccessRelatedness, Node};
|
||||
use super::unimap::{UniIndex, UniValMap};
|
||||
use crate::BorTag;
|
||||
use crate::borrow_tracker::AccessKind;
|
||||
#[cfg(feature = "expensive-consistency-checks")]
|
||||
use crate::borrow_tracker::GlobalState;
|
||||
|
||||
/// Represents the maximum access level that is possible.
|
||||
///
|
||||
/// Note that we derive Ord and PartialOrd, so the order in which variants are listed below matters:
|
||||
/// None < Read < Write. Do not change that order.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||
pub enum WildcardAccessLevel {
|
||||
#[default]
|
||||
None,
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
/// Where the access happened relative to the current node.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum WildcardAccessRelatedness {
|
||||
/// The access definitively happened through a local node.
|
||||
LocalAccess,
|
||||
/// The access definitively happened through a foreign node.
|
||||
ForeignAccess,
|
||||
/// We do not know if the access is foreign or local.
|
||||
EitherAccess,
|
||||
}
|
||||
impl WildcardAccessRelatedness {
|
||||
pub fn to_relatedness(self) -> Option<AccessRelatedness> {
|
||||
match self {
|
||||
Self::LocalAccess => Some(AccessRelatedness::LocalAccess),
|
||||
Self::ForeignAccess => Some(AccessRelatedness::ForeignAccess),
|
||||
Self::EitherAccess => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// State per location per node keeping track of where relative to this
|
||||
/// node exposed nodes are and what access permissions they have.
|
||||
///
|
||||
/// Designed to be completely determined by its parent, siblings and
|
||||
/// direct children's max_local_access/max_foreign_access.
|
||||
#[derive(Clone, Default, PartialEq, Eq)]
|
||||
pub struct WildcardState {
|
||||
/// How many of this node's direct children have `max_local_access()==Write`.
|
||||
child_writes: u16,
|
||||
/// How many of this node's direct children have `max_local_access()>=Read`.
|
||||
child_reads: u16,
|
||||
/// The maximum access level that could happen from an exposed node
|
||||
/// that is foreign to this node.
|
||||
///
|
||||
/// This is calculated as the `max()` of the parent's `max_foreign_access`,
|
||||
/// `exposed_as` and the siblings' `max_local_access()`.
|
||||
max_foreign_access: WildcardAccessLevel,
|
||||
/// At what access level this node itself is exposed.
|
||||
exposed_as: WildcardAccessLevel,
|
||||
}
|
||||
impl Debug for WildcardState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("WildcardState")
|
||||
.field("child_r/w", &(self.child_reads, self.child_writes))
|
||||
.field("foreign", &self.max_foreign_access)
|
||||
.field("exposed_as", &self.exposed_as)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl WildcardState {
|
||||
/// The maximum access level that could happen from an exposed
|
||||
/// node that is local to this node.
|
||||
fn max_local_access(&self) -> WildcardAccessLevel {
|
||||
use WildcardAccessLevel::*;
|
||||
max(
|
||||
self.exposed_as,
|
||||
if self.child_writes > 0 {
|
||||
Write
|
||||
} else if self.child_reads > 0 {
|
||||
Read
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// From where relative to the node with this wildcard info a read or write access could happen.
|
||||
pub fn access_relatedness(&self, kind: AccessKind) -> Option<WildcardAccessRelatedness> {
|
||||
match kind {
|
||||
AccessKind::Read => self.read_access_relatedness(),
|
||||
AccessKind::Write => self.write_access_relatedness(),
|
||||
}
|
||||
}
|
||||
|
||||
/// From where relative to the node with this wildcard info a read access could happen.
|
||||
fn read_access_relatedness(&self) -> Option<WildcardAccessRelatedness> {
|
||||
let has_foreign = self.max_foreign_access >= WildcardAccessLevel::Read;
|
||||
let has_local = self.max_local_access() >= WildcardAccessLevel::Read;
|
||||
use WildcardAccessRelatedness as E;
|
||||
match (has_foreign, has_local) {
|
||||
(true, true) => Some(E::EitherAccess),
|
||||
(true, false) => Some(E::ForeignAccess),
|
||||
(false, true) => Some(E::LocalAccess),
|
||||
(false, false) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// From where relative to the node with this wildcard info a write access could happen.
|
||||
fn write_access_relatedness(&self) -> Option<WildcardAccessRelatedness> {
|
||||
let has_foreign = self.max_foreign_access == WildcardAccessLevel::Write;
|
||||
let has_local = self.max_local_access() == WildcardAccessLevel::Write;
|
||||
use WildcardAccessRelatedness as E;
|
||||
match (has_foreign, has_local) {
|
||||
(true, true) => Some(E::EitherAccess),
|
||||
(true, false) => Some(E::ForeignAccess),
|
||||
(false, true) => Some(E::LocalAccess),
|
||||
(false, false) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the access tracking information for a new child node of a parent with this
|
||||
/// wildcard info.
|
||||
/// The new node doesn't have any child reads/writes, but calculates `max_foreign_access`
|
||||
/// from its parent.
|
||||
pub fn for_new_child(&self) -> Self {
|
||||
Self {
|
||||
max_foreign_access: max(self.max_foreign_access, self.max_local_access()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes the nodes of `children` onto the stack who's `max_foreign_access`
|
||||
/// needs to be updated.
|
||||
///
|
||||
/// * `children`: A list of nodes with the same parent. `children` doesn't
|
||||
/// necessarily have to contain all children of parent, but can just be
|
||||
/// a subset.
|
||||
///
|
||||
/// * `child_reads`, `child_writes`: How many of `children` have `max_local_access()`
|
||||
/// of at least `read`/`write`
|
||||
///
|
||||
/// * `new_foreign_access`, `old_foreign_access`:
|
||||
/// The max possible access level that is foreign to all `children`
|
||||
/// (i.e., it is not local to *any* of them).
|
||||
/// This can be calculated as the max of the parent's `exposed_as()`, `max_foreign_access`
|
||||
/// and of all `max_local_access()` of any nodes with the same parent that are
|
||||
/// not listed in `children`.
|
||||
///
|
||||
/// This access level changed from `old` to `new`, which is why we need to
|
||||
/// update `children`.
|
||||
fn push_relevant_children(
|
||||
stack: &mut Vec<(UniIndex, WildcardAccessLevel)>,
|
||||
new_foreign_access: WildcardAccessLevel,
|
||||
old_foreign_access: WildcardAccessLevel,
|
||||
child_reads: u16,
|
||||
child_writes: u16,
|
||||
children: impl Iterator<Item = UniIndex>,
|
||||
|
||||
wildcard_accesses: &UniValMap<WildcardState>,
|
||||
) {
|
||||
use WildcardAccessLevel::*;
|
||||
|
||||
// Nothing changed so we don't need to update anything.
|
||||
if new_foreign_access == old_foreign_access {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to consider that the children's `max_local_access()` affect each
|
||||
// other's `max_foreign_access`, but do not affect their own `max_foreign_access`.
|
||||
|
||||
// The new `max_foreign_acces` for children with `max_local_access()==Write`.
|
||||
let write_foreign_access = max(
|
||||
new_foreign_access,
|
||||
if child_writes > 1 {
|
||||
// There exists at least one more child with exposed write access.
|
||||
// This means that a foreign write through that node is possible.
|
||||
Write
|
||||
} else if child_reads > 1 {
|
||||
// There exists at least one more child with exposed read access,
|
||||
// but no other with write access.
|
||||
// This means that a foreign read but no write through that node
|
||||
// is possible.
|
||||
Read
|
||||
} else {
|
||||
// There are no other nodes with read or write access.
|
||||
// This means no foreign writes through other children are possible.
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
// The new `max_foreign_acces` for children with `max_local_access()==Read`.
|
||||
let read_foreign_access = max(
|
||||
new_foreign_access,
|
||||
if child_writes > 0 {
|
||||
// There exists at least one child with write access (and it's not this one).
|
||||
Write
|
||||
} else if child_reads > 1 {
|
||||
// There exists at least one more child with exposed read access,
|
||||
// but no other with write access.
|
||||
Read
|
||||
} else {
|
||||
// There are no other nodes with read or write access,
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
// The new `max_foreign_acces` for children with `max_local_access()==None`.
|
||||
let none_foreign_access = max(
|
||||
new_foreign_access,
|
||||
if child_writes > 0 {
|
||||
// There exists at least one child with write access (and it's not this one).
|
||||
Write
|
||||
} else if child_reads > 0 {
|
||||
// There exists at least one child with read access (and it's not this one),
|
||||
// but none with write access.
|
||||
Read
|
||||
} else {
|
||||
// No children are exposed as read or write.
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
stack.extend(children.filter_map(|child| {
|
||||
let state = wildcard_accesses.get(child).cloned().unwrap_or_default();
|
||||
|
||||
let new_foreign_access = match state.max_local_access() {
|
||||
Write => write_foreign_access,
|
||||
Read => read_foreign_access,
|
||||
None => none_foreign_access,
|
||||
};
|
||||
|
||||
if new_foreign_access != state.max_foreign_access {
|
||||
Some((child, new_foreign_access))
|
||||
} else {
|
||||
Option::None
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/// Update the tracking information of a tree, to reflect that the node specified by `id` is
|
||||
/// now exposed with `new_exposed_as`.
|
||||
///
|
||||
/// Propagates the Willard access information over the tree. This needs to be called every
|
||||
/// time the access level of an exposed node changes, to keep the state in sync with
|
||||
/// the rest of the tree.
|
||||
pub fn update_exposure(
|
||||
id: UniIndex,
|
||||
new_exposed_as: WildcardAccessLevel,
|
||||
nodes: &UniValMap<Node>,
|
||||
wildcard_accesses: &mut UniValMap<WildcardState>,
|
||||
) {
|
||||
let mut entry = wildcard_accesses.entry(id);
|
||||
let src_state = entry.or_insert(Default::default());
|
||||
let old_exposed_as = src_state.exposed_as;
|
||||
|
||||
// If the exposure doesn't change, then we don't need to update anything.
|
||||
if old_exposed_as == new_exposed_as {
|
||||
return;
|
||||
}
|
||||
|
||||
let src_old_local_access = src_state.max_local_access();
|
||||
|
||||
src_state.exposed_as = new_exposed_as;
|
||||
|
||||
let src_new_local_access = src_state.max_local_access();
|
||||
|
||||
// Stack of nodes for which the max_foreign_access field needs to be updated.
|
||||
// Will be filled with the children of this node and its parents children before
|
||||
// we begin downwards traversal.
|
||||
let mut stack: Vec<(UniIndex, WildcardAccessLevel)> = Vec::new();
|
||||
|
||||
// Add the direct children of this node to the stack.
|
||||
{
|
||||
let node = nodes.get(id).unwrap();
|
||||
Self::push_relevant_children(
|
||||
&mut stack,
|
||||
// new_foreign_access
|
||||
max(src_state.max_foreign_access, new_exposed_as),
|
||||
// old_foreign_access
|
||||
max(src_state.max_foreign_access, old_exposed_as),
|
||||
// Consider all children.
|
||||
src_state.child_reads,
|
||||
src_state.child_writes,
|
||||
node.children.iter().copied(),
|
||||
wildcard_accesses,
|
||||
);
|
||||
}
|
||||
// We need to propagate the tracking info up the tree, for this we traverse
|
||||
// up the parents.
|
||||
// We can skip propagating info to the parent and siblings of a node if its
|
||||
// access didn't change.
|
||||
{
|
||||
// The child from which we came.
|
||||
let mut child = id;
|
||||
// This is the `max_local_access()` of the child we came from, before
|
||||
// this update...
|
||||
let mut old_child_access = src_old_local_access;
|
||||
// and after this update.
|
||||
let mut new_child_access = src_new_local_access;
|
||||
while let Some(parent_id) = nodes.get(child).unwrap().parent {
|
||||
let parent_node = nodes.get(parent_id).unwrap();
|
||||
let mut entry = wildcard_accesses.entry(parent_id);
|
||||
let parent_state = entry.or_insert(Default::default());
|
||||
|
||||
let old_parent_local_access = parent_state.max_local_access();
|
||||
use WildcardAccessLevel::*;
|
||||
// Updating this node's tracking state for its children.
|
||||
match (old_child_access, new_child_access) {
|
||||
(None | Read, Write) => parent_state.child_writes += 1,
|
||||
(Write, None | Read) => parent_state.child_writes -= 1,
|
||||
_ => {}
|
||||
}
|
||||
match (old_child_access, new_child_access) {
|
||||
(None, Read | Write) => parent_state.child_reads += 1,
|
||||
(Read | Write, None) => parent_state.child_reads -= 1,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let new_parent_local_access = parent_state.max_local_access();
|
||||
|
||||
{
|
||||
// We need to update the `max_foreign_access` of `child`'s
|
||||
// siblings. For this we can reuse the `push_relevant_children`
|
||||
// function.
|
||||
//
|
||||
// We pass it just the siblings without child itself. Since
|
||||
// `child`'s `max_local_access()` is foreign to all of its
|
||||
// siblings we can pass it as part of the foreign access.
|
||||
|
||||
let parent_access =
|
||||
max(parent_state.exposed_as, parent_state.max_foreign_access);
|
||||
// This is how many of `child`'s siblings have read/write local access.
|
||||
// If `child` itself has access, then we need to subtract its access from the count.
|
||||
let sibling_reads =
|
||||
parent_state.child_reads - if new_child_access >= Read { 1 } else { 0 };
|
||||
let sibling_writes =
|
||||
parent_state.child_writes - if new_child_access >= Write { 1 } else { 0 };
|
||||
Self::push_relevant_children(
|
||||
&mut stack,
|
||||
// new_foreign_access
|
||||
max(parent_access, new_child_access),
|
||||
// old_foreign_access
|
||||
max(parent_access, old_child_access),
|
||||
// Consider only siblings of child.
|
||||
sibling_reads,
|
||||
sibling_writes,
|
||||
parent_node.children.iter().copied().filter(|id| child != *id),
|
||||
wildcard_accesses,
|
||||
);
|
||||
}
|
||||
if old_parent_local_access == new_parent_local_access {
|
||||
// We didn't change `max_local_access()` for parent, so we don't need to propagate further upwards.
|
||||
break;
|
||||
}
|
||||
|
||||
old_child_access = old_parent_local_access;
|
||||
new_child_access = new_parent_local_access;
|
||||
child = parent_id;
|
||||
}
|
||||
}
|
||||
// Traverses down the tree to update max_foreign_access fields of children and cousins who need to be updated.
|
||||
while let Some((id, new_access)) = stack.pop() {
|
||||
let node = nodes.get(id).unwrap();
|
||||
let mut entry = wildcard_accesses.entry(id);
|
||||
let state = entry.or_insert(Default::default());
|
||||
|
||||
let old_access = state.max_foreign_access;
|
||||
state.max_foreign_access = new_access;
|
||||
|
||||
Self::push_relevant_children(
|
||||
&mut stack,
|
||||
// new_foreign_access
|
||||
max(state.exposed_as, new_access),
|
||||
// old_foreign_access
|
||||
max(state.exposed_as, old_access),
|
||||
// Consider all children.
|
||||
state.child_reads,
|
||||
state.child_writes,
|
||||
node.children.iter().copied(),
|
||||
wildcard_accesses,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
/// Marks the tag as exposed & updates the wildcard tracking data structure
|
||||
/// to represent its access level.
|
||||
/// Also takes as an argument whether the tag is protected or not.
|
||||
pub fn expose_tag(&mut self, tag: BorTag, protected: bool) {
|
||||
let id = self.tag_mapping.get(&tag).unwrap();
|
||||
let node = self.nodes.get_mut(id).unwrap();
|
||||
node.is_exposed = true;
|
||||
let node = self.nodes.get(id).unwrap();
|
||||
|
||||
// When the first tag gets exposed then we initialize the
|
||||
// wildcard state for every node and location in the tree.
|
||||
for (_, loc) in self.locations.iter_mut_all() {
|
||||
let perm = loc
|
||||
.perms
|
||||
.get(id)
|
||||
.map(|p| p.permission())
|
||||
.unwrap_or_else(|| node.default_location_state().permission());
|
||||
|
||||
let access_type = perm.strongest_allowed_child_access(protected);
|
||||
WildcardState::update_exposure(
|
||||
id,
|
||||
access_type,
|
||||
&self.nodes,
|
||||
&mut loc.wildcard_accesses,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// This updates the wildcard tracking data structure to reflect the release of
|
||||
/// the protector on `tag`.
|
||||
pub(super) fn update_exposure_for_protector_release(&mut self, tag: BorTag) {
|
||||
let idx = self.tag_mapping.get(&tag).unwrap();
|
||||
|
||||
// We check if the node is already exposed, as we don't want to expose any
|
||||
// nodes which aren't already exposed.
|
||||
|
||||
if self.nodes.get(idx).unwrap().is_exposed {
|
||||
// Updates the exposure to the new permission on every location.
|
||||
self.expose_tag(tag, /* protected */ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "expensive-consistency-checks")]
|
||||
impl Tree {
|
||||
/// Checks that the wildcard tracking data structure is internally consistent and
|
||||
/// has the correct `exposed_as` values.
|
||||
pub fn verify_wildcard_consistency(&self, global: &GlobalState) {
|
||||
let protected_tags = &global.borrow().protected_tags;
|
||||
for (_, loc) in self.locations.iter_all() {
|
||||
let wildcard_accesses = &loc.wildcard_accesses;
|
||||
let perms = &loc.perms;
|
||||
// Checks if accesses is empty.
|
||||
if wildcard_accesses.is_empty() {
|
||||
return;
|
||||
}
|
||||
for (id, node) in self.nodes.iter() {
|
||||
let state = wildcard_accesses.get(id).unwrap();
|
||||
|
||||
let expected_exposed_as = if node.is_exposed {
|
||||
let perm = perms.get(id).unwrap();
|
||||
|
||||
perm.permission()
|
||||
.strongest_allowed_child_access(protected_tags.contains_key(&node.tag))
|
||||
} else {
|
||||
WildcardAccessLevel::None
|
||||
};
|
||||
|
||||
// The foreign wildcard accesses possible at a node are determined by which
|
||||
// accesses can originate from their siblings, their parent, and from above
|
||||
// their parent.
|
||||
let expected_max_foreign_access = if let Some(parent) = node.parent {
|
||||
let parent_node = self.nodes.get(parent).unwrap();
|
||||
let parent_state = wildcard_accesses.get(parent).unwrap();
|
||||
|
||||
let max_sibling_access = parent_node
|
||||
.children
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|child| *child != id)
|
||||
.map(|child| {
|
||||
let state = wildcard_accesses.get(child).unwrap();
|
||||
state.max_local_access()
|
||||
})
|
||||
.fold(WildcardAccessLevel::None, max);
|
||||
|
||||
max_sibling_access
|
||||
.max(parent_state.max_foreign_access)
|
||||
.max(parent_state.exposed_as)
|
||||
} else {
|
||||
WildcardAccessLevel::None
|
||||
};
|
||||
|
||||
// Count how many children can be the source of wildcard reads or writes
|
||||
// (either directly, or via their children).
|
||||
let child_accesses = node.children.iter().copied().map(|child| {
|
||||
let state = wildcard_accesses.get(child).unwrap();
|
||||
state.max_local_access()
|
||||
});
|
||||
let expected_child_reads =
|
||||
child_accesses.clone().filter(|a| *a >= WildcardAccessLevel::Read).count();
|
||||
let expected_child_writes =
|
||||
child_accesses.filter(|a| *a >= WildcardAccessLevel::Write).count();
|
||||
|
||||
assert_eq!(
|
||||
expected_exposed_as, state.exposed_as,
|
||||
"tag {:?} (id:{id:?}) should be exposed as {expected_exposed_as:?} but is exposed as {:?}",
|
||||
node.tag, state.exposed_as
|
||||
);
|
||||
assert_eq!(
|
||||
expected_max_foreign_access, state.max_foreign_access,
|
||||
"expected {:?}'s (id:{id:?}) max_foreign_access to be {:?} instead of {:?}",
|
||||
node.tag, expected_max_foreign_access, state.max_foreign_access
|
||||
);
|
||||
let child_reads: usize = state.child_reads.into();
|
||||
assert_eq!(
|
||||
expected_child_reads, child_reads,
|
||||
"expected {:?}'s (id:{id:?}) child_reads to be {} instead of {}",
|
||||
node.tag, expected_child_reads, child_reads
|
||||
);
|
||||
let child_writes: usize = state.child_writes.into();
|
||||
assert_eq!(
|
||||
expected_child_writes, child_writes,
|
||||
"expected {:?}'s (id:{id:?}) child_writes to be {} instead of {}",
|
||||
node.tag, expected_child_writes, child_writes
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/tools/miri/tests/fail/tree_borrows/wildcard/dealloc.rs
Normal file
21
src/tools/miri/tests/fail/tree_borrows/wildcard/dealloc.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks that deallocation through a wildcard ref fails,
|
||||
/// if all exposed references are disabled.
|
||||
pub fn main() {
|
||||
use std::alloc::Layout;
|
||||
let x = unsafe { std::alloc::alloc_zeroed(Layout::new::<u32>()) as *mut u32 };
|
||||
|
||||
let ref1 = unsafe { &mut *x };
|
||||
let ref2 = unsafe { &mut *x };
|
||||
|
||||
let int = ref1 as *mut u32 as usize;
|
||||
let wild = int as *mut u32;
|
||||
// Disables ref1 and therefore also wild.
|
||||
*ref2 = 14;
|
||||
|
||||
// Tries to dealloc through a wildcard reference even though all exposed
|
||||
// references are disabled.
|
||||
|
||||
unsafe { std::alloc::dealloc(wild as *mut u8, Layout::new::<u32>()) }; //~ ERROR: /deallocation through <wildcard> .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
error: Undefined Behavior: deallocation through <wildcard> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/dealloc.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { std::alloc::dealloc(wild as *mut u8, Layout::new::<u32>()) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: there are no exposed tags which may perform this access here
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
20
src/tools/miri/tests/fail/tree_borrows/wildcard/gc.rs
Normal file
20
src/tools/miri/tests/fail/tree_borrows/wildcard/gc.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
#[path = "../../../utils/mod.rs"]
|
||||
mod utils;
|
||||
|
||||
/// Checks that the garbage collector doesn't remove any exposed tags.
|
||||
fn main() {
|
||||
let mut _x: u32 = 4;
|
||||
let int = {
|
||||
let y = &_x;
|
||||
y as *const u32 as usize
|
||||
};
|
||||
// If y wasn't exposed, this would gc it.
|
||||
utils::run_provenance_gc();
|
||||
// This should disable y.
|
||||
_x = 5;
|
||||
let wild = int as *const u32;
|
||||
|
||||
let _fail = unsafe { *wild }; //~ ERROR: /read access through <wildcard> at .* is forbidden/
|
||||
}
|
||||
14
src/tools/miri/tests/fail/tree_borrows/wildcard/gc.stderr
Normal file
14
src/tools/miri/tests/fail/tree_borrows/wildcard/gc.stderr
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
error: Undefined Behavior: read access through <wildcard> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/gc.rs:LL:CC
|
||||
|
|
||||
LL | let _fail = unsafe { *wild };
|
||||
| ^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: there are no exposed tags which may perform this access here
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks that disabling an exposed reference correctly narrows the
|
||||
/// possible locations a wildcard access could happen from.
|
||||
/// Also checks that an access is treated as foreign, if all exposed
|
||||
/// (non-disabled) references are ancestors.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ref1 = &mut x;
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
|
||||
let ref2 = &mut *ref1;
|
||||
|
||||
let ref3 = &mut *ref2;
|
||||
let _int3 = ref3 as *mut u32 as usize;
|
||||
|
||||
// Write through ref3 so that all references are active.
|
||||
*ref3 = 43;
|
||||
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref1(Act)* │
|
||||
// │ │
|
||||
// └──────┬─────┘
|
||||
// │
|
||||
// │
|
||||
// ▼
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref2(Act) │
|
||||
// │ │
|
||||
// └──────┬─────┘
|
||||
// │
|
||||
// │
|
||||
// ▼
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref3(Act)* │
|
||||
// │ │
|
||||
// └────────────┘
|
||||
|
||||
// Writes through either ref1 or ref3, which is either a child or foreign
|
||||
// access to ref2.
|
||||
unsafe { wild.write(42) };
|
||||
|
||||
// Reading from ref2 still works, since the previous access could have been
|
||||
// through its child.
|
||||
// This also freezes ref3.
|
||||
let _x = *ref2;
|
||||
|
||||
// We can still write through wild, as there is still the exposed ref1 with
|
||||
// write permissions under proper exposed provenance, this would be UB as the
|
||||
// only tag wild can assume to not invalidate ref2 is ref3, which we just
|
||||
// invalidated.
|
||||
//
|
||||
// This disables ref2, ref3.
|
||||
unsafe { wild.write(43) };
|
||||
|
||||
// Fails because ref2 is disabled.
|
||||
let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_child.rs:LL:CC
|
||||
|
|
||||
LL | let _fail = *ref2;
|
||||
| ^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
|
||||
help: the accessed tag <TAG> was created here, in the initial state Reserved
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_child.rs:LL:CC
|
||||
|
|
||||
LL | let ref2 = &mut *ref1;
|
||||
| ^^^^^^^^^^
|
||||
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_child.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { wild.write(43) };
|
||||
| ^^^^^^^^^^^^^^
|
||||
= help: this transition corresponds to a loss of read and write permissions
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks if we correctly determine the correct exposed reference a write
|
||||
/// access could happen through,
|
||||
/// if there are also exposed reference through which only a read could happen.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ref1 = &mut x;
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
|
||||
let ref2 = &mut *ref1;
|
||||
|
||||
let ref3 = &*ref2;
|
||||
let _int3 = ref3 as *const u32 as usize;
|
||||
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref1(Res)* │
|
||||
// │ │
|
||||
// └──────┬─────┘
|
||||
// │
|
||||
// │
|
||||
// ▼
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref2(Res) │
|
||||
// │ │
|
||||
// └──────┬─────┘
|
||||
// │
|
||||
// │
|
||||
// ▼
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref3(Frz)* │
|
||||
// │ │
|
||||
// └────────────┘
|
||||
|
||||
// Writes through ref1 as we cannot write through ref3 since it's frozen.
|
||||
// Disables ref2, ref3.
|
||||
unsafe { wild.write(42) };
|
||||
|
||||
// ref2 is disabled.
|
||||
let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs:LL:CC
|
||||
|
|
||||
LL | let _fail = *ref2;
|
||||
| ^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
|
||||
help: the accessed tag <TAG> was created here, in the initial state Reserved
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs:LL:CC
|
||||
|
|
||||
LL | let ref2 = &mut *ref1;
|
||||
| ^^^^^^^^^^
|
||||
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_child_unique_writer.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { wild.write(42) };
|
||||
| ^^^^^^^^^^^^^^
|
||||
= help: this transition corresponds to a loss of read and write permissions
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks with multiple exposed nodes, that if they are all disabled
|
||||
/// then no wildcard accesses are possible.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
let ref3 = unsafe { &mut *ptr_base };
|
||||
|
||||
// Both references get exposed.
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
let _int2 = ref2 as *mut u32 as usize;
|
||||
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ptr_base ├──────────────┬───────────────────┐
|
||||
// │ │ │ │
|
||||
// └──────┬─────┘ │ │
|
||||
// │ │ │
|
||||
// │ │ │
|
||||
// ▼ ▼ ▼
|
||||
// ┌────────────┐ ┌────────────┐ ┌───────────┐
|
||||
// │ │ │ │ │ │
|
||||
// │ ref1(Res)* │ │ ref2(Res)* │ │ ref3(Res) │
|
||||
// │ │ │ │ │ │
|
||||
// └────────────┘ └────────────┘ └───────────┘
|
||||
|
||||
// Disables ref1,ref2.
|
||||
*ref3 = 13;
|
||||
|
||||
// Both exposed references are disabled so this fails.
|
||||
let _fail = unsafe { *wild }; //~ ERROR: /read access through .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
error: Undefined Behavior: read access through <wildcard> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_disable.rs:LL:CC
|
||||
|
|
||||
LL | let _fail = unsafe { *wild };
|
||||
| ^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: there are no exposed tags which may perform this access here
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks that if for a node all exposed references are foreign,
|
||||
/// that a wildcard access gets treated as foreign to it.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
let ref3 = unsafe { &mut *ptr_base };
|
||||
|
||||
// Both references get exposed.
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
let _int2 = ref2 as *mut u32 as usize;
|
||||
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ptr_base ├──────────────┬───────────────────┐
|
||||
// │ │ │ │
|
||||
// └──────┬─────┘ │ │
|
||||
// │ │ │
|
||||
// │ │ │
|
||||
// ▼ ▼ ▼
|
||||
// ┌────────────┐ ┌────────────┐ ┌───────────┐
|
||||
// │ │ │ │ │ │
|
||||
// │ ref1(Res)* │ │ ref2(Res)* │ │ ref3(Res) │
|
||||
// │ │ │ │ │ │
|
||||
// └────────────┘ └────────────┘ └───────────┘
|
||||
|
||||
// Disables ref3 as both exposed pointers are foreign to it.
|
||||
unsafe { wild.write(13) };
|
||||
|
||||
// Fails because ref3 is disabled.
|
||||
let _fail = *ref3; //~ ERROR: /read access through .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs:LL:CC
|
||||
|
|
||||
LL | let _fail = *ref3;
|
||||
| ^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
|
||||
help: the accessed tag <TAG> was created here, in the initial state Reserved
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs:LL:CC
|
||||
|
|
||||
LL | let ref3 = unsafe { &mut *ptr_base };
|
||||
| ^^^^^^^^^^^^^^
|
||||
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_foreign.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { wild.write(13) };
|
||||
| ^^^^^^^^^^^^^^
|
||||
= help: this transition corresponds to a loss of read and write permissions
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks if a local access gets correctly triggered, if we know that
|
||||
/// all exposed references are local to a node.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
|
||||
// Both references get exposed.
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
let _int2 = ref2 as *mut u32 as usize;
|
||||
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// Activates ptr_base.
|
||||
unsafe { wild.write(41) };
|
||||
|
||||
// ┌─────────────┐
|
||||
// │ │
|
||||
// │ x (Act) │
|
||||
// │ │
|
||||
// └──────┬──────┘
|
||||
// │
|
||||
// │
|
||||
// ▼
|
||||
// ┌────────────────┐
|
||||
// │ │
|
||||
// │ ptr_base (Act) ├──────────┐
|
||||
// │ │ │
|
||||
// └──────┬─────────┘ │
|
||||
// │ │
|
||||
// │ │
|
||||
// ▼ ▼
|
||||
// ┌────────────┐ ┌────────────┐
|
||||
// │ │ │ │
|
||||
// │ ref1(Res)* │ │ ref2(Res)* │
|
||||
// │ │ │ │
|
||||
// └────────────┘ └────────────┘
|
||||
|
||||
// We read from x causing a foreign access to ptr_base, freezing it
|
||||
// (as the previous wildcard access has made it active).
|
||||
let _y = x;
|
||||
|
||||
// While both exposed references are still enabled for writes, any write
|
||||
// through them would cause UB at ptr_base.
|
||||
unsafe { wild.write(0) }; //~ ERROR: /write access through <wildcard> at .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
error: Undefined Behavior: write access through <wildcard> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { wild.write(0) };
|
||||
| ^^^^^^^^^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: the accessed tag <wildcard> is a child of the conflicting tag <TAG>
|
||||
= help: the conflicting tag <TAG> has state Frozen which forbids this child write access
|
||||
help: the conflicting tag <TAG> was created here, in the initial state Reserved
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC
|
||||
|
|
||||
LL | let ptr_base = &mut x as *mut u32;
|
||||
| ^^^^^^
|
||||
help: the conflicting tag <TAG> later transitioned to Unique due to a child write access at offsets [0x0..0x4]
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { wild.write(41) };
|
||||
| ^^^^^^^^^^^^^^
|
||||
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
|
||||
help: the conflicting tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4]
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_local.rs:LL:CC
|
||||
|
|
||||
LL | let _y = x;
|
||||
| ^
|
||||
= help: this transition corresponds to a loss of write permissions
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks if we correctly determine the correct exposed reference a write
|
||||
/// access could happen through, if there are also exposed reference
|
||||
/// through which only a read access could happen.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &*ptr_base };
|
||||
|
||||
// Both references get exposed.
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
let _int2 = ref2 as *const u32 as usize;
|
||||
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ptr_base ├──────────────┐
|
||||
// │ │ │
|
||||
// └──────┬─────┘ │
|
||||
// │ │
|
||||
// │ │
|
||||
// ▼ ▼
|
||||
// ┌────────────┐ ┌────────────┐
|
||||
// │ │ │ │
|
||||
// │ ref1(Res)* │ │ ref2(Frz)* │
|
||||
// │ │ │ │
|
||||
// └────────────┘ └────────────┘
|
||||
|
||||
// Disables ref2 as the only write could happen through ref1.
|
||||
unsafe { wild.write(13) };
|
||||
|
||||
// Fails because ref2 is disabled.
|
||||
let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs:LL:CC
|
||||
|
|
||||
LL | let _fail = *ref2;
|
||||
| ^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
|
||||
help: the accessed tag <TAG> was created here, in the initial state Frozen
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs:LL:CC
|
||||
|
|
||||
LL | let ref2 = unsafe { &*ptr_base };
|
||||
| ^^^^^^^^^^
|
||||
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
|
||||
--> tests/fail/tree_borrows/wildcard/multi_exposed_siblings_unique_writer.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { wild.write(13) };
|
||||
| ^^^^^^^^^^^^^^
|
||||
= help: this transition corresponds to a loss of read permissions
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks that wildcard accesses correctly infers the allowed permissions
|
||||
/// on protected conflicted pointers.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
|
||||
let protect = |arg: &mut u32| {
|
||||
// Expose arg.
|
||||
let int = arg as *mut u32 as usize;
|
||||
let wild = int as *mut u32;
|
||||
|
||||
// Does a foreign read to arg marking it as conflicted and making child
|
||||
// writes UB while it's protected.
|
||||
let _x = *ref2;
|
||||
|
||||
// The only exposed reference (arg) doesn't allow child writes, so this is UB.
|
||||
unsafe { *wild = 4 }; //~ ERROR: /write access through <wildcard> at .* is forbidden/
|
||||
};
|
||||
|
||||
protect(ref1);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
error: Undefined Behavior: write access through <wildcard> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/protector_conflicted.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { *wild = 4 };
|
||||
| ^^^^^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: there are no exposed tags which may perform this access here
|
||||
= note: BACKTRACE:
|
||||
= note: inside closure at tests/fail/tree_borrows/wildcard/protector_conflicted.rs:LL:CC
|
||||
note: inside `main`
|
||||
--> tests/fail/tree_borrows/wildcard/protector_conflicted.rs:LL:CC
|
||||
|
|
||||
LL | protect(ref1);
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks that with only one exposed reference, that if this reference gets
|
||||
/// disabled no wildcard accesses are possible.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ptr_base ├───────────┐
|
||||
// │ │ │
|
||||
// └──────┬─────┘ │
|
||||
// │ │
|
||||
// │ │
|
||||
// ▼ ▼
|
||||
// ┌────────────┐ ┌───────────┐
|
||||
// │ │ │ │
|
||||
// │ ref1(Res)* │ │ ref2(Res) │
|
||||
// │ │ │ │
|
||||
// └────────────┘ └───────────┘
|
||||
|
||||
// Disables ref1.
|
||||
*ref2 = 13;
|
||||
|
||||
// Tries to do a wildcard access through the only exposed reference ref1,
|
||||
// which is disabled.
|
||||
let _fail = unsafe { *wild }; //~ ERROR: /read access through .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
error: Undefined Behavior: read access through <wildcard> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/single_exposed_disable.rs:LL:CC
|
||||
|
|
||||
LL | let _fail = unsafe { *wild };
|
||||
| ^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: there are no exposed tags which may perform this access here
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks that with only one exposed reference, wildcard accesses
|
||||
/// correctly cause foreign accesses.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ptr_base ├───────────┐
|
||||
// │ │ │
|
||||
// └──────┬─────┘ │
|
||||
// │ │
|
||||
// │ │
|
||||
// ▼ ▼
|
||||
// ┌────────────┐ ┌───────────┐
|
||||
// │ │ │ │
|
||||
// │ ref1(Res)* │ │ ref2(Res) │
|
||||
// │ │ │ │
|
||||
// └────────────┘ └───────────┘
|
||||
|
||||
// Write through the wildcard to the only exposed reference ref1,
|
||||
// disabling ref2.
|
||||
unsafe { wild.write(13) };
|
||||
|
||||
let _fail = *ref2; //~ ERROR: /read access through .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
error: Undefined Behavior: read access through <TAG> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs:LL:CC
|
||||
|
|
||||
LL | let _fail = *ref2;
|
||||
| ^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: the accessed tag <TAG> has state Disabled which forbids this child read access
|
||||
help: the accessed tag <TAG> was created here, in the initial state Reserved
|
||||
--> tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs:LL:CC
|
||||
|
|
||||
LL | let ref2 = unsafe { &mut *ptr_base };
|
||||
| ^^^^^^^^^^^^^^
|
||||
help: the accessed tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4]
|
||||
--> tests/fail/tree_borrows/wildcard/single_exposed_foreign.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { wild.write(13) };
|
||||
| ^^^^^^^^^^^^^^
|
||||
= help: this transition corresponds to a loss of read and write permissions
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// Checks if a local access gets correctly triggered during wildcard access,
|
||||
/// if we know that the only exposed reference is local to the node.
|
||||
pub fn main() {
|
||||
let mut x: u32 = 0;
|
||||
|
||||
let ref1 = &mut x;
|
||||
|
||||
let int = ref1 as *mut u32 as usize;
|
||||
let wild = int as *mut u32;
|
||||
|
||||
// Activates ref1.
|
||||
unsafe { wild.write(41) };
|
||||
|
||||
// Reads from x causing a foreign read on ref1, freezing it
|
||||
// (because it was active).
|
||||
let _y = x;
|
||||
|
||||
// The only exposed reference (ref1) is frozen, so wildcard writes are UB.
|
||||
unsafe { wild.write(0) }; //~ ERROR: /write access through <wildcard> at .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
error: Undefined Behavior: write access through <wildcard> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/single_exposed_local.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { wild.write(0) };
|
||||
| ^^^^^^^^^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: there are no exposed tags which may perform this access here
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
/// If we have only exposed read-only pointers, doing a write through a
|
||||
/// wildcard ptr should fail.
|
||||
fn main() {
|
||||
let mut x = 0;
|
||||
let _fool = &mut x as *mut i32; // this would have fooled the old untagged pointer logic
|
||||
let addr = (&x as *const i32).expose_provenance();
|
||||
let ptr = std::ptr::with_exposed_provenance_mut::<i32>(addr);
|
||||
unsafe { *ptr = 0 }; //~ ERROR: /write access through <wildcard> at .* is forbidden/
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
error: Undefined Behavior: write access through <wildcard> at ALLOC[0x0] is forbidden
|
||||
--> tests/fail/tree_borrows/wildcard/single_exposed_only_ro.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { *ptr = 0 };
|
||||
| ^^^^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: there are no exposed tags which may perform this access here
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
//@error-in-other-file: /deallocation through .* is forbidden/
|
||||
|
||||
fn inner(x: &mut i32, f: fn(usize)) {
|
||||
// `f` may mutate, but it may not deallocate!
|
||||
// `f` takes a raw pointer so that the only protector
|
||||
// is that on `x`
|
||||
f(x as *mut i32 as usize)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
inner(Box::leak(Box::new(0)), |raw| {
|
||||
drop(unsafe { Box::from_raw(raw as *mut i32) });
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
error: Undefined Behavior: deallocation through <wildcard> at ALLOC[0x0] is forbidden
|
||||
--> RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
|
|
||||
LL | self.1.deallocate(From::from(ptr.cast()), layout);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
|
||||
= help: the allocation of the accessed tag <wildcard> also contains the strongly protected tag <TAG>
|
||||
= help: the strongly protected tag <TAG> disallows deallocations
|
||||
help: the strongly protected tag <TAG> was created here, in the initial state Reserved
|
||||
--> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
|
||||
|
|
||||
LL | fn inner(x: &mut i32, f: fn(usize)) {
|
||||
| ^
|
||||
= note: BACKTRACE (of the first span):
|
||||
= note: inside `<std::boxed::Box<i32> as std::ops::Drop>::drop` at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||
= note: inside `std::ptr::drop_in_place::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
|
||||
= note: inside `std::mem::drop::<std::boxed::Box<i32>>` at RUSTLIB/core/src/mem/mod.rs:LL:CC
|
||||
note: inside closure
|
||||
--> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
|
||||
|
|
||||
LL | drop(unsafe { Box::from_raw(raw as *mut i32) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= note: inside `<{closure@tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC} as std::ops::FnOnce<(usize,)>>::call_once - shim` at RUSTLIB/core/src/ops/function.rs:LL:CC
|
||||
note: inside `inner`
|
||||
--> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
|
||||
|
|
||||
LL | f(x as *mut i32 as usize)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
note: inside `main`
|
||||
--> tests/fail/tree_borrows/wildcard/strongly_protected_wildcard.rs:LL:CC
|
||||
|
|
||||
LL | / inner(Box::leak(Box::new(0)), |raw| {
|
||||
LL | | drop(unsafe { Box::from_raw(raw as *mut i32) });
|
||||
LL | | });
|
||||
| |______^
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
//@revisions: stack tree
|
||||
//@compile-flags: -Zmiri-permissive-provenance
|
||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||
use std::ptr;
|
||||
|
||||
// Just to make sure that casting a ref to raw, to int and back to raw
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
//@revisions: stack tree
|
||||
//@compile-flags: -Zmiri-permissive-provenance
|
||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||
use std::cell::Cell;
|
||||
|
||||
fn main() {
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
warning: integer-to-pointer cast
|
||||
--> tests/pass/stacked_borrows/issue-miri-2389.rs:LL:CC
|
||||
|
|
||||
LL | let wildcard = &root0 as *const Cell<i32> as usize as *const Cell<i32>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
||||
|
|
||||
= help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program
|
||||
= help: see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation
|
||||
= help: to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead
|
||||
= help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics
|
||||
= help: alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning
|
||||
|
||||
145
src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs
Normal file
145
src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
// NOTE: This file documents UB that is not detected by wildcard provenance.
|
||||
|
||||
pub fn main() {
|
||||
uncertain_provenance();
|
||||
protected_exposed();
|
||||
protected_wildcard();
|
||||
}
|
||||
|
||||
/// Currently, if we do not know for a tag if an access is local or foreign,
|
||||
/// then we do not do any state transitions on that tag. However, to implement
|
||||
/// proper provenance we would have to instead pick the correct transition
|
||||
/// non-deterministically.
|
||||
///
|
||||
/// This test contains such UB that will not be detectable with wildcard provenance,
|
||||
/// but would be detectable if we implemented this behavior correctly.
|
||||
pub fn uncertain_provenance() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
// We create 2 mutable references, each with a unique tag.
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
|
||||
// Both references get exposed.
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
let _int2 = ref2 as *mut u32 as usize;
|
||||
//ref1 : Reserved
|
||||
//ref2 : Reserved
|
||||
|
||||
// We need to pick the "correct" tag for wild from the exposed tags.
|
||||
let wild = int1 as *mut u32;
|
||||
// wild=ref1 wild=ref2
|
||||
//ref1 : Reserved Reserved
|
||||
//ref2 : Reserved Reserved
|
||||
|
||||
// We write to wild, disabling the other tag.
|
||||
unsafe { wild.write(13) };
|
||||
// wild=ref1 wild=ref2
|
||||
//ref1 : Unique Disabled
|
||||
//ref2 : Disabled Unique
|
||||
|
||||
// We access both references, even though one of them should be
|
||||
// disabled under proper exposed provenance.
|
||||
// This is UB, however, wildcard provenance cannot detect this.
|
||||
assert_eq!(*ref1, 13);
|
||||
// wild=ref1 wild=ref2
|
||||
//ref1 : Unique UB
|
||||
//ref2 : Disabled Frozen
|
||||
assert_eq!(*ref2, 13);
|
||||
// wild=ref1 wild=ref2
|
||||
//ref1 : Frozen UB
|
||||
//ref2 : UB Frozen
|
||||
}
|
||||
|
||||
/// If a reference is protected, then all foreign writes to it cause UB.
|
||||
/// This effectively means any write needs to happen through a child of
|
||||
/// the protected reference.
|
||||
/// With this information we could further narrow the possible candidates
|
||||
/// for a wildcard write.
|
||||
/// However, currently tree borrows doesn't do this, so this test has UB
|
||||
/// that isn't detected.
|
||||
pub fn protected_exposed() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
|
||||
let _int2 = ref2 as *mut u32 as usize;
|
||||
|
||||
fn protect(ref3: &mut u32) {
|
||||
let int3 = ref3 as *mut u32 as usize;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ptr_base ├──────────────┐
|
||||
// │ │ │
|
||||
// └──────┬─────┘ │
|
||||
// │ │
|
||||
// │ │
|
||||
// ▼ ▼
|
||||
// ┌────────────┐ ┌────────────┐
|
||||
// │ │ │ │
|
||||
// │ ref1(Res) │ │ ref2(Res)* │
|
||||
// │ │ │ │
|
||||
// └──────┬─────┘ └────────────┘
|
||||
// │
|
||||
// │
|
||||
// ▼
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref3(Res)* │
|
||||
// │ │
|
||||
// └────────────┘
|
||||
|
||||
// Since ref3 is protected, we could know that every write from outside it will be UB.
|
||||
// This means we know that the access is through ref3, disabling ref2.
|
||||
let wild = int3 as *mut u32;
|
||||
unsafe { wild.write(13) }
|
||||
}
|
||||
protect(ref1);
|
||||
|
||||
// ref2 is disabled, so this read causes UB, but we currently don't protect this.
|
||||
let _fail = *ref2;
|
||||
}
|
||||
|
||||
/// Currently, we do not assign protectors to wildcard references.
|
||||
/// This test has UB because it does a foreign write to a protected reference.
|
||||
/// However, that reference is a wildcard, so this doesn't get detected.
|
||||
#[allow(unused_variables)]
|
||||
pub fn protected_wildcard() {
|
||||
let mut x: u32 = 32;
|
||||
let ref1 = &mut x;
|
||||
let ref2 = &mut *ref1;
|
||||
|
||||
let int = ref2 as *mut u32 as usize;
|
||||
let wild = int as *mut u32;
|
||||
let wild_ref = unsafe { &mut *wild };
|
||||
|
||||
let mut protect = |arg: &mut u32| {
|
||||
// arg is a protected pointer with wildcard provenance.
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref1(Res) │
|
||||
// │ │
|
||||
// └──────┬─────┘
|
||||
// │
|
||||
// │
|
||||
// ▼
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref2(Res)* │
|
||||
// │ │
|
||||
// └────────────┘
|
||||
|
||||
// Writes to ref1, disabling ref2, i.e. disabling all exposed references.
|
||||
// Since a wildcard reference is protected, this is UB. But we currently don't detect this.
|
||||
*ref1 = 13;
|
||||
};
|
||||
|
||||
// We pass a pointer with wildcard provenance to the function.
|
||||
protect(wild_ref);
|
||||
}
|
||||
171
src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs
Normal file
171
src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
|
||||
|
||||
pub fn main() {
|
||||
multiple_exposed_siblings();
|
||||
multiple_exposed_child();
|
||||
dealloc();
|
||||
protector();
|
||||
protector_conflicted_release();
|
||||
returned_mut_is_usable();
|
||||
}
|
||||
|
||||
/// Checks that an access through a wildcard reference
|
||||
/// doesn't disable any exposed references.
|
||||
/// It tests this with exposed references that are siblings of each other.
|
||||
pub fn multiple_exposed_siblings() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
|
||||
// Both references get exposed.
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
let _int2 = ref2 as *mut u32 as usize;
|
||||
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ptr_base ├────────────┐
|
||||
// │ │ │
|
||||
// └──────┬─────┘ │
|
||||
// │ │
|
||||
// │ │
|
||||
// ▼ ▼
|
||||
// ┌────────────┐ ┌────────────┐
|
||||
// │ │ │ │
|
||||
// │ ref1(Res)* │ │ ref2(Res)* │
|
||||
// │ │ │ │
|
||||
// └────────────┘ └────────────┘
|
||||
|
||||
// Writes through either of the two exposed references.
|
||||
// We do not know which so we cannot disable the other.
|
||||
unsafe { wild.write(13) };
|
||||
|
||||
// Reading through either of these references should be valid.
|
||||
assert_eq!(*ref2, 13);
|
||||
}
|
||||
|
||||
/// Checks that an access through a wildcard reference
|
||||
/// doesn't disable any exposed references.
|
||||
/// It tests this with exposed references where one is the ancestor of the other.
|
||||
pub fn multiple_exposed_child() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ref1 = &mut x;
|
||||
let int1 = ref1 as *mut u32 as usize;
|
||||
|
||||
let ref2 = &mut *ref1;
|
||||
|
||||
let ref3 = &mut *ref2;
|
||||
let _int3 = ref3 as *mut u32 as usize;
|
||||
|
||||
let wild = int1 as *mut u32;
|
||||
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref1(Res)* │
|
||||
// │ │
|
||||
// └──────┬─────┘
|
||||
// │
|
||||
// │
|
||||
// ▼
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref2(Res) │
|
||||
// │ │
|
||||
// └──────┬─────┘
|
||||
// │
|
||||
// │
|
||||
// ▼
|
||||
// ┌────────────┐
|
||||
// │ │
|
||||
// │ ref3(Res)* │
|
||||
// │ │
|
||||
// └────────────┘
|
||||
|
||||
// This writes either through ref1 or ref3, which is either a child or foreign access to ref2.
|
||||
unsafe { wild.write(42) };
|
||||
|
||||
// Reading from ref2 still works, since the previous access could have been through its child.
|
||||
// This also freezes ref3.
|
||||
let _x = *ref2;
|
||||
|
||||
// We can still write through wild, as there is still the exposed ref1 with write permissions.
|
||||
unsafe { wild.write(43) };
|
||||
}
|
||||
|
||||
/// Checks that we can deallocate through a wildcard reference.
|
||||
fn dealloc() {
|
||||
use std::alloc::Layout;
|
||||
let x = unsafe { std::alloc::alloc_zeroed(Layout::new::<u32>()) as *mut u32 };
|
||||
let ref1 = unsafe { &mut *x };
|
||||
let int = ref1 as *mut u32 as usize;
|
||||
let wild = int as *mut u32;
|
||||
|
||||
unsafe { std::alloc::dealloc(wild as *mut u8, Layout::new::<u32>()) };
|
||||
}
|
||||
|
||||
/// Checks that we can pass a wildcard reference to a function.
|
||||
fn protector() {
|
||||
fn protect(arg: &mut u32) {
|
||||
*arg = 4;
|
||||
}
|
||||
let mut x: u32 = 32;
|
||||
let ref1 = &mut x;
|
||||
let int = ref1 as *mut u32 as usize;
|
||||
let wild = int as *mut u32;
|
||||
let wild_ref = unsafe { &mut *wild };
|
||||
|
||||
protect(wild_ref);
|
||||
|
||||
assert_eq!(*ref1, 4);
|
||||
}
|
||||
|
||||
/// Checks whether we correctly handle the protector being released on
|
||||
/// a conflicted exposed reference.
|
||||
fn protector_conflicted_release() {
|
||||
let mut x: u32 = 42;
|
||||
|
||||
let ptr_base = &mut x as *mut u32;
|
||||
let ref1 = unsafe { &mut *ptr_base };
|
||||
let ref2 = unsafe { &mut *ptr_base };
|
||||
|
||||
let protect = |arg: &mut u32| {
|
||||
// Expose arg.
|
||||
let int = arg as *mut u32 as usize;
|
||||
let wild = int as *mut u32;
|
||||
|
||||
// Do a foreign read to arg marking it as conflicted and making child_writes UB while its protected.
|
||||
let _x = *ref2;
|
||||
|
||||
return wild;
|
||||
};
|
||||
|
||||
let wild = protect(ref1);
|
||||
|
||||
// The protector on arg got released so writes through arg should work again.
|
||||
unsafe { *wild = 4 };
|
||||
}
|
||||
|
||||
/// Analogous to same test in `../tree-borrows.rs` but with a protected wildcard reference.
|
||||
fn returned_mut_is_usable() {
|
||||
// NOTE: Currently we ignore protectors on wildcard references.
|
||||
fn reborrow(x: &mut u8) -> &mut u8 {
|
||||
let y = &mut *x;
|
||||
// Activate the reference so that it is vulnerable to foreign reads.
|
||||
*y = *y;
|
||||
y
|
||||
// An implicit read through `x` is inserted here.
|
||||
}
|
||||
let mut x: u8 = 0;
|
||||
let ref1 = &mut x;
|
||||
let int = ref1 as *mut u8 as usize;
|
||||
let wild = int as *mut u8;
|
||||
let wild_ref = unsafe { &mut *wild };
|
||||
|
||||
let y = reborrow(wild_ref);
|
||||
|
||||
*y = 1;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue