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