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}