miri/borrow_tracker/tree_borrows/
diagnostics.rs

1use std::fmt;
2use std::ops::Range;
3
4use rustc_data_structures::fx::FxHashMap;
5use rustc_span::{Span, SpanData};
6
7use crate::borrow_tracker::tree_borrows::perms::{PermTransition, Permission};
8use crate::borrow_tracker::tree_borrows::tree::LocationState;
9use crate::borrow_tracker::tree_borrows::unimap::UniIndex;
10use crate::borrow_tracker::{AccessKind, ProtectorKind};
11use crate::*;
12
13/// Cause of an access: either a real access or one
14/// inserted by Tree Borrows due to a reborrow or a deallocation.
15#[derive(Clone, Copy, Debug)]
16pub enum AccessCause {
17    Explicit(AccessKind),
18    Reborrow,
19    Dealloc,
20    FnExit(AccessKind),
21}
22
23impl fmt::Display for AccessCause {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::Explicit(kind) => write!(f, "{kind}"),
27            Self::Reborrow => write!(f, "reborrow"),
28            Self::Dealloc => write!(f, "deallocation"),
29            // This is dead code, since the protector release access itself can never
30            // cause UB (while the protector is active, if some other access invalidates
31            // further use of the protected tag, that is immediate UB).
32            // Describing the cause of UB is the only time this function is called.
33            Self::FnExit(_) => unreachable!("protector accesses can never be the source of UB"),
34        }
35    }
36}
37
38impl AccessCause {
39    fn print_as_access(self, is_foreign: bool) -> String {
40        let rel = if is_foreign { "foreign" } else { "child" };
41        match self {
42            Self::Explicit(kind) => format!("{rel} {kind}"),
43            Self::Reborrow => format!("reborrow (acting as a {rel} read access)"),
44            Self::Dealloc => format!("deallocation (acting as a {rel} write access)"),
45            Self::FnExit(kind) => format!("protector release (acting as a {rel} {kind})"),
46        }
47    }
48}
49
50/// Complete data for an event:
51#[derive(Clone, Debug)]
52pub struct Event {
53    /// Transformation of permissions that occurred because of this event.
54    pub transition: PermTransition,
55    /// Kind of the access that triggered this event.
56    pub access_cause: AccessCause,
57    /// Relative position of the tag to the one used for the access.
58    pub is_foreign: bool,
59    /// User-visible range of the access.
60    /// `None` means that this is an implicit access to the entire allocation
61    /// (used for the implicit read on protector release).
62    pub access_range: Option<AllocRange>,
63    /// The transition recorded by this event only occurred on a subrange of
64    /// `access_range`: a single access on `access_range` triggers several events,
65    /// each with their own mutually disjoint `transition_range`. No-op transitions
66    /// should not be recorded as events, so the union of all `transition_range` is not
67    /// necessarily the entire `access_range`.
68    ///
69    /// No data from any `transition_range` should ever be user-visible, because
70    /// both the start and end of `transition_range` are entirely dependent on the
71    /// internal representation of `RangeMap` which is supposed to be opaque.
72    /// What will be shown in the error message is the first byte `error_offset` of
73    /// the `TbError`, which should satisfy
74    /// `event.transition_range.contains(error.error_offset)`.
75    pub transition_range: Range<u64>,
76    /// Line of code that triggered this event.
77    pub span: Span,
78}
79
80/// Diagnostics data about the current access and the location we are accessing.
81/// Used to create history events and errors.
82#[derive(Clone, Debug)]
83pub struct DiagnosticInfo {
84    pub alloc_id: AllocId,
85    pub span: Span,
86    /// The range the diagnostic actually applies to.
87    /// This is always a subset of `access_range`.
88    pub transition_range: Range<u64>,
89    /// The range the access is happening to. Is `None` if this is the protector release access
90    pub access_range: Option<AllocRange>,
91    pub access_cause: AccessCause,
92}
93impl DiagnosticInfo {
94    /// Creates a history event.
95    pub fn create_event(&self, transition: PermTransition, is_foreign: bool) -> Event {
96        Event {
97            transition,
98            is_foreign,
99            access_cause: self.access_cause,
100            access_range: self.access_range,
101            transition_range: self.transition_range.clone(),
102            span: self.span,
103        }
104    }
105}
106/// List of all events that affected a tag.
107/// NOTE: not all of these events are relevant for a particular location,
108/// the events should be filtered before the generation of diagnostics.
109/// Available filtering methods include `History::forget` and `History::extract_relevant`.
110#[derive(Clone, Debug)]
111pub struct History {
112    tag: BorTag,
113    created: (Span, Permission),
114    events: Vec<Event>,
115}
116
117/// History formatted for use by `src/diagnostics.rs`.
118///
119/// NOTE: needs to be `Send` because of a bound on `MachineStopType`, hence
120/// the use of `SpanData` rather than `Span`.
121#[derive(Debug, Clone, Default)]
122pub struct HistoryData {
123    pub events: Vec<(Option<SpanData>, String)>, // includes creation
124}
125
126impl History {
127    /// Record an additional event to the history.
128    pub fn push(&mut self, event: Event) {
129        self.events.push(event);
130    }
131}
132
133impl HistoryData {
134    // Format events from `new_history` into those recorded by `self`.
135    //
136    // NOTE: also converts `Span` to `SpanData`.
137    fn extend(&mut self, new_history: History, tag_name: &'static str, show_initial_state: bool) {
138        let History { tag, created, events } = new_history;
139        let this = format!("the {tag_name} tag {tag:?}");
140        let msg_initial_state = format!(", in the initial state {}", created.1);
141        let msg_creation = format!(
142            "{this} was created here{maybe_msg_initial_state}",
143            maybe_msg_initial_state = if show_initial_state { &msg_initial_state } else { "" },
144        );
145
146        self.events.push((Some(created.0.data()), msg_creation));
147        for &Event {
148            transition,
149            is_foreign,
150            access_cause,
151            access_range,
152            span,
153            transition_range: _,
154        } in &events
155        {
156            // NOTE: `transition_range` is explicitly absent from the error message, it has no significance
157            // to the user. The meaningful one is `access_range`.
158            let access = access_cause.print_as_access(is_foreign);
159            let access_range_text = match access_range {
160                Some(r) => format!("at offsets {r:?}"),
161                None => format!("on every location previously accessed by this tag"),
162            };
163            self.events.push((
164                Some(span.data()),
165                format!(
166                    "{this} later transitioned to {endpoint} due to a {access} {access_range_text}",
167                    endpoint = transition.endpoint()
168                ),
169            ));
170            self.events
171                .push((None, format!("this transition corresponds to {}", transition.summary())));
172        }
173    }
174}
175
176/// Some information that is irrelevant for the algorithm but very
177/// convenient to know about a tag for debugging and testing.
178#[derive(Clone, Debug)]
179pub struct NodeDebugInfo {
180    /// The tag in question.
181    pub tag: BorTag,
182    /// Name(s) that were associated with this tag (comma-separated).
183    /// Typically the name of the variable holding the corresponding
184    /// pointer in the source code.
185    /// Helps match tag numbers to human-readable names.
186    pub name: Option<String>,
187    /// Notable events in the history of this tag, used for
188    /// diagnostics.
189    ///
190    /// NOTE: by virtue of being part of `NodeDebugInfo`,
191    /// the history is automatically cleaned up by the GC.
192    /// NOTE: this is `!Send`, it needs to be converted before displaying
193    /// the actual diagnostics because `src/diagnostics.rs` requires `Send`.
194    pub history: History,
195}
196
197impl NodeDebugInfo {
198    /// Information for a new node. By default it has no
199    /// name and an empty history.
200    pub fn new(tag: BorTag, initial: Permission, span: Span) -> Self {
201        let history = History { tag, created: (span, initial), events: Vec::new() };
202        Self { tag, name: None, history }
203    }
204
205    /// Add a name to the tag. If a same tag is associated to several pointers,
206    /// it can have several names which will be separated by commas.
207    pub fn add_name(&mut self, name: &str) {
208        if let Some(prev_name) = &mut self.name {
209            prev_name.push_str(", ");
210            prev_name.push_str(name);
211        } else {
212            self.name = Some(String::from(name));
213        }
214    }
215}
216
217impl fmt::Display for NodeDebugInfo {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        if let Some(ref name) = self.name {
220            write!(f, "{tag:?} ({name})", tag = self.tag)
221        } else {
222            write!(f, "{tag:?}", tag = self.tag)
223        }
224    }
225}
226
227impl<'tcx> Tree {
228    /// Climb the tree to get the tag of a distant ancestor.
229    /// Allows operations on tags that are unreachable by the program
230    /// but still exist in the tree. Not guaranteed to perform consistently
231    /// if `provenance-gc=1`.
232    fn nth_parent(&self, tag: BorTag, nth_parent: u8) -> Option<BorTag> {
233        let mut idx = self.tag_mapping.get(&tag).unwrap();
234        for _ in 0..nth_parent {
235            let node = self.nodes.get(idx).unwrap();
236            idx = node.parent?;
237        }
238        Some(self.nodes.get(idx).unwrap().tag)
239    }
240
241    /// Debug helper: assign name to tag.
242    pub fn give_pointer_debug_name(
243        &mut self,
244        tag: BorTag,
245        nth_parent: u8,
246        name: &str,
247    ) -> InterpResult<'tcx> {
248        let tag = self.nth_parent(tag, nth_parent).unwrap();
249        let idx = self.tag_mapping.get(&tag).unwrap();
250        if let Some(node) = self.nodes.get_mut(idx) {
251            node.debug_info.add_name(name);
252        } else {
253            eprintln!("Tag {tag:?} (to be named '{name}') not found!");
254        }
255        interp_ok(())
256    }
257
258    /// Debug helper: determines if the tree contains a tag.
259    pub fn is_allocation_of(&self, tag: BorTag) -> bool {
260        self.tag_mapping.contains_key(&tag)
261    }
262}
263
264#[derive(Debug, Clone, Copy)]
265pub(super) enum TransitionError {
266    /// This access is not allowed because some parent tag has insufficient permissions.
267    /// For example, if a tag is `Frozen` and encounters a child write this will
268    /// produce a `ChildAccessForbidden(Frozen)`.
269    /// This kind of error can only occur on child accesses.
270    ChildAccessForbidden(Permission),
271    /// A protector was triggered due to an invalid transition that loses
272    /// too much permissions.
273    /// For example, if a protected tag goes from `Unique` to `Disabled` due
274    /// to a foreign write this will produce a `ProtectedDisabled(Unique)`.
275    /// This kind of error can only occur on foreign accesses.
276    ProtectedDisabled(Permission),
277    /// Cannot deallocate because some tag in the allocation is strongly protected.
278    /// This kind of error can only occur on deallocations.
279    ProtectedDealloc,
280}
281
282impl History {
283    /// Keep only the tag and creation
284    fn forget(&self) -> Self {
285        History { events: Vec::new(), created: self.created, tag: self.tag }
286    }
287
288    /// Reconstruct the history relevant to `error_offset` by filtering
289    /// only events whose range contains the offset we are interested in.
290    fn extract_relevant(&self, error_offset: u64, error_kind: TransitionError) -> Self {
291        History {
292            events: self
293                .events
294                .iter()
295                .filter(|e| e.transition_range.contains(&error_offset))
296                .filter(|e| e.transition.is_relevant(error_kind))
297                .cloned()
298                .collect::<Vec<_>>(),
299            created: self.created,
300            tag: self.tag,
301        }
302    }
303}
304
305/// Failures that can occur during the execution of Tree Borrows procedures.
306pub(super) struct TbError<'node> {
307    /// What failure occurred.
308    pub error_kind: TransitionError,
309    /// Diagnostic data about the access that caused the error.
310    pub access_info: &'node DiagnosticInfo,
311    /// The tag on which the error was triggered.
312    /// On protector violations, this is the tag that was protected.
313    /// On accesses rejected due to insufficient permissions, this is the
314    /// tag that lacked those permissions.
315    pub conflicting_node_info: &'node NodeDebugInfo,
316    /// Which tag, if any, the access that caused this error was made through, i.e.
317    /// which tag was used to read/write/deallocate.
318    /// Not set on wildcard accesses.
319    pub accessed_node_info: Option<&'node NodeDebugInfo>,
320}
321
322impl TbError<'_> {
323    /// Produce a UB error.
324    pub fn build<'tcx>(self) -> InterpErrorKind<'tcx> {
325        use TransitionError::*;
326        let cause = self.access_info.access_cause;
327        let error_offset = self.access_info.transition_range.start;
328        let accessed = self.accessed_node_info;
329        let accessed_str =
330            self.accessed_node_info.map(|v| format!("{v}")).unwrap_or_else(|| "<wildcard>".into());
331        let conflicting = self.conflicting_node_info;
332        // An access is considered conflicting if it happened through a
333        // different tag than the one who caused UB.
334        // When doing a wildcard access (where `accessed` is `None`) we
335        // do not know which precise tag the accessed happened from,
336        // however we can be certain that it did not come from the
337        // conflicting tag.
338        // This is because the wildcard data structure already removes
339        // all tags through which an access would cause UB.
340        let accessed_is_conflicting = accessed.map(|a| a.tag) == Some(conflicting.tag);
341        let title = format!(
342            "{cause} through {accessed_str} at {alloc_id:?}[{error_offset:#x}] is forbidden",
343            alloc_id = self.access_info.alloc_id
344        );
345        let (title, details, conflicting_tag_name) = match self.error_kind {
346            ChildAccessForbidden(perm) => {
347                let conflicting_tag_name =
348                    if accessed_is_conflicting { "accessed" } else { "conflicting" };
349                let mut details = Vec::new();
350                if !accessed_is_conflicting {
351                    details.push(format!(
352                        "the accessed tag {accessed_str} is a child of the conflicting tag {conflicting}"
353                    ));
354                }
355                let access = cause.print_as_access(/* is_foreign */ false);
356                details.push(format!(
357                    "the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids this {access}"
358                ));
359                (title, details, conflicting_tag_name)
360            }
361            ProtectedDisabled(before_disabled) => {
362                let conflicting_tag_name = "protected";
363                let access = cause.print_as_access(/* is_foreign */ true);
364                let details = vec![
365                    format!(
366                        "the accessed tag {accessed_str} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
367                    ),
368                    format!(
369                        "this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled"
370                    ),
371                    format!("protected tags must never be Disabled"),
372                ];
373                (title, details, conflicting_tag_name)
374            }
375            ProtectedDealloc => {
376                let conflicting_tag_name = "strongly protected";
377                let details = vec![
378                    format!(
379                        "the allocation of the accessed tag {accessed_str} also contains the {conflicting_tag_name} tag {conflicting}"
380                    ),
381                    format!("the {conflicting_tag_name} tag {conflicting} disallows deallocations"),
382                ];
383                (title, details, conflicting_tag_name)
384            }
385        };
386        let mut history = HistoryData::default();
387        if let Some(accessed_info) = self.accessed_node_info
388            && !accessed_is_conflicting
389        {
390            history.extend(accessed_info.history.forget(), "accessed", false);
391        }
392        history.extend(
393            self.conflicting_node_info.history.extract_relevant(error_offset, self.error_kind),
394            conflicting_tag_name,
395            true,
396        );
397        err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
398    }
399}
400
401/// Cannot access this allocation with wildcard provenance, as there are no
402/// valid exposed references for this access kind.
403pub fn no_valid_exposed_references_error<'tcx>(
404    DiagnosticInfo { alloc_id, transition_range, access_cause, .. }: &DiagnosticInfo,
405) -> InterpErrorKind<'tcx> {
406    let title = format!(
407        "{access_cause} through <wildcard> at {alloc_id:?}[{offset:#x}] is forbidden",
408        offset = transition_range.start
409    );
410    let details = vec![format!("there are no exposed tags which may perform this access here")];
411    let history = HistoryData::default();
412    err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
413}
414
415type S = &'static str;
416/// Pretty-printing details
417///
418/// Example:
419/// ```rust,ignore (private type)
420/// DisplayFmtWrapper {
421///     top: '>',
422///     bot: '<',
423///     warning_text: "Some tags have been hidden",
424/// }
425/// ```
426/// will wrap the entire text with
427/// ```text
428/// >>>>>>>>>>>>>>>>>>>>>>>>>>
429/// Some tags have been hidden
430///
431/// [ main display here ]
432///
433/// <<<<<<<<<<<<<<<<<<<<<<<<<<
434/// ```
435struct DisplayFmtWrapper {
436    /// Character repeated to make the upper border.
437    top: char,
438    /// Character repeated to make the lower border.
439    bot: char,
440    /// Warning about some tags (unnamed) being hidden.
441    warning_text: S,
442}
443
444/// Formatting of the permissions on each range.
445///
446/// Example:
447/// ```rust,ignore (private type)
448/// DisplayFmtPermission {
449///     open: "[",
450///     sep: "|",
451///     close: "]",
452///     uninit: "___",
453///     range_sep: "..",
454/// }
455/// ```
456/// will show each permission line as
457/// ```text
458/// 0.. 1.. 2.. 3.. 4.. 5
459/// [Act|Res|Frz|Dis|___]
460/// ```
461struct DisplayFmtPermission {
462    /// Text that starts the permission block.
463    open: S,
464    /// Text that separates permissions on different ranges.
465    sep: S,
466    /// Text that ends the permission block.
467    close: S,
468    /// Text to show when a permission is not initialized.
469    /// Should have the same width as a `Permission`'s `.short_name()`, i.e.
470    /// 3 if using the `Res/Act/Frz/Dis` notation.
471    uninit: S,
472    /// Text to separate the `start` and `end` values of a range.
473    range_sep: S,
474}
475
476/// Formatting of the tree structure.
477///
478/// Example:
479/// ```rust,ignore (private type)
480/// DisplayFmtPadding {
481///     join_middle: "|-",
482///     join_last: "'-",
483///     join_haschild: "-+-",
484///     join_default: "---",
485///     indent_middle: "| ",
486///     indent_last: "  ",
487/// }
488/// ```
489/// will show the tree as
490/// ```text
491/// -+- root
492///  |--+- a
493///  |  '--+- b
494///  |     '---- c
495///  |--+- d
496///  |  '---- e
497///  '---- f
498/// ```
499struct DisplayFmtPadding {
500    /// Connector for a child other than the last.
501    join_middle: S,
502    /// Connector for the last child. Should have the same width as `join_middle`.
503    join_last: S,
504    /// Connector for a node that itself has a child.
505    join_haschild: S,
506    /// Connector for a node that does not have a child. Should have the same width
507    /// as `join_haschild`.
508    join_default: S,
509    /// Indentation when there is a next child.
510    indent_middle: S,
511    /// Indentation for the last child.
512    indent_last: S,
513    /// Replaces `join_last` for a wildcard root.
514    wildcard_root: S,
515}
516/// How to show whether a location has been accessed
517///
518/// Example:
519/// ```rust,ignore (private type)
520/// DisplayFmtAccess {
521///     yes: " ",
522///     no: "?",
523///     meh: "_",
524/// }
525/// ```
526/// will show states as
527/// ```text
528///  Act
529/// ?Res
530/// ____
531/// ```
532struct DisplayFmtAccess {
533    /// Used when `State.initialized = true`.
534    yes: S,
535    /// Used when `State.initialized = false`.
536    /// Should have the same width as `yes`.
537    no: S,
538    /// Used when there is no `State`.
539    /// Should have the same width as `yes`.
540    meh: S,
541}
542
543/// All parameters to determine how the tree is formatted.
544struct DisplayFmt {
545    wrapper: DisplayFmtWrapper,
546    perm: DisplayFmtPermission,
547    padding: DisplayFmtPadding,
548    accessed: DisplayFmtAccess,
549}
550impl DisplayFmt {
551    /// Print the permission with the format
552    /// ` Res`/` Re*`/` Act`/` Frz`/` Dis` for accessed locations
553    /// and `?Res`/`?Re*`/`?Act`/`?Frz`/`?Dis` for unaccessed locations.
554    fn print_perm(&self, perm: Option<LocationState>) -> String {
555        if let Some(perm) = perm {
556            format!(
557                "{ac}{st}",
558                ac = if perm.accessed() { self.accessed.yes } else { self.accessed.no },
559                st = perm.permission().short_name(),
560            )
561        } else {
562            format!("{}{}", self.accessed.meh, self.perm.uninit)
563        }
564    }
565
566    /// Print the tag with the format `<XYZ>` if the tag is unnamed,
567    /// and `<XYZ=name>` if the tag is named.
568    fn print_tag(&self, tag: BorTag, name: &Option<String>) -> String {
569        let printable_tag = tag.get();
570        if let Some(name) = name {
571            format!("<{printable_tag}={name}>")
572        } else {
573            format!("<{printable_tag}>")
574        }
575    }
576
577    /// Print extra text if the tag has a protector.
578    fn print_protector(&self, protector: Option<&ProtectorKind>) -> &'static str {
579        protector
580            .map(|p| {
581                match *p {
582                    ProtectorKind::WeakProtector => " Weakly protected",
583                    ProtectorKind::StrongProtector => " Strongly protected",
584                }
585            })
586            .unwrap_or("")
587    }
588
589    /// Print extra text if the tag is exposed.
590    fn print_exposed(&self, exposed: bool) -> S {
591        if exposed { " (exposed)" } else { "" }
592    }
593}
594
595/// Track the indentation of the tree.
596struct DisplayIndent {
597    curr: String,
598}
599impl DisplayIndent {
600    fn new() -> Self {
601        Self { curr: "    ".to_string() }
602    }
603
604    /// Increment the indentation by one. Note: need to know if this
605    /// is the last child or not because the presence of other children
606    /// changes the way the indentation is shown.
607    fn increment(&mut self, formatter: &DisplayFmt, is_last: bool) {
608        self.curr.push_str(if is_last {
609            formatter.padding.indent_last
610        } else {
611            formatter.padding.indent_middle
612        });
613    }
614
615    /// Pop the last level of indentation.
616    fn decrement(&mut self, formatter: &DisplayFmt) {
617        for _ in 0..formatter.padding.indent_last.len() {
618            let _ = self.curr.pop();
619        }
620    }
621
622    /// Print the current indentation.
623    fn write(&self, s: &mut String) {
624        s.push_str(&self.curr);
625    }
626}
627
628/// Repeat a character a number of times.
629fn char_repeat(c: char, n: usize) -> String {
630    std::iter::once(c).cycle().take(n).collect::<String>()
631}
632
633/// Extracted information from the tree, in a form that is readily accessible
634/// for printing. I.e. resolve parent-child pointers into an actual tree,
635/// zip permissions with their tag, remove wrappers, stringify data.
636struct DisplayRepr {
637    tag: BorTag,
638    name: Option<String>,
639    exposed: bool,
640    rperm: Vec<Option<LocationState>>,
641    children: Vec<DisplayRepr>,
642}
643
644impl DisplayRepr {
645    fn from(tree: &Tree, root: UniIndex, show_unnamed: bool) -> Option<Self> {
646        let mut v = Vec::new();
647        extraction_aux(tree, root, show_unnamed, &mut v);
648        let Some(root) = v.pop() else {
649            if show_unnamed {
650                unreachable!(
651                    "This allocation contains no tags, not even a root. This should not happen."
652                );
653            }
654            return None;
655        };
656        assert!(v.is_empty());
657        return Some(root);
658
659        fn extraction_aux(
660            tree: &Tree,
661            idx: UniIndex,
662            show_unnamed: bool,
663            acc: &mut Vec<DisplayRepr>,
664        ) {
665            let node = tree.nodes.get(idx).unwrap();
666            let name = node.debug_info.name.clone();
667            let exposed = node.is_exposed;
668            let children_sorted = {
669                let mut children = node.children.iter().cloned().collect::<Vec<_>>();
670                children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag);
671                children
672            };
673            if !show_unnamed && name.is_none() {
674                // We skip this node
675                for child_idx in children_sorted {
676                    extraction_aux(tree, child_idx, show_unnamed, acc);
677                }
678            } else {
679                // We take this node
680                let rperm = tree
681                    .locations
682                    .iter_all()
683                    .map(move |(_offset, loc)| {
684                        let perm = loc.perms.get(idx);
685                        perm.cloned()
686                    })
687                    .collect::<Vec<_>>();
688                let mut children = Vec::new();
689                for child_idx in children_sorted {
690                    extraction_aux(tree, child_idx, show_unnamed, &mut children);
691                }
692                acc.push(DisplayRepr { tag: node.tag, name, rperm, children, exposed });
693            }
694        }
695    }
696    fn print(
697        main_root: &Option<DisplayRepr>,
698        wildcard_subtrees: &[DisplayRepr],
699        fmt: &DisplayFmt,
700        indenter: &mut DisplayIndent,
701        protected_tags: &FxHashMap<BorTag, ProtectorKind>,
702        ranges: Vec<Range<u64>>,
703        print_warning: bool,
704    ) {
705        let mut block = Vec::new();
706        // Push the header and compute the required paddings for the body.
707        // Header looks like this: `0.. 1.. 2.. 3.. 4.. 5.. 6.. 7.. 8`,
708        // and is properly aligned with the `|` of the body.
709        let (range_header, range_padding) = {
710            let mut header_top = String::new();
711            header_top.push_str("0..");
712            let mut padding = Vec::new();
713            for (i, range) in ranges.iter().enumerate() {
714                if i > 0 {
715                    header_top.push_str(fmt.perm.range_sep);
716                }
717                let s = range.end.to_string();
718                let l = s.chars().count() + fmt.perm.range_sep.chars().count();
719                {
720                    let target_len =
721                        fmt.perm.uninit.chars().count() + fmt.accessed.yes.chars().count() + 1;
722                    let tot_len = target_len.max(l);
723                    let header_top_pad_len = target_len.saturating_sub(l);
724                    let body_pad_len = tot_len.saturating_sub(target_len);
725                    header_top.push_str(&format!("{}{}", char_repeat(' ', header_top_pad_len), s));
726                    padding.push(body_pad_len);
727                }
728            }
729            ([header_top], padding)
730        };
731        for s in range_header {
732            block.push(s);
733        }
734        // This is the actual work
735        if let Some(root) = main_root {
736            print_aux(
737                root,
738                &range_padding,
739                fmt,
740                indenter,
741                protected_tags,
742                true,  /* root _is_ the last child */
743                false, /* not a wildcard_root*/
744                &mut block,
745            );
746        }
747        for tree in wildcard_subtrees.iter() {
748            let mut gap_line = String::new();
749            gap_line.push_str(fmt.perm.open);
750            for (i, &pad) in range_padding.iter().enumerate() {
751                if i > 0 {
752                    gap_line.push_str(fmt.perm.sep);
753                }
754                gap_line.push_str(&format!("{}{}", char_repeat(' ', pad), "     "));
755            }
756            gap_line.push_str(fmt.perm.close);
757            block.push(gap_line);
758
759            print_aux(
760                tree,
761                &range_padding,
762                fmt,
763                indenter,
764                protected_tags,
765                true, /* root _is_ the last child */
766                true, /* wildcard_root*/
767                &mut block,
768            );
769        }
770        // Then it's just prettifying it with a border of dashes.
771        {
772            let wr = &fmt.wrapper;
773            let max_width = {
774                let block_width = block.iter().map(|s| s.chars().count()).max().unwrap();
775                if print_warning {
776                    block_width.max(wr.warning_text.chars().count())
777                } else {
778                    block_width
779                }
780            };
781            eprintln!("{}", char_repeat(wr.top, max_width));
782            if print_warning {
783                eprintln!("{}", wr.warning_text,);
784            }
785            for line in block {
786                eprintln!("{line}");
787            }
788            eprintln!("{}", char_repeat(wr.bot, max_width));
789        }
790
791        // Here is the function that does the heavy lifting
792        fn print_aux(
793            tree: &DisplayRepr,
794            padding: &[usize],
795            fmt: &DisplayFmt,
796            indent: &mut DisplayIndent,
797            protected_tags: &FxHashMap<BorTag, ProtectorKind>,
798            is_last_child: bool,
799            is_wildcard_root: bool,
800            acc: &mut Vec<String>,
801        ) {
802            let mut line = String::new();
803            // Format the permissions on each range.
804            // Looks like `| Act| Res| Res| Act|`.
805            line.push_str(fmt.perm.open);
806            for (i, (perm, &pad)) in tree.rperm.iter().zip(padding.iter()).enumerate() {
807                if i > 0 {
808                    line.push_str(fmt.perm.sep);
809                }
810                let show_perm = fmt.print_perm(*perm);
811                line.push_str(&format!("{}{}", char_repeat(' ', pad), show_perm));
812            }
813            line.push_str(fmt.perm.close);
814            // Format the tree structure.
815            // Main difficulty is handling the indentation properly.
816            indent.write(&mut line);
817            {
818                // padding
819                line.push_str(if is_wildcard_root {
820                    fmt.padding.wildcard_root
821                } else if is_last_child {
822                    fmt.padding.join_last
823                } else {
824                    fmt.padding.join_middle
825                });
826                line.push_str(fmt.padding.join_default);
827                line.push_str(if tree.children.is_empty() {
828                    fmt.padding.join_default
829                } else {
830                    fmt.padding.join_haschild
831                });
832                line.push_str(fmt.padding.join_default);
833                line.push_str(fmt.padding.join_default);
834            }
835            line.push_str(&fmt.print_tag(tree.tag, &tree.name));
836            let protector = protected_tags.get(&tree.tag);
837            line.push_str(fmt.print_protector(protector));
838            line.push_str(fmt.print_exposed(tree.exposed));
839            // Push the line to the accumulator then recurse.
840            acc.push(line);
841            let nb_children = tree.children.len();
842            for (i, child) in tree.children.iter().enumerate() {
843                indent.increment(fmt, is_last_child);
844                print_aux(
845                    child,
846                    padding,
847                    fmt,
848                    indent,
849                    protected_tags,
850                    /* is_last_child */ i + 1 == nb_children,
851                    /* is_wildcard_root */ false,
852                    acc,
853                );
854                indent.decrement(fmt);
855            }
856        }
857    }
858}
859
860const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt {
861    wrapper: DisplayFmtWrapper {
862        top: '─',
863        bot: '─',
864        warning_text: "Warning: this tree is indicative only. Some tags may have been hidden.",
865    },
866    perm: DisplayFmtPermission { open: "|", sep: "|", close: "|", uninit: "----", range_sep: ".." },
867    padding: DisplayFmtPadding {
868        join_middle: "├",
869        join_last: "└",
870        indent_middle: "│ ",
871        indent_last: "  ",
872        join_haschild: "┬",
873        join_default: "─",
874        wildcard_root: "*",
875    },
876    accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" },
877};
878
879impl<'tcx> Tree {
880    /// Display the contents of the tree.
881    pub fn print_tree(
882        &self,
883        protected_tags: &FxHashMap<BorTag, ProtectorKind>,
884        show_unnamed: bool,
885    ) -> InterpResult<'tcx> {
886        let mut indenter = DisplayIndent::new();
887        let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
888        let main_tree = DisplayRepr::from(self, self.roots[0], show_unnamed);
889        let wildcard_subtrees = self.roots[1..]
890            .iter()
891            .filter_map(|root| DisplayRepr::from(self, *root, show_unnamed))
892            .collect::<Vec<_>>();
893
894        if main_tree.is_none() && wildcard_subtrees.is_empty() {
895            eprintln!(
896                "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags."
897            );
898        }
899
900        DisplayRepr::print(
901            &main_tree,
902            wildcard_subtrees.as_slice(),
903            &DEFAULT_FORMATTER,
904            &mut indenter,
905            protected_tags,
906            ranges,
907            /* print warning message about tags not shown */ !show_unnamed,
908        );
909        interp_ok(())
910    }
911}