Skip to content

Commit a054ef1

Browse files
committed
exract a perform_access, check read-read commutation exhaustively
1 parent 19ec08c commit a054ef1

18 files changed

+193
-100
lines changed

src/borrow_tracker/tree_borrows/diagnostics.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,9 @@ impl TbError<'_> {
310310
"the accessed tag {accessed} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
311311
),
312312
format!(
313-
"this {access} would cause the {conflicting_tag_name} tag {conflicting} currently {before_disabled} to become Disabled"
313+
"this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled"
314314
),
315-
format!("protected tags must not become Disabled"),
315+
format!("protected tags must never be Disabled"),
316316
];
317317
(title, details, conflicting_tag_name)
318318
}

src/borrow_tracker/tree_borrows/perms.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ mod propagation_optimization_checks {
381381
pub use super::*;
382382
impl PermissionPriv {
383383
/// Enumerate all states
384-
pub fn all() -> impl Iterator<Item = PermissionPriv> {
384+
pub fn all() -> impl Iterator<Item = Self> {
385385
vec![
386386
Active,
387387
Reserved { ty_is_freeze: true },
@@ -393,17 +393,23 @@ mod propagation_optimization_checks {
393393
}
394394
}
395395

396+
impl Permission {
397+
pub fn all() -> impl Iterator<Item = Self> {
398+
PermissionPriv::all().map(|inner| Self { inner })
399+
}
400+
}
401+
396402
impl AccessKind {
397403
/// Enumerate all AccessKind.
398-
pub fn all() -> impl Iterator<Item = AccessKind> {
404+
pub fn all() -> impl Iterator<Item = Self> {
399405
use AccessKind::*;
400406
[Read, Write].into_iter()
401407
}
402408
}
403409

404410
impl AccessRelatedness {
405411
/// Enumerate all relative positions
406-
pub fn all() -> impl Iterator<Item = AccessRelatedness> {
412+
pub fn all() -> impl Iterator<Item = Self> {
407413
use AccessRelatedness::*;
408414
[This, StrictChildAccess, AncestorAccess, DistantAccess].into_iter()
409415
}

src/borrow_tracker/tree_borrows/tree.rs

+138-53
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use rustc_target::abi::Size;
1919

2020
use crate::borrow_tracker::tree_borrows::{
2121
diagnostics::{self, NodeDebugInfo, TbError, TransitionError},
22+
perms::PermTransition,
2223
unimap::{UniEntry, UniIndex, UniKeyMap, UniValMap},
2324
Permission,
2425
};
@@ -69,6 +70,74 @@ impl LocationState {
6970
pub fn permission(&self) -> Permission {
7071
self.permission
7172
}
73+
74+
/// Apply the effect of an access to one location, including
75+
/// - applying `Permission::perform_access` to the inner `Permission`,
76+
/// - emitting protector UB if the location is initialized,
77+
/// - updating the initialized status (child accesses produce initialized locations).
78+
fn perform_access(
79+
&mut self,
80+
access_kind: AccessKind,
81+
rel_pos: AccessRelatedness,
82+
protected: bool,
83+
) -> Result<PermTransition, TransitionError> {
84+
let old_perm = self.permission;
85+
let transition = Permission::perform_access(access_kind, rel_pos, old_perm, protected)
86+
.ok_or(TransitionError::ChildAccessForbidden(old_perm))?;
87+
if protected
88+
// Can't trigger Protector on uninitialized locations
89+
&& self.initialized
90+
&& transition.produces_disabled()
91+
{
92+
return Err(TransitionError::ProtectedDisabled(old_perm));
93+
}
94+
self.permission = transition.applied(old_perm).unwrap();
95+
self.initialized |= !rel_pos.is_foreign();
96+
Ok(transition)
97+
}
98+
99+
// Optimize the tree traversal.
100+
// The optimization here consists of observing thanks to the tests
101+
// `foreign_read_is_noop_after_write` and `all_transitions_idempotent`
102+
// that if we apply twice in a row the effects of a foreign access
103+
// we can skip some branches.
104+
// "two foreign accesses in a row" occurs when `perm.latest_foreign_access` is `Some(_)`
105+
// AND the `rel_pos` of the current access corresponds to a foreign access.
106+
fn skip_if_known_noop(
107+
&mut self,
108+
access_kind: AccessKind,
109+
rel_pos: AccessRelatedness,
110+
) -> ContinueTraversal {
111+
if rel_pos.is_foreign() {
112+
let new_access_noop = match (self.latest_foreign_access, access_kind) {
113+
// Previously applied transition makes the new one a guaranteed
114+
// noop in the two following cases:
115+
// (1) justified by `foreign_read_is_noop_after_write`
116+
(Some(AccessKind::Write), AccessKind::Read) => true,
117+
// (2) justified by `all_transitions_idempotent`
118+
(Some(old), new) if old == new => true,
119+
// In all other cases there has been a recent enough
120+
// child access that the effects of the new foreign access
121+
// need to be applied to this subtree.
122+
_ => false,
123+
};
124+
if new_access_noop {
125+
// Abort traversal if the new transition is indeed guaranteed
126+
// to be noop.
127+
return ContinueTraversal::SkipChildren;
128+
} else {
129+
// Otherwise propagate this time, and also record the
130+
// access that just occurred so that we can skip the propagation
131+
// next time.
132+
self.latest_foreign_access = Some(access_kind);
133+
}
134+
} else {
135+
// A child access occurred, this breaks the streak of "two foreign
136+
// accesses in a row" and we reset this field.
137+
self.latest_foreign_access = None;
138+
}
139+
ContinueTraversal::Recurse
140+
}
72141
}
73142

74143
/// Tree structure with both parents and children since we want to be
@@ -387,11 +456,15 @@ impl<'tcx> Tree {
387456
Ok(())
388457
}
389458

390-
/// Maps the following propagation procedure to each range:
391-
/// - initialize if needed;
392-
/// - compute new state after transition;
393-
/// - check that there is no protector that would forbid this;
394-
/// - record this specific location as accessed.
459+
/// Map the per-node and per-location `LocationState::perform_access`
460+
/// to each location of `access_range`, on every tag of the allocation.
461+
///
462+
/// `LocationState::perform_access` will take care of raising transition
463+
/// errors and updating the `initialized` status of each location,
464+
/// this traversal adds to that:
465+
/// - inserting into the map locations that do not exist yet,
466+
/// - trimming the traversal,
467+
/// - recording the history.
395468
pub fn perform_access(
396469
&mut self,
397470
access_kind: AccessKind,
@@ -411,55 +484,16 @@ impl<'tcx> Tree {
411484
let old_state =
412485
perm.or_insert_with(|| LocationState::new(node.default_initial_perm));
413486

414-
// Optimize the tree traversal.
415-
// The optimization here consists of observing thanks to the tests
416-
// `foreign_read_is_noop_after_write` and `all_transitions_idempotent`
417-
// that if we apply twice in a row the effects of a foreign access
418-
// we can skip some branches.
419-
// "two foreign accesses in a row" occurs when `perm.latest_foreign_access` is `Some(_)`
420-
// AND the `rel_pos` of the current access corresponds to a foreign access.
421-
if rel_pos.is_foreign() {
422-
let new_access_noop =
423-
match (old_state.latest_foreign_access, access_kind) {
424-
// Previously applied transition makes the new one a guaranteed
425-
// noop in the two following cases:
426-
// (1) justified by `foreign_read_is_noop_after_write`
427-
(Some(AccessKind::Write), AccessKind::Read) => true,
428-
// (2) justified by `all_transitions_idempotent`
429-
(Some(old), new) if old == new => true,
430-
// In all other cases there has been a recent enough
431-
// child access that the effects of the new foreign access
432-
// need to be applied to this subtree.
433-
_ => false,
434-
};
435-
if new_access_noop {
436-
// Abort traversal if the new transition is indeed guaranteed
437-
// to be noop.
438-
return Ok(ContinueTraversal::SkipChildren);
439-
} else {
440-
// Otherwise propagate this time, and also record the
441-
// access that just occurred so that we can skip the propagation
442-
// next time.
443-
old_state.latest_foreign_access = Some(access_kind);
444-
}
445-
} else {
446-
// A child access occurred, this breaks the streak of "two foreign
447-
// accesses in a row" and we reset this field.
448-
old_state.latest_foreign_access = None;
487+
match old_state.skip_if_known_noop(access_kind, rel_pos) {
488+
ContinueTraversal::SkipChildren =>
489+
return Ok(ContinueTraversal::SkipChildren),
490+
_ => {}
449491
}
450492

451-
let old_perm = old_state.permission;
452493
let protected = global.borrow().protected_tags.contains_key(&node.tag);
453494
let transition =
454-
Permission::perform_access(access_kind, rel_pos, old_perm, protected)
455-
.ok_or(TransitionError::ChildAccessForbidden(old_perm))?;
456-
if protected
457-
// Can't trigger Protector on uninitialized locations
458-
&& old_state.initialized
459-
&& transition.produces_disabled()
460-
{
461-
return Err(TransitionError::ProtectedDisabled(old_perm));
462-
}
495+
old_state.perform_access(access_kind, rel_pos, protected)?;
496+
463497
// Record the event as part of the history
464498
if !transition.is_noop() {
465499
node.debug_info.history.push(diagnostics::Event {
@@ -470,10 +504,7 @@ impl<'tcx> Tree {
470504
transition_range: perms_range.clone(),
471505
span,
472506
});
473-
old_state.permission =
474-
transition.applied(old_state.permission).unwrap();
475507
}
476-
old_state.initialized |= !rel_pos.is_foreign();
477508
Ok(ContinueTraversal::Recurse)
478509
},
479510
|args: ErrHandlerArgs<'_, TransitionError>| -> InterpError<'tcx> {
@@ -602,3 +633,57 @@ impl AccessRelatedness {
602633
}
603634
}
604635
}
636+
637+
#[cfg(test)]
638+
mod commutation_tests {
639+
use super::*;
640+
impl LocationState {
641+
pub fn all_without_access() -> impl Iterator<Item = Self> {
642+
Permission::all().flat_map(|permission| {
643+
[false, true].into_iter().map(move |initialized| {
644+
Self { permission, initialized, latest_foreign_access: None }
645+
})
646+
})
647+
}
648+
}
649+
650+
#[test]
651+
#[rustfmt::skip]
652+
// Exhaustive check that for any starting configuration loc,
653+
// for any two read accesses r1 and r2, if `loc + r1 + r2` is not UB
654+
// and results in `loc'`, then `loc + r2 + r1` is also not UB and results
655+
// in the same final state `loc'`.
656+
// This lets us justify arbitrary read-read reorderings.
657+
fn all_read_accesses_commute() {
658+
let kind = AccessKind::Read;
659+
// Two of the four combinations of `AccessRelatedness` are trivial,
660+
// but we might as well check them all.
661+
for rel1 in AccessRelatedness::all() {
662+
for rel2 in AccessRelatedness::all() {
663+
// Any protector state works, but we can't move reads across function boundaries
664+
// so the two read accesses occur under the same protector.
665+
for &protected in &[true, false] {
666+
for loc in LocationState::all_without_access() {
667+
// Apply 1 then 2. Failure here means that there is UB in the source
668+
// and we skip the check in the target.
669+
let mut loc12 = loc;
670+
let Ok(_) = loc12.perform_access(kind, rel1, protected) else { continue; };
671+
let Ok(_) = loc12.perform_access(kind, rel2, protected) else { continue; };
672+
673+
// If 1 followed by 2 succeeded, then 2 followed by 1 must also succeed...
674+
let mut loc21 = loc;
675+
loc21.perform_access(kind, rel2, protected).unwrap();
676+
loc21.perform_access(kind, rel1, protected).unwrap();
677+
678+
// ... and produce the same final result.
679+
assert_eq!(
680+
loc12, loc21,
681+
"Read accesses {:?} followed by {:?} do not commute !",
682+
rel1, rel2
683+
);
684+
}
685+
}
686+
}
687+
}
688+
}
689+
}

tests/fail/both_borrows/aliasing_mut4.tree.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ LL | ptr::write(dest, src);
66
|
77
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
88
= help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
9-
= help: this foreign write access would cause the protected tag <TAG> currently Frozen to become Disabled
10-
= help: protected tags must not become Disabled
9+
= help: this foreign write access would cause the protected tag <TAG> (currently Frozen) to become Disabled
10+
= help: protected tags must never be Disabled
1111
help: the accessed tag <TAG> was created here
1212
--> $DIR/aliasing_mut4.rs:LL:CC
1313
|

tests/fail/both_borrows/box_noalias_violation.tree.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ LL | *y
66
|
77
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
88
= help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
9-
= help: this foreign read access would cause the protected tag <TAG> currently Active to become Disabled
10-
= help: protected tags must not become Disabled
9+
= help: this foreign read access would cause the protected tag <TAG> (currently Active) to become Disabled
10+
= help: protected tags must never be Disabled
1111
help: the accessed tag <TAG> was created here
1212
--> $DIR/box_noalias_violation.rs:LL:CC
1313
|

tests/fail/both_borrows/illegal_write6.tree.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ LL | unsafe { *y = 2 };
66
|
77
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
88
= help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
9-
= help: this foreign write access would cause the protected tag <TAG> currently Active to become Disabled
10-
= help: protected tags must not become Disabled
9+
= help: this foreign write access would cause the protected tag <TAG> (currently Active) to become Disabled
10+
= help: protected tags must never be Disabled
1111
help: the accessed tag <TAG> was created here
1212
--> $DIR/illegal_write6.rs:LL:CC
1313
|

tests/fail/both_borrows/invalidate_against_protector2.tree.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ LL | unsafe { *x = 0 };
66
|
77
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
88
= help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
9-
= help: this foreign write access would cause the protected tag <TAG> currently Frozen to become Disabled
10-
= help: protected tags must not become Disabled
9+
= help: this foreign write access would cause the protected tag <TAG> (currently Frozen) to become Disabled
10+
= help: protected tags must never be Disabled
1111
help: the accessed tag <TAG> was created here
1212
--> $DIR/invalidate_against_protector2.rs:LL:CC
1313
|

tests/fail/both_borrows/invalidate_against_protector3.tree.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ LL | unsafe { *x = 0 };
66
|
77
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
88
= help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
9-
= help: this foreign write access would cause the protected tag <TAG> currently Frozen to become Disabled
10-
= help: protected tags must not become Disabled
9+
= help: this foreign write access would cause the protected tag <TAG> (currently Frozen) to become Disabled
10+
= help: protected tags must never be Disabled
1111
help: the accessed tag <TAG> was created here
1212
--> $DIR/invalidate_against_protector3.rs:LL:CC
1313
|

tests/fail/both_borrows/newtype_pair_retagging.tree.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
66
|
77
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
88
= help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
9-
= help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> currently Frozen to become Disabled
10-
= help: protected tags must not become Disabled
9+
= help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> (currently Frozen) to become Disabled
10+
= help: protected tags must never be Disabled
1111
help: the accessed tag <TAG> was created here
1212
--> $DIR/newtype_pair_retagging.rs:LL:CC
1313
|

tests/fail/both_borrows/newtype_retagging.tree.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
66
|
77
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
88
= help: the accessed tag <TAG> is foreign to the protected tag <TAG> (i.e., it is not a child)
9-
= help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> currently Frozen to become Disabled
10-
= help: protected tags must not become Disabled
9+
= help: this deallocation (acting as a foreign write access) would cause the protected tag <TAG> (currently Frozen) to become Disabled
10+
= help: protected tags must never be Disabled
1111
help: the accessed tag <TAG> was created here
1212
--> $DIR/newtype_retagging.rs:LL:CC
1313
|

tests/fail/function_calls/arg_inplace_mutate.tree.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ LL | unsafe { ptr.write(S(0)) };
66
|
77
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
88
= help: the accessed tag <TAG> (root of the allocation) is foreign to the protected tag <TAG> (i.e., it is not a child)
9-
= help: this foreign write access would cause the protected tag <TAG> currently Active to become Disabled
10-
= help: protected tags must not become Disabled
9+
= help: this foreign write access would cause the protected tag <TAG> (currently Active) to become Disabled
10+
= help: protected tags must never be Disabled
1111
help: the accessed tag <TAG> was created here
1212
--> $DIR/arg_inplace_mutate.rs:LL:CC
1313
|

0 commit comments

Comments
 (0)