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/// 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(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 `Unique` to `Disabled` due
248    /// to a foreign write this will produce a `ProtectedDisabled(Unique)`.
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, if any, the access that caused this error was made through, i.e.
295    /// which tag was used to read/write/deallocate.
296    /// Not set on wildcard accesses.
297    pub accessed_info: Option<&'node NodeDebugInfo>,
298}
299
300impl TbError<'_> {
301    /// Produce a UB error.
302    pub fn build<'tcx>(self) -> InterpErrorKind<'tcx> {
303        use TransitionError::*;
304        let cause = self.access_cause;
305        let accessed = self.accessed_info;
306        let accessed_str =
307            self.accessed_info.map(|v| format!("{v}")).unwrap_or_else(|| "<wildcard>".into());
308        let conflicting = self.conflicting_info;
309        // An access is considered conflicting if it happened through a
310        // different tag than the one who caused UB.
311        // When doing a wildcard access (where `accessed` is `None`) we
312        // do not know which precise tag the accessed happened from,
313        // however we can be certain that it did not come from the
314        // conflicting tag.
315        // This is because the wildcard data structure already removes
316        // all tags through which an access would cause UB.
317        let accessed_is_conflicting = accessed.map(|a| a.tag) == Some(conflicting.tag);
318        let title = format!(
319            "{cause} through {accessed_str} at {alloc_id:?}[{offset:#x}] is forbidden",
320            alloc_id = self.alloc_id,
321            offset = self.error_offset
322        );
323        let (title, details, conflicting_tag_name) = match self.error_kind {
324            ChildAccessForbidden(perm) => {
325                let conflicting_tag_name =
326                    if accessed_is_conflicting { "accessed" } else { "conflicting" };
327                let mut details = Vec::new();
328                if !accessed_is_conflicting {
329                    details.push(format!(
330                        "the accessed tag {accessed_str} is a child of the conflicting tag {conflicting}"
331                    ));
332                }
333                let access = cause.print_as_access(/* is_foreign */ false);
334                details.push(format!(
335                    "the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids this {access}"
336                ));
337                (title, details, conflicting_tag_name)
338            }
339            ProtectedDisabled(before_disabled) => {
340                let conflicting_tag_name = "protected";
341                let access = cause.print_as_access(/* is_foreign */ true);
342                let details = vec![
343                    format!(
344                        "the accessed tag {accessed_str} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
345                    ),
346                    format!(
347                        "this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled"
348                    ),
349                    format!("protected tags must never be Disabled"),
350                ];
351                (title, details, conflicting_tag_name)
352            }
353            ProtectedDealloc => {
354                let conflicting_tag_name = "strongly protected";
355                let details = vec![
356                    format!(
357                        "the allocation of the accessed tag {accessed_str} also contains the {conflicting_tag_name} tag {conflicting}"
358                    ),
359                    format!("the {conflicting_tag_name} tag {conflicting} disallows deallocations"),
360                ];
361                (title, details, conflicting_tag_name)
362            }
363        };
364        let mut history = HistoryData::default();
365        if let Some(accessed_info) = self.accessed_info
366            && !accessed_is_conflicting
367        {
368            history.extend(accessed_info.history.forget(), "accessed", false);
369        }
370        history.extend(
371            self.conflicting_info.history.extract_relevant(self.error_offset, self.error_kind),
372            conflicting_tag_name,
373            true,
374        );
375        err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
376    }
377}
378
379/// Cannot access this allocation with wildcard provenance, as there are no
380/// valid exposed references for this access kind.
381pub fn no_valid_exposed_references_error<'tcx>(
382    alloc_id: AllocId,
383    offset: u64,
384    access_cause: AccessCause,
385) -> InterpErrorKind<'tcx> {
386    let title =
387        format!("{access_cause} through <wildcard> at {alloc_id:?}[{offset:#x}] is forbidden");
388    let details = vec![format!("there are no exposed tags which may perform this access here")];
389    let history = HistoryData::default();
390    err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
391}
392
393type S = &'static str;
394/// Pretty-printing details
395///
396/// Example:
397/// ```rust,ignore (private type)
398/// DisplayFmtWrapper {
399///     top: '>',
400///     bot: '<',
401///     warning_text: "Some tags have been hidden",
402/// }
403/// ```
404/// will wrap the entire text with
405/// ```text
406/// >>>>>>>>>>>>>>>>>>>>>>>>>>
407/// Some tags have been hidden
408///
409/// [ main display here ]
410///
411/// <<<<<<<<<<<<<<<<<<<<<<<<<<
412/// ```
413struct DisplayFmtWrapper {
414    /// Character repeated to make the upper border.
415    top: char,
416    /// Character repeated to make the lower border.
417    bot: char,
418    /// Warning about some tags (unnamed) being hidden.
419    warning_text: S,
420}
421
422/// Formatting of the permissions on each range.
423///
424/// Example:
425/// ```rust,ignore (private type)
426/// DisplayFmtPermission {
427///     open: "[",
428///     sep: "|",
429///     close: "]",
430///     uninit: "___",
431///     range_sep: "..",
432/// }
433/// ```
434/// will show each permission line as
435/// ```text
436/// 0.. 1.. 2.. 3.. 4.. 5
437/// [Act|Res|Frz|Dis|___]
438/// ```
439struct DisplayFmtPermission {
440    /// Text that starts the permission block.
441    open: S,
442    /// Text that separates permissions on different ranges.
443    sep: S,
444    /// Text that ends the permission block.
445    close: S,
446    /// Text to show when a permission is not initialized.
447    /// Should have the same width as a `Permission`'s `.short_name()`, i.e.
448    /// 3 if using the `Res/Act/Frz/Dis` notation.
449    uninit: S,
450    /// Text to separate the `start` and `end` values of a range.
451    range_sep: S,
452}
453
454/// Formatting of the tree structure.
455///
456/// Example:
457/// ```rust,ignore (private type)
458/// DisplayFmtPadding {
459///     join_middle: "|-",
460///     join_last: "'-",
461///     join_haschild: "-+-",
462///     join_default: "---",
463///     indent_middle: "| ",
464///     indent_last: "  ",
465/// }
466/// ```
467/// will show the tree as
468/// ```text
469/// -+- root
470///  |--+- a
471///  |  '--+- b
472///  |     '---- c
473///  |--+- d
474///  |  '---- e
475///  '---- f
476/// ```
477struct DisplayFmtPadding {
478    /// Connector for a child other than the last.
479    join_middle: S,
480    /// Connector for the last child. Should have the same width as `join_middle`.
481    join_last: S,
482    /// Connector for a node that itself has a child.
483    join_haschild: S,
484    /// Connector for a node that does not have a child. Should have the same width
485    /// as `join_haschild`.
486    join_default: S,
487    /// Indentation when there is a next child.
488    indent_middle: S,
489    /// Indentation for the last child.
490    indent_last: S,
491}
492/// How to show whether a location has been accessed
493///
494/// Example:
495/// ```rust,ignore (private type)
496/// DisplayFmtAccess {
497///     yes: " ",
498///     no: "?",
499///     meh: "_",
500/// }
501/// ```
502/// will show states as
503/// ```text
504///  Act
505/// ?Res
506/// ____
507/// ```
508struct DisplayFmtAccess {
509    /// Used when `State.initialized = true`.
510    yes: S,
511    /// Used when `State.initialized = false`.
512    /// Should have the same width as `yes`.
513    no: S,
514    /// Used when there is no `State`.
515    /// Should have the same width as `yes`.
516    meh: S,
517}
518
519/// All parameters to determine how the tree is formatted.
520struct DisplayFmt {
521    wrapper: DisplayFmtWrapper,
522    perm: DisplayFmtPermission,
523    padding: DisplayFmtPadding,
524    accessed: DisplayFmtAccess,
525}
526impl DisplayFmt {
527    /// Print the permission with the format
528    /// ` Res`/` Re*`/` Act`/` Frz`/` Dis` for accessed locations
529    /// and `?Res`/`?Re*`/`?Act`/`?Frz`/`?Dis` for unaccessed locations.
530    fn print_perm(&self, perm: Option<LocationState>) -> String {
531        if let Some(perm) = perm {
532            format!(
533                "{ac}{st}",
534                ac = if perm.accessed() { self.accessed.yes } else { self.accessed.no },
535                st = perm.permission().short_name(),
536            )
537        } else {
538            format!("{}{}", self.accessed.meh, self.perm.uninit)
539        }
540    }
541
542    /// Print the tag with the format `<XYZ>` if the tag is unnamed,
543    /// and `<XYZ=name>` if the tag is named.
544    fn print_tag(&self, tag: BorTag, name: &Option<String>) -> String {
545        let printable_tag = tag.get();
546        if let Some(name) = name {
547            format!("<{printable_tag}={name}>")
548        } else {
549            format!("<{printable_tag}>")
550        }
551    }
552
553    /// Print extra text if the tag has a protector.
554    fn print_protector(&self, protector: Option<&ProtectorKind>) -> &'static str {
555        protector
556            .map(|p| {
557                match *p {
558                    ProtectorKind::WeakProtector => " Weakly protected",
559                    ProtectorKind::StrongProtector => " Strongly protected",
560                }
561            })
562            .unwrap_or("")
563    }
564}
565
566/// Track the indentation of the tree.
567struct DisplayIndent {
568    curr: String,
569}
570impl DisplayIndent {
571    fn new() -> Self {
572        Self { curr: "    ".to_string() }
573    }
574
575    /// Increment the indentation by one. Note: need to know if this
576    /// is the last child or not because the presence of other children
577    /// changes the way the indentation is shown.
578    fn increment(&mut self, formatter: &DisplayFmt, is_last: bool) {
579        self.curr.push_str(if is_last {
580            formatter.padding.indent_last
581        } else {
582            formatter.padding.indent_middle
583        });
584    }
585
586    /// Pop the last level of indentation.
587    fn decrement(&mut self, formatter: &DisplayFmt) {
588        for _ in 0..formatter.padding.indent_last.len() {
589            let _ = self.curr.pop();
590        }
591    }
592
593    /// Print the current indentation.
594    fn write(&self, s: &mut String) {
595        s.push_str(&self.curr);
596    }
597}
598
599/// Repeat a character a number of times.
600fn char_repeat(c: char, n: usize) -> String {
601    std::iter::once(c).cycle().take(n).collect::<String>()
602}
603
604/// Extracted information from the tree, in a form that is readily accessible
605/// for printing. I.e. resolve parent-child pointers into an actual tree,
606/// zip permissions with their tag, remove wrappers, stringify data.
607struct DisplayRepr {
608    tag: BorTag,
609    name: Option<String>,
610    rperm: Vec<Option<LocationState>>,
611    children: Vec<DisplayRepr>,
612}
613
614impl DisplayRepr {
615    fn from(tree: &Tree, show_unnamed: bool) -> Option<Self> {
616        let mut v = Vec::new();
617        extraction_aux(tree, tree.root, show_unnamed, &mut v);
618        let Some(root) = v.pop() else {
619            if show_unnamed {
620                unreachable!(
621                    "This allocation contains no tags, not even a root. This should not happen."
622                );
623            }
624            eprintln!(
625                "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags."
626            );
627            return None;
628        };
629        assert!(v.is_empty());
630        return Some(root);
631
632        fn extraction_aux(
633            tree: &Tree,
634            idx: UniIndex,
635            show_unnamed: bool,
636            acc: &mut Vec<DisplayRepr>,
637        ) {
638            let node = tree.nodes.get(idx).unwrap();
639            let name = node.debug_info.name.clone();
640            let children_sorted = {
641                let mut children = node.children.iter().cloned().collect::<Vec<_>>();
642                children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag);
643                children
644            };
645            if !show_unnamed && name.is_none() {
646                // We skip this node
647                for child_idx in children_sorted {
648                    extraction_aux(tree, child_idx, show_unnamed, acc);
649                }
650            } else {
651                // We take this node
652                let rperm = tree
653                    .locations
654                    .iter_all()
655                    .map(move |(_offset, loc)| {
656                        let perm = loc.perms.get(idx);
657                        perm.cloned()
658                    })
659                    .collect::<Vec<_>>();
660                let mut children = Vec::new();
661                for child_idx in children_sorted {
662                    extraction_aux(tree, child_idx, show_unnamed, &mut children);
663                }
664                acc.push(DisplayRepr { tag: node.tag, name, rperm, children });
665            }
666        }
667    }
668    fn print(
669        &self,
670        fmt: &DisplayFmt,
671        indenter: &mut DisplayIndent,
672        protected_tags: &FxHashMap<BorTag, ProtectorKind>,
673        ranges: Vec<Range<u64>>,
674        print_warning: bool,
675    ) {
676        let mut block = Vec::new();
677        // Push the header and compute the required paddings for the body.
678        // Header looks like this: `0.. 1.. 2.. 3.. 4.. 5.. 6.. 7.. 8`,
679        // and is properly aligned with the `|` of the body.
680        let (range_header, range_padding) = {
681            let mut header_top = String::new();
682            header_top.push_str("0..");
683            let mut padding = Vec::new();
684            for (i, range) in ranges.iter().enumerate() {
685                if i > 0 {
686                    header_top.push_str(fmt.perm.range_sep);
687                }
688                let s = range.end.to_string();
689                let l = s.chars().count() + fmt.perm.range_sep.chars().count();
690                {
691                    let target_len =
692                        fmt.perm.uninit.chars().count() + fmt.accessed.yes.chars().count() + 1;
693                    let tot_len = target_len.max(l);
694                    let header_top_pad_len = target_len.saturating_sub(l);
695                    let body_pad_len = tot_len.saturating_sub(target_len);
696                    header_top.push_str(&format!("{}{}", char_repeat(' ', header_top_pad_len), s));
697                    padding.push(body_pad_len);
698                }
699            }
700            ([header_top], padding)
701        };
702        for s in range_header {
703            block.push(s);
704        }
705        // This is the actual work
706        print_aux(
707            self,
708            &range_padding,
709            fmt,
710            indenter,
711            protected_tags,
712            true, /* root _is_ the last child */
713            &mut block,
714        );
715        // Then it's just prettifying it with a border of dashes.
716        {
717            let wr = &fmt.wrapper;
718            let max_width = {
719                let block_width = block.iter().map(|s| s.chars().count()).max().unwrap();
720                if print_warning {
721                    block_width.max(wr.warning_text.chars().count())
722                } else {
723                    block_width
724                }
725            };
726            eprintln!("{}", char_repeat(wr.top, max_width));
727            if print_warning {
728                eprintln!("{}", wr.warning_text,);
729            }
730            for line in block {
731                eprintln!("{line}");
732            }
733            eprintln!("{}", char_repeat(wr.bot, max_width));
734        }
735
736        // Here is the function that does the heavy lifting
737        fn print_aux(
738            tree: &DisplayRepr,
739            padding: &[usize],
740            fmt: &DisplayFmt,
741            indent: &mut DisplayIndent,
742            protected_tags: &FxHashMap<BorTag, ProtectorKind>,
743            is_last_child: bool,
744            acc: &mut Vec<String>,
745        ) {
746            let mut line = String::new();
747            // Format the permissions on each range.
748            // Looks like `| Act| Res| Res| Act|`.
749            line.push_str(fmt.perm.open);
750            for (i, (perm, &pad)) in tree.rperm.iter().zip(padding.iter()).enumerate() {
751                if i > 0 {
752                    line.push_str(fmt.perm.sep);
753                }
754                let show_perm = fmt.print_perm(*perm);
755                line.push_str(&format!("{}{}", char_repeat(' ', pad), show_perm));
756            }
757            line.push_str(fmt.perm.close);
758            // Format the tree structure.
759            // Main difficulty is handling the indentation properly.
760            indent.write(&mut line);
761            {
762                // padding
763                line.push_str(if is_last_child {
764                    fmt.padding.join_last
765                } else {
766                    fmt.padding.join_middle
767                });
768                line.push_str(fmt.padding.join_default);
769                line.push_str(if tree.children.is_empty() {
770                    fmt.padding.join_default
771                } else {
772                    fmt.padding.join_haschild
773                });
774                line.push_str(fmt.padding.join_default);
775                line.push_str(fmt.padding.join_default);
776            }
777            line.push_str(&fmt.print_tag(tree.tag, &tree.name));
778            let protector = protected_tags.get(&tree.tag);
779            line.push_str(fmt.print_protector(protector));
780            // Push the line to the accumulator then recurse.
781            acc.push(line);
782            let nb_children = tree.children.len();
783            for (i, child) in tree.children.iter().enumerate() {
784                indent.increment(fmt, is_last_child);
785                print_aux(child, padding, fmt, indent, protected_tags, i + 1 == nb_children, acc);
786                indent.decrement(fmt);
787            }
788        }
789    }
790}
791
792const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt {
793    wrapper: DisplayFmtWrapper {
794        top: '─',
795        bot: '─',
796        warning_text: "Warning: this tree is indicative only. Some tags may have been hidden.",
797    },
798    perm: DisplayFmtPermission { open: "|", sep: "|", close: "|", uninit: "----", range_sep: ".." },
799    padding: DisplayFmtPadding {
800        join_middle: "├",
801        join_last: "└",
802        indent_middle: "│ ",
803        indent_last: "  ",
804        join_haschild: "┬",
805        join_default: "─",
806    },
807    accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" },
808};
809
810impl<'tcx> Tree {
811    /// Display the contents of the tree.
812    pub fn print_tree(
813        &self,
814        protected_tags: &FxHashMap<BorTag, ProtectorKind>,
815        show_unnamed: bool,
816    ) -> InterpResult<'tcx> {
817        let mut indenter = DisplayIndent::new();
818        let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
819        if let Some(repr) = DisplayRepr::from(self, show_unnamed) {
820            repr.print(
821                &DEFAULT_FORMATTER,
822                &mut indenter,
823                protected_tags,
824                ranges,
825                /* print warning message about tags not shown */ !show_unnamed,
826            );
827        }
828        interp_ok(())
829    }
830}