about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNeven Villani <vanille@crans.org>2023-03-16 14:40:17 +0100
committerNeven Villani <vanille@crans.org>2023-03-16 14:52:46 +0100
commit8fcfd9e11db56d40739fe7816540e3f98d6283b5 (patch)
tree121ca482b38df48061355872766718fec8a17b3d
parent3ad2fece144996fb3591dc3250b9fc6b49288a91 (diff)
downloadrust-8fcfd9e11db56d40739fe7816540e3f98d6283b5.tar.gz
rust-8fcfd9e11db56d40739fe7816540e3f98d6283b5.zip
TB: encoding of the underlying state machine
+ properties about the transitions
-rw-r--r--src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs214
1 files changed, 214 insertions, 0 deletions
diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs
new file mode 100644
index 00000000000..dc7f934a705
--- /dev/null
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs
@@ -0,0 +1,214 @@
+use std::cmp::{Ordering, PartialOrd};
+use std::fmt;
+
+/// The activation states of a pointer
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum PermissionPriv {
+    /// represents: a local reference that has not yet been written to;
+    /// allows: child reads, foreign reads, foreign writes if type is freeze;
+    /// rejects: child writes (Active), foreign writes (Disabled, except if type is not freeze).
+    /// special case: behaves differently when protected to adhere more closely to noalias
+    Reserved { ty_is_freeze: bool },
+    /// represents: a unique pointer;
+    /// allows: child reads, child writes;
+    /// rejects: foreign reads (Frozen), foreign writes (Disabled).
+    Active,
+    /// represents: a shared pointer;
+    /// allows: all read accesses;
+    /// rejects child writes (UB), foreign writes (Disabled).
+    Frozen,
+    /// represents: a dead pointer;
+    /// allows: all foreign accesses;
+    /// rejects: all child accesses (UB).
+    Disabled,
+}
+use PermissionPriv::*;
+
+impl PartialOrd for PermissionPriv {
+    /// PermissionPriv is ordered as follows:
+    /// - Reserved(_) < Active < Frozen < Disabled;
+    /// - different kinds of `Reserved` (with or without interior mutability)
+    /// are incomparable to each other.
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        use Ordering::*;
+        Some(match (self, other) {
+            (a, b) if a == b => Equal,
+            (Disabled, _) => Greater,
+            (_, Disabled) => Less,
+            (Frozen, _) => Greater,
+            (_, Frozen) => Less,
+            (Active, _) => Greater,
+            (_, Active) => Less,
+            (Reserved { .. }, Reserved { .. }) => return None,
+        })
+    }
+}
+
+/// This module controls how each permission individually reacts to an access.
+/// Although these functions take `protected` as an argument, this is NOT because
+/// we check protector violations here, but because some permissions behave differently
+/// when protected.
+mod transition {
+    use super::*;
+    /// A child node was read-accessed: UB on Disabled, noop on the rest.
+    fn child_read(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> {
+        Some(match state {
+            Disabled => return None,
+            // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
+            // accesses, since the data is not being mutated. Hence the `{ .. }`
+            readable @ (Reserved { .. } | Active | Frozen) => readable,
+        })
+    }
+
+    /// A non-child node was read-accessed: noop on non-protected Reserved, advance to Frozen otherwise.
+    fn foreign_read(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
+        use Option::*;
+        Some(match state {
+            // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
+            // accesses, since the data is not being mutated. Hence the `{ .. }`
+            res @ Reserved { .. } if !protected => res,
+            Reserved { .. } => Frozen, // protected reserved
+            Active => Frozen,
+            non_writeable @ (Frozen | Disabled) => non_writeable,
+        })
+    }
+
+    /// A child node was write-accessed: `Reserved` must become `Active` to obtain
+    /// write permissions, `Frozen` and `Disabled` cannot obtain such permissions and produce UB.
+    fn child_write(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> {
+        Some(match state {
+            // A write always activates the 2-phase borrow, even with interior
+            // mutability
+            Reserved { .. } | Active => Active,
+            Frozen | Disabled => return None,
+        })
+    }
+
+    /// A non-child node was write-accessed: this makes everything `Disabled` except for
+    /// non-protected interior mutable `Reserved` which stay the same.
+    fn foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
+        Some(match state {
+            cell @ Reserved { ty_is_freeze: false } if !protected => cell,
+            _ => Disabled,
+        })
+    }
+}
+
+impl PermissionPriv {
+    /// Determines whether a transition that occured is compatible with the presence
+    /// of a Protector. This is not included in the `transition` functions because
+    /// it would distract from the few places where the transition is modified
+    /// because of a protector, but not forbidden.
+    fn protector_allows_transition(self, new: Self) -> bool {
+        match (self, new) {
+            _ if self == new => true,
+            // It is always a protector violation to not be readable anymore
+            (_, Disabled) => false,
+            // In the case of a `Reserved` under a protector, both transitions
+            // `Reserved => Active` and `Reserved => Frozen` can legitimately occur.
+            // The first is standard (Child Write), the second is for Foreign Writes
+            // on protected Reserved where we must ensure that the pointer is not
+            // written to in the future.
+            (Reserved { .. }, Active) | (Reserved { .. }, Frozen) => true,
+            // This pointer should have stayed writeable for the whole function
+            (Active, Frozen) => false,
+            _ => unreachable!("Transition from {self:?} to {new:?} should never be possible"),
+        }
+    }
+}
+
+#[cfg(test)]
+mod propagation_optimization_checks {
+    pub use super::*;
+
+    mod util {
+        pub use super::*;
+        impl PermissionPriv {
+            /// Enumerate all states
+            pub fn all() -> impl Iterator<Item = PermissionPriv> {
+                vec![
+                    Active,
+                    Reserved { ty_is_freeze: true },
+                    Reserved { ty_is_freeze: false },
+                    Frozen,
+                    Disabled,
+                ]
+                .into_iter()
+            }
+        }
+
+        impl AccessKind {
+            /// Enumerate all AccessKind.
+            pub fn all() -> impl Iterator<Item = AccessKind> {
+                use AccessKind::*;
+                [Read, Write].into_iter()
+            }
+        }
+
+        impl AccessRelatedness {
+            /// Enumerate all relative positions
+            pub fn all() -> impl Iterator<Item = AccessRelatedness> {
+                use AccessRelatedness::*;
+                [This, StrictChildAccess, AncestorAccess, DistantAccess].into_iter()
+            }
+        }
+    }
+
+    #[test]
+    // For any kind of access, if we do it twice the second should be a no-op.
+    // Even if the protector has disappeared.
+    fn all_transitions_idempotent() {
+        use transition::*;
+        for old in PermissionPriv::all() {
+            for (old_protected, new_protected) in [(true, true), (true, false), (false, false)] {
+                for access in AccessKind::all() {
+                    for rel_pos in AccessRelatedness::all() {
+                        if let Some(new) = perform_access(access, rel_pos, old, old_protected) {
+                            assert_eq!(
+                                new,
+                                perform_access(access, rel_pos, new, new_protected).unwrap()
+                            );
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn foreign_read_is_noop_after_write() {
+        use transition::*;
+        let old_access = AccessKind::Write;
+        let new_access = AccessKind::Read;
+        for old in PermissionPriv::all() {
+            for (old_protected, new_protected) in [(true, true), (true, false), (false, false)] {
+                for rel_pos in AccessRelatedness::all().filter(|rel| rel.is_foreign()) {
+                    if let Some(new) = perform_access(old_access, rel_pos, old, old_protected) {
+                        assert_eq!(
+                            new,
+                            perform_access(new_access, rel_pos, new, new_protected).unwrap()
+                        );
+                    }
+                }
+            }
+        }
+    }
+
+    #[test]
+    // Check that all transitions are consistent with the order on PermissionPriv,
+    // i.e. Reserved -> Active -> Frozen -> Disabled
+    fn access_transitions_progress_increasing() {
+        use transition::*;
+        for old in PermissionPriv::all() {
+            for protected in [true, false] {
+                for access in AccessKind::all() {
+                    for rel_pos in AccessRelatedness::all() {
+                        if let Some(new) = perform_access(access, rel_pos, old, protected) {
+                            assert!(old <= new);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}