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