Skip to content

Commit 8bbea47

Browse files
authored
Merge pull request #4273 from yoctocell/new-cell-state
TB: add `Cell` state to support more fine-grained tracking of interior mutable data
2 parents 06e0293 + 8cc866d commit 8bbea47

File tree

9 files changed

+141
-72
lines changed

9 files changed

+141
-72
lines changed

src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs

+23-14
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ struct NewPermission {
123123
/// Whether this pointer is part of the arguments of a function call.
124124
/// `protector` is `Some(_)` for all pointers marked `noalias`.
125125
protector: Option<ProtectorKind>,
126+
/// Whether a read should be performed on a retag. This should be `false`
127+
/// for `Cell` because this could cause data races when using thread-safe
128+
/// data types like `Mutex<T>`.
129+
initial_read: bool,
126130
}
127131

128132
impl<'tcx> NewPermission {
@@ -141,18 +145,19 @@ impl<'tcx> NewPermission {
141145
// To eliminate the case of Protected Reserved IM we override interior mutability
142146
// in the case of a protected reference: protected references are always considered
143147
// "freeze" in their reservation phase.
144-
let initial_state = match mutability {
145-
Mutability::Mut if ty_is_unpin => Permission::new_reserved(ty_is_freeze, is_protected),
146-
Mutability::Not if ty_is_freeze => Permission::new_frozen(),
148+
let (initial_state, initial_read) = match mutability {
149+
Mutability::Mut if ty_is_unpin =>
150+
(Permission::new_reserved(ty_is_freeze, is_protected), true),
151+
Mutability::Not if ty_is_freeze => (Permission::new_frozen(), true),
152+
Mutability::Not if !ty_is_freeze => (Permission::new_cell(), false),
147153
// Raw pointers never enter this function so they are not handled.
148154
// However raw pointers are not the only pointers that take the parent
149-
// tag, this also happens for `!Unpin` `&mut`s and interior mutable
150-
// `&`s, which are excluded above.
155+
// tag, this also happens for `!Unpin` `&mut`s, which are excluded above.
151156
_ => return None,
152157
};
153158

154159
let protector = is_protected.then_some(ProtectorKind::StrongProtector);
155-
Some(Self { zero_size: false, initial_state, protector })
160+
Some(Self { zero_size: false, initial_state, protector, initial_read })
156161
}
157162

158163
/// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
@@ -175,6 +180,7 @@ impl<'tcx> NewPermission {
175180
zero_size,
176181
initial_state,
177182
protector: protected.then_some(ProtectorKind::WeakProtector),
183+
initial_read: true,
178184
}
179185
})
180186
}
@@ -289,13 +295,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
289295
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
290296

291297
// All reborrows incur a (possibly zero-sized) read access to the parent
292-
tree_borrows.perform_access(
293-
orig_tag,
294-
Some((range, AccessKind::Read, diagnostics::AccessCause::Reborrow)),
295-
this.machine.borrow_tracker.as_ref().unwrap(),
296-
alloc_id,
297-
this.machine.current_span(),
298-
)?;
298+
if new_perm.initial_read {
299+
tree_borrows.perform_access(
300+
orig_tag,
301+
Some((range, AccessKind::Read, diagnostics::AccessCause::Reborrow)),
302+
this.machine.borrow_tracker.as_ref().unwrap(),
303+
alloc_id,
304+
this.machine.current_span(),
305+
)?;
306+
}
299307
// Record the parent-child pair in the tree.
300308
tree_borrows.new_child(
301309
orig_tag,
@@ -308,7 +316,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
308316
drop(tree_borrows);
309317

310318
// Also inform the data race model (but only if any bytes are actually affected).
311-
if range.size.bytes() > 0 {
319+
if range.size.bytes() > 0 && new_perm.initial_read {
312320
if let Some(data_race) = alloc_extra.data_race.as_ref() {
313321
data_race.read(
314322
alloc_id,
@@ -535,6 +543,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
535543
initial_state: Permission::new_reserved(ty_is_freeze, /* protected */ true),
536544
zero_size: false,
537545
protector: Some(ProtectorKind::StrongProtector),
546+
initial_read: true,
538547
};
539548
this.tb_retag_place(place, new_perm)
540549
}

src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs

+79-32
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ use crate::borrow_tracker::tree_borrows::tree::AccessRelatedness;
88
/// The activation states of a pointer.
99
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1010
enum PermissionPriv {
11+
/// represents: a shared reference to interior mutable data.
12+
/// allows: all foreign and child accesses;
13+
/// rejects: nothing
14+
Cell,
1115
/// represents: a local mutable reference that has not yet been written to;
1216
/// allows: child reads, foreign reads;
1317
/// affected by: child writes (becomes Active),
@@ -60,6 +64,14 @@ impl PartialOrd for PermissionPriv {
6064
use Ordering::*;
6165
Some(match (self, other) {
6266
(a, b) if a == b => Equal,
67+
// Versions of `Reserved` with different interior mutability are incomparable with each
68+
// other.
69+
(ReservedIM, ReservedFrz { .. })
70+
| (ReservedFrz { .. }, ReservedIM)
71+
// `Cell` is not comparable with any other permission
72+
// since it never transitions to any other state and we
73+
// can never get to `Cell` from another state.
74+
| (Cell, _) | (_, Cell) => return None,
6375
(Disabled, _) => Greater,
6476
(_, Disabled) => Less,
6577
(Frozen, _) => Greater,
@@ -71,27 +83,29 @@ impl PartialOrd for PermissionPriv {
7183
// `bool` is ordered such that `false <= true`, so this works as intended.
7284
c1.cmp(c2)
7385
}
74-
// Versions of `Reserved` with different interior mutability are incomparable with each
75-
// other.
76-
(ReservedIM, ReservedFrz { .. }) | (ReservedFrz { .. }, ReservedIM) => return None,
7786
})
7887
}
7988
}
8089

8190
impl PermissionPriv {
8291
/// Check if `self` can be the initial state of a pointer.
8392
fn is_initial(&self) -> bool {
84-
matches!(self, ReservedFrz { conflicted: false } | Frozen | ReservedIM)
93+
matches!(self, ReservedFrz { conflicted: false } | Frozen | ReservedIM | Cell)
8594
}
8695

8796
/// Reject `ReservedIM` that cannot exist in the presence of a protector.
8897
fn compatible_with_protector(&self) -> bool {
89-
!matches!(self, ReservedIM)
98+
// FIXME(TB-Cell): It is unclear what to do here.
99+
// `Cell` will occur with a protector but won't provide the guarantees
100+
// of noalias (it will fail the `protected_enforces_noalias` test).
101+
!matches!(self, ReservedIM | Cell)
90102
}
91103

92104
/// See `foreign_access_skipping.rs`. Computes the SIFA of a permission.
93105
fn strongest_idempotent_foreign_access(&self, prot: bool) -> IdempotentForeignAccess {
94106
match self {
107+
// Cell survives any foreign access
108+
Cell => IdempotentForeignAccess::Write,
95109
// A protected non-conflicted Reserved will become conflicted under a foreign read,
96110
// and is hence not idempotent under it.
97111
ReservedFrz { conflicted } if prot && !conflicted => IdempotentForeignAccess::None,
@@ -124,14 +138,16 @@ mod transition {
124138
Disabled => return None,
125139
// The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
126140
// accesses, since the data is not being mutated. Hence the `{ .. }`.
127-
readable @ (ReservedFrz { .. } | ReservedIM | Active | Frozen) => readable,
141+
readable @ (Cell | ReservedFrz { .. } | ReservedIM | Active | Frozen) => readable,
128142
})
129143
}
130144

131145
/// A non-child node was read-accessed: keep `Reserved` but mark it as `conflicted` if it
132146
/// is protected; invalidate `Active`.
133147
fn foreign_read(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
134148
Some(match state {
149+
// Cell ignores foreign reads.
150+
Cell => Cell,
135151
// Non-writeable states just ignore foreign reads.
136152
non_writeable @ (Frozen | Disabled) => non_writeable,
137153
// Writeable states are more tricky, and depend on whether things are protected.
@@ -167,6 +183,8 @@ mod transition {
167183
/// write permissions, `Frozen` and `Disabled` cannot obtain such permissions and produce UB.
168184
fn child_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
169185
Some(match state {
186+
// Cell ignores child writes.
187+
Cell => Cell,
170188
// If the `conflicted` flag is set, then there was a foreign read during
171189
// the function call that is still ongoing (still `protected`),
172190
// this is UB (`noalias` violation).
@@ -185,6 +203,8 @@ mod transition {
185203
// types receive a `ReservedFrz` instead of `ReservedIM` when retagged under a protector,
186204
// so the result of this function does indirectly depend on (past) protector status.
187205
Some(match state {
206+
// Cell ignores foreign writes.
207+
Cell => Cell,
188208
res @ ReservedIM => {
189209
// We can never create a `ReservedIM` under a protector, only `ReservedFrz`.
190210
assert!(!protected);
@@ -242,6 +262,11 @@ impl Permission {
242262
self.inner == Frozen
243263
}
244264

265+
/// Check if `self` is the shared-reference-to-interior-mutable-data state of a pointer.
266+
pub fn is_cell(&self) -> bool {
267+
self.inner == Cell
268+
}
269+
245270
/// Default initial permission of the root of a new tree at inbounds positions.
246271
/// Must *only* be used for the root, this is not in general an "initial" permission!
247272
pub fn new_active() -> Self {
@@ -278,11 +303,27 @@ impl Permission {
278303
Self { inner: Disabled }
279304
}
280305

306+
/// Default initial permission of a shared reference to interior mutable data.
307+
pub fn new_cell() -> Self {
308+
Self { inner: Cell }
309+
}
310+
281311
/// Reject `ReservedIM` that cannot exist in the presence of a protector.
282312
pub fn compatible_with_protector(&self) -> bool {
283313
self.inner.compatible_with_protector()
284314
}
285315

316+
/// What kind of access to perform before releasing the protector.
317+
pub fn protector_end_access(&self) -> Option<AccessKind> {
318+
match self.inner {
319+
// Do not do perform access if it is a `Cell`, as this
320+
// can cause data races when using thread-safe data types.
321+
Cell => None,
322+
Active => Some(AccessKind::Write),
323+
_ => Some(AccessKind::Read),
324+
}
325+
}
326+
286327
/// Apply the transition to the inner PermissionPriv.
287328
pub fn perform_access(
288329
kind: AccessKind,
@@ -306,30 +347,32 @@ impl Permission {
306347
/// remove protected parents.
307348
pub fn can_be_replaced_by_child(self, child: Self) -> bool {
308349
match (self.inner, child.inner) {
309-
// ReservedIM can be replaced by anything, as it allows all
310-
// transitions.
350+
// Cell allows all transitions.
351+
(Cell, _) => true,
352+
// Cell is the most permissive, nothing can be replaced by Cell.
353+
// (ReservedIM, Cell) => true,
354+
(_, Cell) => false,
355+
// ReservedIM can be replaced by anything besides Cell.
356+
// ReservedIM allows all transitions, but unlike Cell, a local write
357+
// to ReservedIM transitions to Active, while it is a no-op for Cell.
311358
(ReservedIM, _) => true,
359+
(_, ReservedIM) => false,
312360
// Reserved (as parent, where conflictedness does not matter)
313-
// can be replaced by all but ReservedIM,
314-
// since ReservedIM alone would survive foreign writes
315-
(ReservedFrz { .. }, ReservedIM) => false,
361+
// can be replaced by all but ReservedIM and Cell,
362+
// since ReservedIM and Cell alone would survive foreign writes
316363
(ReservedFrz { .. }, _) => true,
364+
(_, ReservedFrz { .. }) => false,
317365
// Active can not be replaced by something surviving
318-
// foreign reads and then remaining writable.
319-
(Active, ReservedIM) => false,
320-
(Active, ReservedFrz { .. }) => false,
366+
// foreign reads and then remaining writable (i.e., Reserved*).
321367
// Replacing a state by itself is always okay, even if the child state is protected.
322-
(Active, Active) => true,
323368
// Active can be replaced by Frozen, since it is not protected.
324-
(Active, Frozen) => true,
325-
(Active, Disabled) => true,
369+
(Active, Active | Frozen | Disabled) => true,
370+
(_, Active) => false,
326371
// Frozen can only be replaced by Disabled (and itself).
327-
(Frozen, Frozen) => true,
328-
(Frozen, Disabled) => true,
329-
(Frozen, _) => false,
372+
(Frozen, Frozen | Disabled) => true,
373+
(_, Frozen) => false,
330374
// Disabled can not be replaced by anything else.
331375
(Disabled, Disabled) => true,
332-
(Disabled, _) => false,
333376
}
334377
}
335378

@@ -383,6 +426,7 @@ pub mod diagnostics {
383426
f,
384427
"{}",
385428
match self {
429+
Cell => "Cell",
386430
ReservedFrz { conflicted: false } => "Reserved",
387431
ReservedFrz { conflicted: true } => "Reserved (conflicted)",
388432
ReservedIM => "Reserved (interior mutable)",
@@ -413,6 +457,7 @@ pub mod diagnostics {
413457
// and also as `diagnostics::DisplayFmtPermission.uninit` otherwise
414458
// alignment will be incorrect.
415459
match self.inner {
460+
Cell => "Cel ",
416461
ReservedFrz { conflicted: false } => "Res ",
417462
ReservedFrz { conflicted: true } => "ResC",
418463
ReservedIM => "ReIM",
@@ -459,7 +504,7 @@ pub mod diagnostics {
459504
/// (Reserved < Active < Frozen < Disabled);
460505
/// - between `self` and `err` the permission should also be increasing,
461506
/// so all permissions inside `err` should be greater than `self.1`;
462-
/// - `Active` and `Reserved(conflicted=false)` cannot cause an error
507+
/// - `Active`, `Reserved(conflicted=false)`, and `Cell` cannot cause an error
463508
/// due to insufficient permissions, so `err` cannot be a `ChildAccessForbidden(_)`
464509
/// of either of them;
465510
/// - `err` should not be `ProtectedDisabled(Disabled)`, because the protected
@@ -492,13 +537,14 @@ pub mod diagnostics {
492537
(ReservedFrz { conflicted: true } | Active | Frozen, Disabled) => false,
493538
(ReservedFrz { conflicted: true }, Frozen) => false,
494539

495-
// `Active` and `Reserved` have all permissions, so a
540+
// `Active`, `Reserved`, and `Cell` have all permissions, so a
496541
// `ChildAccessForbidden(Reserved | Active)` can never exist.
497-
(_, Active) | (_, ReservedFrz { conflicted: false }) =>
542+
(_, Active) | (_, ReservedFrz { conflicted: false }) | (_, Cell) =>
498543
unreachable!("this permission cannot cause an error"),
499544
// No transition has `Reserved { conflicted: false }` or `ReservedIM`
500-
// as its `.to` unless it's a noop.
501-
(ReservedFrz { conflicted: false } | ReservedIM, _) =>
545+
// as its `.to` unless it's a noop. `Cell` cannot be in its `.to`
546+
// because all child accesses are a noop.
547+
(ReservedFrz { conflicted: false } | ReservedIM | Cell, _) =>
502548
unreachable!("self is a noop transition"),
503549
// All transitions produced in normal executions (using `apply_access`)
504550
// change permissions in the order `Reserved -> Active -> Frozen -> Disabled`.
@@ -544,16 +590,17 @@ pub mod diagnostics {
544590
"permission that results in Disabled should not itself be Disabled in the first place"
545591
),
546592
// No transition has `Reserved { conflicted: false }` or `ReservedIM` as its `.to`
547-
// unless it's a noop.
548-
(ReservedFrz { conflicted: false } | ReservedIM, _) =>
593+
// unless it's a noop. `Cell` cannot be in its `.to` because all child
594+
// accesses are a noop.
595+
(ReservedFrz { conflicted: false } | ReservedIM | Cell, _) =>
549596
unreachable!("self is a noop transition"),
550597

551598
// Permissions only evolve in the order `Reserved -> Active -> Frozen -> Disabled`,
552599
// so permissions found must be increasing in the order
553600
// `self.from < self.to <= forbidden.from < forbidden.to`.
554-
(Disabled, ReservedFrz { .. } | ReservedIM | Active | Frozen)
555-
| (Frozen, ReservedFrz { .. } | ReservedIM | Active)
556-
| (Active, ReservedFrz { .. } | ReservedIM) =>
601+
(Disabled, Cell | ReservedFrz { .. } | ReservedIM | Active | Frozen)
602+
| (Frozen, Cell | ReservedFrz { .. } | ReservedIM | Active)
603+
| (Active, Cell | ReservedFrz { .. } | ReservedIM) =>
557604
unreachable!("permissions between self and err must be increasing"),
558605
}
559606
}
@@ -590,7 +637,7 @@ mod propagation_optimization_checks {
590637
impl Exhaustive for PermissionPriv {
591638
fn exhaustive() -> Box<dyn Iterator<Item = Self>> {
592639
Box::new(
593-
vec![Active, Frozen, Disabled, ReservedIM]
640+
vec![Active, Frozen, Disabled, ReservedIM, Cell]
594641
.into_iter()
595642
.chain(<bool>::exhaustive().map(|conflicted| ReservedFrz { conflicted })),
596643
)

src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -721,9 +721,14 @@ impl<'tcx> Tree {
721721
// visit all children, skipping none
722722
|_| ContinueTraversal::Recurse,
723723
|args: NodeAppArgs<'_>| -> Result<(), TransitionError> {
724-
let NodeAppArgs { node, .. } = args;
724+
let NodeAppArgs { node, perm, .. } = args;
725+
let perm =
726+
perm.get().copied().unwrap_or_else(|| node.default_location_state());
725727
if global.borrow().protected_tags.get(&node.tag)
726728
== Some(&ProtectorKind::StrongProtector)
729+
// Don't check for protector if it is a Cell (see `unsafe_cell_deallocate` in `interior_mutability.rs`).
730+
// Related to https://github.com/rust-lang/rust/issues/55005.
731+
&& !perm.permission().is_cell()
727732
{
728733
Err(TransitionError::ProtectedDealloc)
729734
} else {
@@ -865,10 +870,9 @@ impl<'tcx> Tree {
865870
let idx = self.tag_mapping.get(&tag).unwrap();
866871
// Only visit initialized permissions
867872
if let Some(p) = perms.get(idx)
873+
&& let Some(access_kind) = p.permission.protector_end_access()
868874
&& p.initialized
869875
{
870-
let access_kind =
871-
if p.permission.is_active() { AccessKind::Write } else { AccessKind::Read };
872876
let access_cause = diagnostics::AccessCause::FnExit(access_kind);
873877
TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms }
874878
.traverse_nonchildren(

0 commit comments

Comments
 (0)