@@ -29,17 +29,19 @@ use crate::*;
2929/// Data for a single *location*.
3030#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
3131pub ( super ) struct LocationState {
32- /// This pointer's current permission
33- permission : Permission ,
34- /// A location is initialized when it is child accessed for the first time,
35- /// and it then stays initialized forever.
36- /// Before initialization we still apply some preemptive transitions on
37- /// `permission` to know what to do in case it ever gets initialized,
38- /// but these can never cause any immediate UB. There can however be UB
39- /// the moment we attempt to initialize (i.e. child-access) because some
40- /// foreign access done between the creation and the initialization is
41- /// incompatible with child accesses .
32+ /// A location is initialized when it is child-accessed for the first time (and the initial
33+ /// retag initializes the location for the range covered by the type), and it then stays
34+ /// initialized forever.
35+ /// For initialized locations, "permission" is the current permission. However, for
36+ /// uninitialized locations, we still need to track the "future initial permission": this will
37+ /// start out to be `default_initial_perm`, but foreign accesses need to be taken into account.
38+ /// Crucially however, while transitions to `Disabled` would usually be UB if this location is
39+ /// protected, that is *not* the case for uninitialized locations. Instead we just have a latent
40+ /// "future initial permission" of `Disabled`, causing UB only if an access is ever actually
41+ /// performed .
4242 initialized : bool ,
43+ /// This pointer's current permission / future initial permission.
44+ permission : Permission ,
4345 /// Strongest foreign access whose effects have already been applied to
4446 /// this node and all its children since the last child access.
4547 /// This is `None` if the most recent access is a child access,
@@ -84,25 +86,50 @@ impl LocationState {
8486 let old_perm = self . permission ;
8587 let transition = Permission :: perform_access ( access_kind, rel_pos, old_perm, protected)
8688 . 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- {
89+ // Why do only initialized locations cause protector errors?
90+ // Consider two mutable references `x`, `y` into disjoint parts of
91+ // the same allocation. A priori, these may actually both be used to
92+ // access the entire allocation, as long as only reads occur. However,
93+ // a write to `y` needs to somehow record that `x` can no longer be used
94+ // on that location at all. For these uninitialized locations (i.e., locations
95+ // that haven't been accessed with `x` yet), we track the "future initial state":
96+ // it defaults to whatever the initial state of the tag is,
97+ // but the access to `y` moves that "future initial state" of `x` to `Disabled`.
98+ // However, usually a `Reserved -> Disabled` transition would be UB due to the protector!
99+ // So clearly protectors shouldn't fire for such "future initial state" transitions.
100+ //
101+ // See the test `two_mut_protected_same_alloc` in `tests/pass/tree_borrows/tree-borrows.rs`
102+ // for an example of safe code that would be UB if we forgot to check `self.initialized`.
103+ if protected && self . initialized && transition. produces_disabled ( ) {
92104 return Err ( TransitionError :: ProtectedDisabled ( old_perm) ) ;
93105 }
94106 self . permission = transition. applied ( old_perm) . unwrap ( ) ;
95107 self . initialized |= !rel_pos. is_foreign ( ) ;
96108 Ok ( transition)
97109 }
98110
99- // Optimize the tree traversal.
111+ // Helper to optimize the tree traversal.
100112 // 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.
113+ // `foreign_read_is_noop_after_write` and `all_transitions_idempotent`,
114+ // that there are actually just three possible sequences of events that can occur
115+ // in between two child accesses that produce different results.
116+ //
117+ // Indeed,
118+ // - applying any number of foreign read accesses is the same as applying
119+ // exactly one foreign read,
120+ // - applying any number of foreign read or write accesses is the same
121+ // as applying exactly one foreign write.
122+ // therefore the three sequences of events that can produce different
123+ // outcomes are
124+ // - an empty sequence (`self.latest_foreign_access = None`)
125+ // - a nonempty read-only sequence (`self.latest_foreign_access = Some(Read)`)
126+ // - a nonempty sequence with at least one write (`self.latest_foreign_access = Some(Write)`)
127+ //
128+ // This function not only determines if skipping the propagation right now
129+ // is possible, it also updates the internal state to keep track of whether
130+ // the propagation can be skipped next time.
131+ // It is a performance loss not to call this function when a foreign access occurs.
132+ // It is unsound not to call this function when a child access occurs.
106133 fn skip_if_known_noop (
107134 & mut self ,
108135 access_kind : AccessKind ,
@@ -124,19 +151,24 @@ impl LocationState {
124151 if new_access_noop {
125152 // Abort traversal if the new transition is indeed guaranteed
126153 // to be noop.
127- return ContinueTraversal :: SkipChildren ;
154+ // No need to update `self.latest_foreign_access`,
155+ // the type of the current streak among nonempty read-only
156+ // or nonempty with at least one write has not changed.
157+ ContinueTraversal :: SkipChildren
128158 } else {
129159 // Otherwise propagate this time, and also record the
130160 // access that just occurred so that we can skip the propagation
131161 // next time.
132162 self . latest_foreign_access = Some ( access_kind) ;
163+ ContinueTraversal :: Recurse
133164 }
134165 } else {
135- // A child access occurred, this breaks the streak of "two foreign
136- // accesses in a row" and we reset this field.
166+ // A child access occurred, this breaks the streak of foreign
167+ // accesses in a row and the sequence since the previous child access
168+ // is now empty.
137169 self . latest_foreign_access = None ;
170+ ContinueTraversal :: Recurse
138171 }
139- ContinueTraversal :: Recurse
140172 }
141173}
142174
0 commit comments