|
| 1 | +use std::cmp::{Ordering, PartialOrd}; |
| 2 | +use std::fmt; |
| 3 | + |
| 4 | +/// The activation states of a pointer |
| 5 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 6 | +enum PermissionPriv { |
| 7 | + /// represents: a local reference that has not yet been written to; |
| 8 | + /// allows: child reads, foreign reads, foreign writes if type is freeze; |
| 9 | + /// rejects: child writes (Active), foreign writes (Disabled, except if type is not freeze). |
| 10 | + /// special case: behaves differently when protected to adhere more closely to noalias |
| 11 | + Reserved { ty_is_freeze: bool }, |
| 12 | + /// represents: a unique pointer; |
| 13 | + /// allows: child reads, child writes; |
| 14 | + /// rejects: foreign reads (Frozen), foreign writes (Disabled). |
| 15 | + Active, |
| 16 | + /// represents: a shared pointer; |
| 17 | + /// allows: all read accesses; |
| 18 | + /// rejects child writes (UB), foreign writes (Disabled). |
| 19 | + Frozen, |
| 20 | + /// represents: a dead pointer; |
| 21 | + /// allows: all foreign accesses; |
| 22 | + /// rejects: all child accesses (UB). |
| 23 | + Disabled, |
| 24 | +} |
| 25 | +use PermissionPriv::*; |
| 26 | + |
| 27 | +impl PartialOrd for PermissionPriv { |
| 28 | + /// PermissionPriv is ordered as follows: |
| 29 | + /// - Reserved(_) < Active < Frozen < Disabled; |
| 30 | + /// - different kinds of `Reserved` (with or without interior mutability) |
| 31 | + /// are incomparable to each other. |
| 32 | + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
| 33 | + use Ordering::*; |
| 34 | + Some(match (self, other) { |
| 35 | + (a, b) if a == b => Equal, |
| 36 | + (Disabled, _) => Greater, |
| 37 | + (_, Disabled) => Less, |
| 38 | + (Frozen, _) => Greater, |
| 39 | + (_, Frozen) => Less, |
| 40 | + (Active, _) => Greater, |
| 41 | + (_, Active) => Less, |
| 42 | + (Reserved { .. }, Reserved { .. }) => return None, |
| 43 | + }) |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +/// This module controls how each permission individually reacts to an access. |
| 48 | +/// Although these functions take `protected` as an argument, this is NOT because |
| 49 | +/// we check protector violations here, but because some permissions behave differently |
| 50 | +/// when protected. |
| 51 | +mod transition { |
| 52 | + use super::*; |
| 53 | + /// A child node was read-accessed: UB on Disabled, noop on the rest. |
| 54 | + fn child_read(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> { |
| 55 | + Some(match state { |
| 56 | + Disabled => return None, |
| 57 | + // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read |
| 58 | + // accesses, since the data is not being mutated. Hence the `{ .. }` |
| 59 | + readable @ (Reserved { .. } | Active | Frozen) => readable, |
| 60 | + }) |
| 61 | + } |
| 62 | + |
| 63 | + /// A non-child node was read-accessed: noop on non-protected Reserved, advance to Frozen otherwise. |
| 64 | + fn foreign_read(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> { |
| 65 | + use Option::*; |
| 66 | + Some(match state { |
| 67 | + // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read |
| 68 | + // accesses, since the data is not being mutated. Hence the `{ .. }` |
| 69 | + res @ Reserved { .. } if !protected => res, |
| 70 | + Reserved { .. } => Frozen, // protected reserved |
| 71 | + Active => Frozen, |
| 72 | + non_writeable @ (Frozen | Disabled) => non_writeable, |
| 73 | + }) |
| 74 | + } |
| 75 | + |
| 76 | + /// A child node was write-accessed: `Reserved` must become `Active` to obtain |
| 77 | + /// write permissions, `Frozen` and `Disabled` cannot obtain such permissions and produce UB. |
| 78 | + fn child_write(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> { |
| 79 | + Some(match state { |
| 80 | + // A write always activates the 2-phase borrow, even with interior |
| 81 | + // mutability |
| 82 | + Reserved { .. } | Active => Active, |
| 83 | + Frozen | Disabled => return None, |
| 84 | + }) |
| 85 | + } |
| 86 | + |
| 87 | + /// A non-child node was write-accessed: this makes everything `Disabled` except for |
| 88 | + /// non-protected interior mutable `Reserved` which stay the same. |
| 89 | + fn foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> { |
| 90 | + Some(match state { |
| 91 | + cell @ Reserved { ty_is_freeze: false } if !protected => cell, |
| 92 | + _ => Disabled, |
| 93 | + }) |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +impl PermissionPriv { |
| 98 | + /// Determines whether a transition that occured is compatible with the presence |
| 99 | + /// of a Protector. This is not included in the `transition` functions because |
| 100 | + /// it would distract from the few places where the transition is modified |
| 101 | + /// because of a protector, but not forbidden. |
| 102 | + fn protector_allows_transition(self, new: Self) -> bool { |
| 103 | + match (self, new) { |
| 104 | + _ if self == new => true, |
| 105 | + // It is always a protector violation to not be readable anymore |
| 106 | + (_, Disabled) => false, |
| 107 | + // In the case of a `Reserved` under a protector, both transitions |
| 108 | + // `Reserved => Active` and `Reserved => Frozen` can legitimately occur. |
| 109 | + // The first is standard (Child Write), the second is for Foreign Writes |
| 110 | + // on protected Reserved where we must ensure that the pointer is not |
| 111 | + // written to in the future. |
| 112 | + (Reserved { .. }, Active) | (Reserved { .. }, Frozen) => true, |
| 113 | + // This pointer should have stayed writeable for the whole function |
| 114 | + (Active, Frozen) => false, |
| 115 | + _ => unreachable!("Transition from {self:?} to {new:?} should never be possible"), |
| 116 | + } |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +#[cfg(test)] |
| 121 | +mod propagation_optimization_checks { |
| 122 | + pub use super::*; |
| 123 | + |
| 124 | + mod util { |
| 125 | + pub use super::*; |
| 126 | + impl PermissionPriv { |
| 127 | + /// Enumerate all states |
| 128 | + pub fn all() -> impl Iterator<Item = PermissionPriv> { |
| 129 | + vec![ |
| 130 | + Active, |
| 131 | + Reserved { ty_is_freeze: true }, |
| 132 | + Reserved { ty_is_freeze: false }, |
| 133 | + Frozen, |
| 134 | + Disabled, |
| 135 | + ] |
| 136 | + .into_iter() |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + impl AccessKind { |
| 141 | + /// Enumerate all AccessKind. |
| 142 | + pub fn all() -> impl Iterator<Item = AccessKind> { |
| 143 | + use AccessKind::*; |
| 144 | + [Read, Write].into_iter() |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + impl AccessRelatedness { |
| 149 | + /// Enumerate all relative positions |
| 150 | + pub fn all() -> impl Iterator<Item = AccessRelatedness> { |
| 151 | + use AccessRelatedness::*; |
| 152 | + [This, StrictChildAccess, AncestorAccess, DistantAccess].into_iter() |
| 153 | + } |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + #[test] |
| 158 | + // For any kind of access, if we do it twice the second should be a no-op. |
| 159 | + // Even if the protector has disappeared. |
| 160 | + fn all_transitions_idempotent() { |
| 161 | + use transition::*; |
| 162 | + for old in PermissionPriv::all() { |
| 163 | + for (old_protected, new_protected) in [(true, true), (true, false), (false, false)] { |
| 164 | + for access in AccessKind::all() { |
| 165 | + for rel_pos in AccessRelatedness::all() { |
| 166 | + if let Some(new) = perform_access(access, rel_pos, old, old_protected) { |
| 167 | + assert_eq!( |
| 168 | + new, |
| 169 | + perform_access(access, rel_pos, new, new_protected).unwrap() |
| 170 | + ); |
| 171 | + } |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + } |
| 177 | + |
| 178 | + #[test] |
| 179 | + fn foreign_read_is_noop_after_write() { |
| 180 | + use transition::*; |
| 181 | + let old_access = AccessKind::Write; |
| 182 | + let new_access = AccessKind::Read; |
| 183 | + for old in PermissionPriv::all() { |
| 184 | + for (old_protected, new_protected) in [(true, true), (true, false), (false, false)] { |
| 185 | + for rel_pos in AccessRelatedness::all().filter(|rel| rel.is_foreign()) { |
| 186 | + if let Some(new) = perform_access(old_access, rel_pos, old, old_protected) { |
| 187 | + assert_eq!( |
| 188 | + new, |
| 189 | + perform_access(new_access, rel_pos, new, new_protected).unwrap() |
| 190 | + ); |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + #[test] |
| 198 | + // Check that all transitions are consistent with the order on PermissionPriv, |
| 199 | + // i.e. Reserved -> Active -> Frozen -> Disabled |
| 200 | + fn access_transitions_progress_increasing() { |
| 201 | + use transition::*; |
| 202 | + for old in PermissionPriv::all() { |
| 203 | + for protected in [true, false] { |
| 204 | + for access in AccessKind::all() { |
| 205 | + for rel_pos in AccessRelatedness::all() { |
| 206 | + if let Some(new) = perform_access(access, rel_pos, old, protected) { |
| 207 | + assert!(old <= new); |
| 208 | + } |
| 209 | + } |
| 210 | + } |
| 211 | + } |
| 212 | + } |
| 213 | + } |
| 214 | +} |
0 commit comments