]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
Rollup merge of #105005 - estebank:where-clause-lts, r=compiler-errors
[rust.git] / src / tools / miri / src / borrow_tracker / stacked_borrows / mod.rs
1 //! Implements "Stacked Borrows".  See <https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md>
2 //! for further information.
3
4 use log::trace;
5 use std::cmp;
6 use std::fmt::{self, Write};
7
8 use rustc_data_structures::fx::FxHashSet;
9 use rustc_middle::mir::{Mutability, RetagKind};
10 use rustc_middle::ty::{
11     self,
12     layout::{HasParamEnv, LayoutOf},
13 };
14 use rustc_target::abi::{Abi, Size};
15
16 use crate::borrow_tracker::{
17     stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder, TagHistory},
18     AccessKind, GlobalStateInner, ProtectorKind, RetagCause, RetagFields,
19 };
20 use crate::*;
21
22 mod item;
23 pub use item::{Item, Permission};
24 mod stack;
25 pub use stack::Stack;
26 pub mod diagnostics;
27
28 pub type AllocState = Stacks;
29
30 /// Extra per-allocation state.
31 #[derive(Clone, Debug)]
32 pub struct Stacks {
33     // Even reading memory can have effects on the stack, so we need a `RefCell` here.
34     stacks: RangeMap<Stack>,
35     /// Stores past operations on this allocation
36     history: AllocHistory,
37     /// The set of tags that have been exposed inside this allocation.
38     exposed_tags: FxHashSet<BorTag>,
39     /// Whether this memory has been modified since the last time the tag GC ran
40     modified_since_last_gc: bool,
41 }
42
43 /// Indicates which kind of reference is being created.
44 /// Used by high-level `reborrow` to compute which permissions to grant to the
45 /// new pointer.
46 #[derive(Copy, Clone, Hash, PartialEq, Eq)]
47 enum RefKind {
48     /// `Box`.
49     Box,
50     /// `&mut`.
51     Unique { two_phase: bool },
52     /// `&` with or without interior mutability.
53     Shared,
54     /// `*mut`/`*const` (raw pointers).
55     Raw { mutable: bool },
56 }
57
58 impl fmt::Display for RefKind {
59     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60         match self {
61             RefKind::Box => write!(f, "Box"),
62             RefKind::Unique { two_phase: false } => write!(f, "unique reference"),
63             RefKind::Unique { two_phase: true } => write!(f, "unique reference (two-phase)"),
64             RefKind::Shared => write!(f, "shared reference"),
65             RefKind::Raw { mutable: true } => write!(f, "raw (mutable) pointer"),
66             RefKind::Raw { mutable: false } => write!(f, "raw (constant) pointer"),
67         }
68     }
69 }
70
71 /// Error reporting
72 pub fn err_sb_ub<'tcx>(
73     msg: String,
74     help: Option<String>,
75     history: Option<TagHistory>,
76 ) -> InterpError<'tcx> {
77     err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history })
78 }
79
80 // # Stacked Borrows Core Begin
81
82 /// We need to make at least the following things true:
83 ///
84 /// U1: After creating a `Uniq`, it is at the top.
85 /// U2: If the top is `Uniq`, accesses must be through that `Uniq` or remove it.
86 /// U3: If an access happens with a `Uniq`, it requires the `Uniq` to be in the stack.
87 ///
88 /// F1: After creating a `&`, the parts outside `UnsafeCell` have our `SharedReadOnly` on top.
89 /// F2: If a write access happens, it pops the `SharedReadOnly`.  This has three pieces:
90 ///     F2a: If a write happens granted by an item below our `SharedReadOnly`, the `SharedReadOnly`
91 ///          gets popped.
92 ///     F2b: No `SharedReadWrite` or `Unique` will ever be added on top of our `SharedReadOnly`.
93 /// F3: If an access happens with an `&` outside `UnsafeCell`,
94 ///     it requires the `SharedReadOnly` to still be in the stack.
95
96 /// Core relation on `Permission` to define which accesses are allowed
97 impl Permission {
98     /// This defines for a given permission, whether it permits the given kind of access.
99     fn grants(self, access: AccessKind) -> bool {
100         // Disabled grants nothing. Otherwise, all items grant read access, and except for SharedReadOnly they grant write access.
101         self != Permission::Disabled
102             && (access == AccessKind::Read || self != Permission::SharedReadOnly)
103     }
104 }
105
106 /// Determines whether an item was invalidated by a conflicting access, or by deallocation.
107 #[derive(Copy, Clone, Debug)]
108 enum ItemInvalidationCause {
109     Conflict,
110     Dealloc,
111 }
112
113 /// Core per-location operations: access, dealloc, reborrow.
114 impl<'tcx> Stack {
115     /// Find the first write-incompatible item above the given one --
116     /// i.e, find the height to which the stack will be truncated when writing to `granting`.
117     fn find_first_write_incompatible(&self, granting: usize) -> usize {
118         let perm = self.get(granting).unwrap().perm();
119         match perm {
120             Permission::SharedReadOnly => bug!("Cannot use SharedReadOnly for writing"),
121             Permission::Disabled => bug!("Cannot use Disabled for anything"),
122             Permission::Unique => {
123                 // On a write, everything above us is incompatible.
124                 granting + 1
125             }
126             Permission::SharedReadWrite => {
127                 // The SharedReadWrite *just* above us are compatible, to skip those.
128                 let mut idx = granting + 1;
129                 while let Some(item) = self.get(idx) {
130                     if item.perm() == Permission::SharedReadWrite {
131                         // Go on.
132                         idx += 1;
133                     } else {
134                         // Found first incompatible!
135                         break;
136                     }
137                 }
138                 idx
139             }
140         }
141     }
142
143     /// The given item was invalidated -- check its protectors for whether that will cause UB.
144     fn item_invalidated(
145         item: &Item,
146         global: &GlobalStateInner,
147         dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>,
148         cause: ItemInvalidationCause,
149     ) -> InterpResult<'tcx> {
150         if !global.tracked_pointer_tags.is_empty() {
151             dcx.check_tracked_tag_popped(item, global);
152         }
153
154         if !item.protected() {
155             return Ok(());
156         }
157
158         // We store tags twice, once in global.protected_tags and once in each call frame.
159         // We do this because consulting a single global set in this function is faster
160         // than attempting to search all call frames in the program for the `FrameExtra`
161         // (if any) which is protecting the popped tag.
162         //
163         // This duplication trades off making `end_call` slower to make this function faster. This
164         // trade-off is profitable in practice for a combination of two reasons.
165         // 1. A single protected tag can (and does in some programs) protect thousands of `Item`s.
166         //    Therefore, adding overhead in function call/return is profitable even if it only
167         //    saves a little work in this function.
168         // 2. Most frames protect only one or two tags. So this duplicative global turns a search
169         //    which ends up about linear in the number of protected tags in the program into a
170         //    constant time check (and a slow linear, because the tags in the frames aren't contiguous).
171         if let Some(&protector_kind) = global.protected_tags.get(&item.tag()) {
172             // The only way this is okay is if the protector is weak and we are deallocating with
173             // the right pointer.
174             let allowed = matches!(cause, ItemInvalidationCause::Dealloc)
175                 && matches!(protector_kind, ProtectorKind::WeakProtector);
176             if !allowed {
177                 return Err(dcx.protector_error(item, protector_kind).into());
178             }
179         }
180         Ok(())
181     }
182
183     /// Test if a memory `access` using pointer tagged `tag` is granted.
184     /// If yes, return the index of the item that granted it.
185     /// `range` refers the entire operation, and `offset` refers to the specific offset into the
186     /// allocation that we are currently checking.
187     fn access(
188         &mut self,
189         access: AccessKind,
190         tag: ProvenanceExtra,
191         global: &GlobalStateInner,
192         dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>,
193         exposed_tags: &FxHashSet<BorTag>,
194     ) -> InterpResult<'tcx> {
195         // Two main steps: Find granting item, remove incompatible items above.
196
197         // Step 1: Find granting item.
198         let granting_idx =
199             self.find_granting(access, tag, exposed_tags).map_err(|()| dcx.access_error(self))?;
200
201         // Step 2: Remove incompatible items above them.  Make sure we do not remove protected
202         // items.  Behavior differs for reads and writes.
203         // In case of wildcards/unknown matches, we remove everything that is *definitely* gone.
204         if access == AccessKind::Write {
205             // Remove everything above the write-compatible items, like a proper stack. This makes sure read-only and unique
206             // pointers become invalid on write accesses (ensures F2a, and ensures U2 for write accesses).
207             let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
208                 // The granting_idx *might* be approximate, but any lower idx would remove more
209                 // things. Even if this is a Unique and the lower idx is an SRW (which removes
210                 // less), there is an SRW group boundary here so strictly more would get removed.
211                 self.find_first_write_incompatible(granting_idx)
212             } else {
213                 // We are writing to something in the unknown part.
214                 // There is a SRW group boundary between the unknown and the known, so everything is incompatible.
215                 0
216             };
217             self.pop_items_after(first_incompatible_idx, |item| {
218                 Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Conflict)?;
219                 dcx.log_invalidation(item.tag());
220                 Ok(())
221             })?;
222         } else {
223             // On a read, *disable* all `Unique` above the granting item.  This ensures U2 for read accesses.
224             // The reason this is not following the stack discipline (by removing the first Unique and
225             // everything on top of it) is that in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement
226             // would pop the `Unique` from the reborrow of the first statement, and subsequently also pop the
227             // `SharedReadWrite` for `raw`.
228             // This pattern occurs a lot in the standard library: create a raw pointer, then also create a shared
229             // reference and use that.
230             // We *disable* instead of removing `Unique` to avoid "connecting" two neighbouring blocks of SRWs.
231             let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
232                 // The granting_idx *might* be approximate, but any lower idx would disable more things.
233                 granting_idx + 1
234             } else {
235                 // We are reading from something in the unknown part. That means *all* `Unique` we know about are dead now.
236                 0
237             };
238             self.disable_uniques_starting_at(first_incompatible_idx, |item| {
239                 Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Conflict)?;
240                 dcx.log_invalidation(item.tag());
241                 Ok(())
242             })?;
243         }
244
245         // If this was an approximate action, we now collapse everything into an unknown.
246         if granting_idx.is_none() || matches!(tag, ProvenanceExtra::Wildcard) {
247             // Compute the upper bound of the items that remain.
248             // (This is why we did all the work above: to reduce the items we have to consider here.)
249             let mut max = BorTag::one();
250             for i in 0..self.len() {
251                 let item = self.get(i).unwrap();
252                 // Skip disabled items, they cannot be matched anyway.
253                 if !matches!(item.perm(), Permission::Disabled) {
254                     // We are looking for a strict upper bound, so add 1 to this tag.
255                     max = cmp::max(item.tag().succ().unwrap(), max);
256                 }
257             }
258             if let Some(unk) = self.unknown_bottom() {
259                 max = cmp::max(unk, max);
260             }
261             // Use `max` as new strict upper bound for everything.
262             trace!(
263                 "access: forgetting stack to upper bound {max} due to wildcard or unknown access",
264                 max = max.get(),
265             );
266             self.set_unknown_bottom(max);
267         }
268
269         // Done.
270         Ok(())
271     }
272
273     /// Deallocate a location: Like a write access, but also there must be no
274     /// active protectors at all because we will remove all items.
275     fn dealloc(
276         &mut self,
277         tag: ProvenanceExtra,
278         global: &GlobalStateInner,
279         dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>,
280         exposed_tags: &FxHashSet<BorTag>,
281     ) -> InterpResult<'tcx> {
282         // Step 1: Make a write access.
283         // As part of this we do regular protector checking, i.e. even weakly protected items cause UB when popped.
284         self.access(AccessKind::Write, tag, global, dcx, exposed_tags)?;
285
286         // Step 2: Pretend we remove the remaining items, checking if any are strongly protected.
287         for idx in (0..self.len()).rev() {
288             let item = self.get(idx).unwrap();
289             Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Dealloc)?;
290         }
291
292         Ok(())
293     }
294
295     /// Derive a new pointer from one with the given tag.
296     ///
297     /// `access` indicates which kind of memory access this retag itself should correspond to.
298     fn grant(
299         &mut self,
300         derived_from: ProvenanceExtra,
301         new: Item,
302         access: Option<AccessKind>,
303         global: &GlobalStateInner,
304         dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>,
305         exposed_tags: &FxHashSet<BorTag>,
306     ) -> InterpResult<'tcx> {
307         dcx.start_grant(new.perm());
308
309         // Compute where to put the new item.
310         // Either way, we ensure that we insert the new item in a way such that between
311         // `derived_from` and the new one, there are only items *compatible with* `derived_from`.
312         let new_idx = if let Some(access) = access {
313             // Simple case: We are just a regular memory access, and then push our thing on top,
314             // like a regular stack.
315             // This ensures F2b for `Unique`, by removing offending `SharedReadOnly`.
316             self.access(access, derived_from, global, dcx, exposed_tags)?;
317
318             // We insert "as far up as possible": We know only compatible items are remaining
319             // on top of `derived_from`, and we want the new item at the top so that we
320             // get the strongest possible guarantees.
321             // This ensures U1 and F1.
322             self.len()
323         } else {
324             // The tricky case: creating a new SRW permission without actually being an access.
325             assert!(new.perm() == Permission::SharedReadWrite);
326
327             // First we figure out which item grants our parent (`derived_from`) this kind of access.
328             // We use that to determine where to put the new item.
329             let granting_idx = self
330                 .find_granting(AccessKind::Write, derived_from, exposed_tags)
331                 .map_err(|()| dcx.grant_error(self))?;
332
333             let (Some(granting_idx), ProvenanceExtra::Concrete(_)) = (granting_idx, derived_from) else {
334                 // The parent is a wildcard pointer or matched the unknown bottom.
335                 // This is approximate. Nobody knows what happened, so forget everything.
336                 // The new thing is SRW anyway, so we cannot push it "on top of the unkown part"
337                 // (for all we know, it might join an SRW group inside the unknown).
338                 trace!("reborrow: forgetting stack entirely due to SharedReadWrite reborrow from wildcard or unknown");
339                 self.set_unknown_bottom(global.next_ptr_tag);
340                 return Ok(());
341             };
342
343             // SharedReadWrite can coexist with "existing loans", meaning they don't act like a write
344             // access.  Instead of popping the stack, we insert the item at the place the stack would
345             // be popped to (i.e., we insert it above all the write-compatible items).
346             // This ensures F2b by adding the new item below any potentially existing `SharedReadOnly`.
347             self.find_first_write_incompatible(granting_idx)
348         };
349
350         // Put the new item there.
351         trace!("reborrow: adding item {:?}", new);
352         self.insert(new_idx, new);
353         Ok(())
354     }
355 }
356 // # Stacked Borrows Core End
357
358 /// Integration with the BorTag garbage collector
359 impl Stacks {
360     pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
361         if self.modified_since_last_gc {
362             for stack in self.stacks.iter_mut_all() {
363                 if stack.len() > 64 {
364                     stack.retain(live_tags);
365                 }
366             }
367             self.modified_since_last_gc = false;
368         }
369     }
370 }
371
372 impl VisitTags for Stacks {
373     fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
374         for tag in self.exposed_tags.iter().copied() {
375             visit(tag);
376         }
377     }
378 }
379
380 /// Map per-stack operations to higher-level per-location-range operations.
381 impl<'tcx> Stacks {
382     /// Creates a new stack with an initial tag. For diagnostic purposes, we also need to know
383     /// the [`AllocId`] of the allocation this is associated with.
384     fn new(
385         size: Size,
386         perm: Permission,
387         tag: BorTag,
388         id: AllocId,
389         machine: &MiriMachine<'_, '_>,
390     ) -> Self {
391         let item = Item::new(tag, perm, false);
392         let stack = Stack::new(item);
393
394         Stacks {
395             stacks: RangeMap::new(size, stack),
396             history: AllocHistory::new(id, item, machine),
397             exposed_tags: FxHashSet::default(),
398             modified_since_last_gc: false,
399         }
400     }
401
402     /// Call `f` on every stack in the range.
403     fn for_each(
404         &mut self,
405         range: AllocRange,
406         mut dcx_builder: DiagnosticCxBuilder<'_, '_, 'tcx>,
407         mut f: impl FnMut(
408             &mut Stack,
409             &mut DiagnosticCx<'_, '_, '_, 'tcx>,
410             &mut FxHashSet<BorTag>,
411         ) -> InterpResult<'tcx>,
412     ) -> InterpResult<'tcx> {
413         self.modified_since_last_gc = true;
414         for (offset, stack) in self.stacks.iter_mut(range.start, range.size) {
415             let mut dcx = dcx_builder.build(&mut self.history, offset);
416             f(stack, &mut dcx, &mut self.exposed_tags)?;
417             dcx_builder = dcx.unbuild();
418         }
419         Ok(())
420     }
421 }
422
423 /// Glue code to connect with Miri Machine Hooks
424 impl Stacks {
425     pub fn new_allocation(
426         id: AllocId,
427         size: Size,
428         state: &mut GlobalStateInner,
429         kind: MemoryKind<MiriMemoryKind>,
430         machine: &MiriMachine<'_, '_>,
431     ) -> Self {
432         let (base_tag, perm) = match kind {
433             // New unique borrow. This tag is not accessible by the program,
434             // so it will only ever be used when using the local directly (i.e.,
435             // not through a pointer). That is, whenever we directly write to a local, this will pop
436             // everything else off the stack, invalidating all previous pointers,
437             // and in particular, *all* raw pointers.
438             MemoryKind::Stack => (state.base_ptr_tag(id, machine), Permission::Unique),
439             // Everything else is shared by default.
440             _ => (state.base_ptr_tag(id, machine), Permission::SharedReadWrite),
441         };
442         Stacks::new(size, perm, base_tag, id, machine)
443     }
444
445     #[inline(always)]
446     pub fn before_memory_read<'tcx, 'mir, 'ecx>(
447         &mut self,
448         alloc_id: AllocId,
449         tag: ProvenanceExtra,
450         range: AllocRange,
451         machine: &'ecx MiriMachine<'mir, 'tcx>,
452     ) -> InterpResult<'tcx>
453     where
454         'tcx: 'ecx,
455     {
456         trace!(
457             "read access with tag {:?}: {:?}, size {}",
458             tag,
459             Pointer::new(alloc_id, range.start),
460             range.size.bytes()
461         );
462         let dcx = DiagnosticCxBuilder::read(machine, tag, range);
463         let state = machine.borrow_tracker.as_ref().unwrap().borrow();
464         self.for_each(range, dcx, |stack, dcx, exposed_tags| {
465             stack.access(AccessKind::Read, tag, &state, dcx, exposed_tags)
466         })
467     }
468
469     #[inline(always)]
470     pub fn before_memory_write<'tcx>(
471         &mut self,
472         alloc_id: AllocId,
473         tag: ProvenanceExtra,
474         range: AllocRange,
475         machine: &mut MiriMachine<'_, 'tcx>,
476     ) -> InterpResult<'tcx> {
477         trace!(
478             "write access with tag {:?}: {:?}, size {}",
479             tag,
480             Pointer::new(alloc_id, range.start),
481             range.size.bytes()
482         );
483         let dcx = DiagnosticCxBuilder::write(machine, tag, range);
484         let state = machine.borrow_tracker.as_ref().unwrap().borrow();
485         self.for_each(range, dcx, |stack, dcx, exposed_tags| {
486             stack.access(AccessKind::Write, tag, &state, dcx, exposed_tags)
487         })
488     }
489
490     #[inline(always)]
491     pub fn before_memory_deallocation<'tcx>(
492         &mut self,
493         alloc_id: AllocId,
494         tag: ProvenanceExtra,
495         range: AllocRange,
496         machine: &mut MiriMachine<'_, 'tcx>,
497     ) -> InterpResult<'tcx> {
498         trace!("deallocation with tag {:?}: {:?}, size {}", tag, alloc_id, range.size.bytes());
499         let dcx = DiagnosticCxBuilder::dealloc(machine, tag);
500         let state = machine.borrow_tracker.as_ref().unwrap().borrow();
501         self.for_each(range, dcx, |stack, dcx, exposed_tags| {
502             stack.dealloc(tag, &state, dcx, exposed_tags)
503         })?;
504         Ok(())
505     }
506 }
507
508 /// Retagging/reborrowing.  There is some policy in here, such as which permissions
509 /// to grant for which references, and when to add protectors.
510 impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx>
511     for crate::MiriInterpCx<'mir, 'tcx>
512 {
513 }
514 trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> {
515     /// Returns the `AllocId` the reborrow was done in, if some actual borrow stack manipulation
516     /// happened.
517     fn sb_reborrow(
518         &mut self,
519         place: &MPlaceTy<'tcx, Provenance>,
520         size: Size,
521         kind: RefKind,
522         retag_cause: RetagCause, // What caused this retag, for diagnostics only
523         new_tag: BorTag,
524         protect: Option<ProtectorKind>,
525     ) -> InterpResult<'tcx, Option<AllocId>> {
526         let this = self.eval_context_mut();
527
528         // It is crucial that this gets called on all code paths, to ensure we track tag creation.
529         let log_creation = |this: &MiriInterpCx<'mir, 'tcx>,
530                             loc: Option<(AllocId, Size, ProvenanceExtra)>| // alloc_id, base_offset, orig_tag
531          -> InterpResult<'tcx> {
532             let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
533             let ty = place.layout.ty;
534             if global.tracked_pointer_tags.contains(&new_tag) {
535                 let mut kind_str = format!("{kind}");
536                 match kind {
537                     RefKind::Unique { two_phase: false }
538                         if !ty.is_unpin(*this.tcx, this.param_env()) =>
539                     {
540                         write!(kind_str, " (!Unpin pointee type {ty})").unwrap()
541                     },
542                     RefKind::Shared
543                         if !ty.is_freeze(*this.tcx, this.param_env()) =>
544                     {
545                         write!(kind_str, " (!Freeze pointee type {ty})").unwrap()
546                     },
547                     _ => write!(kind_str, " (pointee type {ty})").unwrap(),
548                 };
549                 this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
550                     new_tag.inner(),
551                     Some(kind_str),
552                     loc.map(|(alloc_id, base_offset, orig_tag)| (alloc_id, alloc_range(base_offset, size), orig_tag)),
553                 ));
554             }
555             drop(global); // don't hold that reference any longer than we have to
556
557             let Some((alloc_id, base_offset, orig_tag)) = loc else {
558                 return Ok(())
559             };
560
561             let (_size, _align, alloc_kind) = this.get_alloc_info(alloc_id);
562             match alloc_kind {
563                 AllocKind::LiveData => {
564                     // This should have alloc_extra data, but `get_alloc_extra` can still fail
565                     // if converting this alloc_id from a global to a local one
566                     // uncovers a non-supported `extern static`.
567                     let extra = this.get_alloc_extra(alloc_id)?;
568                     let mut stacked_borrows = extra
569                         .borrow_tracker_sb()
570                         .borrow_mut();
571                     // Note that we create a *second* `DiagnosticCxBuilder` below for the actual retag.
572                     // FIXME: can this be done cleaner?
573                     let dcx = DiagnosticCxBuilder::retag(
574                         &this.machine,
575                         retag_cause,
576                         new_tag,
577                         orig_tag,
578                         alloc_range(base_offset, size),
579                     );
580                     let mut dcx = dcx.build(&mut stacked_borrows.history, base_offset);
581                     dcx.log_creation();
582                     if protect.is_some() {
583                         dcx.log_protector();
584                     }
585                 },
586                 AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
587                     // No stacked borrows on these allocations.
588                 }
589             }
590             Ok(())
591         };
592
593         if size == Size::ZERO {
594             trace!(
595                 "reborrow of size 0: {} reference {:?} derived from {:?} (pointee {})",
596                 kind,
597                 new_tag,
598                 place.ptr,
599                 place.layout.ty,
600             );
601             // Don't update any stacks for a zero-sized access; borrow stacks are per-byte and this
602             // touches no bytes so there is no stack to put this tag in.
603             // However, if the pointer for this operation points at a real allocation we still
604             // record where it was created so that we can issue a helpful diagnostic if there is an
605             // attempt to use it for a non-zero-sized access.
606             // Dangling slices are a common case here; it's valid to get their length but with raw
607             // pointer tagging for example all calls to get_unchecked on them are invalid.
608             if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr) {
609                 log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
610                 return Ok(Some(alloc_id));
611             }
612             // This pointer doesn't come with an AllocId. :shrug:
613             log_creation(this, None)?;
614             return Ok(None);
615         }
616
617         let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?;
618         log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
619
620         // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
621         let (alloc_size, _) = this.get_live_alloc_size_and_align(alloc_id)?;
622         if base_offset + size > alloc_size {
623             throw_ub!(PointerOutOfBounds {
624                 alloc_id,
625                 alloc_size,
626                 ptr_offset: this.machine_usize_to_isize(base_offset.bytes()),
627                 ptr_size: size,
628                 msg: CheckInAllocMsg::InboundsTest
629             });
630         }
631
632         trace!(
633             "reborrow: {} reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
634             kind,
635             new_tag,
636             orig_tag,
637             place.layout.ty,
638             Pointer::new(alloc_id, base_offset),
639             size.bytes()
640         );
641
642         if let Some(protect) = protect {
643             // See comment in `Stack::item_invalidated` for why we store the tag twice.
644             this.frame_mut().extra.borrow_tracker.as_mut().unwrap().protected_tags.push(new_tag);
645             this.machine
646                 .borrow_tracker
647                 .as_mut()
648                 .unwrap()
649                 .get_mut()
650                 .protected_tags
651                 .insert(new_tag, protect);
652         }
653
654         // Update the stacks.
655         // Make sure that raw pointers and mutable shared references are reborrowed "weak":
656         // There could be existing unique pointers reborrowed from them that should remain valid!
657         let (perm, access) = match kind {
658             RefKind::Unique { two_phase } => {
659                 // Permission is Unique only if the type is `Unpin` and this is not twophase
660                 if !two_phase && place.layout.ty.is_unpin(*this.tcx, this.param_env()) {
661                     (Permission::Unique, Some(AccessKind::Write))
662                 } else {
663                     // FIXME: We emit `dereferenceable` for `!Unpin` mutable references, so we
664                     // should do fake accesses here. But then we run into
665                     // <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>, so for now
666                     // we don't do that.
667                     (Permission::SharedReadWrite, None)
668                 }
669             }
670             RefKind::Box => (Permission::Unique, Some(AccessKind::Write)),
671             RefKind::Raw { mutable: true } => {
672                 // Creating a raw ptr does not count as an access
673                 (Permission::SharedReadWrite, None)
674             }
675             RefKind::Shared | RefKind::Raw { mutable: false } => {
676                 // Shared references and *const are a whole different kind of game, the
677                 // permission is not uniform across the entire range!
678                 // We need a frozen-sensitive reborrow.
679                 // We have to use shared references to alloc/memory_extra here since
680                 // `visit_freeze_sensitive` needs to access the global state.
681                 let alloc_extra = this.get_alloc_extra(alloc_id)?;
682                 let mut stacked_borrows = alloc_extra.borrow_tracker_sb().borrow_mut();
683                 this.visit_freeze_sensitive(place, size, |mut range, frozen| {
684                     // Adjust range.
685                     range.start += base_offset;
686                     // We are only ever `SharedReadOnly` inside the frozen bits.
687                     let (perm, access) = if frozen {
688                         (Permission::SharedReadOnly, Some(AccessKind::Read))
689                     } else {
690                         // Inside UnsafeCell, this does *not* count as an access, as there
691                         // might actually be mutable references further up the stack that
692                         // we have to keep alive.
693                         (Permission::SharedReadWrite, None)
694                     };
695                     let protected = if frozen {
696                         protect.is_some()
697                     } else {
698                         // We do not protect inside UnsafeCell.
699                         // This fixes https://github.com/rust-lang/rust/issues/55005.
700                         false
701                     };
702                     let item = Item::new(new_tag, perm, protected);
703                     let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
704                     let dcx = DiagnosticCxBuilder::retag(
705                         &this.machine,
706                         retag_cause,
707                         new_tag,
708                         orig_tag,
709                         alloc_range(base_offset, size),
710                     );
711                     stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| {
712                         stack.grant(orig_tag, item, access, &global, dcx, exposed_tags)
713                     })?;
714                     drop(global);
715                     if let Some(access) = access {
716                         assert_eq!(access, AccessKind::Read);
717                         // Make sure the data race model also knows about this.
718                         if let Some(data_race) = alloc_extra.data_race.as_ref() {
719                             data_race.read(alloc_id, range, &this.machine)?;
720                         }
721                     }
722                     Ok(())
723                 })?;
724                 return Ok(Some(alloc_id));
725             }
726         };
727
728         // Here we can avoid `borrow()` calls because we have mutable references.
729         // Note that this asserts that the allocation is mutable -- but since we are creating a
730         // mutable pointer, that seems reasonable.
731         let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?;
732         let stacked_borrows = alloc_extra.borrow_tracker_sb_mut().get_mut();
733         let item = Item::new(new_tag, perm, protect.is_some());
734         let range = alloc_range(base_offset, size);
735         let global = machine.borrow_tracker.as_ref().unwrap().borrow();
736         let dcx = DiagnosticCxBuilder::retag(
737             machine,
738             retag_cause,
739             new_tag,
740             orig_tag,
741             alloc_range(base_offset, size),
742         );
743         stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| {
744             stack.grant(orig_tag, item, access, &global, dcx, exposed_tags)
745         })?;
746         drop(global);
747         if let Some(access) = access {
748             assert_eq!(access, AccessKind::Write);
749             // Make sure the data race model also knows about this.
750             if let Some(data_race) = alloc_extra.data_race.as_mut() {
751                 data_race.write(alloc_id, range, machine)?;
752             }
753         }
754
755         Ok(Some(alloc_id))
756     }
757
758     /// Retags an indidual pointer, returning the retagged version.
759     /// `kind` indicates what kind of reference is being created.
760     fn sb_retag_reference(
761         &mut self,
762         val: &ImmTy<'tcx, Provenance>,
763         kind: RefKind,
764         retag_cause: RetagCause, // What caused this retag, for diagnostics only
765         protect: Option<ProtectorKind>,
766     ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
767         let this = self.eval_context_mut();
768         // We want a place for where the ptr *points to*, so we get one.
769         let place = this.ref_to_mplace(val)?;
770         let size = this.size_and_align_of_mplace(&place)?.map(|(size, _)| size);
771         // FIXME: If we cannot determine the size (because the unsized tail is an `extern type`),
772         // bail out -- we cannot reasonably figure out which memory range to reborrow.
773         // See https://github.com/rust-lang/unsafe-code-guidelines/issues/276.
774         let size = match size {
775             Some(size) => size,
776             None => return Ok(val.clone()),
777         };
778
779         // Compute new borrow.
780         let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
781
782         // Reborrow.
783         let alloc_id = this.sb_reborrow(&place, size, kind, retag_cause, new_tag, protect)?;
784
785         // Adjust pointer.
786         let new_place = place.map_provenance(|p| {
787             p.map(|prov| {
788                 match alloc_id {
789                     Some(alloc_id) => {
790                         // If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one.
791                         // Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation.
792                         Provenance::Concrete { alloc_id, tag: new_tag }
793                     }
794                     None => {
795                         // Looks like this has to stay a wildcard pointer.
796                         assert!(matches!(prov, Provenance::Wildcard));
797                         Provenance::Wildcard
798                     }
799                 }
800             })
801         });
802
803         // Return new pointer.
804         Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
805     }
806 }
807
808 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
809 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
810     fn sb_retag(
811         &mut self,
812         kind: RetagKind,
813         place: &PlaceTy<'tcx, Provenance>,
814     ) -> InterpResult<'tcx> {
815         let this = self.eval_context_mut();
816         let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields;
817         let retag_cause = match kind {
818             RetagKind::TwoPhase { .. } => RetagCause::TwoPhase,
819             RetagKind::FnEntry => RetagCause::FnEntry,
820             RetagKind::Raw | RetagKind::Default => RetagCause::Normal,
821         };
822         let mut visitor = RetagVisitor { ecx: this, kind, retag_cause, retag_fields };
823         return visitor.visit_value(place);
824
825         // The actual visitor.
826         struct RetagVisitor<'ecx, 'mir, 'tcx> {
827             ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
828             kind: RetagKind,
829             retag_cause: RetagCause,
830             retag_fields: RetagFields,
831         }
832         impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
833             #[inline(always)] // yes this helps in our benchmarks
834             fn retag_place(
835                 &mut self,
836                 place: &PlaceTy<'tcx, Provenance>,
837                 ref_kind: RefKind,
838                 retag_cause: RetagCause,
839                 protector: Option<ProtectorKind>,
840             ) -> InterpResult<'tcx> {
841                 let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
842                 let val = self.ecx.sb_retag_reference(&val, ref_kind, retag_cause, protector)?;
843                 self.ecx.write_immediate(*val, place)?;
844                 Ok(())
845             }
846         }
847         impl<'ecx, 'mir, 'tcx> MutValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>>
848             for RetagVisitor<'ecx, 'mir, 'tcx>
849         {
850             type V = PlaceTy<'tcx, Provenance>;
851
852             #[inline(always)]
853             fn ecx(&mut self) -> &mut MiriInterpCx<'mir, 'tcx> {
854                 self.ecx
855             }
856
857             fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
858                 // Boxes get a weak protectors, since they may be deallocated.
859                 self.retag_place(
860                     place,
861                     RefKind::Box,
862                     self.retag_cause,
863                     /*protector*/
864                     (self.kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector),
865                 )
866             }
867
868             fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
869                 // If this place is smaller than a pointer, we know that it can't contain any
870                 // pointers we need to retag, so we can stop recursion early.
871                 // This optimization is crucial for ZSTs, because they can contain way more fields
872                 // than we can ever visit.
873                 if place.layout.is_sized() && place.layout.size < self.ecx.pointer_size() {
874                     return Ok(());
875                 }
876
877                 // Check the type of this value to see what to do with it (retag, or recurse).
878                 match place.layout.ty.kind() {
879                     ty::Ref(_, _, mutbl) => {
880                         let ref_kind = match mutbl {
881                             Mutability::Mut =>
882                                 RefKind::Unique { two_phase: self.kind == RetagKind::TwoPhase },
883                             Mutability::Not => RefKind::Shared,
884                         };
885                         self.retag_place(
886                             place,
887                             ref_kind,
888                             self.retag_cause,
889                             /*protector*/
890                             (self.kind == RetagKind::FnEntry)
891                                 .then_some(ProtectorKind::StrongProtector),
892                         )?;
893                     }
894                     ty::RawPtr(tym) => {
895                         // We definitely do *not* want to recurse into raw pointers -- wide raw
896                         // pointers have fields, and for dyn Trait pointees those can have reference
897                         // type!
898                         if self.kind == RetagKind::Raw {
899                             // Raw pointers need to be enabled.
900                             self.retag_place(
901                                 place,
902                                 RefKind::Raw { mutable: tym.mutbl == Mutability::Mut },
903                                 self.retag_cause,
904                                 /*protector*/ None,
905                             )?;
906                         }
907                     }
908                     _ if place.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_box()) => {
909                         // Recurse for boxes, they require some tricky handling and will end up in `visit_box` above.
910                         // (Yes this means we technically also recursively retag the allocator itself
911                         // even if field retagging is not enabled. *shrug*)
912                         self.walk_value(place)?;
913                     }
914                     _ => {
915                         // Not a reference/pointer/box. Only recurse if configured appropriately.
916                         let recurse = match self.retag_fields {
917                             RetagFields::No => false,
918                             RetagFields::Yes => true,
919                             RetagFields::OnlyScalar => {
920                                 // Matching `ArgAbi::new` at the time of writing, only fields of
921                                 // `Scalar` and `ScalarPair` ABI are considered.
922                                 matches!(place.layout.abi, Abi::Scalar(..) | Abi::ScalarPair(..))
923                             }
924                         };
925                         if recurse {
926                             self.walk_value(place)?;
927                         }
928                     }
929                 }
930
931                 Ok(())
932             }
933         }
934     }
935
936     /// After a stack frame got pushed, retag the return place so that we are sure
937     /// it does not alias with anything.
938     ///
939     /// This is a HACK because there is nothing in MIR that would make the retag
940     /// explicit. Also see <https://github.com/rust-lang/rust/issues/71117>.
941     fn sb_retag_return_place(&mut self) -> InterpResult<'tcx> {
942         let this = self.eval_context_mut();
943         let return_place = &this.frame().return_place;
944         if return_place.layout.is_zst() {
945             // There may not be any memory here, nothing to do.
946             return Ok(());
947         }
948         // We need this to be in-memory to use tagged pointers.
949         let return_place = this.force_allocation(&return_place.clone())?;
950
951         // We have to turn the place into a pointer to use the existing code.
952         // (The pointer type does not matter, so we use a raw pointer.)
953         let ptr_layout = this.layout_of(this.tcx.mk_mut_ptr(return_place.layout.ty))?;
954         let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout);
955         // Reborrow it. With protection! That is part of the point.
956         let val = this.sb_retag_reference(
957             &val,
958             RefKind::Unique { two_phase: false },
959             RetagCause::FnReturn,
960             /*protector*/ Some(ProtectorKind::StrongProtector),
961         )?;
962         // And use reborrowed pointer for return place.
963         let return_place = this.ref_to_mplace(&val)?;
964         this.frame_mut().return_place = return_place.into();
965
966         Ok(())
967     }
968
969     /// Mark the given tag as exposed. It was found on a pointer with the given AllocId.
970     fn sb_expose_tag(&mut self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
971         let this = self.eval_context_mut();
972
973         // Function pointers and dead objects don't have an alloc_extra so we ignore them.
974         // This is okay because accessing them is UB anyway, no need for any Stacked Borrows checks.
975         // NOT using `get_alloc_extra_mut` since this might be a read-only allocation!
976         let (_size, _align, kind) = this.get_alloc_info(alloc_id);
977         match kind {
978             AllocKind::LiveData => {
979                 // This should have alloc_extra data, but `get_alloc_extra` can still fail
980                 // if converting this alloc_id from a global to a local one
981                 // uncovers a non-supported `extern static`.
982                 let alloc_extra = this.get_alloc_extra(alloc_id)?;
983                 trace!("Stacked Borrows tag {tag:?} exposed in {alloc_id:?}");
984                 alloc_extra.borrow_tracker_sb().borrow_mut().exposed_tags.insert(tag);
985             }
986             AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
987                 // No stacked borrows on these allocations.
988             }
989         }
990         Ok(())
991     }
992
993     fn print_stacks(&mut self, alloc_id: AllocId) -> InterpResult<'tcx> {
994         let this = self.eval_context_mut();
995         let alloc_extra = this.get_alloc_extra(alloc_id)?;
996         let stacks = alloc_extra.borrow_tracker_sb().borrow();
997         for (range, stack) in stacks.stacks.iter_all() {
998             print!("{range:?}: [");
999             if let Some(bottom) = stack.unknown_bottom() {
1000                 print!(" unknown-bottom(..{bottom:?})");
1001             }
1002             for i in 0..stack.len() {
1003                 let item = stack.get(i).unwrap();
1004                 print!(" {:?}{:?}", item.perm(), item.tag());
1005             }
1006             println!(" ]");
1007         }
1008         Ok(())
1009     }
1010 }