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