• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::cmp::{Ordering, PartialOrd};
2 use std::fmt;
3 
4 use crate::borrow_tracker::tree_borrows::diagnostics::TransitionError;
5 use crate::borrow_tracker::tree_borrows::tree::AccessRelatedness;
6 use crate::borrow_tracker::AccessKind;
7 
8 /// The activation states of a pointer.
9 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
10 enum PermissionPriv {
11     /// represents: a local reference that has not yet been written to;
12     /// allows: child reads, foreign reads, foreign writes if type is freeze;
13     /// rejects: child writes (Active), foreign writes (Disabled, except if type is not freeze).
14     /// special case: behaves differently when protected to adhere more closely to noalias
15     Reserved { ty_is_freeze: bool },
16     /// represents: a unique pointer;
17     /// allows: child reads, child writes;
18     /// rejects: foreign reads (Frozen), foreign writes (Disabled).
19     Active,
20     /// represents: a shared pointer;
21     /// allows: all read accesses;
22     /// rejects child writes (UB), foreign writes (Disabled).
23     Frozen,
24     /// represents: a dead pointer;
25     /// allows: all foreign accesses;
26     /// rejects: all child accesses (UB).
27     Disabled,
28 }
29 use PermissionPriv::*;
30 
31 impl PartialOrd for PermissionPriv {
32     /// PermissionPriv is ordered as follows:
33     /// - Reserved(_) < Active < Frozen < Disabled;
34     /// - different kinds of `Reserved` (with or without interior mutability)
35     /// are incomparable to each other.
partial_cmp(&self, other: &Self) -> Option<Ordering>36     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
37         use Ordering::*;
38         Some(match (self, other) {
39             (a, b) if a == b => Equal,
40             (Disabled, _) => Greater,
41             (_, Disabled) => Less,
42             (Frozen, _) => Greater,
43             (_, Frozen) => Less,
44             (Active, _) => Greater,
45             (_, Active) => Less,
46             (Reserved { .. }, Reserved { .. }) => return None,
47         })
48     }
49 }
50 
51 /// This module controls how each permission individually reacts to an access.
52 /// Although these functions take `protected` as an argument, this is NOT because
53 /// we check protector violations here, but because some permissions behave differently
54 /// when protected.
55 mod transition {
56     use super::*;
57     /// A child node was read-accessed: UB on Disabled, noop on the rest.
child_read(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv>58     fn child_read(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> {
59         Some(match state {
60             Disabled => return None,
61             // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
62             // accesses, since the data is not being mutated. Hence the `{ .. }`
63             readable @ (Reserved { .. } | Active | Frozen) => readable,
64         })
65     }
66 
67     /// A non-child node was read-accessed: noop on non-protected Reserved, advance to Frozen otherwise.
foreign_read(state: PermissionPriv, protected: bool) -> Option<PermissionPriv>68     fn foreign_read(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
69         use Option::*;
70         Some(match state {
71             // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
72             // accesses, since the data is not being mutated. Hence the `{ .. }`
73             res @ Reserved { .. } if !protected => res,
74             Reserved { .. } => Frozen, // protected reserved
75             Active => Frozen,
76             non_writeable @ (Frozen | Disabled) => non_writeable,
77         })
78     }
79 
80     /// A child node was write-accessed: `Reserved` must become `Active` to obtain
81     /// write permissions, `Frozen` and `Disabled` cannot obtain such permissions and produce UB.
child_write(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv>82     fn child_write(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> {
83         Some(match state {
84             // A write always activates the 2-phase borrow, even with interior
85             // mutability
86             Reserved { .. } | Active => Active,
87             Frozen | Disabled => return None,
88         })
89     }
90 
91     /// A non-child node was write-accessed: this makes everything `Disabled` except for
92     /// non-protected interior mutable `Reserved` which stay the same.
foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv>93     fn foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
94         Some(match state {
95             cell @ Reserved { ty_is_freeze: false } if !protected => cell,
96             _ => Disabled,
97         })
98     }
99 
100     /// Dispatch handler depending on the kind of access and its position.
perform_access( kind: AccessKind, rel_pos: AccessRelatedness, child: PermissionPriv, protected: bool, ) -> Option<PermissionPriv>101     pub(super) fn perform_access(
102         kind: AccessKind,
103         rel_pos: AccessRelatedness,
104         child: PermissionPriv,
105         protected: bool,
106     ) -> Option<PermissionPriv> {
107         match (kind, rel_pos.is_foreign()) {
108             (AccessKind::Write, true) => foreign_write(child, protected),
109             (AccessKind::Read, true) => foreign_read(child, protected),
110             (AccessKind::Write, false) => child_write(child, protected),
111             (AccessKind::Read, false) => child_read(child, protected),
112         }
113     }
114 }
115 
116 /// Public interface to the state machine that controls read-write permissions.
117 /// This is the "private `enum`" pattern.
118 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
119 pub struct Permission {
120     inner: PermissionPriv,
121 }
122 
123 /// Transition from one permission to the next.
124 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
125 pub struct PermTransition {
126     from: PermissionPriv,
127     to: PermissionPriv,
128 }
129 
130 impl Permission {
131     /// Default initial permission of the root of a new tree.
new_root() -> Self132     pub fn new_root() -> Self {
133         Self { inner: Active }
134     }
135 
136     /// Default initial permission of a reborrowed mutable reference.
new_unique_2phase(ty_is_freeze: bool) -> Self137     pub fn new_unique_2phase(ty_is_freeze: bool) -> Self {
138         Self { inner: Reserved { ty_is_freeze } }
139     }
140 
141     /// Default initial permission of a reborrowed shared reference
new_frozen() -> Self142     pub fn new_frozen() -> Self {
143         Self { inner: Frozen }
144     }
145 
146     /// Apply the transition to the inner PermissionPriv.
perform_access( kind: AccessKind, rel_pos: AccessRelatedness, old_perm: Self, protected: bool, ) -> Option<PermTransition>147     pub fn perform_access(
148         kind: AccessKind,
149         rel_pos: AccessRelatedness,
150         old_perm: Self,
151         protected: bool,
152     ) -> Option<PermTransition> {
153         let old_state = old_perm.inner;
154         transition::perform_access(kind, rel_pos, old_state, protected)
155             .map(|new_state| PermTransition { from: old_state, to: new_state })
156     }
157 }
158 
159 impl PermTransition {
160     /// All transitions created through normal means (using `perform_access`)
161     /// should be possible, but the same is not guaranteed by construction of
162     /// transitions inferred by diagnostics. This checks that a transition
163     /// reconstructed by diagnostics is indeed one that could happen.
is_possible(self) -> bool164     fn is_possible(self) -> bool {
165         self.from <= self.to
166     }
167 
from(from: Permission, to: Permission) -> Option<Self>168     pub fn from(from: Permission, to: Permission) -> Option<Self> {
169         let t = Self { from: from.inner, to: to.inner };
170         t.is_possible().then_some(t)
171     }
172 
is_noop(self) -> bool173     pub fn is_noop(self) -> bool {
174         self.from == self.to
175     }
176 
177     /// Extract result of a transition (checks that the starting point matches).
applied(self, starting_point: Permission) -> Option<Permission>178     pub fn applied(self, starting_point: Permission) -> Option<Permission> {
179         (starting_point.inner == self.from).then_some(Permission { inner: self.to })
180     }
181 
182     /// Extract starting point of a transition
started(self) -> Permission183     pub fn started(self) -> Permission {
184         Permission { inner: self.from }
185     }
186 
187     /// Determines whether a transition that occured is compatible with the presence
188     /// of a Protector. This is not included in the `transition` functions because
189     /// it would distract from the few places where the transition is modified
190     /// because of a protector, but not forbidden.
191     ///
192     /// Note: this is not in charge of checking that there *is* a protector,
193     /// it should be used as
194     /// ```
195     /// let no_protector_error = if is_protected(tag) {
196     ///     transition.is_allowed_by_protector()
197     /// };
198     /// ```
is_allowed_by_protector(&self) -> bool199     pub fn is_allowed_by_protector(&self) -> bool {
200         assert!(self.is_possible());
201         match (self.from, self.to) {
202             _ if self.from == self.to => true,
203             // It is always a protector violation to not be readable anymore
204             (_, Disabled) => false,
205             // In the case of a `Reserved` under a protector, both transitions
206             // `Reserved => Active` and `Reserved => Frozen` can legitimately occur.
207             // The first is standard (Child Write), the second is for Foreign Writes
208             // on protected Reserved where we must ensure that the pointer is not
209             // written to in the future.
210             (Reserved { .. }, Active) | (Reserved { .. }, Frozen) => true,
211             // This pointer should have stayed writeable for the whole function
212             (Active, Frozen) => false,
213             _ => unreachable!("Transition {} should never be possible", self),
214         }
215     }
216 }
217 
218 pub mod diagnostics {
219     use super::*;
220     impl fmt::Display for PermissionPriv {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result221         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222             write!(
223                 f,
224                 "{}",
225                 match self {
226                     Reserved { .. } => "Reserved",
227                     Active => "Active",
228                     Frozen => "Frozen",
229                     Disabled => "Disabled",
230                 }
231             )
232         }
233     }
234 
235     impl fmt::Display for PermTransition {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result236         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237             write!(f, "from {} to {}", self.from, self.to)
238         }
239     }
240 
241     impl fmt::Display for Permission {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result242         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243             write!(f, "{}", self.inner)
244         }
245     }
246 
247     impl Permission {
248         /// Abbreviated name of the permission (uniformly 3 letters for nice alignment).
short_name(self) -> &'static str249         pub fn short_name(self) -> &'static str {
250             // Make sure there are all of the same length as each other
251             // and also as `diagnostics::DisplayFmtPermission.uninit` otherwise
252             // alignment will be incorrect.
253             match self.inner {
254                 Reserved { ty_is_freeze: true } => "Res",
255                 Reserved { ty_is_freeze: false } => "Re*",
256                 Active => "Act",
257                 Frozen => "Frz",
258                 Disabled => "Dis",
259             }
260         }
261     }
262 
263     impl PermTransition {
264         /// Readable explanation of the consequences of an event.
265         /// Fits in the sentence "This accessed caused {trans.summary()}".
266         ///
267         /// Important: for the purposes of this explanation, `Reserved` is considered
268         /// to have write permissions, because that's what the diagnostics care about
269         /// (otherwise `Reserved -> Frozen` would be considered a noop).
summary(&self) -> &'static str270         pub fn summary(&self) -> &'static str {
271             assert!(self.is_possible());
272             match (self.from, self.to) {
273                 (_, Active) => "the first write to a 2-phase borrowed mutable reference",
274                 (_, Frozen) => "a loss of write permissions",
275                 (Frozen, Disabled) => "a loss of read permissions",
276                 (_, Disabled) => "a loss of read and write permissions",
277                 (old, new) =>
278                     unreachable!("Transition from {old:?} to {new:?} should never be possible"),
279             }
280         }
281 
282         /// Determines whether `self` is a relevant transition for the error `err`.
283         /// `self` will be a transition that happened to a tag some time before
284         /// that tag caused the error.
285         ///
286         /// Irrelevant events:
287         /// - modifications of write permissions when the error is related to read permissions
288         ///   (on failed reads and protected `Frozen -> Disabled`, ignore `Reserved -> Active`,
289         ///   `Reserved -> Frozen`, and `Active -> Frozen`)
290         /// - all transitions for attempts to deallocate strongly protected tags
291         ///
292         /// # Panics
293         ///
294         /// This function assumes that its arguments apply to the same location
295         /// and that they were obtained during a normal execution. It will panic otherwise.
296         /// - `err` cannot be a `ProtectedTransition(_)` of a noop transition, as those
297         /// never trigger protectors;
298         /// - all transitions involved in `self` and `err` should be increasing
299         /// (Reserved < Active < Frozen < Disabled);
300         /// - between `self` and `err` the permission should also be increasing,
301         /// so all permissions inside `err` should be greater than `self.1`;
302         /// - `Active` and `Reserved` cannot cause an error due to insufficient permissions,
303         /// so `err` cannot be a `ChildAccessForbidden(_)` of either of them;
is_relevant(&self, err: TransitionError) -> bool304         pub(in super::super) fn is_relevant(&self, err: TransitionError) -> bool {
305             // NOTE: `super::super` is the visibility of `TransitionError`
306             assert!(self.is_possible());
307             if self.is_noop() {
308                 return false;
309             }
310             match err {
311                 TransitionError::ChildAccessForbidden(insufficient) => {
312                     // Show where the permission was gained then lost,
313                     // but ignore unrelated permissions.
314                     // This eliminates transitions like `Active -> Frozen`
315                     // when the error is a failed `Read`.
316                     match (self.to, insufficient.inner) {
317                         (Frozen, Frozen) => true,
318                         (Active, Frozen) => true,
319                         (Disabled, Disabled) => true,
320                         // A pointer being `Disabled` is a strictly stronger source of
321                         // errors than it being `Frozen`. If we try to access a `Disabled`,
322                         // then where it became `Frozen` (or `Active`) is the least of our concerns for now.
323                         (Active | Frozen, Disabled) => false,
324 
325                         // `Active` and `Reserved` have all permissions, so a
326                         // `ChildAccessForbidden(Reserved | Active)` can never exist.
327                         (_, Active) | (_, Reserved { .. }) =>
328                             unreachable!("this permission cannot cause an error"),
329                         // No transition has `Reserved` as its `.to` unless it's a noop.
330                         (Reserved { .. }, _) => unreachable!("self is a noop transition"),
331                         // All transitions produced in normal executions (using `apply_access`)
332                         // change permissions in the order `Reserved -> Active -> Frozen -> Disabled`.
333                         // We assume that the error was triggered on the same location that
334                         // the transition `self` applies to, so permissions found must be increasing
335                         // in the order `self.from < self.to <= insufficient.inner`
336                         (Disabled, Frozen) =>
337                             unreachable!("permissions between self and err must be increasing"),
338                     }
339                 }
340                 TransitionError::ProtectedTransition(forbidden) => {
341                     assert!(!forbidden.is_noop());
342                     // Show how we got to the starting point of the forbidden transition,
343                     // but ignore what came before.
344                     // This eliminates transitions like `Reserved -> Active`
345                     // when the error is a `Frozen -> Disabled`.
346                     match (self.to, forbidden.from, forbidden.to) {
347                         // We absolutely want to know where it was activated.
348                         (Active, Active, Frozen | Disabled) => true,
349                         // And knowing where it became Frozen is also important.
350                         (Frozen, Frozen, Disabled) => true,
351                         // If the error is a transition `Frozen -> Disabled`, then we don't really
352                         // care whether before that was `Reserved -> Active -> Frozen` or
353                         // `Reserved -> Frozen` or even `Frozen` directly.
354                         // The error will only show either
355                         // - created as Frozen, then Frozen -> Disabled is forbidden
356                         // - created as Reserved, later became Frozen, then Frozen -> Disabled is forbidden
357                         // In both cases the `Reserved -> Active` part is inexistant or irrelevant.
358                         (Active, Frozen, Disabled) => false,
359 
360                         // `Reserved -> Frozen` does not trigger protectors.
361                         (_, Reserved { .. }, Frozen) =>
362                             unreachable!("this transition cannot cause an error"),
363                         // No transition has `Reserved` as its `.to` unless it's a noop.
364                         (Reserved { .. }, _, _) => unreachable!("self is a noop transition"),
365                         (_, Disabled, Disabled) | (_, Frozen, Frozen) | (_, Active, Active) =>
366                             unreachable!("err contains a noop transition"),
367 
368                         // Permissions only evolve in the order `Reserved -> Active -> Frozen -> Disabled`,
369                         // so permissions found must be increasing in the order
370                         // `self.from < self.to <= forbidden.from < forbidden.to`.
371                         (Disabled, Reserved { .. } | Active | Frozen, _)
372                         | (Frozen, Reserved { .. } | Active, _)
373                         | (Active, Reserved { .. }, _) =>
374                             unreachable!("permissions between self and err must be increasing"),
375                         (_, Disabled, Reserved { .. } | Active | Frozen)
376                         | (_, Frozen, Reserved { .. } | Active)
377                         | (_, Active, Reserved { .. }) =>
378                             unreachable!("permissions within err must be increasing"),
379                     }
380                 }
381                 // We don't care because protectors evolve independently from
382                 // permissions.
383                 TransitionError::ProtectedDealloc => false,
384             }
385         }
386 
387         /// Endpoint of a transition.
388         /// Meant only for diagnostics, use `applied` in non-diagnostics
389         /// code, which also checks that the starting point matches the current state.
endpoint(&self) -> Permission390         pub fn endpoint(&self) -> Permission {
391             Permission { inner: self.to }
392         }
393     }
394 }
395 
396 #[cfg(test)]
397 mod propagation_optimization_checks {
398     pub use super::*;
399 
400     mod util {
401         pub use super::*;
402         impl PermissionPriv {
403             /// Enumerate all states
all() -> impl Iterator<Item = PermissionPriv>404             pub fn all() -> impl Iterator<Item = PermissionPriv> {
405                 vec![
406                     Active,
407                     Reserved { ty_is_freeze: true },
408                     Reserved { ty_is_freeze: false },
409                     Frozen,
410                     Disabled,
411                 ]
412                 .into_iter()
413             }
414         }
415 
416         impl AccessKind {
417             /// Enumerate all AccessKind.
all() -> impl Iterator<Item = AccessKind>418             pub fn all() -> impl Iterator<Item = AccessKind> {
419                 use AccessKind::*;
420                 [Read, Write].into_iter()
421             }
422         }
423 
424         impl AccessRelatedness {
425             /// Enumerate all relative positions
all() -> impl Iterator<Item = AccessRelatedness>426             pub fn all() -> impl Iterator<Item = AccessRelatedness> {
427                 use AccessRelatedness::*;
428                 [This, StrictChildAccess, AncestorAccess, DistantAccess].into_iter()
429             }
430         }
431     }
432 
433     #[test]
434     // For any kind of access, if we do it twice the second should be a no-op.
435     // Even if the protector has disappeared.
all_transitions_idempotent()436     fn all_transitions_idempotent() {
437         use transition::*;
438         for old in PermissionPriv::all() {
439             for (old_protected, new_protected) in [(true, true), (true, false), (false, false)] {
440                 for access in AccessKind::all() {
441                     for rel_pos in AccessRelatedness::all() {
442                         if let Some(new) = perform_access(access, rel_pos, old, old_protected) {
443                             assert_eq!(
444                                 new,
445                                 perform_access(access, rel_pos, new, new_protected).unwrap()
446                             );
447                         }
448                     }
449                 }
450             }
451         }
452     }
453 
454     #[test]
foreign_read_is_noop_after_write()455     fn foreign_read_is_noop_after_write() {
456         use transition::*;
457         let old_access = AccessKind::Write;
458         let new_access = AccessKind::Read;
459         for old in PermissionPriv::all() {
460             for (old_protected, new_protected) in [(true, true), (true, false), (false, false)] {
461                 for rel_pos in AccessRelatedness::all().filter(|rel| rel.is_foreign()) {
462                     if let Some(new) = perform_access(old_access, rel_pos, old, old_protected) {
463                         assert_eq!(
464                             new,
465                             perform_access(new_access, rel_pos, new, new_protected).unwrap()
466                         );
467                     }
468                 }
469             }
470         }
471     }
472 
473     #[test]
474     // Check that all transitions are consistent with the order on PermissionPriv,
475     // i.e. Reserved -> Active -> Frozen -> Disabled
access_transitions_progress_increasing()476     fn access_transitions_progress_increasing() {
477         use transition::*;
478         for old in PermissionPriv::all() {
479             for protected in [true, false] {
480                 for access in AccessKind::all() {
481                     for rel_pos in AccessRelatedness::all() {
482                         if let Some(new) = perform_access(access, rel_pos, old, protected) {
483                             assert!(old <= new);
484                         }
485                     }
486                 }
487             }
488         }
489     }
490 }
491