@@ -3,6 +3,8 @@ use rustc_middle::mir::{Mutability, RetagKind};
33use rustc_middle:: ty:: layout:: HasTypingEnv ;
44use rustc_middle:: ty:: { self , Ty } ;
55
6+ use self :: foreign_access_skipping:: IdempotentForeignAccess ;
7+ use self :: tree:: LocationState ;
68use crate :: borrow_tracker:: { GlobalState , GlobalStateInner , ProtectorKind } ;
79use crate :: concurrency:: data_race:: NaReadType ;
810use crate :: * ;
@@ -95,7 +97,7 @@ impl<'tcx> Tree {
9597 /// A tag just lost its protector.
9698 ///
9799 /// 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
99101 /// tags not having been made aware of the existence of this
100102 /// protector.
101103 pub fn release_protector (
@@ -113,16 +115,19 @@ impl<'tcx> Tree {
113115
114116/// Policy for a new borrow.
115117#[ 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 ,
119128 /// Whether this pointer is part of the arguments of a function call.
120129 /// `protector` is `Some(_)` for all pointers marked `noalias`.
121130 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 ,
126131}
127132
128133impl < ' tcx > NewPermission {
@@ -133,27 +138,42 @@ impl<'tcx> NewPermission {
133138 kind : RetagKind ,
134139 cx : & crate :: MiriInterpCx < ' tcx > ,
135140 ) -> Option < Self > {
136- let ty_is_freeze = pointee. is_freeze ( * cx. tcx , cx. typing_env ( ) ) ;
137141 let ty_is_unpin = pointee. is_unpin ( * cx. tcx , cx. typing_env ( ) ) ;
138142 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 {
145146 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+ } ,
152175 _ => return None ,
153- } ;
154-
155- let protector = is_protected. then_some ( ProtectorKind :: StrongProtector ) ;
156- Some ( Self { initial_state, protector, initial_read } )
176+ } )
157177 }
158178
159179 /// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
@@ -168,13 +188,17 @@ impl<'tcx> NewPermission {
168188 pointee. is_unpin ( * cx. tcx , cx. typing_env ( ) ) . then_some ( ( ) ) . map ( |( ) | {
169189 // Regular `Unpin` box, give it `noalias` but only a weak protector
170190 // 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,
178202 }
179203 } )
180204 }
@@ -194,8 +218,6 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
194218 new_tag : BorTag ,
195219 ) -> InterpResult < ' tcx , Option < Provenance > > {
196220 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( ) ) ;
199221 // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
200222 this. check_ptr_access ( place. ptr ( ) , ptr_size, CheckInAllocMsg :: Dereferenceable ) ?;
201223
@@ -206,7 +228,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
206228 let global = this. machine . borrow_tracker . as_ref ( ) . unwrap ( ) . borrow ( ) ;
207229 let ty = place. layout . ty ;
208230 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+ } ;
210238 this. emit_diagnostic ( NonHaltingDiagnostic :: CreatedPointerTag (
211239 new_tag. inner ( ) ,
212240 Some ( kind_str) ,
@@ -285,43 +313,103 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
285313
286314 let span = this. machine . current_span ( ) ;
287315 let alloc_extra = this. get_alloc_extra ( alloc_id) ?;
288- let range = alloc_range ( base_offset, ptr_size) ;
289316 let mut tree_borrows = alloc_extra. borrow_tracker_tb ( ) . borrow_mut ( ) ;
290317
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+
301400 // Record the parent-child pair in the tree.
302401 tree_borrows. new_child (
402+ base_offset,
303403 orig_tag,
304404 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,
307409 span,
308- new_perm. protector . is_some ( ) ,
309410 ) ?;
310411 drop ( tree_borrows) ;
311412
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-
325413 interp_ok ( Some ( Provenance :: Concrete { alloc_id, tag : new_tag } ) )
326414 }
327415
@@ -508,15 +596,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
508596 fn tb_protect_place ( & mut self , place : & MPlaceTy < ' tcx > ) -> InterpResult < ' tcx , MPlaceTy < ' tcx > > {
509597 let this = self . eval_context_mut ( ) ;
510598
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 ( ) ) ;
515599 // Retag it. With protection! That is the entire point.
516600 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 ,
518613 protector : Some ( ProtectorKind :: StrongProtector ) ,
519- initial_read : true ,
520614 } ;
521615 this. tb_retag_place ( place, new_perm)
522616 }
0 commit comments