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