@@ -29,17 +29,19 @@ use crate::*;
29
29
/// Data for a single *location*.
30
30
#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
31
31
pub ( 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 .
42
42
initialized : bool ,
43
+ /// This pointer's current permission / future initial permission.
44
+ permission : Permission ,
43
45
/// Strongest foreign access whose effects have already been applied to
44
46
/// this node and all its children since the last child access.
45
47
/// This is `None` if the most recent access is a child access,
@@ -84,25 +86,50 @@ impl LocationState {
84
86
let old_perm = self . permission ;
85
87
let transition = Permission :: perform_access ( access_kind, rel_pos, old_perm, protected)
86
88
. 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 ( ) {
92
104
return Err ( TransitionError :: ProtectedDisabled ( old_perm) ) ;
93
105
}
94
106
self . permission = transition. applied ( old_perm) . unwrap ( ) ;
95
107
self . initialized |= !rel_pos. is_foreign ( ) ;
96
108
Ok ( transition)
97
109
}
98
110
99
- // Optimize the tree traversal.
111
+ // Helper to optimize the tree traversal.
100
112
// 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.
106
133
fn skip_if_known_noop (
107
134
& mut self ,
108
135
access_kind : AccessKind ,
@@ -124,19 +151,24 @@ impl LocationState {
124
151
if new_access_noop {
125
152
// Abort traversal if the new transition is indeed guaranteed
126
153
// 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
128
158
} else {
129
159
// Otherwise propagate this time, and also record the
130
160
// access that just occurred so that we can skip the propagation
131
161
// next time.
132
162
self . latest_foreign_access = Some ( access_kind) ;
163
+ ContinueTraversal :: Recurse
133
164
}
134
165
} 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.
137
169
self . latest_foreign_access = None ;
170
+ ContinueTraversal :: Recurse
138
171
}
139
- ContinueTraversal :: Recurse
140
172
}
141
173
}
142
174
0 commit comments