Skip to main content

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