Skip to main content

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