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