Skip to content

Commit 3ad93a5

Browse files
authored
Merge pull request #4540 from RalfJung/tb-refactors
tree borrows: refactor new-permission logic
2 parents 972ee78 + 2422b25 commit 3ad93a5

File tree

6 files changed

+116
-144
lines changed

6 files changed

+116
-144
lines changed

src/borrow_tracker/tree_borrows/mod.rs

Lines changed: 77 additions & 124 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,16 +296,6 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
313296

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

316-
// Store initial permissions and their corresponding range.
317-
let mut perms_map: DedupRangeMap<LocationState> = DedupRangeMap::new(
318-
ptr_size,
319-
LocationState::new_accessed(Permission::new_disabled(), IdempotentForeignAccess::None), // this will be overwritten
320-
);
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;
325-
326299
// When adding a new node, the SIFA of its parents needs to be updated, potentially across
327300
// the entire memory range. For the parts that are being accessed below, the access itself
328301
// trivially takes care of that. However, we have to do some more work to also deal with
@@ -350,66 +323,48 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
350323
.get_tree_borrows_params()
351324
.precise_interior_mut;
352325

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.
357-
let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
358-
let (perm, access) = if ty_is_freeze {
326+
// Compute initial "inside" permissions.
327+
let loc_state = |frozen: bool| -> LocationState {
328+
let (perm, access) = if frozen {
359329
(new_perm.freeze_perm, new_perm.freeze_access)
360330
} else {
361331
(new_perm.nonfreeze_perm, new_perm.nonfreeze_access)
362332
};
363333
let sifa = perm.strongest_idempotent_foreign_access(protected);
364-
let new_loc = if access {
334+
if access {
365335
LocationState::new_accessed(perm, sifa)
366336
} else {
367337
LocationState::new_non_accessed(perm, sifa)
368-
};
369-
370-
for (_loc_range, loc) in perms_map.iter_mut_all() {
371-
*loc = new_loc;
372338
}
373-
374-
perm
339+
};
340+
let perms_map = if !precise_interior_mut {
341+
// For `!Freeze` types, just pretend the entire thing is an `UnsafeCell`.
342+
let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
343+
let state = loc_state(ty_is_freeze);
344+
DedupRangeMap::new(ptr_size, state)
375345
} else {
346+
// The initial state will be overwritten by the visitor below.
347+
let mut perms_map: DedupRangeMap<LocationState> = DedupRangeMap::new(
348+
ptr_size,
349+
LocationState::new_accessed(
350+
Permission::new_disabled(),
351+
IdempotentForeignAccess::None,
352+
),
353+
);
376354
this.visit_freeze_sensitive(place, ptr_size, |range, frozen| {
377-
has_unsafe_cell = has_unsafe_cell || !frozen;
378-
379-
// We are only ever `Frozen` inside the frozen bits.
380-
let (perm, access) = if frozen {
381-
(new_perm.freeze_perm, new_perm.freeze_access)
382-
} else {
383-
(new_perm.nonfreeze_perm, new_perm.nonfreeze_access)
384-
};
385-
let sifa = perm.strongest_idempotent_foreign_access(protected);
386-
// NOTE: Currently, `access` is false if and only if `perm` is Cell, so this `if`
387-
// doesn't not change whether any code is UB or not. We could just always use
388-
// `new_accessed` and everything would stay the same. But that seems conceptually
389-
// odd, so we keep the initial "accessed" bit of the `LocationState` in sync with whether
390-
// a read access is performed below.
391-
let new_loc = if access {
392-
LocationState::new_accessed(perm, sifa)
393-
} else {
394-
LocationState::new_non_accessed(perm, sifa)
395-
};
396-
397-
// Store initial permissions.
355+
let state = loc_state(frozen);
398356
for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) {
399-
*loc = new_loc;
357+
*loc = state;
400358
}
401-
402359
interp_ok(())
403360
})?;
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 }
361+
perms_map
407362
};
408363

409364
let alloc_extra = this.get_alloc_extra(alloc_id)?;
410365
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
411366

412-
for (perm_range, perm) in perms_map.iter_mut_all() {
367+
for (perm_range, perm) in perms_map.iter_all() {
413368
if perm.is_accessed() {
414369
// Some reborrows incur a read access to the parent.
415370
// Adjust range to be relative to allocation start (rather than to `place`).
@@ -447,7 +402,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
447402
orig_tag,
448403
new_tag,
449404
perms_map,
450-
default_perm,
405+
new_perm.outside_perm,
451406
protected,
452407
span,
453408
)?;
@@ -514,7 +469,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
514469
let this = self.eval_context_mut();
515470
let new_perm = match val.layout.ty.kind() {
516471
&ty::Ref(_, pointee, mutability) =>
517-
NewPermission::from_ref_ty(pointee, mutability, kind, this),
472+
NewPermission::new(pointee, Some(mutability), kind, this),
518473
_ => None,
519474
};
520475
if let Some(new_perm) = new_perm {
@@ -571,8 +526,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
571526
fn visit_box(&mut self, box_ty: Ty<'tcx>, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
572527
// Only boxes for the global allocator get any special treatment.
573528
if box_ty.is_box_global(*self.ecx.tcx) {
529+
let pointee = place.layout.ty.builtin_deref(true).unwrap();
574530
let new_perm =
575-
NewPermission::from_unique_ty(place.layout.ty, self.kind, self.ecx);
531+
NewPermission::new(pointee, /* not a ref */ None, self.kind, self.ecx);
576532
self.retag_ptr_inplace(place, new_perm)?;
577533
}
578534
interp_ok(())
@@ -591,7 +547,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
591547
match place.layout.ty.kind() {
592548
&ty::Ref(_, pointee, mutability) => {
593549
let new_perm =
594-
NewPermission::from_ref_ty(pointee, mutability, self.kind, self.ecx);
550+
NewPermission::new(pointee, Some(mutability), self.kind, self.ecx);
595551
self.retag_ptr_inplace(place, new_perm)?;
596552
}
597553
ty::RawPtr(_, _) => {
@@ -643,14 +599,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
643599
// never be ReservedIM, the value of the `ty_is_freeze`
644600
// argument doesn't matter
645601
// (`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-
),
602+
freeze_perm: Permission::new_reserved_frz(),
649603
freeze_access: true,
650-
nonfreeze_perm: Permission::new_reserved(
651-
/* ty_is_freeze */ false, /* protected */ true,
652-
),
604+
nonfreeze_perm: Permission::new_reserved_frz(),
653605
nonfreeze_access: true,
606+
outside_perm: Permission::new_reserved_frz(),
654607
protector: Some(ProtectorKind::StrongProtector),
655608
};
656609
this.tb_retag_place(place, new_perm)

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/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+

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)