miri/borrow_tracker/tree_borrows/
mod.rs

1use rustc_abi::{BackendRepr, Size};
2use rustc_middle::mir::{Mutability, RetagKind};
3use rustc_middle::ty::layout::HasTypingEnv;
4use rustc_middle::ty::{self, Ty};
5
6use self::foreign_access_skipping::IdempotentForeignAccess;
7use self::tree::LocationState;
8use crate::borrow_tracker::{GlobalState, GlobalStateInner, ProtectorKind};
9use crate::concurrency::data_race::NaReadType;
10use crate::*;
11
12pub mod diagnostics;
13mod foreign_access_skipping;
14mod perms;
15mod tree;
16mod unimap;
17
18#[cfg(test)]
19mod exhaustive;
20
21use self::perms::Permission;
22pub use self::tree::Tree;
23
24pub type AllocState = Tree;
25
26impl<'tcx> Tree {
27    /// Create a new allocation, i.e. a new tree
28    pub fn new_allocation(
29        id: AllocId,
30        size: Size,
31        state: &mut GlobalStateInner,
32        _kind: MemoryKind,
33        machine: &MiriMachine<'tcx>,
34    ) -> Self {
35        let tag = state.root_ptr_tag(id, machine); // Fresh tag for the root
36        let span = machine.current_span();
37        Tree::new(tag, size, span)
38    }
39
40    /// Check that an access on the entire range is permitted, and update
41    /// the tree.
42    pub fn before_memory_access(
43        &mut self,
44        access_kind: AccessKind,
45        alloc_id: AllocId,
46        prov: ProvenanceExtra,
47        range: AllocRange,
48        machine: &MiriMachine<'tcx>,
49    ) -> InterpResult<'tcx> {
50        trace!(
51            "{} with tag {:?}: {:?}, size {}",
52            access_kind,
53            prov,
54            interpret::Pointer::new(alloc_id, range.start),
55            range.size.bytes(),
56        );
57        // TODO: for now we bail out on wildcard pointers. Eventually we should
58        // handle them as much as we can.
59        let tag = match prov {
60            ProvenanceExtra::Concrete(tag) => tag,
61            ProvenanceExtra::Wildcard => return interp_ok(()),
62        };
63        let global = machine.borrow_tracker.as_ref().unwrap();
64        let span = machine.current_span();
65        self.perform_access(
66            tag,
67            Some((range, access_kind, diagnostics::AccessCause::Explicit(access_kind))),
68            global,
69            alloc_id,
70            span,
71        )
72    }
73
74    /// Check that this pointer has permission to deallocate this range.
75    pub fn before_memory_deallocation(
76        &mut self,
77        alloc_id: AllocId,
78        prov: ProvenanceExtra,
79        size: Size,
80        machine: &MiriMachine<'tcx>,
81    ) -> InterpResult<'tcx> {
82        // TODO: for now we bail out on wildcard pointers. Eventually we should
83        // handle them as much as we can.
84        let tag = match prov {
85            ProvenanceExtra::Concrete(tag) => tag,
86            ProvenanceExtra::Wildcard => return interp_ok(()),
87        };
88        let global = machine.borrow_tracker.as_ref().unwrap();
89        let span = machine.current_span();
90        self.dealloc(tag, alloc_range(Size::ZERO, size), global, alloc_id, span)
91    }
92
93    pub fn expose_tag(&mut self, _tag: BorTag) {
94        // TODO
95    }
96
97    /// A tag just lost its protector.
98    ///
99    /// This emits a special kind of access that is only applied
100    /// to accessed locations, as a protection against other
101    /// tags not having been made aware of the existence of this
102    /// protector.
103    pub fn release_protector(
104        &mut self,
105        machine: &MiriMachine<'tcx>,
106        global: &GlobalState,
107        tag: BorTag,
108        alloc_id: AllocId, // diagnostics
109    ) -> InterpResult<'tcx> {
110        let span = machine.current_span();
111        // `None` makes it the magic on-protector-end operation
112        self.perform_access(tag, None, global, alloc_id, span)
113    }
114}
115
116/// Policy for a new borrow.
117#[derive(Debug, Clone, Copy)]
118pub struct NewPermission {
119    /// Permission for the frozen part of the range.
120    freeze_perm: Permission,
121    /// Whether a read access should be performed on the frozen part on a retag.
122    freeze_access: bool,
123    /// Permission for the non-frozen part of the range.
124    nonfreeze_perm: Permission,
125    /// Whether a read access should be performed on the non-frozen
126    /// part on a retag.
127    nonfreeze_access: bool,
128    /// Whether this pointer is part of the arguments of a function call.
129    /// `protector` is `Some(_)` for all pointers marked `noalias`.
130    protector: Option<ProtectorKind>,
131}
132
133impl<'tcx> NewPermission {
134    /// Determine NewPermission of the reference from the type of the pointee.
135    fn from_ref_ty(
136        pointee: Ty<'tcx>,
137        mutability: Mutability,
138        kind: RetagKind,
139        cx: &crate::MiriInterpCx<'tcx>,
140    ) -> Option<Self> {
141        let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env());
142        let is_protected = kind == RetagKind::FnEntry;
143        let protector = is_protected.then_some(ProtectorKind::StrongProtector);
144
145        Some(match mutability {
146            Mutability::Mut if ty_is_unpin =>
147                NewPermission {
148                    freeze_perm: Permission::new_reserved(
149                        /* ty_is_freeze */ true,
150                        is_protected,
151                    ),
152                    freeze_access: true,
153                    nonfreeze_perm: Permission::new_reserved(
154                        /* ty_is_freeze */ false,
155                        is_protected,
156                    ),
157                    // If we have a mutable reference, then the non-frozen part will
158                    // have state `ReservedIM` or `Reserved`, which can have an initial read access
159                    // performed on it because you cannot have multiple mutable borrows.
160                    nonfreeze_access: true,
161                    protector,
162                },
163            Mutability::Not =>
164                NewPermission {
165                    freeze_perm: Permission::new_frozen(),
166                    freeze_access: true,
167                    nonfreeze_perm: Permission::new_cell(),
168                    // If it is a shared reference, then the non-frozen
169                    // part will have state `Cell`, which should not have an initial access,
170                    // as this can cause data races when using thread-safe data types like
171                    // `Mutex<T>`.
172                    nonfreeze_access: false,
173                    protector,
174                },
175            _ => return None,
176        })
177    }
178
179    /// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
180    /// These pointers allow deallocation so need a different kind of protector not handled
181    /// by `from_ref_ty`.
182    fn from_unique_ty(
183        ty: Ty<'tcx>,
184        kind: RetagKind,
185        cx: &crate::MiriInterpCx<'tcx>,
186    ) -> Option<Self> {
187        let pointee = ty.builtin_deref(true).unwrap();
188        pointee.is_unpin(*cx.tcx, cx.typing_env()).then_some(()).map(|()| {
189            // Regular `Unpin` box, give it `noalias` but only a weak protector
190            // because it is valid to deallocate it within the function.
191            let is_protected = kind == RetagKind::FnEntry;
192            let protector = is_protected.then_some(ProtectorKind::WeakProtector);
193            NewPermission {
194                freeze_perm: Permission::new_reserved(/* ty_is_freeze */ true, is_protected),
195                freeze_access: true,
196                nonfreeze_perm: Permission::new_reserved(
197                    /* ty_is_freeze */ false,
198                    is_protected,
199                ),
200                nonfreeze_access: true,
201                protector,
202            }
203        })
204    }
205}
206
207/// Retagging/reborrowing.
208/// Policy on which permission to grant to each pointer should be left to
209/// the implementation of NewPermission.
210impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
211trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
212    /// Returns the provenance that should be used henceforth.
213    fn tb_reborrow(
214        &mut self,
215        place: &MPlaceTy<'tcx>, // parent tag extracted from here
216        ptr_size: Size,
217        new_perm: NewPermission,
218        new_tag: BorTag,
219    ) -> InterpResult<'tcx, Option<Provenance>> {
220        let this = self.eval_context_mut();
221        // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
222        this.check_ptr_access(place.ptr(), ptr_size, CheckInAllocMsg::Dereferenceable)?;
223
224        // It is crucial that this gets called on all code paths, to ensure we track tag creation.
225        let log_creation = |this: &MiriInterpCx<'tcx>,
226                            loc: Option<(AllocId, Size, ProvenanceExtra)>| // alloc_id, base_offset, orig_tag
227         -> InterpResult<'tcx> {
228            let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
229            let ty = place.layout.ty;
230            if global.tracked_pointer_tags.contains(&new_tag) {
231                 let ty_is_freeze = ty.is_freeze(*this.tcx, this.typing_env());
232                 let kind_str =
233                     if ty_is_freeze {
234                         format!("initial state {} (pointee type {ty})", new_perm.freeze_perm)
235                     } else {
236                         format!("initial state {}/{} outside/inside UnsafeCell (pointee type {ty})", new_perm.freeze_perm, new_perm.nonfreeze_perm)
237                     };
238                this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
239                    new_tag.inner(),
240                    Some(kind_str),
241                    loc.map(|(alloc_id, base_offset, orig_tag)| (alloc_id, alloc_range(base_offset, ptr_size), orig_tag)),
242                ));
243            }
244            drop(global); // don't hold that reference any longer than we have to
245            interp_ok(())
246        };
247
248        trace!("Reborrow of size {:?}", ptr_size);
249        let (alloc_id, base_offset, parent_prov) = match this.ptr_try_get_alloc_id(place.ptr(), 0) {
250            Ok(data) => {
251                // Unlike SB, we *do* a proper retag for size 0 if can identify the allocation.
252                // After all, the pointer may be lazily initialized outside this initial range.
253                data
254            }
255            Err(_) => {
256                assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
257                // This pointer doesn't come with an AllocId, so there's no
258                // memory to do retagging in.
259                trace!(
260                    "reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
261                    new_tag,
262                    place.ptr(),
263                    place.layout.ty,
264                );
265                log_creation(this, None)?;
266                // Keep original provenance.
267                return interp_ok(place.ptr().provenance);
268            }
269        };
270        log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
271
272        let orig_tag = match parent_prov {
273            ProvenanceExtra::Wildcard => return interp_ok(place.ptr().provenance), // TODO: handle wildcard pointers
274            ProvenanceExtra::Concrete(tag) => tag,
275        };
276
277        trace!(
278            "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
279            new_tag,
280            orig_tag,
281            place.layout.ty,
282            interpret::Pointer::new(alloc_id, base_offset),
283            ptr_size.bytes()
284        );
285
286        if let Some(protect) = new_perm.protector {
287            // We register the protection in two different places.
288            // This makes creating a protector slower, but checking whether a tag
289            // is protected faster.
290            this.frame_mut()
291                .extra
292                .borrow_tracker
293                .as_mut()
294                .unwrap()
295                .protected_tags
296                .push((alloc_id, new_tag));
297            this.machine
298                .borrow_tracker
299                .as_mut()
300                .expect("We should have borrow tracking data")
301                .get_mut()
302                .protected_tags
303                .insert(new_tag, protect);
304        }
305
306        let alloc_kind = this.get_alloc_info(alloc_id).kind;
307        if !matches!(alloc_kind, AllocKind::LiveData) {
308            assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
309            // There's not actually any bytes here where accesses could even be tracked.
310            // Just produce the new provenance, nothing else to do.
311            return interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }));
312        }
313
314        let span = this.machine.current_span();
315        let alloc_extra = this.get_alloc_extra(alloc_id)?;
316        let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
317
318        // Store initial permissions and their corresponding range.
319        let mut perms_map: RangeMap<LocationState> = RangeMap::new(
320            ptr_size,
321            LocationState::new_accessed(Permission::new_disabled(), IdempotentForeignAccess::None), // this will be overwritten
322        );
323        // Keep track of whether the node has any part that allows for interior mutability.
324        // FIXME: This misses `PhantomData<UnsafeCell<T>>` which could be considered a marker
325        // for requesting interior mutability.
326        let mut has_unsafe_cell = false;
327
328        // When adding a new node, the SIFA of its parents needs to be updated, potentially across
329        // the entire memory range. For the parts that are being accessed below, the access itself
330        // trivially takes care of that. However, we have to do some more work to also deal with
331        // the parts that are not being accessed. Specifically what we do is that we
332        // call `update_last_accessed_after_retag` on the SIFA of the permission set for the part of
333        // memory outside `perm_map` -- so that part is definitely taken care of. The remaining concern
334        // is the part of memory that is in the range of `perms_map`, but not accessed below.
335        // There we have two cases:
336        // * If we do have an `UnsafeCell` (`has_unsafe_cell` becomes true), then the non-accessed part
337        //   uses `nonfreeze_perm`, so the `nonfreeze_perm` initialized parts are also fine. We enforce
338        //   the `freeze_perm` parts to be accessed, and thus everything is taken care of.
339        // * If there is no `UnsafeCell`, then `freeze_perm` is used everywhere (both inside and outside the initial range),
340        //   and we update everything to have the `freeze_perm`'s SIFA, so there are no issues. (And this assert below is not
341        //   actually needed in this case).
342        assert!(new_perm.freeze_access);
343
344        let protected = new_perm.protector.is_some();
345        this.visit_freeze_sensitive(place, ptr_size, |range, frozen| {
346            has_unsafe_cell = has_unsafe_cell || !frozen;
347
348            // We are only ever `Frozen` inside the frozen bits.
349            let (perm, access) = if frozen {
350                (new_perm.freeze_perm, new_perm.freeze_access)
351            } else {
352                (new_perm.nonfreeze_perm, new_perm.nonfreeze_access)
353            };
354
355            // Store initial permissions.
356            for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) {
357                let sifa = perm.strongest_idempotent_foreign_access(protected);
358                // NOTE: Currently, `access` is false if and only if `perm` is Cell, so this `if`
359                // doesn't not change whether any code is UB or not. We could just always use
360                // `new_accessed` and everything would stay the same. But that seems conceptually
361                // odd, so we keep the initial "accessed" bit of the `LocationState` in sync with whether
362                // a read access is performed below.
363                if access {
364                    *loc = LocationState::new_accessed(perm, sifa);
365                } else {
366                    *loc = LocationState::new_non_accessed(perm, sifa);
367                }
368            }
369
370            // Some reborrows incur a read access to the parent.
371            if access {
372                // Adjust range to be relative to allocation start (rather than to `place`).
373                let mut range_in_alloc = range;
374                range_in_alloc.start += base_offset;
375
376                tree_borrows.perform_access(
377                    orig_tag,
378                    Some((range_in_alloc, AccessKind::Read, diagnostics::AccessCause::Reborrow)),
379                    this.machine.borrow_tracker.as_ref().unwrap(),
380                    alloc_id,
381                    this.machine.current_span(),
382                )?;
383
384                // Also inform the data race model (but only if any bytes are actually affected).
385                if range.size.bytes() > 0 {
386                    if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() {
387                        data_race.read(
388                            alloc_id,
389                            range_in_alloc,
390                            NaReadType::Retag,
391                            Some(place.layout.ty),
392                            &this.machine,
393                        )?
394                    }
395                }
396            }
397            interp_ok(())
398        })?;
399
400        // Record the parent-child pair in the tree.
401        tree_borrows.new_child(
402            base_offset,
403            orig_tag,
404            new_tag,
405            perms_map,
406            // Allow lazily writing to surrounding data if we found an `UnsafeCell`.
407            if has_unsafe_cell { new_perm.nonfreeze_perm } else { new_perm.freeze_perm },
408            protected,
409            span,
410        )?;
411        drop(tree_borrows);
412
413        interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
414    }
415
416    fn tb_retag_place(
417        &mut self,
418        place: &MPlaceTy<'tcx>,
419        new_perm: NewPermission,
420    ) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
421        let this = self.eval_context_mut();
422
423        // Determine the size of the reborrow.
424        // For most types this is the entire size of the place, however
425        // - when `extern type` is involved we use the size of the known prefix,
426        // - if the pointer is not reborrowed (raw pointer) then we override the size
427        //   to do a zero-length reborrow.
428        let reborrow_size = this
429            .size_and_align_of_mplace(place)?
430            .map(|(size, _)| size)
431            .unwrap_or(place.layout.size);
432        trace!("Creating new permission: {:?} with size {:?}", new_perm, reborrow_size);
433
434        // This new tag is not guaranteed to actually be used.
435        //
436        // If you run out of tags, consider the following optimization: adjust `tb_reborrow`
437        // so that rather than taking as input a fresh tag and deciding whether it uses this
438        // one or the parent it instead just returns whether a new tag should be created.
439        // This will avoid creating tags than end up never being used.
440        let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
441
442        // Compute the actual reborrow.
443        let new_prov = this.tb_reborrow(place, reborrow_size, new_perm, new_tag)?;
444
445        // Adjust place.
446        // (If the closure gets called, that means the old provenance was `Some`, and hence the new
447        // one must also be `Some`.)
448        interp_ok(place.clone().map_provenance(|_| new_prov.unwrap()))
449    }
450
451    /// Retags an individual pointer, returning the retagged version.
452    fn tb_retag_reference(
453        &mut self,
454        val: &ImmTy<'tcx>,
455        new_perm: NewPermission,
456    ) -> InterpResult<'tcx, ImmTy<'tcx>> {
457        let this = self.eval_context_mut();
458        let place = this.ref_to_mplace(val)?;
459        let new_place = this.tb_retag_place(&place, new_perm)?;
460        interp_ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
461    }
462}
463
464impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
465pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
466    /// Retag a pointer. References are passed to `from_ref_ty` and
467    /// raw pointers are never reborrowed.
468    fn tb_retag_ptr_value(
469        &mut self,
470        kind: RetagKind,
471        val: &ImmTy<'tcx>,
472    ) -> InterpResult<'tcx, ImmTy<'tcx>> {
473        let this = self.eval_context_mut();
474        let new_perm = match val.layout.ty.kind() {
475            &ty::Ref(_, pointee, mutability) =>
476                NewPermission::from_ref_ty(pointee, mutability, kind, this),
477            _ => None,
478        };
479        if let Some(new_perm) = new_perm {
480            this.tb_retag_reference(val, new_perm)
481        } else {
482            interp_ok(val.clone())
483        }
484    }
485
486    /// Retag all pointers that are stored in this place.
487    fn tb_retag_place_contents(
488        &mut self,
489        kind: RetagKind,
490        place: &PlaceTy<'tcx>,
491    ) -> InterpResult<'tcx> {
492        let this = self.eval_context_mut();
493        let options = this.machine.borrow_tracker.as_mut().unwrap().get_mut();
494        let retag_fields = options.retag_fields;
495        let mut visitor = RetagVisitor { ecx: this, kind, retag_fields };
496        return visitor.visit_value(place);
497
498        // The actual visitor.
499        struct RetagVisitor<'ecx, 'tcx> {
500            ecx: &'ecx mut MiriInterpCx<'tcx>,
501            kind: RetagKind,
502            retag_fields: RetagFields,
503        }
504        impl<'ecx, 'tcx> RetagVisitor<'ecx, 'tcx> {
505            #[inline(always)] // yes this helps in our benchmarks
506            fn retag_ptr_inplace(
507                &mut self,
508                place: &PlaceTy<'tcx>,
509                new_perm: Option<NewPermission>,
510            ) -> InterpResult<'tcx> {
511                if let Some(new_perm) = new_perm {
512                    let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
513                    let val = self.ecx.tb_retag_reference(&val, new_perm)?;
514                    self.ecx.write_immediate(*val, place)?;
515                }
516                interp_ok(())
517            }
518        }
519        impl<'ecx, 'tcx> ValueVisitor<'tcx, MiriMachine<'tcx>> for RetagVisitor<'ecx, 'tcx> {
520            type V = PlaceTy<'tcx>;
521
522            #[inline(always)]
523            fn ecx(&self) -> &MiriInterpCx<'tcx> {
524                self.ecx
525            }
526
527            /// Regardless of how `Unique` is handled, Boxes are always reborrowed.
528            /// When `Unique` is also reborrowed, then it behaves exactly like `Box`
529            /// except for the fact that `Box` has a non-zero-sized reborrow.
530            fn visit_box(&mut self, box_ty: Ty<'tcx>, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
531                // Only boxes for the global allocator get any special treatment.
532                if box_ty.is_box_global(*self.ecx.tcx) {
533                    let new_perm =
534                        NewPermission::from_unique_ty(place.layout.ty, self.kind, self.ecx);
535                    self.retag_ptr_inplace(place, new_perm)?;
536                }
537                interp_ok(())
538            }
539
540            fn visit_value(&mut self, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
541                // If this place is smaller than a pointer, we know that it can't contain any
542                // pointers we need to retag, so we can stop recursion early.
543                // This optimization is crucial for ZSTs, because they can contain way more fields
544                // than we can ever visit.
545                if place.layout.is_sized() && place.layout.size < self.ecx.pointer_size() {
546                    return interp_ok(());
547                }
548
549                // Check the type of this value to see what to do with it (retag, or recurse).
550                match place.layout.ty.kind() {
551                    &ty::Ref(_, pointee, mutability) => {
552                        let new_perm =
553                            NewPermission::from_ref_ty(pointee, mutability, self.kind, self.ecx);
554                        self.retag_ptr_inplace(place, new_perm)?;
555                    }
556                    ty::RawPtr(_, _) => {
557                        // We definitely do *not* want to recurse into raw pointers -- wide raw
558                        // pointers have fields, and for dyn Trait pointees those can have reference
559                        // type!
560                        // We also do not want to reborrow them.
561                    }
562                    ty::Adt(adt, _) if adt.is_box() => {
563                        // Recurse for boxes, they require some tricky handling and will end up in `visit_box` above.
564                        // (Yes this means we technically also recursively retag the allocator itself
565                        // even if field retagging is not enabled. *shrug*)
566                        self.walk_value(place)?;
567                    }
568                    _ => {
569                        // Not a reference/pointer/box. Only recurse if configured appropriately.
570                        let recurse = match self.retag_fields {
571                            RetagFields::No => false,
572                            RetagFields::Yes => true,
573                            RetagFields::OnlyScalar => {
574                                // Matching `ArgAbi::new` at the time of writing, only fields of
575                                // `Scalar` and `ScalarPair` ABI are considered.
576                                matches!(
577                                    place.layout.backend_repr,
578                                    BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..)
579                                )
580                            }
581                        };
582                        if recurse {
583                            self.walk_value(place)?;
584                        }
585                    }
586                }
587                interp_ok(())
588            }
589        }
590    }
591
592    /// Protect a place so that it cannot be used any more for the duration of the current function
593    /// call.
594    ///
595    /// This is used to ensure soundness of in-place function argument/return passing.
596    fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
597        let this = self.eval_context_mut();
598
599        // Retag it. With protection! That is the entire point.
600        let new_perm = NewPermission {
601            // Note: If we are creating a protected Reserved, which can
602            // never be ReservedIM, the value of the `ty_is_freeze`
603            // argument doesn't matter
604            // (`ty_is_freeze || true` in `new_reserved` will always be `true`).
605            freeze_perm: Permission::new_reserved(
606                /* ty_is_freeze */ true, /* protected */ true,
607            ),
608            freeze_access: true,
609            nonfreeze_perm: Permission::new_reserved(
610                /* ty_is_freeze */ false, /* protected */ true,
611            ),
612            nonfreeze_access: true,
613            protector: Some(ProtectorKind::StrongProtector),
614        };
615        this.tb_retag_place(place, new_perm)
616    }
617
618    /// Mark the given tag as exposed. It was found on a pointer with the given AllocId.
619    fn tb_expose_tag(&self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
620        let this = self.eval_context_ref();
621
622        // Function pointers and dead objects don't have an alloc_extra so we ignore them.
623        // This is okay because accessing them is UB anyway, no need for any Tree Borrows checks.
624        // NOT using `get_alloc_extra_mut` since this might be a read-only allocation!
625        let kind = this.get_alloc_info(alloc_id).kind;
626        match kind {
627            AllocKind::LiveData => {
628                // This should have alloc_extra data, but `get_alloc_extra` can still fail
629                // if converting this alloc_id from a global to a local one
630                // uncovers a non-supported `extern static`.
631                let alloc_extra = this.get_alloc_extra(alloc_id)?;
632                trace!("Tree Borrows tag {tag:?} exposed in {alloc_id:?}");
633                alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag);
634            }
635            AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
636                // No tree borrows on these allocations.
637            }
638        }
639        interp_ok(())
640    }
641
642    /// Display the tree.
643    fn print_tree(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
644        let this = self.eval_context_mut();
645        let alloc_extra = this.get_alloc_extra(alloc_id)?;
646        let tree_borrows = alloc_extra.borrow_tracker_tb().borrow();
647        let borrow_tracker = &this.machine.borrow_tracker.as_ref().unwrap().borrow();
648        tree_borrows.print_tree(&borrow_tracker.protected_tags, show_unnamed)
649    }
650
651    /// Give a name to the pointer, usually the name it has in the source code (for debugging).
652    /// The name given is `name` and the pointer that receives it is the `nth_parent`
653    /// of `ptr` (with 0 representing `ptr` itself)
654    fn tb_give_pointer_debug_name(
655        &mut self,
656        ptr: Pointer,
657        nth_parent: u8,
658        name: &str,
659    ) -> InterpResult<'tcx> {
660        let this = self.eval_context_mut();
661        let (tag, alloc_id) = match ptr.provenance {
662            Some(Provenance::Concrete { tag, alloc_id }) => (tag, alloc_id),
663            _ => {
664                eprintln!("Can't give the name {name} to Wildcard pointer");
665                return interp_ok(());
666            }
667        };
668        let alloc_extra = this.get_alloc_extra(alloc_id)?;
669        let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
670        tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
671    }
672}