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