Skip to content

Commit 8fcfd9e

Browse files
committed
TB: encoding of the underlying state machine
+ properties about the transitions
1 parent 3ad2fec commit 8fcfd9e

File tree

1 file changed

+214
-0
lines changed
  • src/tools/miri/src/borrow_tracker/tree_borrows

1 file changed

+214
-0
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
use std::cmp::{Ordering, PartialOrd};
2+
use std::fmt;
3+
4+
/// The activation states of a pointer
5+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6+
enum PermissionPriv {
7+
/// represents: a local reference that has not yet been written to;
8+
/// allows: child reads, foreign reads, foreign writes if type is freeze;
9+
/// rejects: child writes (Active), foreign writes (Disabled, except if type is not freeze).
10+
/// special case: behaves differently when protected to adhere more closely to noalias
11+
Reserved { ty_is_freeze: bool },
12+
/// represents: a unique pointer;
13+
/// allows: child reads, child writes;
14+
/// rejects: foreign reads (Frozen), foreign writes (Disabled).
15+
Active,
16+
/// represents: a shared pointer;
17+
/// allows: all read accesses;
18+
/// rejects child writes (UB), foreign writes (Disabled).
19+
Frozen,
20+
/// represents: a dead pointer;
21+
/// allows: all foreign accesses;
22+
/// rejects: all child accesses (UB).
23+
Disabled,
24+
}
25+
use PermissionPriv::*;
26+
27+
impl PartialOrd for PermissionPriv {
28+
/// PermissionPriv is ordered as follows:
29+
/// - Reserved(_) < Active < Frozen < Disabled;
30+
/// - different kinds of `Reserved` (with or without interior mutability)
31+
/// are incomparable to each other.
32+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
33+
use Ordering::*;
34+
Some(match (self, other) {
35+
(a, b) if a == b => Equal,
36+
(Disabled, _) => Greater,
37+
(_, Disabled) => Less,
38+
(Frozen, _) => Greater,
39+
(_, Frozen) => Less,
40+
(Active, _) => Greater,
41+
(_, Active) => Less,
42+
(Reserved { .. }, Reserved { .. }) => return None,
43+
})
44+
}
45+
}
46+
47+
/// This module controls how each permission individually reacts to an access.
48+
/// Although these functions take `protected` as an argument, this is NOT because
49+
/// we check protector violations here, but because some permissions behave differently
50+
/// when protected.
51+
mod transition {
52+
use super::*;
53+
/// A child node was read-accessed: UB on Disabled, noop on the rest.
54+
fn child_read(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> {
55+
Some(match state {
56+
Disabled => return None,
57+
// The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
58+
// accesses, since the data is not being mutated. Hence the `{ .. }`
59+
readable @ (Reserved { .. } | Active | Frozen) => readable,
60+
})
61+
}
62+
63+
/// A non-child node was read-accessed: noop on non-protected Reserved, advance to Frozen otherwise.
64+
fn foreign_read(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
65+
use Option::*;
66+
Some(match state {
67+
// The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
68+
// accesses, since the data is not being mutated. Hence the `{ .. }`
69+
res @ Reserved { .. } if !protected => res,
70+
Reserved { .. } => Frozen, // protected reserved
71+
Active => Frozen,
72+
non_writeable @ (Frozen | Disabled) => non_writeable,
73+
})
74+
}
75+
76+
/// A child node was write-accessed: `Reserved` must become `Active` to obtain
77+
/// write permissions, `Frozen` and `Disabled` cannot obtain such permissions and produce UB.
78+
fn child_write(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> {
79+
Some(match state {
80+
// A write always activates the 2-phase borrow, even with interior
81+
// mutability
82+
Reserved { .. } | Active => Active,
83+
Frozen | Disabled => return None,
84+
})
85+
}
86+
87+
/// A non-child node was write-accessed: this makes everything `Disabled` except for
88+
/// non-protected interior mutable `Reserved` which stay the same.
89+
fn foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
90+
Some(match state {
91+
cell @ Reserved { ty_is_freeze: false } if !protected => cell,
92+
_ => Disabled,
93+
})
94+
}
95+
}
96+
97+
impl PermissionPriv {
98+
/// Determines whether a transition that occured is compatible with the presence
99+
/// of a Protector. This is not included in the `transition` functions because
100+
/// it would distract from the few places where the transition is modified
101+
/// because of a protector, but not forbidden.
102+
fn protector_allows_transition(self, new: Self) -> bool {
103+
match (self, new) {
104+
_ if self == new => true,
105+
// It is always a protector violation to not be readable anymore
106+
(_, Disabled) => false,
107+
// In the case of a `Reserved` under a protector, both transitions
108+
// `Reserved => Active` and `Reserved => Frozen` can legitimately occur.
109+
// The first is standard (Child Write), the second is for Foreign Writes
110+
// on protected Reserved where we must ensure that the pointer is not
111+
// written to in the future.
112+
(Reserved { .. }, Active) | (Reserved { .. }, Frozen) => true,
113+
// This pointer should have stayed writeable for the whole function
114+
(Active, Frozen) => false,
115+
_ => unreachable!("Transition from {self:?} to {new:?} should never be possible"),
116+
}
117+
}
118+
}
119+
120+
#[cfg(test)]
121+
mod propagation_optimization_checks {
122+
pub use super::*;
123+
124+
mod util {
125+
pub use super::*;
126+
impl PermissionPriv {
127+
/// Enumerate all states
128+
pub fn all() -> impl Iterator<Item = PermissionPriv> {
129+
vec![
130+
Active,
131+
Reserved { ty_is_freeze: true },
132+
Reserved { ty_is_freeze: false },
133+
Frozen,
134+
Disabled,
135+
]
136+
.into_iter()
137+
}
138+
}
139+
140+
impl AccessKind {
141+
/// Enumerate all AccessKind.
142+
pub fn all() -> impl Iterator<Item = AccessKind> {
143+
use AccessKind::*;
144+
[Read, Write].into_iter()
145+
}
146+
}
147+
148+
impl AccessRelatedness {
149+
/// Enumerate all relative positions
150+
pub fn all() -> impl Iterator<Item = AccessRelatedness> {
151+
use AccessRelatedness::*;
152+
[This, StrictChildAccess, AncestorAccess, DistantAccess].into_iter()
153+
}
154+
}
155+
}
156+
157+
#[test]
158+
// For any kind of access, if we do it twice the second should be a no-op.
159+
// Even if the protector has disappeared.
160+
fn all_transitions_idempotent() {
161+
use transition::*;
162+
for old in PermissionPriv::all() {
163+
for (old_protected, new_protected) in [(true, true), (true, false), (false, false)] {
164+
for access in AccessKind::all() {
165+
for rel_pos in AccessRelatedness::all() {
166+
if let Some(new) = perform_access(access, rel_pos, old, old_protected) {
167+
assert_eq!(
168+
new,
169+
perform_access(access, rel_pos, new, new_protected).unwrap()
170+
);
171+
}
172+
}
173+
}
174+
}
175+
}
176+
}
177+
178+
#[test]
179+
fn foreign_read_is_noop_after_write() {
180+
use transition::*;
181+
let old_access = AccessKind::Write;
182+
let new_access = AccessKind::Read;
183+
for old in PermissionPriv::all() {
184+
for (old_protected, new_protected) in [(true, true), (true, false), (false, false)] {
185+
for rel_pos in AccessRelatedness::all().filter(|rel| rel.is_foreign()) {
186+
if let Some(new) = perform_access(old_access, rel_pos, old, old_protected) {
187+
assert_eq!(
188+
new,
189+
perform_access(new_access, rel_pos, new, new_protected).unwrap()
190+
);
191+
}
192+
}
193+
}
194+
}
195+
}
196+
197+
#[test]
198+
// Check that all transitions are consistent with the order on PermissionPriv,
199+
// i.e. Reserved -> Active -> Frozen -> Disabled
200+
fn access_transitions_progress_increasing() {
201+
use transition::*;
202+
for old in PermissionPriv::all() {
203+
for protected in [true, false] {
204+
for access in AccessKind::all() {
205+
for rel_pos in AccessRelatedness::all() {
206+
if let Some(new) = perform_access(access, rel_pos, old, protected) {
207+
assert!(old <= new);
208+
}
209+
}
210+
}
211+
}
212+
}
213+
}
214+
}

0 commit comments

Comments
 (0)