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