Skip to content

Commit c68450a

Browse files
committed
tree borrows: refactor new-permission logic
1 parent ad8b241 commit c68450a

File tree

6 files changed

+98
-110
lines changed

6 files changed

+98
-110
lines changed

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

Lines changed: 59 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -125,81 +125,64 @@ pub struct NewPermission {
125125
/// Whether a read access should be performed on the non-frozen
126126
/// part on a retag.
127127
nonfreeze_access: bool,
128+
/// Permission for memory outside the range.
129+
outside_perm: Permission,
128130
/// Whether this pointer is part of the arguments of a function call.
129131
/// `protector` is `Some(_)` for all pointers marked `noalias`.
130132
protector: Option<ProtectorKind>,
131133
}
132134

133135
impl<'tcx> NewPermission {
134-
/// Determine NewPermission of the reference from the type of the pointee.
135-
fn from_ref_ty(
136+
/// Determine NewPermission of the reference/Box from the type of the pointee.
137+
///
138+
/// A `ref_mutability` of `None` indicates a `Box` type.
139+
fn new(
136140
pointee: Ty<'tcx>,
137-
mutability: Mutability,
138-
kind: RetagKind,
141+
ref_mutability: Option<Mutability>,
142+
retag_kind: RetagKind,
139143
cx: &crate::MiriInterpCx<'tcx>,
140144
) -> Option<Self> {
141145
let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env());
142-
let is_protected = kind == RetagKind::FnEntry;
143-
let protector = is_protected.then_some(ProtectorKind::StrongProtector);
144-
145-
Some(match mutability {
146-
Mutability::Mut if ty_is_unpin =>
147-
NewPermission {
148-
freeze_perm: Permission::new_reserved(
149-
/* ty_is_freeze */ true,
150-
is_protected,
151-
),
152-
freeze_access: true,
153-
nonfreeze_perm: Permission::new_reserved(
154-
/* ty_is_freeze */ false,
155-
is_protected,
156-
),
157-
// If we have a mutable reference, then the non-frozen part will
158-
// have state `ReservedIM` or `Reserved`, which can have an initial read access
159-
// performed on it because you cannot have multiple mutable borrows.
160-
nonfreeze_access: true,
161-
protector,
162-
},
163-
Mutability::Not =>
164-
NewPermission {
165-
freeze_perm: Permission::new_frozen(),
166-
freeze_access: true,
167-
nonfreeze_perm: Permission::new_cell(),
168-
// If it is a shared reference, then the non-frozen
169-
// part will have state `Cell`, which should not have an initial access,
170-
// as this can cause data races when using thread-safe data types like
171-
// `Mutex<T>`.
172-
nonfreeze_access: false,
173-
protector,
174-
},
175-
_ => return None,
176-
})
177-
}
146+
let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
147+
let is_protected = retag_kind == RetagKind::FnEntry;
178148

179-
/// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
180-
/// These pointers allow deallocation so need a different kind of protector not handled
181-
/// by `from_ref_ty`.
182-
fn from_unique_ty(
183-
ty: Ty<'tcx>,
184-
kind: RetagKind,
185-
cx: &crate::MiriInterpCx<'tcx>,
186-
) -> Option<Self> {
187-
let pointee = ty.builtin_deref(true).unwrap();
188-
pointee.is_unpin(*cx.tcx, cx.typing_env()).then_some(()).map(|()| {
189-
// Regular `Unpin` box, give it `noalias` but only a weak protector
190-
// because it is valid to deallocate it within the function.
191-
let is_protected = kind == RetagKind::FnEntry;
192-
let protector = is_protected.then_some(ProtectorKind::WeakProtector);
193-
NewPermission {
194-
freeze_perm: Permission::new_reserved(/* ty_is_freeze */ true, is_protected),
195-
freeze_access: true,
196-
nonfreeze_perm: Permission::new_reserved(
197-
/* ty_is_freeze */ false,
198-
is_protected,
199-
),
200-
nonfreeze_access: true,
201-
protector,
202-
}
149+
if matches!(ref_mutability, Some(Mutability::Mut) | None if !ty_is_unpin) {
150+
// Mutable reference / Box to pinning type: retagging is a NOP.
151+
// FIXME: with `UnsafePinned`, this should do proper per-byte tracking.
152+
return None;
153+
}
154+
155+
let freeze_perm = match ref_mutability {
156+
// Shared references are frozen.
157+
Some(Mutability::Not) => Permission::new_frozen(),
158+
// Mutable references and Boxes are reserved.
159+
_ => Permission::new_reserved_frz(),
160+
};
161+
let nonfreeze_perm = match ref_mutability {
162+
// Shared references are "transparent".
163+
Some(Mutability::Not) => Permission::new_cell(),
164+
// *Protected* mutable references and boxes are reserved without regarding for interior mutability.
165+
_ if is_protected => Permission::new_reserved_frz(),
166+
// Unprotected mutable references and boxes start in `ReservedIm`.
167+
_ => Permission::new_reserved_im(),
168+
};
169+
170+
// Everything except for `Cell` gets an initial access.
171+
let initial_access = |perm: &Permission| !perm.is_cell();
172+
173+
Some(NewPermission {
174+
freeze_perm,
175+
freeze_access: initial_access(&freeze_perm),
176+
nonfreeze_perm,
177+
nonfreeze_access: initial_access(&nonfreeze_perm),
178+
outside_perm: if ty_is_freeze { freeze_perm } else { nonfreeze_perm },
179+
protector: is_protected.then_some(if ref_mutability.is_some() {
180+
// Strong protector for references
181+
ProtectorKind::StrongProtector
182+
} else {
183+
// Weak protector for boxes
184+
ProtectorKind::WeakProtector
185+
}),
203186
})
204187
}
205188
}
@@ -313,15 +296,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
313296

314297
let span = this.machine.current_span();
315298

316-
// Store initial permissions and their corresponding range.
299+
// Store initial permissions for the "inside" part.
317300
let mut perms_map: DedupRangeMap<LocationState> = DedupRangeMap::new(
318301
ptr_size,
319302
LocationState::new_accessed(Permission::new_disabled(), IdempotentForeignAccess::None), // this will be overwritten
320303
);
321-
// Keep track of whether the node has any part that allows for interior mutability.
322-
// FIXME: This misses `PhantomData<UnsafeCell<T>>` which could be considered a marker
323-
// for requesting interior mutability.
324-
let mut has_unsafe_cell = false;
325304

326305
// When adding a new node, the SIFA of its parents needs to be updated, potentially across
327306
// the entire memory range. For the parts that are being accessed below, the access itself
@@ -350,14 +329,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
350329
.get_tree_borrows_params()
351330
.precise_interior_mut;
352331

353-
let default_perm = if !precise_interior_mut {
354-
// NOTE: Using `ty_is_freeze` doesn't give the same result as going through the range
355-
// and computing `has_unsafe_cell`. This is because of zero-sized `UnsafeCell`, for which
356-
// `has_unsafe_cell` is false, but `!ty_is_freeze` is true.
332+
// Set "inside" permissions.
333+
if !precise_interior_mut {
357334
let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
358335
let (perm, access) = if ty_is_freeze {
359336
(new_perm.freeze_perm, new_perm.freeze_access)
360337
} else {
338+
// Just pretend the entire thing is an `UnsafeCell`.
361339
(new_perm.nonfreeze_perm, new_perm.nonfreeze_access)
362340
};
363341
let sifa = perm.strongest_idempotent_foreign_access(protected);
@@ -370,12 +348,8 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
370348
for (_loc_range, loc) in perms_map.iter_mut_all() {
371349
*loc = new_loc;
372350
}
373-
374-
perm
375351
} else {
376352
this.visit_freeze_sensitive(place, ptr_size, |range, frozen| {
377-
has_unsafe_cell = has_unsafe_cell || !frozen;
378-
379353
// We are only ever `Frozen` inside the frozen bits.
380354
let (perm, access) = if frozen {
381355
(new_perm.freeze_perm, new_perm.freeze_access)
@@ -401,9 +375,6 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
401375

402376
interp_ok(())
403377
})?;
404-
405-
// Allow lazily writing to surrounding data if we found an `UnsafeCell`.
406-
if has_unsafe_cell { new_perm.nonfreeze_perm } else { new_perm.freeze_perm }
407378
};
408379

409380
let alloc_extra = this.get_alloc_extra(alloc_id)?;
@@ -447,7 +418,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
447418
orig_tag,
448419
new_tag,
449420
perms_map,
450-
default_perm,
421+
new_perm.outside_perm,
451422
protected,
452423
span,
453424
)?;
@@ -514,7 +485,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
514485
let this = self.eval_context_mut();
515486
let new_perm = match val.layout.ty.kind() {
516487
&ty::Ref(_, pointee, mutability) =>
517-
NewPermission::from_ref_ty(pointee, mutability, kind, this),
488+
NewPermission::new(pointee, Some(mutability), kind, this),
518489
_ => None,
519490
};
520491
if let Some(new_perm) = new_perm {
@@ -571,8 +542,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
571542
fn visit_box(&mut self, box_ty: Ty<'tcx>, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
572543
// Only boxes for the global allocator get any special treatment.
573544
if box_ty.is_box_global(*self.ecx.tcx) {
545+
let pointee = place.layout.ty.builtin_deref(true).unwrap();
574546
let new_perm =
575-
NewPermission::from_unique_ty(place.layout.ty, self.kind, self.ecx);
547+
NewPermission::new(pointee, /* not a ref */ None, self.kind, self.ecx);
576548
self.retag_ptr_inplace(place, new_perm)?;
577549
}
578550
interp_ok(())
@@ -591,7 +563,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
591563
match place.layout.ty.kind() {
592564
&ty::Ref(_, pointee, mutability) => {
593565
let new_perm =
594-
NewPermission::from_ref_ty(pointee, mutability, self.kind, self.ecx);
566+
NewPermission::new(pointee, Some(mutability), self.kind, self.ecx);
595567
self.retag_ptr_inplace(place, new_perm)?;
596568
}
597569
ty::RawPtr(_, _) => {
@@ -643,14 +615,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
643615
// never be ReservedIM, the value of the `ty_is_freeze`
644616
// argument doesn't matter
645617
// (`ty_is_freeze || true` in `new_reserved` will always be `true`).
646-
freeze_perm: Permission::new_reserved(
647-
/* ty_is_freeze */ true, /* protected */ true,
648-
),
618+
freeze_perm: Permission::new_reserved_frz(),
649619
freeze_access: true,
650-
nonfreeze_perm: Permission::new_reserved(
651-
/* ty_is_freeze */ false, /* protected */ true,
652-
),
620+
nonfreeze_perm: Permission::new_reserved_frz(),
653621
nonfreeze_access: true,
622+
outside_perm: Permission::new_reserved_frz(),
654623
protector: Some(ProtectorKind::StrongProtector),
655624
};
656625
this.tb_retag_place(place, new_perm)

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

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -272,28 +272,15 @@ impl Permission {
272272

273273
/// Default initial permission of a reborrowed mutable reference that is either
274274
/// protected or not interior mutable.
275-
fn new_reserved_frz() -> Self {
275+
pub fn new_reserved_frz() -> Self {
276276
Self { inner: ReservedFrz { conflicted: false } }
277277
}
278278

279279
/// Default initial permission of an unprotected interior mutable reference.
280-
fn new_reserved_im() -> Self {
280+
pub fn new_reserved_im() -> Self {
281281
Self { inner: ReservedIM }
282282
}
283283

284-
/// Wrapper around `new_reserved_frz` and `new_reserved_im` that decides
285-
/// which to call based on the interior mutability and the retag kind (whether there
286-
/// is a protector is relevant because being protected takes priority over being
287-
/// interior mutable)
288-
pub fn new_reserved(ty_is_freeze: bool, protected: bool) -> Self {
289-
// As demonstrated by `tests/fail/tree_borrows/reservedim_spurious_write.rs`,
290-
// interior mutability and protectors interact poorly.
291-
// To eliminate the case of Protected Reserved IM we override interior mutability
292-
// in the case of a protected reference: protected references are always considered
293-
// "freeze" in their reservation phase.
294-
if ty_is_freeze || protected { Self::new_reserved_frz() } else { Self::new_reserved_im() }
295-
}
296-
297284
/// Default initial permission of a reborrowed shared reference.
298285
pub fn new_frozen() -> Self {
299286
Self { inner: Frozen }

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ mod spurious_read {
610610
},
611611
y: LocStateProt {
612612
state: LocationState::new_non_accessed(
613-
Permission::new_reserved(/* freeze */ true, /* protected */ true),
613+
Permission::new_reserved_frz(),
614614
IdempotentForeignAccess::default(),
615615
),
616616
prot: true,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//@compile-flags: -Zmiri-tree-borrows
2+
3+
fn main() {
4+
// Since the "inside" part is `!Freeze`, the permission to mutate is gone.
5+
let pair = ((), 1);
6+
let x = &pair.0;
7+
let ptr = (&raw const *x).cast::<i32>().cast_mut();
8+
unsafe { ptr.write(0) }; //~ERROR: /write access .* forbidden/
9+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error: Undefined Behavior: write access through <TAG> at ALLOC[0x0] is forbidden
2+
--> tests/fail/tree_borrows/frozen-lazy-write-to-surrounding.rs:LL:CC
3+
|
4+
LL | unsafe { ptr.write(0) };
5+
| ^^^^^^^^^^^^ Undefined Behavior occurred here
6+
|
7+
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
8+
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
9+
= help: the accessed tag <TAG> has state Frozen which forbids this child write access
10+
help: the accessed tag <TAG> was created here, in the initial state Frozen
11+
--> tests/fail/tree_borrows/frozen-lazy-write-to-surrounding.rs:LL:CC
12+
|
13+
LL | let x = &pair.0;
14+
| ^^^^^^^
15+
= note: BACKTRACE (of the first span):
16+
= note: inside `main` at tests/fail/tree_borrows/frozen-lazy-write-to-surrounding.rs:LL:CC
17+
18+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
19+
20+
error: aborting due to 1 previous error
21+

src/tools/miri/tests/pass/tree_borrows/cell-lazy-write-to-surrounding.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ fn main() {
1414
foo(&arr[0]);
1515

1616
let pair = (Cell::new(1), 1);
17-
// TODO: Ideally, this would result in UB since the second element
18-
// in `pair` is Frozen. We would need some way to express a
19-
// "shared reference with permission to access surrounding
20-
// interior mutable data".
2117
foo(&pair.0);
18+
19+
// As long as the "inside" part is `!Freeze`, the permission to mutate the "outside" is preserved.
20+
let pair = (Cell::new(()), 1);
21+
let x = &pair.0;
22+
let ptr = (&raw const *x).cast::<i32>().cast_mut();
23+
unsafe { ptr.write(0) };
2224
}

0 commit comments

Comments
 (0)