miri/borrow_tracker/tree_borrows/wildcard.rs
1use std::cmp::max;
2use std::fmt::Debug;
3
4use super::Tree;
5use super::tree::{AccessRelatedness, Node};
6use super::unimap::{UniIndex, UniValMap};
7use crate::BorTag;
8use crate::borrow_tracker::AccessKind;
9#[cfg(feature = "expensive-consistency-checks")]
10use crate::borrow_tracker::GlobalState;
11
12/// Represents the maximum access level that is possible.
13///
14/// Note that we derive Ord and PartialOrd, so the order in which variants are listed below matters:
15/// None < Read < Write. Do not change that order.
16#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
17pub enum WildcardAccessLevel {
18 #[default]
19 None,
20 Read,
21 Write,
22}
23
24/// Where the access happened relative to the current node.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum WildcardAccessRelatedness {
27 /// The access definitively happened through a local node.
28 LocalAccess,
29 /// The access definitively happened through a foreign node.
30 ForeignAccess,
31 /// We do not know if the access is foreign or local.
32 EitherAccess,
33}
34impl WildcardAccessRelatedness {
35 pub fn to_relatedness(self) -> Option<AccessRelatedness> {
36 match self {
37 Self::LocalAccess => Some(AccessRelatedness::LocalAccess),
38 Self::ForeignAccess => Some(AccessRelatedness::ForeignAccess),
39 Self::EitherAccess => None,
40 }
41 }
42}
43
44/// State per location per node keeping track of where relative to this
45/// node exposed nodes are and what access permissions they have.
46///
47/// Designed to be completely determined by its parent, siblings and
48/// direct children's max_local_access/max_foreign_access.
49#[derive(Clone, Default, PartialEq, Eq)]
50pub struct WildcardState {
51 /// How many of this node's direct children have `max_local_access()==Write`.
52 child_writes: u16,
53 /// How many of this node's direct children have `max_local_access()>=Read`.
54 child_reads: u16,
55 /// The maximum access level that could happen from an exposed node
56 /// that is foreign to this node.
57 ///
58 /// This is calculated as the `max()` of the parent's `max_foreign_access`,
59 /// `exposed_as` and the siblings' `max_local_access()`.
60 max_foreign_access: WildcardAccessLevel,
61 /// At what access level this node itself is exposed.
62 exposed_as: WildcardAccessLevel,
63}
64impl Debug for WildcardState {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 f.debug_struct("WildcardState")
67 .field("child_r/w", &(self.child_reads, self.child_writes))
68 .field("foreign", &self.max_foreign_access)
69 .field("exposed_as", &self.exposed_as)
70 .finish()
71 }
72}
73impl WildcardState {
74 /// The maximum access level that could happen from an exposed
75 /// node that is local to this node.
76 fn max_local_access(&self) -> WildcardAccessLevel {
77 use WildcardAccessLevel::*;
78 max(
79 self.exposed_as,
80 if self.child_writes > 0 {
81 Write
82 } else if self.child_reads > 0 {
83 Read
84 } else {
85 None
86 },
87 )
88 }
89
90 /// From where relative to the node with this wildcard info a read or write access could happen.
91 pub fn access_relatedness(&self, kind: AccessKind) -> Option<WildcardAccessRelatedness> {
92 match kind {
93 AccessKind::Read => self.read_access_relatedness(),
94 AccessKind::Write => self.write_access_relatedness(),
95 }
96 }
97
98 /// From where relative to the node with this wildcard info a read access could happen.
99 fn read_access_relatedness(&self) -> Option<WildcardAccessRelatedness> {
100 let has_foreign = self.max_foreign_access >= WildcardAccessLevel::Read;
101 let has_local = self.max_local_access() >= WildcardAccessLevel::Read;
102 use WildcardAccessRelatedness as E;
103 match (has_foreign, has_local) {
104 (true, true) => Some(E::EitherAccess),
105 (true, false) => Some(E::ForeignAccess),
106 (false, true) => Some(E::LocalAccess),
107 (false, false) => None,
108 }
109 }
110
111 /// From where relative to the node with this wildcard info a write access could happen.
112 fn write_access_relatedness(&self) -> Option<WildcardAccessRelatedness> {
113 let has_foreign = self.max_foreign_access == WildcardAccessLevel::Write;
114 let has_local = self.max_local_access() == WildcardAccessLevel::Write;
115 use WildcardAccessRelatedness as E;
116 match (has_foreign, has_local) {
117 (true, true) => Some(E::EitherAccess),
118 (true, false) => Some(E::ForeignAccess),
119 (false, true) => Some(E::LocalAccess),
120 (false, false) => None,
121 }
122 }
123
124 /// Gets the access tracking information for a new child node of a parent with this
125 /// wildcard info.
126 /// The new node doesn't have any child reads/writes, but calculates `max_foreign_access`
127 /// from its parent.
128 pub fn for_new_child(&self) -> Self {
129 Self {
130 max_foreign_access: max(self.max_foreign_access, self.max_local_access()),
131 ..Default::default()
132 }
133 }
134
135 /// Pushes the nodes of `children` onto the stack who's `max_foreign_access`
136 /// needs to be updated.
137 ///
138 /// * `children`: A list of nodes with the same parent. `children` doesn't
139 /// necessarily have to contain all children of parent, but can just be
140 /// a subset.
141 ///
142 /// * `child_reads`, `child_writes`: How many of `children` have `max_local_access()`
143 /// of at least `read`/`write`
144 ///
145 /// * `new_foreign_access`, `old_foreign_access`:
146 /// The max possible access level that is foreign to all `children`
147 /// (i.e., it is not local to *any* of them).
148 /// This can be calculated as the max of the parent's `exposed_as()`, `max_foreign_access`
149 /// and of all `max_local_access()` of any nodes with the same parent that are
150 /// not listed in `children`.
151 ///
152 /// This access level changed from `old` to `new`, which is why we need to
153 /// update `children`.
154 fn push_relevant_children(
155 stack: &mut Vec<(UniIndex, WildcardAccessLevel)>,
156 new_foreign_access: WildcardAccessLevel,
157 old_foreign_access: WildcardAccessLevel,
158 child_reads: u16,
159 child_writes: u16,
160 children: impl Iterator<Item = UniIndex>,
161
162 wildcard_accesses: &UniValMap<WildcardState>,
163 ) {
164 use WildcardAccessLevel::*;
165
166 // Nothing changed so we don't need to update anything.
167 if new_foreign_access == old_foreign_access {
168 return;
169 }
170
171 // We need to consider that the children's `max_local_access()` affect each
172 // other's `max_foreign_access`, but do not affect their own `max_foreign_access`.
173
174 // The new `max_foreign_acces` for children with `max_local_access()==Write`.
175 let write_foreign_access = max(
176 new_foreign_access,
177 if child_writes > 1 {
178 // There exists at least one more child with exposed write access.
179 // This means that a foreign write through that node is possible.
180 Write
181 } else if child_reads > 1 {
182 // There exists at least one more child with exposed read access,
183 // but no other with write access.
184 // This means that a foreign read but no write through that node
185 // is possible.
186 Read
187 } else {
188 // There are no other nodes with read or write access.
189 // This means no foreign writes through other children are possible.
190 None
191 },
192 );
193
194 // The new `max_foreign_acces` for children with `max_local_access()==Read`.
195 let read_foreign_access = max(
196 new_foreign_access,
197 if child_writes > 0 {
198 // There exists at least one child with write access (and it's not this one).
199 Write
200 } else if child_reads > 1 {
201 // There exists at least one more child with exposed read access,
202 // but no other with write access.
203 Read
204 } else {
205 // There are no other nodes with read or write access,
206 None
207 },
208 );
209
210 // The new `max_foreign_acces` for children with `max_local_access()==None`.
211 let none_foreign_access = max(
212 new_foreign_access,
213 if child_writes > 0 {
214 // There exists at least one child with write access (and it's not this one).
215 Write
216 } else if child_reads > 0 {
217 // There exists at least one child with read access (and it's not this one),
218 // but none with write access.
219 Read
220 } else {
221 // No children are exposed as read or write.
222 None
223 },
224 );
225
226 stack.extend(children.filter_map(|child| {
227 let state = wildcard_accesses.get(child).cloned().unwrap_or_default();
228
229 let new_foreign_access = match state.max_local_access() {
230 Write => write_foreign_access,
231 Read => read_foreign_access,
232 None => none_foreign_access,
233 };
234
235 if new_foreign_access != state.max_foreign_access {
236 Some((child, new_foreign_access))
237 } else {
238 Option::None
239 }
240 }));
241 }
242
243 /// Update the tracking information of a tree, to reflect that the node specified by `id` is
244 /// now exposed with `new_exposed_as`.
245 ///
246 /// Propagates the Willard access information over the tree. This needs to be called every
247 /// time the access level of an exposed node changes, to keep the state in sync with
248 /// the rest of the tree.
249 pub fn update_exposure(
250 id: UniIndex,
251 new_exposed_as: WildcardAccessLevel,
252 nodes: &UniValMap<Node>,
253 wildcard_accesses: &mut UniValMap<WildcardState>,
254 ) {
255 let mut entry = wildcard_accesses.entry(id);
256 let src_state = entry.or_insert(Default::default());
257 let old_exposed_as = src_state.exposed_as;
258
259 // If the exposure doesn't change, then we don't need to update anything.
260 if old_exposed_as == new_exposed_as {
261 return;
262 }
263
264 let src_old_local_access = src_state.max_local_access();
265
266 src_state.exposed_as = new_exposed_as;
267
268 let src_new_local_access = src_state.max_local_access();
269
270 // Stack of nodes for which the max_foreign_access field needs to be updated.
271 // Will be filled with the children of this node and its parents children before
272 // we begin downwards traversal.
273 let mut stack: Vec<(UniIndex, WildcardAccessLevel)> = Vec::new();
274
275 // Add the direct children of this node to the stack.
276 {
277 let node = nodes.get(id).unwrap();
278 Self::push_relevant_children(
279 &mut stack,
280 // new_foreign_access
281 max(src_state.max_foreign_access, new_exposed_as),
282 // old_foreign_access
283 max(src_state.max_foreign_access, old_exposed_as),
284 // Consider all children.
285 src_state.child_reads,
286 src_state.child_writes,
287 node.children.iter().copied(),
288 wildcard_accesses,
289 );
290 }
291 // We need to propagate the tracking info up the tree, for this we traverse
292 // up the parents.
293 // We can skip propagating info to the parent and siblings of a node if its
294 // access didn't change.
295 {
296 // The child from which we came.
297 let mut child = id;
298 // This is the `max_local_access()` of the child we came from, before
299 // this update...
300 let mut old_child_access = src_old_local_access;
301 // and after this update.
302 let mut new_child_access = src_new_local_access;
303 while let Some(parent_id) = nodes.get(child).unwrap().parent {
304 let parent_node = nodes.get(parent_id).unwrap();
305 let mut entry = wildcard_accesses.entry(parent_id);
306 let parent_state = entry.or_insert(Default::default());
307
308 let old_parent_local_access = parent_state.max_local_access();
309 use WildcardAccessLevel::*;
310 // Updating this node's tracking state for its children.
311 match (old_child_access, new_child_access) {
312 (None | Read, Write) => parent_state.child_writes += 1,
313 (Write, None | Read) => parent_state.child_writes -= 1,
314 _ => {}
315 }
316 match (old_child_access, new_child_access) {
317 (None, Read | Write) => parent_state.child_reads += 1,
318 (Read | Write, None) => parent_state.child_reads -= 1,
319 _ => {}
320 }
321
322 let new_parent_local_access = parent_state.max_local_access();
323
324 {
325 // We need to update the `max_foreign_access` of `child`'s
326 // siblings. For this we can reuse the `push_relevant_children`
327 // function.
328 //
329 // We pass it just the siblings without child itself. Since
330 // `child`'s `max_local_access()` is foreign to all of its
331 // siblings we can pass it as part of the foreign access.
332
333 let parent_access =
334 max(parent_state.exposed_as, parent_state.max_foreign_access);
335 // This is how many of `child`'s siblings have read/write local access.
336 // If `child` itself has access, then we need to subtract its access from the count.
337 let sibling_reads =
338 parent_state.child_reads - if new_child_access >= Read { 1 } else { 0 };
339 let sibling_writes =
340 parent_state.child_writes - if new_child_access >= Write { 1 } else { 0 };
341 Self::push_relevant_children(
342 &mut stack,
343 // new_foreign_access
344 max(parent_access, new_child_access),
345 // old_foreign_access
346 max(parent_access, old_child_access),
347 // Consider only siblings of child.
348 sibling_reads,
349 sibling_writes,
350 parent_node.children.iter().copied().filter(|id| child != *id),
351 wildcard_accesses,
352 );
353 }
354 if old_parent_local_access == new_parent_local_access {
355 // We didn't change `max_local_access()` for parent, so we don't need to propagate further upwards.
356 break;
357 }
358
359 old_child_access = old_parent_local_access;
360 new_child_access = new_parent_local_access;
361 child = parent_id;
362 }
363 }
364 // Traverses down the tree to update max_foreign_access fields of children and cousins who need to be updated.
365 while let Some((id, new_access)) = stack.pop() {
366 let node = nodes.get(id).unwrap();
367 let mut entry = wildcard_accesses.entry(id);
368 let state = entry.or_insert(Default::default());
369
370 let old_access = state.max_foreign_access;
371 state.max_foreign_access = new_access;
372
373 Self::push_relevant_children(
374 &mut stack,
375 // new_foreign_access
376 max(state.exposed_as, new_access),
377 // old_foreign_access
378 max(state.exposed_as, old_access),
379 // Consider all children.
380 state.child_reads,
381 state.child_writes,
382 node.children.iter().copied(),
383 wildcard_accesses,
384 );
385 }
386 }
387}
388
389impl Tree {
390 /// Marks the tag as exposed & updates the wildcard tracking data structure
391 /// to represent its access level.
392 /// Also takes as an argument whether the tag is protected or not.
393 pub fn expose_tag(&mut self, tag: BorTag, protected: bool) {
394 let id = self.tag_mapping.get(&tag).unwrap();
395 let node = self.nodes.get_mut(id).unwrap();
396 node.is_exposed = true;
397 let node = self.nodes.get(id).unwrap();
398
399 // When the first tag gets exposed then we initialize the
400 // wildcard state for every node and location in the tree.
401 for (_, loc) in self.locations.iter_mut_all() {
402 let perm = loc
403 .perms
404 .get(id)
405 .map(|p| p.permission())
406 .unwrap_or_else(|| node.default_location_state().permission());
407
408 let access_type = perm.strongest_allowed_child_access(protected);
409 WildcardState::update_exposure(
410 id,
411 access_type,
412 &self.nodes,
413 &mut loc.wildcard_accesses,
414 );
415 }
416 }
417
418 /// This updates the wildcard tracking data structure to reflect the release of
419 /// the protector on `tag`.
420 pub(super) fn update_exposure_for_protector_release(&mut self, tag: BorTag) {
421 let idx = self.tag_mapping.get(&tag).unwrap();
422
423 // We check if the node is already exposed, as we don't want to expose any
424 // nodes which aren't already exposed.
425
426 if self.nodes.get(idx).unwrap().is_exposed {
427 // Updates the exposure to the new permission on every location.
428 self.expose_tag(tag, /* protected */ false);
429 }
430 }
431}
432
433#[cfg(feature = "expensive-consistency-checks")]
434impl Tree {
435 /// Checks that the wildcard tracking data structure is internally consistent and
436 /// has the correct `exposed_as` values.
437 pub fn verify_wildcard_consistency(&self, global: &GlobalState) {
438 let protected_tags = &global.borrow().protected_tags;
439 for (_, loc) in self.locations.iter_all() {
440 let wildcard_accesses = &loc.wildcard_accesses;
441 let perms = &loc.perms;
442 // Checks if accesses is empty.
443 if wildcard_accesses.is_empty() {
444 return;
445 }
446 for (id, node) in self.nodes.iter() {
447 let state = wildcard_accesses.get(id).unwrap();
448
449 let expected_exposed_as = if node.is_exposed {
450 let perm = perms.get(id).unwrap();
451
452 perm.permission()
453 .strongest_allowed_child_access(protected_tags.contains_key(&node.tag))
454 } else {
455 WildcardAccessLevel::None
456 };
457
458 // The foreign wildcard accesses possible at a node are determined by which
459 // accesses can originate from their siblings, their parent, and from above
460 // their parent.
461 let expected_max_foreign_access = if let Some(parent) = node.parent {
462 let parent_node = self.nodes.get(parent).unwrap();
463 let parent_state = wildcard_accesses.get(parent).unwrap();
464
465 let max_sibling_access = parent_node
466 .children
467 .iter()
468 .copied()
469 .filter(|child| *child != id)
470 .map(|child| {
471 let state = wildcard_accesses.get(child).unwrap();
472 state.max_local_access()
473 })
474 .fold(WildcardAccessLevel::None, max);
475
476 max_sibling_access
477 .max(parent_state.max_foreign_access)
478 .max(parent_state.exposed_as)
479 } else {
480 WildcardAccessLevel::None
481 };
482
483 // Count how many children can be the source of wildcard reads or writes
484 // (either directly, or via their children).
485 let child_accesses = node.children.iter().copied().map(|child| {
486 let state = wildcard_accesses.get(child).unwrap();
487 state.max_local_access()
488 });
489 let expected_child_reads =
490 child_accesses.clone().filter(|a| *a >= WildcardAccessLevel::Read).count();
491 let expected_child_writes =
492 child_accesses.filter(|a| *a >= WildcardAccessLevel::Write).count();
493
494 assert_eq!(
495 expected_exposed_as, state.exposed_as,
496 "tag {:?} (id:{id:?}) should be exposed as {expected_exposed_as:?} but is exposed as {:?}",
497 node.tag, state.exposed_as
498 );
499 assert_eq!(
500 expected_max_foreign_access, state.max_foreign_access,
501 "expected {:?}'s (id:{id:?}) max_foreign_access to be {:?} instead of {:?}",
502 node.tag, expected_max_foreign_access, state.max_foreign_access
503 );
504 let child_reads: usize = state.child_reads.into();
505 assert_eq!(
506 expected_child_reads, child_reads,
507 "expected {:?}'s (id:{id:?}) child_reads to be {} instead of {}",
508 node.tag, expected_child_reads, child_reads
509 );
510 let child_writes: usize = state.child_writes.into();
511 assert_eq!(
512 expected_child_writes, child_writes,
513 "expected {:?}'s (id:{id:?}) child_writes to be {} instead of {}",
514 node.tag, expected_child_writes, child_writes
515 );
516 }
517 }
518 }
519}