Auto merge of #3754 - Vanille-N:master, r=RalfJung

Make unused states of Reserved unrepresentable

In the [previous TB update](https://github.com/rust-lang/miri/pull/3742) we discovered that the existence of `Reserved + !ty_is_freeze + protected` is undesirable.

This has the side effect of making `Reserved { conflicted: true, ty_is_freeze: false }` unreachable.
As such it is desirable that this state would also be unrepresentable.

This PR eliminates the unused configuration by changing
```rs
enum PermissionPriv {
    Reserved { ty_is_freeze: bool, conflicted: bool },
    ...
}
```
into
```rs
enum PermissionPriv {
    ReservedFrz { conflicted: bool },
    ReservedIM,
    ...
}
```
but this is not the only solution and `Reserved(Activable | Conflicted | InteriorMut)` could be discussed.
In addition to making the unreachable state not representable anymore, this change has the nice side effect of enabling `foreign_read` to no longer depend explicitly on the `protected` flag.

Currently waiting for
- `@JoJoDeveloping` to confirm that this is the same representation of `Reserved` as what is being implemented in simuliris,
- `@RalfJung` to approve that this does not introduce too much overhead in the trusted codebase.
This commit is contained in:
bors 2024-08-16 15:48:55 +00:00
commit 1a51dd9247
13 changed files with 200 additions and 134 deletions

View File

@ -1,10 +1,6 @@
use rustc_middle::{ use rustc_middle::{
mir::{Mutability, RetagKind}, mir::{Mutability, RetagKind},
ty::{ ty::{self, layout::HasParamEnv, Ty},
self,
layout::{HasParamEnv, HasTyCtxt},
Ty,
},
}; };
use rustc_span::def_id::DefId; use rustc_span::def_id::DefId;
use rustc_target::abi::{Abi, Size}; use rustc_target::abi::{Abi, Size};
@ -146,10 +142,9 @@ impl<'tcx> NewPermission {
// interior mutability and protectors interact poorly. // interior mutability and protectors interact poorly.
// To eliminate the case of Protected Reserved IM we override interior mutability // To eliminate the case of Protected Reserved IM we override interior mutability
// in the case of a protected reference: protected references are always considered // in the case of a protected reference: protected references are always considered
// "freeze". // "freeze" in their reservation phase.
let initial_state = match mutability { let initial_state = match mutability {
Mutability::Mut if ty_is_unpin => Mutability::Mut if ty_is_unpin => Permission::new_reserved(ty_is_freeze, is_protected),
Permission::new_reserved(ty_is_freeze || is_protected),
Mutability::Not if ty_is_freeze => Permission::new_frozen(), Mutability::Not if ty_is_freeze => Permission::new_frozen(),
// Raw pointers never enter this function so they are not handled. // Raw pointers never enter this function so they are not handled.
// However raw pointers are not the only pointers that take the parent // However raw pointers are not the only pointers that take the parent
@ -176,10 +171,12 @@ impl<'tcx> NewPermission {
// Regular `Unpin` box, give it `noalias` but only a weak protector // Regular `Unpin` box, give it `noalias` but only a weak protector
// because it is valid to deallocate it within the function. // because it is valid to deallocate it within the function.
let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env()); let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env());
let protected = kind == RetagKind::FnEntry;
let initial_state = Permission::new_reserved(ty_is_freeze, protected);
Self { Self {
zero_size, zero_size,
initial_state: Permission::new_reserved(ty_is_freeze), initial_state,
protector: (kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector), protector: protected.then_some(ProtectorKind::WeakProtector),
} }
}) })
} }
@ -521,11 +518,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> { fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
// Note: if we were to inline `new_reserved` below we would find out that
// `ty_is_freeze` is eventually unused because it appears in a `ty_is_freeze || true`.
// We are nevertheless including it here for clarity.
let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.param_env());
// Retag it. With protection! That is the entire point. // Retag it. With protection! That is the entire point.
let new_perm = NewPermission { let new_perm = NewPermission {
initial_state: Permission::new_reserved( initial_state: Permission::new_reserved(ty_is_freeze, /* protected */ true),
place.layout.ty.is_freeze(this.tcx(), this.param_env()),
),
zero_size: false, zero_size: false,
protector: Some(ProtectorKind::StrongProtector), protector: Some(ProtectorKind::StrongProtector),
}; };

View File

@ -8,10 +8,16 @@ use crate::AccessKind;
/// The activation states of a pointer. /// The activation states of a pointer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum PermissionPriv { enum PermissionPriv {
/// represents: a local reference that has not yet been written to; /// represents: a local mutable reference that has not yet been written to;
/// allows: child reads, foreign reads, foreign writes if type is freeze; /// allows: child reads, foreign reads;
/// affected by: child writes (becomes Active), /// affected by: child writes (becomes Active),
/// rejects: foreign writes (Disabled, except if type is not freeze). /// rejects: foreign writes (Disabled).
///
/// `ReservedFrz` is mostly for types that are `Freeze` (no interior mutability).
/// If the type has interior mutability, see `ReservedIM` instead.
/// (Note: since the discovery of `tests/fail/tree_borrows/reservedim_spurious_write.rs`,
/// we also use `ReservedFreeze` for mutable references that were retagged with a protector
/// independently of interior mutability)
/// ///
/// special case: behaves differently when protected, which is where `conflicted` /// special case: behaves differently when protected, which is where `conflicted`
/// is relevant /// is relevant
@ -22,12 +28,12 @@ enum PermissionPriv {
/// - foreign-read then child-write is UB due to `conflicted`, /// - foreign-read then child-write is UB due to `conflicted`,
/// - child-write then foreign-read is UB since child-write will activate and then /// - child-write then foreign-read is UB since child-write will activate and then
/// foreign-read disables a protected `Active`, which is UB. /// foreign-read disables a protected `Active`, which is UB.
/// ReservedFrz { conflicted: bool },
/// Note: since the discovery of `tests/fail/tree_borrows/reservedim_spurious_write.rs`, /// Alternative version of `ReservedFrz` made for types with interior mutability.
/// `ty_is_freeze` does not strictly mean that the type has no interior mutability, /// allows: child reads, foreign reads, foreign writes (extra);
/// it could be an interior mutable type that lost its interior mutability privileges /// affected by: child writes (becomes Active);
/// when retagged with a protector. /// rejects: nothing.
Reserved { ty_is_freeze: bool, conflicted: bool }, ReservedIM,
/// represents: a unique pointer; /// represents: a unique pointer;
/// allows: child reads, child writes; /// allows: child reads, child writes;
/// rejects: foreign reads (Frozen), foreign writes (Disabled). /// rejects: foreign reads (Frozen), foreign writes (Disabled).
@ -59,17 +65,14 @@ impl PartialOrd for PermissionPriv {
(_, Frozen) => Less, (_, Frozen) => Less,
(Active, _) => Greater, (Active, _) => Greater,
(_, Active) => Less, (_, Active) => Less,
( (ReservedIM, ReservedIM) => Equal,
Reserved { ty_is_freeze: f1, conflicted: c1 }, (ReservedFrz { conflicted: c1 }, ReservedFrz { conflicted: c2 }) => {
Reserved { ty_is_freeze: f2, conflicted: c2 },
) => {
// No transition ever changes `ty_is_freeze`.
if f1 != f2 {
return None;
}
// `bool` is ordered such that `false <= true`, so this works as intended. // `bool` is ordered such that `false <= true`, so this works as intended.
c1.cmp(c2) c1.cmp(c2)
} }
// Versions of `Reserved` with different interior mutability are incomparable with each
// other.
(ReservedIM, ReservedFrz { .. }) | (ReservedFrz { .. }, ReservedIM) => return None,
}) })
} }
} }
@ -77,7 +80,12 @@ impl PartialOrd for PermissionPriv {
impl PermissionPriv { impl PermissionPriv {
/// Check if `self` can be the initial state of a pointer. /// Check if `self` can be the initial state of a pointer.
fn is_initial(&self) -> bool { fn is_initial(&self) -> bool {
matches!(self, Reserved { conflicted: false, .. } | Frozen) matches!(self, ReservedFrz { conflicted: false } | Frozen | ReservedIM)
}
/// Reject `ReservedIM` that cannot exist in the presence of a protector.
fn compatible_with_protector(&self) -> bool {
!matches!(self, ReservedIM)
} }
} }
@ -93,7 +101,7 @@ mod transition {
Disabled => return None, Disabled => return None,
// The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
// accesses, since the data is not being mutated. Hence the `{ .. }`. // accesses, since the data is not being mutated. Hence the `{ .. }`.
readable @ (Reserved { .. } | Active | Frozen) => readable, readable @ (ReservedFrz { .. } | ReservedIM | Active | Frozen) => readable,
}) })
} }
@ -109,11 +117,16 @@ mod transition {
// Someone else read. To make sure we won't write before function exit, // Someone else read. To make sure we won't write before function exit,
// we set the "conflicted" flag, which will disallow writes while we are protected. // we set the "conflicted" flag, which will disallow writes while we are protected.
Reserved { ty_is_freeze, .. } if protected => ReservedFrz { .. } if protected => ReservedFrz { conflicted: true },
Reserved { ty_is_freeze, conflicted: true },
// Before activation and without protectors, foreign reads are fine. // Before activation and without protectors, foreign reads are fine.
// That's the entire point of 2-phase borrows. // That's the entire point of 2-phase borrows.
res @ Reserved { .. } => res, res @ (ReservedFrz { .. } | ReservedIM) => {
// Even though we haven't checked `ReservedIM if protected` separately,
// it is a state that cannot occur because under a protector we only
// create `ReservedFrz` never `ReservedIM`.
assert!(!protected);
res
}
Active => Active =>
if protected { if protected {
// We wrote, someone else reads -- that's bad. // We wrote, someone else reads -- that's bad.
@ -134,10 +147,10 @@ mod transition {
// If the `conflicted` flag is set, then there was a foreign read during // If the `conflicted` flag is set, then there was a foreign read during
// the function call that is still ongoing (still `protected`), // the function call that is still ongoing (still `protected`),
// this is UB (`noalias` violation). // this is UB (`noalias` violation).
Reserved { conflicted: true, .. } if protected => return None, ReservedFrz { conflicted: true } if protected => return None,
// A write always activates the 2-phase borrow, even with interior // A write always activates the 2-phase borrow, even with interior
// mutability // mutability
Reserved { .. } | Active => Active, ReservedFrz { .. } | ReservedIM | Active => Active,
Frozen | Disabled => return None, Frozen | Disabled => return None,
}) })
} }
@ -145,15 +158,15 @@ mod transition {
/// A non-child node was write-accessed: this makes everything `Disabled` except for /// A non-child node was write-accessed: this makes everything `Disabled` except for
/// non-protected interior mutable `Reserved` which stay the same. /// non-protected interior mutable `Reserved` which stay the same.
fn foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> { fn foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
// There is no explicit dependency on `protected`, but recall that interior mutable
// types receive a `ReservedFrz` instead of `ReservedIM` when retagged under a protector,
// so the result of this function does indirectly depend on (past) protector status.
Some(match state { Some(match state {
// FIXME: since the fix related to reservedim_spurious_write, it is now possible res @ ReservedIM => {
// to express these transitions of the state machine without an explicit dependency // We can never create a `ReservedIM` under a protector, only `ReservedFrz`.
// on `protected`: because `ty_is_freeze: false` implies `!protected` then assert!(!protected);
// the line handling `Reserved { .. } if protected` could be deleted. res
// This will however require optimizations to the exhaustive tests because }
// fewer initial conditions are valid.
Reserved { .. } if protected => Disabled,
res @ Reserved { ty_is_freeze: false, .. } => res,
_ => Disabled, _ => Disabled,
}) })
} }
@ -208,9 +221,23 @@ impl Permission {
Self { inner: Active } Self { inner: Active }
} }
/// Default initial permission of a reborrowed mutable reference. /// Default initial permission of a reborrowed mutable reference that is either
pub fn new_reserved(ty_is_freeze: bool) -> Self { /// protected or not interior mutable.
Self { inner: Reserved { ty_is_freeze, conflicted: false } } fn new_reserved_frz() -> Self {
Self { inner: ReservedFrz { conflicted: false } }
}
/// Default initial permission of an unprotected interior mutable reference.
fn new_reserved_im() -> Self {
Self { inner: ReservedIM }
}
/// Wrapper around `new_reserved_frz` and `new_reserved_im` that decides
/// which to call based on the interior mutability and the retag kind (whether there
/// is a protector is relevant because being protected takes priority over being
/// interior mutable)
pub fn new_reserved(ty_is_freeze: bool, protected: bool) -> Self {
if ty_is_freeze || protected { Self::new_reserved_frz() } else { Self::new_reserved_im() }
} }
/// Default initial permission of a reborrowed shared reference. /// Default initial permission of a reborrowed shared reference.
@ -224,6 +251,11 @@ impl Permission {
Self { inner: Disabled } Self { inner: Disabled }
} }
/// Reject `ReservedIM` that cannot exist in the presence of a protector.
pub fn compatible_with_protector(&self) -> bool {
self.inner.compatible_with_protector()
}
/// Apply the transition to the inner PermissionPriv. /// Apply the transition to the inner PermissionPriv.
pub fn perform_access( pub fn perform_access(
kind: AccessKind, kind: AccessKind,
@ -279,12 +311,9 @@ pub mod diagnostics {
f, f,
"{}", "{}",
match self { match self {
Reserved { ty_is_freeze: true, conflicted: false } => "Reserved", ReservedFrz { conflicted: false } => "Reserved",
Reserved { ty_is_freeze: true, conflicted: true } => "Reserved (conflicted)", ReservedFrz { conflicted: true } => "Reserved (conflicted)",
Reserved { ty_is_freeze: false, conflicted: false } => ReservedIM => "Reserved (interior mutable)",
"Reserved (interior mutable)",
Reserved { ty_is_freeze: false, conflicted: true } =>
"Reserved (interior mutable, conflicted)",
Active => "Active", Active => "Active",
Frozen => "Frozen", Frozen => "Frozen",
Disabled => "Disabled", Disabled => "Disabled",
@ -312,10 +341,9 @@ pub mod diagnostics {
// and also as `diagnostics::DisplayFmtPermission.uninit` otherwise // and also as `diagnostics::DisplayFmtPermission.uninit` otherwise
// alignment will be incorrect. // alignment will be incorrect.
match self.inner { match self.inner {
Reserved { ty_is_freeze: true, conflicted: false } => "Rs ", ReservedFrz { conflicted: false } => "Res ",
Reserved { ty_is_freeze: true, conflicted: true } => "RsC ", ReservedFrz { conflicted: true } => "ResC",
Reserved { ty_is_freeze: false, conflicted: false } => "RsM ", ReservedIM => "ReIM",
Reserved { ty_is_freeze: false, conflicted: true } => "RsCM",
Active => "Act ", Active => "Act ",
Frozen => "Frz ", Frozen => "Frz ",
Disabled => "Dis ", Disabled => "Dis ",
@ -325,13 +353,14 @@ pub mod diagnostics {
impl PermTransition { impl PermTransition {
/// Readable explanation of the consequences of an event. /// Readable explanation of the consequences of an event.
/// Fits in the sentence "This accessed caused {trans.summary()}". /// Fits in the sentence "This transition corresponds to {trans.summary()}".
pub fn summary(&self) -> &'static str { pub fn summary(&self) -> &'static str {
assert!(self.is_possible()); assert!(self.is_possible());
assert!(!self.is_noop());
match (self.from, self.to) { match (self.from, self.to) {
(_, Active) => "the first write to a 2-phase borrowed mutable reference", (_, Active) => "the first write to a 2-phase borrowed mutable reference",
(_, Frozen) => "a loss of write permissions", (_, Frozen) => "a loss of write permissions",
(Reserved { conflicted: false, .. }, Reserved { conflicted: true, .. }) => (ReservedFrz { conflicted: false }, ReservedFrz { conflicted: true }) =>
"a temporary loss of write permissions until function exit", "a temporary loss of write permissions until function exit",
(Frozen, Disabled) => "a loss of read permissions", (Frozen, Disabled) => "a loss of read permissions",
(_, Disabled) => "a loss of read and write permissions", (_, Disabled) => "a loss of read and write permissions",
@ -380,28 +409,33 @@ pub mod diagnostics {
(Frozen, Frozen) => true, (Frozen, Frozen) => true,
(Active, Frozen) => true, (Active, Frozen) => true,
(Disabled, Disabled) => true, (Disabled, Disabled) => true,
(Reserved { conflicted: true, .. }, Reserved { conflicted: true, .. }) => (
true, ReservedFrz { conflicted: true, .. },
ReservedFrz { conflicted: true, .. },
) => true,
// A pointer being `Disabled` is a strictly stronger source of // A pointer being `Disabled` is a strictly stronger source of
// errors than it being `Frozen`. If we try to access a `Disabled`, // errors than it being `Frozen`. If we try to access a `Disabled`,
// then where it became `Frozen` (or `Active` or `Reserved`) is the least // then where it became `Frozen` (or `Active` or `Reserved`) is the least
// of our concerns for now. // of our concerns for now.
(Reserved { conflicted: true, .. } | Active | Frozen, Disabled) => false, (ReservedFrz { conflicted: true } | Active | Frozen, Disabled) => false,
(Reserved { conflicted: true, .. }, Frozen) => false, (ReservedFrz { conflicted: true }, Frozen) => false,
// `Active` and `Reserved` have all permissions, so a // `Active` and `Reserved` have all permissions, so a
// `ChildAccessForbidden(Reserved | Active)` can never exist. // `ChildAccessForbidden(Reserved | Active)` can never exist.
(_, Active) | (_, Reserved { conflicted: false, .. }) => (_, Active) | (_, ReservedFrz { conflicted: false }) =>
unreachable!("this permission cannot cause an error"), unreachable!("this permission cannot cause an error"),
// No transition has `Reserved(conflicted=false)` as its `.to` unless it's a noop. // No transition has `Reserved { conflicted: false }` or `ReservedIM`
(Reserved { conflicted: false, .. }, _) => // as its `.to` unless it's a noop.
(ReservedFrz { conflicted: false } | ReservedIM, _) =>
unreachable!("self is a noop transition"), unreachable!("self is a noop transition"),
// All transitions produced in normal executions (using `apply_access`) // All transitions produced in normal executions (using `apply_access`)
// change permissions in the order `Reserved -> Active -> Frozen -> Disabled`. // change permissions in the order `Reserved -> Active -> Frozen -> Disabled`.
// We assume that the error was triggered on the same location that // We assume that the error was triggered on the same location that
// the transition `self` applies to, so permissions found must be increasing // the transition `self` applies to, so permissions found must be increasing
// in the order `self.from < self.to <= insufficient.inner` // in the order `self.from < self.to <= insufficient.inner`
(Active | Frozen | Disabled, Reserved { .. }) | (Disabled, Frozen) => (Active | Frozen | Disabled, ReservedFrz { .. } | ReservedIM)
| (Disabled, Frozen)
| (ReservedFrz { .. }, ReservedIM) =>
unreachable!("permissions between self and err must be increasing"), unreachable!("permissions between self and err must be increasing"),
} }
} }
@ -415,8 +449,10 @@ pub mod diagnostics {
// conflicted. // conflicted.
(Active, Active) => true, (Active, Active) => true,
(Frozen, Frozen) => true, (Frozen, Frozen) => true,
(Reserved { conflicted: true, .. }, Reserved { conflicted: true, .. }) => (
true, ReservedFrz { conflicted: true, .. },
ReservedFrz { conflicted: true, .. },
) => true,
// If the error is a transition `Frozen -> Disabled`, then we don't really // If the error is a transition `Frozen -> Disabled`, then we don't really
// care whether before that was `Reserved -> Active -> Frozen` or // care whether before that was `Reserved -> Active -> Frozen` or
// `Frozen` directly. // `Frozen` directly.
@ -429,23 +465,23 @@ pub mod diagnostics {
// -> Reserved { conflicted: true }` is inexistant or irrelevant, // -> Reserved { conflicted: true }` is inexistant or irrelevant,
// and so is the `Reserved { conflicted: false } -> Active` // and so is the `Reserved { conflicted: false } -> Active`
(Active, Frozen) => false, (Active, Frozen) => false,
(Reserved { conflicted: true, .. }, _) => false, (ReservedFrz { conflicted: true }, _) => false,
(_, Disabled) => (_, Disabled) =>
unreachable!( unreachable!(
"permission that results in Disabled should not itself be Disabled in the first place" "permission that results in Disabled should not itself be Disabled in the first place"
), ),
// No transition has `Reserved { conflicted: false }` as its `.to` // No transition has `Reserved { conflicted: false }` or `ReservedIM` as its `.to`
// unless it's a noop. // unless it's a noop.
(Reserved { conflicted: false, .. }, _) => (ReservedFrz { conflicted: false } | ReservedIM, _) =>
unreachable!("self is a noop transition"), unreachable!("self is a noop transition"),
// Permissions only evolve in the order `Reserved -> Active -> Frozen -> Disabled`, // Permissions only evolve in the order `Reserved -> Active -> Frozen -> Disabled`,
// so permissions found must be increasing in the order // so permissions found must be increasing in the order
// `self.from < self.to <= forbidden.from < forbidden.to`. // `self.from < self.to <= forbidden.from < forbidden.to`.
(Disabled, Reserved { .. } | Active | Frozen) (Disabled, ReservedFrz { .. } | ReservedIM | Active | Frozen)
| (Frozen, Reserved { .. } | Active) | (Frozen, ReservedFrz { .. } | ReservedIM | Active)
| (Active, Reserved { .. }) => | (Active, ReservedFrz { .. } | ReservedIM) =>
unreachable!("permissions between self and err must be increasing"), unreachable!("permissions between self and err must be increasing"),
} }
} }
@ -466,9 +502,9 @@ pub mod diagnostics {
#[cfg(test)] #[cfg(test)]
impl Permission { impl Permission {
pub fn is_reserved_with_conflicted(&self, expected_conflicted: bool) -> bool { pub fn is_reserved_frz_with_conflicted(&self, expected_conflicted: bool) -> bool {
match self.inner { match self.inner {
Reserved { conflicted, .. } => conflicted == expected_conflicted, ReservedFrz { conflicted } => conflicted == expected_conflicted,
_ => false, _ => false,
} }
} }
@ -482,10 +518,9 @@ mod propagation_optimization_checks {
impl Exhaustive for PermissionPriv { impl Exhaustive for PermissionPriv {
fn exhaustive() -> Box<dyn Iterator<Item = Self>> { fn exhaustive() -> Box<dyn Iterator<Item = Self>> {
Box::new( Box::new(
vec![Active, Frozen, Disabled].into_iter().chain( vec![Active, Frozen, Disabled, ReservedIM]
<[bool; 2]>::exhaustive() .into_iter()
.map(|[ty_is_freeze, conflicted]| Reserved { ty_is_freeze, conflicted }), .chain(<bool>::exhaustive().map(|conflicted| ReservedFrz { conflicted })),
),
) )
} }
} }
@ -525,6 +560,9 @@ mod propagation_optimization_checks {
// We thus eliminate from this test and all other tests // We thus eliminate from this test and all other tests
// the case where the tag is initially unprotected and later becomes protected. // the case where the tag is initially unprotected and later becomes protected.
precondition!(old_protected || !new_protected); precondition!(old_protected || !new_protected);
if old_protected {
precondition!(old.compatible_with_protector());
}
for (access, rel_pos) in <(AccessKind, AccessRelatedness)>::exhaustive() { for (access, rel_pos) in <(AccessKind, AccessRelatedness)>::exhaustive() {
if let Some(new) = perform_access(access, rel_pos, old, old_protected) { if let Some(new) = perform_access(access, rel_pos, old, old_protected) {
assert_eq!( assert_eq!(
@ -546,6 +584,9 @@ mod propagation_optimization_checks {
for old in PermissionPriv::exhaustive() { for old in PermissionPriv::exhaustive() {
for [old_protected, new_protected] in <[bool; 2]>::exhaustive() { for [old_protected, new_protected] in <[bool; 2]>::exhaustive() {
precondition!(old_protected || !new_protected); precondition!(old_protected || !new_protected);
if old_protected {
precondition!(old.compatible_with_protector());
}
for rel_pos in AccessRelatedness::exhaustive() { for rel_pos in AccessRelatedness::exhaustive() {
precondition!(rel_pos.is_foreign()); precondition!(rel_pos.is_foreign());
if let Some(new) = perform_access(old_access, rel_pos, old, old_protected) { if let Some(new) = perform_access(old_access, rel_pos, old, old_protected) {
@ -570,6 +611,9 @@ mod propagation_optimization_checks {
reach.insert((start, start)); reach.insert((start, start));
for (access, rel) in <(AccessKind, AccessRelatedness)>::exhaustive() { for (access, rel) in <(AccessKind, AccessRelatedness)>::exhaustive() {
for prot in bool::exhaustive() { for prot in bool::exhaustive() {
if prot {
precondition!(start.compatible_with_protector());
}
if let Some(end) = transition::perform_access(access, rel, start, prot) { if let Some(end) = transition::perform_access(access, rel, start, prot) {
reach.insert((start, end)); reach.insert((start, end));
} }

View File

@ -14,6 +14,15 @@ impl Exhaustive for LocationState {
} }
} }
impl LocationState {
/// Ensure that the current internal state can exist at the same time as a protector.
/// In practice this only eliminates `ReservedIM` that is never used in the presence
/// of a protector (we instead emit `ReservedFrz` on retag).
pub fn compatible_with_protector(&self) -> bool {
self.permission.compatible_with_protector()
}
}
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
// Exhaustive check that for any starting configuration loc, // Exhaustive check that for any starting configuration loc,
@ -30,6 +39,9 @@ fn all_read_accesses_commute() {
// so the two read accesses occur under the same protector. // so the two read accesses occur under the same protector.
for protected in bool::exhaustive() { for protected in bool::exhaustive() {
for loc in LocationState::exhaustive() { for loc in LocationState::exhaustive() {
if protected {
precondition!(loc.compatible_with_protector());
}
// Apply 1 then 2. Failure here means that there is UB in the source // Apply 1 then 2. Failure here means that there is UB in the source
// and we skip the check in the target. // and we skip the check in the target.
let mut loc12 = loc; let mut loc12 = loc;
@ -61,8 +73,8 @@ fn protected_enforces_noalias() {
// We want to check pairs of accesses where one is foreign and one is not. // We want to check pairs of accesses where one is foreign and one is not.
precondition!(rel1.is_foreign() != rel2.is_foreign()); precondition!(rel1.is_foreign() != rel2.is_foreign());
for [kind1, kind2] in <[AccessKind; 2]>::exhaustive() { for [kind1, kind2] in <[AccessKind; 2]>::exhaustive() {
for mut state in LocationState::exhaustive() { let protected = true;
let protected = true; for mut state in LocationState::exhaustive().filter(|s| s.compatible_with_protector()) {
precondition!(state.perform_access(kind1, rel1, protected).is_ok()); precondition!(state.perform_access(kind1, rel1, protected).is_ok());
precondition!(state.perform_access(kind2, rel2, protected).is_ok()); precondition!(state.perform_access(kind2, rel2, protected).is_ok());
// If these were both allowed, it must have been two reads. // If these were both allowed, it must have been two reads.
@ -387,6 +399,9 @@ mod spurious_read {
fn retag_y(self, new_y: LocStateProt) -> Result<Self, ()> { fn retag_y(self, new_y: LocStateProt) -> Result<Self, ()> {
assert!(new_y.is_initial()); assert!(new_y.is_initial());
if new_y.prot && !new_y.state.compatible_with_protector() {
return Err(());
}
// `xy_rel` changes to "mutually foreign" now: `y` can no longer be a parent of `x`. // `xy_rel` changes to "mutually foreign" now: `y` can no longer be a parent of `x`.
Self { y: new_y, xy_rel: RelPosXY::MutuallyForeign, ..self } Self { y: new_y, xy_rel: RelPosXY::MutuallyForeign, ..self }
.read_if_initialized(PtrSelector::Y) .read_if_initialized(PtrSelector::Y)
@ -511,7 +526,7 @@ mod spurious_read {
} }
#[test] #[test]
// `Reserved(aliased=false)` and `Reserved(aliased=true)` are properly indistinguishable // `Reserved { conflicted: false }` and `Reserved { conflicted: true }` are properly indistinguishable
// under the conditions where we want to insert a spurious read. // under the conditions where we want to insert a spurious read.
fn reserved_aliased_protected_indistinguishable() { fn reserved_aliased_protected_indistinguishable() {
let source = LocStateProtPair { let source = LocStateProtPair {
@ -521,14 +536,16 @@ mod spurious_read {
prot: true, prot: true,
}, },
y: LocStateProt { y: LocStateProt {
state: LocationState::new_uninit(Permission::new_reserved(false)), state: LocationState::new_uninit(Permission::new_reserved(
/* freeze */ true, /* protected */ true,
)),
prot: true, prot: true,
}, },
}; };
let acc = TestAccess { ptr: PtrSelector::X, kind: AccessKind::Read }; let acc = TestAccess { ptr: PtrSelector::X, kind: AccessKind::Read };
let target = source.clone().perform_test_access(&acc).unwrap(); let target = source.clone().perform_test_access(&acc).unwrap();
assert!(source.y.state.permission.is_reserved_with_conflicted(false)); assert!(source.y.state.permission.is_reserved_frz_with_conflicted(false));
assert!(target.y.state.permission.is_reserved_with_conflicted(true)); assert!(target.y.state.permission.is_reserved_frz_with_conflicted(true));
assert!(!source.distinguishable::<(), ()>(&target)) assert!(!source.distinguishable::<(), ()>(&target))
} }
@ -563,7 +580,13 @@ mod spurious_read {
precondition!(x_retag_perm.initialized); precondition!(x_retag_perm.initialized);
// And `x` just got retagged, so it must be initial. // And `x` just got retagged, so it must be initial.
precondition!(x_retag_perm.permission.is_initial()); precondition!(x_retag_perm.permission.is_initial());
// As stated earlier, `x` is always protected in the patterns we consider here.
precondition!(x_retag_perm.compatible_with_protector());
for y_protected in bool::exhaustive() { for y_protected in bool::exhaustive() {
// Finally `y` that is optionally protected must have a compatible permission.
if y_protected {
precondition!(y_current_perm.compatible_with_protector());
}
v.push(Pattern { xy_rel, x_retag_perm, y_current_perm, y_protected }); v.push(Pattern { xy_rel, x_retag_perm, y_current_perm, y_protected });
} }
} }

View File

@ -2,11 +2,11 @@
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| RsM | └─┬──<TAG=base> | ReIM| └─┬──<TAG=base>
| RsM | ├─┬──<TAG=x> | ReIM| ├─┬──<TAG=x>
| RsM | │ └─┬──<TAG=caller:x> | ReIM| │ └─┬──<TAG=caller:x>
| Rs | │ └────<TAG=callee:x> Strongly protected | Res | │ └────<TAG=callee:x> Strongly protected
| RsM | └────<TAG=y, callee:y, caller:y> | ReIM| └────<TAG=y, callee:y, caller:y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
error: Undefined Behavior: write access through <TAG> (y, callee:y, caller:y) at ALLOC[0x0] is forbidden error: Undefined Behavior: write access through <TAG> (y, callee:y, caller:y) at ALLOC[0x0] is forbidden
--> $DIR/cell-protected-write.rs:LL:CC --> $DIR/cell-protected-write.rs:LL:CC

View File

@ -2,11 +2,11 @@
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=n> | Res | └─┬──<TAG=n>
| Rs | ├─┬──<TAG=x> | Res | ├─┬──<TAG=x>
| Rs | │ └─┬──<TAG=caller:x> | Res | │ └─┬──<TAG=caller:x>
| Rs | │ └────<TAG=callee:x> Strongly protected | Res | │ └────<TAG=callee:x> Strongly protected
| Rs | └────<TAG=y, callee:y, caller:y> | Res | └────<TAG=y, callee:y, caller:y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
error: Undefined Behavior: write access through <TAG> (y, callee:y, caller:y) at ALLOC[0x0] is forbidden error: Undefined Behavior: write access through <TAG> (y, callee:y, caller:y) at ALLOC[0x0] is forbidden
--> $DIR/int-protected-write.rs:LL:CC --> $DIR/int-protected-write.rs:LL:CC

View File

@ -2,7 +2,7 @@
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| RsM | └────<TAG=data, x, y> | ReIM| └────<TAG=data, x, y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
────────────────────────────────────────────────── ──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.

View File

@ -2,27 +2,27 @@
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=data> | Res | └─┬──<TAG=data>
| Rs | └────<TAG=x> | Res | └────<TAG=x>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
────────────────────────────────────────────────── ──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=data> | Res | └─┬──<TAG=data>
| Rs | └─┬──<TAG=x> | Res | └─┬──<TAG=x>
| Rs | └─┬──<TAG=caller:x> | Res | └─┬──<TAG=caller:x>
| Rs | └────<TAG=callee:x> Strongly protected | Res | └────<TAG=callee:x> Strongly protected
────────────────────────────────────────────────── ──────────────────────────────────────────────────
────────────────────────────────────────────────── ──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=data> | Res | └─┬──<TAG=data>
| Rs | ├─┬──<TAG=x> | Res | ├─┬──<TAG=x>
| Rs | │ └─┬──<TAG=caller:x> | Res | │ └─┬──<TAG=caller:x>
| Rs | │ └────<TAG=callee:x> | Res | │ └────<TAG=callee:x>
| Rs | └────<TAG=y> | Res | └────<TAG=y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
────────────────────────────────────────────────── ──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.

View File

@ -2,7 +2,7 @@
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1.. 2.. 10.. 11.. 100.. 101..1000..1001..1024 0.. 1.. 2.. 10.. 11.. 100.. 101..1000..1001..1024
| Act | Act | Act | Act | Act | Act | Act | Act | Act | └─┬──<TAG=root of the allocation> | Act | Act | Act | Act | Act | Act | Act | Act | Act | └─┬──<TAG=root of the allocation>
| Rs | Act | Rs | Act | Rs | Act | Rs | Act | Rs | └─┬──<TAG=data, data> | Res | Act | Res | Act | Res | Act | Res | Act | Res | └─┬──<TAG=data, data>
|-----| Act |-----|?Dis |-----|?Dis |-----|?Dis |-----| ├────<TAG=data[1]> |-----| Act |-----|?Dis |-----|?Dis |-----|?Dis |-----| ├────<TAG=data[1]>
|-----|-----|-----| Act |-----|?Dis |-----|?Dis |-----| ├────<TAG=data[10]> |-----|-----|-----| Act |-----|?Dis |-----|?Dis |-----| ├────<TAG=data[10]>
|-----|-----|-----|-----|-----| Frz |-----|?Dis |-----| ├────<TAG=data[100]> |-----|-----|-----|-----|-----| Frz |-----|?Dis |-----| ├────<TAG=data[100]>

View File

@ -11,5 +11,5 @@ Warning: this tree is indicative only. Some tags may have been hidden.
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Act | └─┬──<TAG=parent> | Act | └─┬──<TAG=parent>
| Frz | ├────<TAG=x> | Frz | ├────<TAG=x>
| Rs | └────<TAG=y> | Res | └────<TAG=y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────

View File

@ -3,20 +3,20 @@
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| RsM | └─┬──<TAG=base> | ReIM| └─┬──<TAG=base>
| RsM | ├─┬──<TAG=x> | ReIM| ├─┬──<TAG=x>
| RsM | │ └─┬──<TAG=caller:x> | ReIM| │ └─┬──<TAG=caller:x>
| RsC | │ └────<TAG=callee:x> | ResC| │ └────<TAG=callee:x>
| RsM | └────<TAG=y, caller:y, callee:y> | ReIM| └────<TAG=y, caller:y, callee:y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
[interior mut] Foreign Read: Re* -> Re* [interior mut] Foreign Read: Re* -> Re*
────────────────────────────────────────────────── ──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 8 0.. 8
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| RsM | └─┬──<TAG=base> | ReIM| └─┬──<TAG=base>
| RsM | ├────<TAG=x> | ReIM| ├────<TAG=x>
| RsM | └────<TAG=y> | ReIM| └────<TAG=y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
[interior mut] Foreign Write: Re* -> Re* [interior mut] Foreign Write: Re* -> Re*
────────────────────────────────────────────────── ──────────────────────────────────────────────────
@ -24,7 +24,7 @@ Warning: this tree is indicative only. Some tags may have been hidden.
0.. 8 0.. 8
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Act | └─┬──<TAG=base> | Act | └─┬──<TAG=base>
| RsM | ├────<TAG=x> | ReIM| ├────<TAG=x>
| Act | └────<TAG=y> | Act | └────<TAG=y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
[protected] Foreign Read: Res -> Frz [protected] Foreign Read: Res -> Frz
@ -32,20 +32,20 @@ Warning: this tree is indicative only. Some tags may have been hidden.
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=base> | Res | └─┬──<TAG=base>
| Rs | ├─┬──<TAG=x> | Res | ├─┬──<TAG=x>
| Rs | │ └─┬──<TAG=caller:x> | Res | │ └─┬──<TAG=caller:x>
| RsC | │ └────<TAG=callee:x> | ResC| │ └────<TAG=callee:x>
| Rs | └────<TAG=y, caller:y, callee:y> | Res | └────<TAG=y, caller:y, callee:y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
[] Foreign Read: Res -> Res [] Foreign Read: Res -> Res
────────────────────────────────────────────────── ──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=base> | Res | └─┬──<TAG=base>
| Rs | ├────<TAG=x> | Res | ├────<TAG=x>
| Rs | └────<TAG=y> | Res | └────<TAG=y>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
[] Foreign Write: Res -> Dis [] Foreign Write: Res -> Dis
────────────────────────────────────────────────── ──────────────────────────────────────────────────

View File

@ -2,8 +2,8 @@
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=base> | Res | └─┬──<TAG=base>
| Rs | └────<TAG=raw, uniq, uniq> | Res | └────<TAG=raw, uniq, uniq>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
────────────────────────────────────────────────── ──────────────────────────────────────────────────
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.

View File

@ -2,8 +2,8 @@
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 1 0.. 1
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Rs | └─┬──<TAG=base> | Res | └─┬──<TAG=base>
| Rs | └─┬──<TAG=raw> | Res | └─┬──<TAG=raw>
|-----| └────<TAG=uniq, uniq> |-----| └────<TAG=uniq, uniq>
────────────────────────────────────────────────── ──────────────────────────────────────────────────
────────────────────────────────────────────────── ──────────────────────────────────────────────────

View File

@ -2,5 +2,5 @@
Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden.
0.. 2 0.. 2
| Act | └─┬──<TAG=root of the allocation> | Act | └─┬──<TAG=root of the allocation>
| Rs | └────<TAG=base.as_ptr(), base.as_ptr(), raw_parts.0, reconstructed.as_ptr(), reconstructed.as_ptr()> | Res | └────<TAG=base.as_ptr(), base.as_ptr(), raw_parts.0, reconstructed.as_ptr(), reconstructed.as_ptr()>
────────────────────────────────────────────────── ──────────────────────────────────────────────────