miri/borrow_tracker/tree_borrows/
mod.rs

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