Merge pull request #4630 from royAmmerschuber/pull-request/wildcard-provenance

initial implementation of wildcard provenence for tree borrows
This commit is contained in:
Ralf Jung 2025-11-17 10:35:12 +00:00 committed by GitHub
commit 18d2f462ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 2037 additions and 152 deletions

View file

@ -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"]

View file

@ -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 {

View file

@ -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(())
}
}

View file

@ -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(())
}

View file

@ -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,

View file

@ -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.

View file

@ -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);
}
}
}

View file

@ -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))
}
}
}
}

View file

@ -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.

View 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
);
}
}
}
}

View 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/
}

View file

@ -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

View 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/
}

View 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

View file

@ -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/
}

View file

@ -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

View file

@ -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/
}

View file

@ -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

View file

@ -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/
}

View file

@ -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

View file

@ -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/
}

View file

@ -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

View file

@ -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/
}

View file

@ -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

View file

@ -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/
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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/
}

View file

@ -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

View file

@ -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/
}

View file

@ -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

View file

@ -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/
}

View file

@ -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

View file

@ -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/
}

View file

@ -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

View file

@ -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) });
});
}

View file

@ -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

View file

@ -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

View file

@ -1,3 +1,6 @@
//@revisions: stack tree
//@compile-flags: -Zmiri-permissive-provenance
//@[tree]compile-flags: -Zmiri-tree-borrows
use std::cell::Cell;
fn main() {

View file

@ -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

View 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);
}

View 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;
}