@@ -3,6 +3,8 @@ use rustc_middle::mir::{Mutability, RetagKind};
3
3
use rustc_middle:: ty:: layout:: HasTypingEnv ;
4
4
use rustc_middle:: ty:: { self , Ty } ;
5
5
6
+ use self :: foreign_access_skipping:: IdempotentForeignAccess ;
7
+ use self :: tree:: LocationState ;
6
8
use crate :: borrow_tracker:: { GlobalState , GlobalStateInner , ProtectorKind } ;
7
9
use crate :: concurrency:: data_race:: NaReadType ;
8
10
use crate :: * ;
@@ -95,7 +97,7 @@ impl<'tcx> Tree {
95
97
/// A tag just lost its protector.
96
98
///
97
99
/// This emits a special kind of access that is only applied
98
- /// to initialized locations, as a protection against other
100
+ /// to accessed locations, as a protection against other
99
101
/// tags not having been made aware of the existence of this
100
102
/// protector.
101
103
pub fn release_protector (
@@ -113,16 +115,19 @@ impl<'tcx> Tree {
113
115
114
116
/// Policy for a new borrow.
115
117
#[ derive( Debug , Clone , Copy ) ]
116
- struct NewPermission {
117
- /// Which permission should the pointer start with.
118
- initial_state : Permission ,
118
+ pub struct NewPermission {
119
+ /// Permission for the frozen part of the range.
120
+ freeze_perm : Permission ,
121
+ /// Whether a read access should be performed on the frozen part on a retag.
122
+ freeze_access : bool ,
123
+ /// Permission for the non-frozen part of the range.
124
+ nonfreeze_perm : Permission ,
125
+ /// Whether a read access should be performed on the non-frozen
126
+ /// part on a retag.
127
+ nonfreeze_access : bool ,
119
128
/// Whether this pointer is part of the arguments of a function call.
120
129
/// `protector` is `Some(_)` for all pointers marked `noalias`.
121
130
protector : Option < ProtectorKind > ,
122
- /// Whether a read should be performed on a retag. This should be `false`
123
- /// for `Cell` because this could cause data races when using thread-safe
124
- /// data types like `Mutex<T>`.
125
- initial_read : bool ,
126
131
}
127
132
128
133
impl < ' tcx > NewPermission {
@@ -133,27 +138,42 @@ impl<'tcx> NewPermission {
133
138
kind : RetagKind ,
134
139
cx : & crate :: MiriInterpCx < ' tcx > ,
135
140
) -> Option < Self > {
136
- let ty_is_freeze = pointee. is_freeze ( * cx. tcx , cx. typing_env ( ) ) ;
137
141
let ty_is_unpin = pointee. is_unpin ( * cx. tcx , cx. typing_env ( ) ) ;
138
142
let is_protected = kind == RetagKind :: FnEntry ;
139
- // As demonstrated by `tests/fail/tree_borrows/reservedim_spurious_write.rs`,
140
- // interior mutability and protectors interact poorly.
141
- // To eliminate the case of Protected Reserved IM we override interior mutability
142
- // in the case of a protected reference: protected references are always considered
143
- // "freeze" in their reservation phase.
144
- let ( initial_state, initial_read) = match mutability {
143
+ let protector = is_protected. then_some ( ProtectorKind :: StrongProtector ) ;
144
+
145
+ Some ( match mutability {
145
146
Mutability :: Mut if ty_is_unpin =>
146
- ( Permission :: new_reserved ( ty_is_freeze, is_protected) , true ) ,
147
- Mutability :: Not if ty_is_freeze => ( Permission :: new_frozen ( ) , true ) ,
148
- Mutability :: Not if !ty_is_freeze => ( Permission :: new_cell ( ) , false ) ,
149
- // Raw pointers never enter this function so they are not handled.
150
- // However raw pointers are not the only pointers that take the parent
151
- // tag, this also happens for `!Unpin` `&mut`s, which are excluded above.
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
+ } ,
152
175
_ => return None ,
153
- } ;
154
-
155
- let protector = is_protected. then_some ( ProtectorKind :: StrongProtector ) ;
156
- Some ( Self { initial_state, protector, initial_read } )
176
+ } )
157
177
}
158
178
159
179
/// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
@@ -168,13 +188,17 @@ impl<'tcx> NewPermission {
168
188
pointee. is_unpin ( * cx. tcx , cx. typing_env ( ) ) . then_some ( ( ) ) . map ( |( ) | {
169
189
// Regular `Unpin` box, give it `noalias` but only a weak protector
170
190
// because it is valid to deallocate it within the function.
171
- let ty_is_freeze = pointee. is_freeze ( * cx. tcx , cx. typing_env ( ) ) ;
172
- let protected = kind == RetagKind :: FnEntry ;
173
- let initial_state = Permission :: new_reserved ( ty_is_freeze, protected) ;
174
- Self {
175
- initial_state,
176
- protector : protected. then_some ( ProtectorKind :: WeakProtector ) ,
177
- initial_read : true ,
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,
178
202
}
179
203
} )
180
204
}
@@ -194,8 +218,6 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
194
218
new_tag : BorTag ,
195
219
) -> InterpResult < ' tcx , Option < Provenance > > {
196
220
let this = self . eval_context_mut ( ) ;
197
- // Make sure the new permission makes sense as the initial permission of a fresh tag.
198
- assert ! ( new_perm. initial_state. is_initial( ) ) ;
199
221
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
200
222
this. check_ptr_access ( place. ptr ( ) , ptr_size, CheckInAllocMsg :: Dereferenceable ) ?;
201
223
@@ -206,7 +228,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
206
228
let global = this. machine . borrow_tracker . as_ref ( ) . unwrap ( ) . borrow ( ) ;
207
229
let ty = place. layout . ty ;
208
230
if global. tracked_pointer_tags . contains ( & new_tag) {
209
- let kind_str = format ! ( "initial state {} (pointee type {ty})" , new_perm. initial_state) ;
231
+ let ty_is_freeze = ty. is_freeze ( * this. tcx , this. typing_env ( ) ) ;
232
+ let kind_str =
233
+ if ty_is_freeze {
234
+ format ! ( "initial state {} (pointee type {ty})" , new_perm. freeze_perm)
235
+ } else {
236
+ format ! ( "initial state {}/{} outside/inside UnsafeCell (pointee type {ty})" , new_perm. freeze_perm, new_perm. nonfreeze_perm)
237
+ } ;
210
238
this. emit_diagnostic ( NonHaltingDiagnostic :: CreatedPointerTag (
211
239
new_tag. inner ( ) ,
212
240
Some ( kind_str) ,
@@ -285,43 +313,103 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
285
313
286
314
let span = this. machine . current_span ( ) ;
287
315
let alloc_extra = this. get_alloc_extra ( alloc_id) ?;
288
- let range = alloc_range ( base_offset, ptr_size) ;
289
316
let mut tree_borrows = alloc_extra. borrow_tracker_tb ( ) . borrow_mut ( ) ;
290
317
291
- // All reborrows incur a (possibly zero-sized) read access to the parent
292
- if new_perm. initial_read {
293
- tree_borrows. perform_access (
294
- orig_tag,
295
- Some ( ( range, AccessKind :: Read , diagnostics:: AccessCause :: Reborrow ) ) ,
296
- this. machine . borrow_tracker . as_ref ( ) . unwrap ( ) ,
297
- alloc_id,
298
- this. machine . current_span ( ) ,
299
- ) ?;
300
- }
318
+ // Store initial permissions and their corresponding range.
319
+ let mut perms_map: RangeMap < LocationState > = RangeMap :: new (
320
+ ptr_size,
321
+ LocationState :: new_accessed ( Permission :: new_disabled ( ) , IdempotentForeignAccess :: None ) , // this will be overwritten
322
+ ) ;
323
+ // Keep track of whether the node has any part that allows for interior mutability.
324
+ // FIXME: This misses `PhantomData<UnsafeCell<T>>` which could be considered a marker
325
+ // for requesting interior mutability.
326
+ let mut has_unsafe_cell = false ;
327
+
328
+ // When adding a new node, the SIFA of its parents needs to be updated, potentially across
329
+ // the entire memory range. For the parts that are being accessed below, the access itself
330
+ // trivially takes care of that. However, we have to do some more work to also deal with
331
+ // the parts that are not being accessed. Specifically what we do is that we
332
+ // call `update_last_accessed_after_retag` on the SIFA of the permission set for the part of
333
+ // memory outside `perm_map` -- so that part is definitely taken care of. The remaining concern
334
+ // is the part of memory that is in the range of `perms_map`, but not accessed below.
335
+ // There we have two cases:
336
+ // * If we do have an `UnsafeCell` (`has_unsafe_cell` becomes true), then the non-accessed part
337
+ // uses `nonfreeze_perm`, so the `nonfreeze_perm` initialized parts are also fine. We enforce
338
+ // the `freeze_perm` parts to be accessed, and thus everything is taken care of.
339
+ // * If there is no `UnsafeCell`, then `freeze_perm` is used everywhere (both inside and outside the initial range),
340
+ // and we update everything to have the `freeze_perm`'s SIFA, so there are no issues. (And this assert below is not
341
+ // actually needed in this case).
342
+ assert ! ( new_perm. freeze_access) ;
343
+
344
+ let protected = new_perm. protector . is_some ( ) ;
345
+ this. visit_freeze_sensitive ( place, ptr_size, |range, frozen| {
346
+ has_unsafe_cell = has_unsafe_cell || !frozen;
347
+
348
+ // We are only ever `Frozen` inside the frozen bits.
349
+ let ( perm, access) = if frozen {
350
+ ( new_perm. freeze_perm , new_perm. freeze_access )
351
+ } else {
352
+ ( new_perm. nonfreeze_perm , new_perm. nonfreeze_access )
353
+ } ;
354
+
355
+ // Store initial permissions.
356
+ for ( _loc_range, loc) in perms_map. iter_mut ( range. start , range. size ) {
357
+ let sifa = perm. strongest_idempotent_foreign_access ( protected) ;
358
+ // NOTE: Currently, `access` is false if and only if `perm` is Cell, so this `if`
359
+ // doesn't not change whether any code is UB or not. We could just always use
360
+ // `new_accessed` and everything would stay the same. But that seems conceptually
361
+ // odd, so we keep the initial "accessed" bit of the `LocationState` in sync with whether
362
+ // a read access is performed below.
363
+ if access {
364
+ * loc = LocationState :: new_accessed ( perm, sifa) ;
365
+ } else {
366
+ * loc = LocationState :: new_non_accessed ( perm, sifa) ;
367
+ }
368
+ }
369
+
370
+ // Some reborrows incur a read access to the parent.
371
+ if access {
372
+ // Adjust range to be relative to allocation start (rather than to `place`).
373
+ let mut range_in_alloc = range;
374
+ range_in_alloc. start += base_offset;
375
+
376
+ tree_borrows. perform_access (
377
+ orig_tag,
378
+ Some ( ( range_in_alloc, AccessKind :: Read , diagnostics:: AccessCause :: Reborrow ) ) ,
379
+ this. machine . borrow_tracker . as_ref ( ) . unwrap ( ) ,
380
+ alloc_id,
381
+ this. machine . current_span ( ) ,
382
+ ) ?;
383
+
384
+ // Also inform the data race model (but only if any bytes are actually affected).
385
+ if range. size . bytes ( ) > 0 {
386
+ if let Some ( data_race) = alloc_extra. data_race . as_vclocks_ref ( ) {
387
+ data_race. read (
388
+ alloc_id,
389
+ range_in_alloc,
390
+ NaReadType :: Retag ,
391
+ Some ( place. layout . ty ) ,
392
+ & this. machine ,
393
+ ) ?
394
+ }
395
+ }
396
+ }
397
+ interp_ok ( ( ) )
398
+ } ) ?;
399
+
301
400
// Record the parent-child pair in the tree.
302
401
tree_borrows. new_child (
402
+ base_offset,
303
403
orig_tag,
304
404
new_tag,
305
- new_perm. initial_state ,
306
- range,
405
+ perms_map,
406
+ // Allow lazily writing to surrounding data if we found an `UnsafeCell`.
407
+ if has_unsafe_cell { new_perm. nonfreeze_perm } else { new_perm. freeze_perm } ,
408
+ protected,
307
409
span,
308
- new_perm. protector . is_some ( ) ,
309
410
) ?;
310
411
drop ( tree_borrows) ;
311
412
312
- // Also inform the data race model (but only if any bytes are actually affected).
313
- if range. size . bytes ( ) > 0 && new_perm. initial_read {
314
- if let Some ( data_race) = alloc_extra. data_race . as_vclocks_ref ( ) {
315
- data_race. read (
316
- alloc_id,
317
- range,
318
- NaReadType :: Retag ,
319
- Some ( place. layout . ty ) ,
320
- & this. machine ,
321
- ) ?;
322
- }
323
- }
324
-
325
413
interp_ok ( Some ( Provenance :: Concrete { alloc_id, tag : new_tag } ) )
326
414
}
327
415
@@ -508,15 +596,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
508
596
fn tb_protect_place ( & mut self , place : & MPlaceTy < ' tcx > ) -> InterpResult < ' tcx , MPlaceTy < ' tcx > > {
509
597
let this = self . eval_context_mut ( ) ;
510
598
511
- // Note: if we were to inline `new_reserved` below we would find out that
512
- // `ty_is_freeze` is eventually unused because it appears in a `ty_is_freeze || true`.
513
- // We are nevertheless including it here for clarity.
514
- let ty_is_freeze = place. layout . ty . is_freeze ( * this. tcx , this. typing_env ( ) ) ;
515
599
// Retag it. With protection! That is the entire point.
516
600
let new_perm = NewPermission {
517
- initial_state : Permission :: new_reserved ( ty_is_freeze, /* protected */ true ) ,
601
+ // Note: If we are creating a protected Reserved, which can
602
+ // never be ReservedIM, the value of the `ty_is_freeze`
603
+ // argument doesn't matter
604
+ // (`ty_is_freeze || true` in `new_reserved` will always be `true`).
605
+ freeze_perm : Permission :: new_reserved (
606
+ /* ty_is_freeze */ true , /* protected */ true ,
607
+ ) ,
608
+ freeze_access : true ,
609
+ nonfreeze_perm : Permission :: new_reserved (
610
+ /* ty_is_freeze */ false , /* protected */ true ,
611
+ ) ,
612
+ nonfreeze_access : true ,
518
613
protector : Some ( ProtectorKind :: StrongProtector ) ,
519
- initial_read : true ,
520
614
} ;
521
615
this. tb_retag_place ( place, new_perm)
522
616
}
0 commit comments