1use std::fmt;
2use std::ops::Range;
3
4use rustc_data_structures::fx::FxHashMap;
5use rustc_span::{Span, SpanData};
6
7use crate::borrow_tracker::ProtectorKind;
8use crate::borrow_tracker::tree_borrows::perms::{PermTransition, Permission};
9use crate::borrow_tracker::tree_borrows::tree::LocationState;
10use crate::borrow_tracker::tree_borrows::unimap::UniIndex;
11use crate::*;
12
13#[derive(Clone, Copy, Debug)]
16pub enum AccessCause {
17 Explicit(AccessKind),
18 Reborrow,
19 Dealloc,
20 FnExit(AccessKind),
21}
22
23impl fmt::Display for AccessCause {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Self::Explicit(kind) => write!(f, "{kind}"),
27 Self::Reborrow => write!(f, "reborrow"),
28 Self::Dealloc => write!(f, "deallocation"),
29 Self::FnExit(_) => unreachable!("protector accesses can never be the source of UB"),
34 }
35 }
36}
37
38impl AccessCause {
39 fn print_as_access(self, is_foreign: bool) -> String {
40 let rel = if is_foreign { "foreign" } else { "child" };
41 match self {
42 Self::Explicit(kind) => format!("{rel} {kind}"),
43 Self::Reborrow => format!("reborrow (acting as a {rel} read access)"),
44 Self::Dealloc => format!("deallocation (acting as a {rel} write access)"),
45 Self::FnExit(kind) => format!("protector release (acting as a {rel} {kind})"),
46 }
47 }
48}
49
50#[derive(Clone, Debug)]
52pub struct Event {
53 pub transition: PermTransition,
55 pub access_cause: AccessCause,
57 pub is_foreign: bool,
59 pub access_range: Option<AllocRange>,
63 pub transition_range: Range<u64>,
76 pub span: Span,
78}
79
80#[derive(Clone, Debug)]
85pub struct History {
86 tag: BorTag,
87 created: (Span, Permission),
88 events: Vec<Event>,
89}
90
91#[derive(Debug, Clone, Default)]
96pub struct HistoryData {
97 pub events: Vec<(Option<SpanData>, String)>, }
99
100impl History {
101 pub fn push(&mut self, event: Event) {
103 self.events.push(event);
104 }
105}
106
107impl HistoryData {
108 fn extend(&mut self, new_history: History, tag_name: &'static str, show_initial_state: bool) {
112 let History { tag, created, events } = new_history;
113 let this = format!("the {tag_name} tag {tag:?}");
114 let msg_initial_state = format!(", in the initial state {}", created.1);
115 let msg_creation = format!(
116 "{this} was created here{maybe_msg_initial_state}",
117 maybe_msg_initial_state = if show_initial_state { &msg_initial_state } else { "" },
118 );
119
120 self.events.push((Some(created.0.data()), msg_creation));
121 for &Event {
122 transition,
123 is_foreign,
124 access_cause,
125 access_range,
126 span,
127 transition_range: _,
128 } in &events
129 {
130 let access = access_cause.print_as_access(is_foreign);
133 let access_range_text = match access_range {
134 Some(r) => format!("at offsets {r:?}"),
135 None => format!("on every location previously accessed by this tag"),
136 };
137 self.events.push((
138 Some(span.data()),
139 format!(
140 "{this} later transitioned to {endpoint} due to a {access} {access_range_text}",
141 endpoint = transition.endpoint()
142 ),
143 ));
144 self.events
145 .push((None, format!("this transition corresponds to {}", transition.summary())));
146 }
147 }
148}
149
150#[derive(Clone, Debug)]
153pub struct NodeDebugInfo {
154 pub tag: BorTag,
156 pub name: Option<String>,
161 pub history: History,
169}
170
171impl NodeDebugInfo {
172 pub fn new(tag: BorTag, initial: Permission, span: Span) -> Self {
175 let history = History { tag, created: (span, initial), events: Vec::new() };
176 Self { tag, name: None, history }
177 }
178
179 pub fn add_name(&mut self, name: &str) {
182 if let Some(ref mut prev_name) = &mut self.name {
183 prev_name.push_str(", ");
184 prev_name.push_str(name);
185 } else {
186 self.name = Some(String::from(name));
187 }
188 }
189}
190
191impl fmt::Display for NodeDebugInfo {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 if let Some(ref name) = self.name {
194 write!(f, "{tag:?} ({name})", tag = self.tag)
195 } else {
196 write!(f, "{tag:?}", tag = self.tag)
197 }
198 }
199}
200
201impl<'tcx> Tree {
202 fn nth_parent(&self, tag: BorTag, nth_parent: u8) -> Option<BorTag> {
207 let mut idx = self.tag_mapping.get(&tag).unwrap();
208 for _ in 0..nth_parent {
209 let node = self.nodes.get(idx).unwrap();
210 idx = node.parent?;
211 }
212 Some(self.nodes.get(idx).unwrap().tag)
213 }
214
215 pub fn give_pointer_debug_name(
217 &mut self,
218 tag: BorTag,
219 nth_parent: u8,
220 name: &str,
221 ) -> InterpResult<'tcx> {
222 let tag = self.nth_parent(tag, nth_parent).unwrap();
223 let idx = self.tag_mapping.get(&tag).unwrap();
224 if let Some(node) = self.nodes.get_mut(idx) {
225 node.debug_info.add_name(name);
226 } else {
227 eprintln!("Tag {tag:?} (to be named '{name}') not found!");
228 }
229 interp_ok(())
230 }
231
232 pub fn is_allocation_of(&self, tag: BorTag) -> bool {
234 self.tag_mapping.contains_key(&tag)
235 }
236}
237
238#[derive(Debug, Clone, Copy)]
239pub(super) enum TransitionError {
240 ChildAccessForbidden(Permission),
245 ProtectedDisabled(Permission),
251 ProtectedDealloc,
254}
255
256impl History {
257 fn forget(&self) -> Self {
259 History { events: Vec::new(), created: self.created, tag: self.tag }
260 }
261
262 fn extract_relevant(&self, error_offset: u64, error_kind: TransitionError) -> Self {
265 History {
266 events: self
267 .events
268 .iter()
269 .filter(|e| e.transition_range.contains(&error_offset))
270 .filter(|e| e.transition.is_relevant(error_kind))
271 .cloned()
272 .collect::<Vec<_>>(),
273 created: self.created,
274 tag: self.tag,
275 }
276 }
277}
278
279pub(super) struct TbError<'node> {
281 pub error_kind: TransitionError,
283 pub alloc_id: AllocId,
285 pub error_offset: u64,
287 pub conflicting_info: &'node NodeDebugInfo,
292 pub access_cause: AccessCause,
294 pub accessed_info: &'node NodeDebugInfo,
297}
298
299impl TbError<'_> {
300 pub fn build<'tcx>(self) -> InterpErrorKind<'tcx> {
302 use TransitionError::*;
303 let cause = self.access_cause;
304 let accessed = self.accessed_info;
305 let conflicting = self.conflicting_info;
306 let accessed_is_conflicting = accessed.tag == conflicting.tag;
307 let title = format!(
308 "{cause} through {accessed} at {alloc_id:?}[{offset:#x}] is forbidden",
309 alloc_id = self.alloc_id,
310 offset = self.error_offset
311 );
312 let (title, details, conflicting_tag_name) = match self.error_kind {
313 ChildAccessForbidden(perm) => {
314 let conflicting_tag_name =
315 if accessed_is_conflicting { "accessed" } else { "conflicting" };
316 let mut details = Vec::new();
317 if !accessed_is_conflicting {
318 details.push(format!(
319 "the accessed tag {accessed} is a child of the conflicting tag {conflicting}"
320 ));
321 }
322 let access = cause.print_as_access(false);
323 details.push(format!(
324 "the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids this {access}"
325 ));
326 (title, details, conflicting_tag_name)
327 }
328 ProtectedDisabled(before_disabled) => {
329 let conflicting_tag_name = "protected";
330 let access = cause.print_as_access(true);
331 let details = vec![
332 format!(
333 "the accessed tag {accessed} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
334 ),
335 format!(
336 "this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled"
337 ),
338 format!("protected tags must never be Disabled"),
339 ];
340 (title, details, conflicting_tag_name)
341 }
342 ProtectedDealloc => {
343 let conflicting_tag_name = "strongly protected";
344 let details = vec![
345 format!(
346 "the allocation of the accessed tag {accessed} also contains the {conflicting_tag_name} tag {conflicting}"
347 ),
348 format!("the {conflicting_tag_name} tag {conflicting} disallows deallocations"),
349 ];
350 (title, details, conflicting_tag_name)
351 }
352 };
353 let mut history = HistoryData::default();
354 if !accessed_is_conflicting {
355 history.extend(self.accessed_info.history.forget(), "accessed", false);
356 }
357 history.extend(
358 self.conflicting_info.history.extract_relevant(self.error_offset, self.error_kind),
359 conflicting_tag_name,
360 true,
361 );
362 err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
363 }
364}
365
366type S = &'static str;
367struct DisplayFmtWrapper {
387 top: char,
389 bot: char,
391 warning_text: S,
393}
394
395struct DisplayFmtPermission {
413 open: S,
415 sep: S,
417 close: S,
419 uninit: S,
423 range_sep: S,
425}
426
427struct DisplayFmtPadding {
451 join_middle: S,
453 join_last: S,
455 join_haschild: S,
457 join_default: S,
460 indent_middle: S,
462 indent_last: S,
464}
465struct DisplayFmtAccess {
482 yes: S,
484 no: S,
487 meh: S,
490}
491
492struct DisplayFmt {
494 wrapper: DisplayFmtWrapper,
495 perm: DisplayFmtPermission,
496 padding: DisplayFmtPadding,
497 accessed: DisplayFmtAccess,
498}
499impl DisplayFmt {
500 fn print_perm(&self, perm: Option<LocationState>) -> String {
504 if let Some(perm) = perm {
505 format!(
506 "{ac}{st}",
507 ac = if perm.is_initialized() { self.accessed.yes } else { self.accessed.no },
508 st = perm.permission().short_name(),
509 )
510 } else {
511 format!("{}{}", self.accessed.meh, self.perm.uninit)
512 }
513 }
514
515 fn print_tag(&self, tag: BorTag, name: &Option<String>) -> String {
518 let printable_tag = tag.get();
519 if let Some(name) = name {
520 format!("<{printable_tag}={name}>")
521 } else {
522 format!("<{printable_tag}>")
523 }
524 }
525
526 fn print_protector(&self, protector: Option<&ProtectorKind>) -> &'static str {
528 protector
529 .map(|p| {
530 match *p {
531 ProtectorKind::WeakProtector => " Weakly protected",
532 ProtectorKind::StrongProtector => " Strongly protected",
533 }
534 })
535 .unwrap_or("")
536 }
537}
538
539struct DisplayIndent {
541 curr: String,
542}
543impl DisplayIndent {
544 fn new() -> Self {
545 Self { curr: " ".to_string() }
546 }
547
548 fn increment(&mut self, formatter: &DisplayFmt, is_last: bool) {
552 self.curr.push_str(if is_last {
553 formatter.padding.indent_last
554 } else {
555 formatter.padding.indent_middle
556 });
557 }
558
559 fn decrement(&mut self, formatter: &DisplayFmt) {
561 for _ in 0..formatter.padding.indent_last.len() {
562 let _ = self.curr.pop();
563 }
564 }
565
566 fn write(&self, s: &mut String) {
568 s.push_str(&self.curr);
569 }
570}
571
572fn char_repeat(c: char, n: usize) -> String {
574 std::iter::once(c).cycle().take(n).collect::<String>()
575}
576
577struct DisplayRepr {
581 tag: BorTag,
582 name: Option<String>,
583 rperm: Vec<Option<LocationState>>,
584 children: Vec<DisplayRepr>,
585}
586
587impl DisplayRepr {
588 fn from(tree: &Tree, show_unnamed: bool) -> Option<Self> {
589 let mut v = Vec::new();
590 extraction_aux(tree, tree.root, show_unnamed, &mut v);
591 let Some(root) = v.pop() else {
592 if show_unnamed {
593 unreachable!(
594 "This allocation contains no tags, not even a root. This should not happen."
595 );
596 }
597 eprintln!(
598 "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags."
599 );
600 return None;
601 };
602 assert!(v.is_empty());
603 return Some(root);
604
605 fn extraction_aux(
606 tree: &Tree,
607 idx: UniIndex,
608 show_unnamed: bool,
609 acc: &mut Vec<DisplayRepr>,
610 ) {
611 let node = tree.nodes.get(idx).unwrap();
612 let name = node.debug_info.name.clone();
613 let children_sorted = {
614 let mut children = node.children.iter().cloned().collect::<Vec<_>>();
615 children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag);
616 children
617 };
618 if !show_unnamed && name.is_none() {
619 for child_idx in children_sorted {
621 extraction_aux(tree, child_idx, show_unnamed, acc);
622 }
623 } else {
624 let rperm = tree
626 .rperms
627 .iter_all()
628 .map(move |(_offset, perms)| {
629 let perm = perms.get(idx);
630 perm.cloned()
631 })
632 .collect::<Vec<_>>();
633 let mut children = Vec::new();
634 for child_idx in children_sorted {
635 extraction_aux(tree, child_idx, show_unnamed, &mut children);
636 }
637 acc.push(DisplayRepr { tag: node.tag, name, rperm, children });
638 }
639 }
640 }
641 fn print(
642 &self,
643 fmt: &DisplayFmt,
644 indenter: &mut DisplayIndent,
645 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
646 ranges: Vec<Range<u64>>,
647 print_warning: bool,
648 ) {
649 let mut block = Vec::new();
650 let (range_header, range_padding) = {
654 let mut header_top = String::new();
655 header_top.push_str("0..");
656 let mut padding = Vec::new();
657 for (i, range) in ranges.iter().enumerate() {
658 if i > 0 {
659 header_top.push_str(fmt.perm.range_sep);
660 }
661 let s = range.end.to_string();
662 let l = s.chars().count() + fmt.perm.range_sep.chars().count();
663 {
664 let target_len =
665 fmt.perm.uninit.chars().count() + fmt.accessed.yes.chars().count() + 1;
666 let tot_len = target_len.max(l);
667 let header_top_pad_len = target_len.saturating_sub(l);
668 let body_pad_len = tot_len.saturating_sub(target_len);
669 header_top.push_str(&format!("{}{}", char_repeat(' ', header_top_pad_len), s));
670 padding.push(body_pad_len);
671 }
672 }
673 ([header_top], padding)
674 };
675 for s in range_header {
676 block.push(s);
677 }
678 print_aux(
680 self,
681 &range_padding,
682 fmt,
683 indenter,
684 protected_tags,
685 true, &mut block,
687 );
688 {
690 let wr = &fmt.wrapper;
691 let max_width = {
692 let block_width = block.iter().map(|s| s.chars().count()).max().unwrap();
693 if print_warning {
694 block_width.max(wr.warning_text.chars().count())
695 } else {
696 block_width
697 }
698 };
699 eprintln!("{}", char_repeat(wr.top, max_width));
700 if print_warning {
701 eprintln!("{}", wr.warning_text,);
702 }
703 for line in block {
704 eprintln!("{line}");
705 }
706 eprintln!("{}", char_repeat(wr.bot, max_width));
707 }
708
709 fn print_aux(
711 tree: &DisplayRepr,
712 padding: &[usize],
713 fmt: &DisplayFmt,
714 indent: &mut DisplayIndent,
715 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
716 is_last_child: bool,
717 acc: &mut Vec<String>,
718 ) {
719 let mut line = String::new();
720 line.push_str(fmt.perm.open);
723 for (i, (perm, &pad)) in tree.rperm.iter().zip(padding.iter()).enumerate() {
724 if i > 0 {
725 line.push_str(fmt.perm.sep);
726 }
727 let show_perm = fmt.print_perm(*perm);
728 line.push_str(&format!("{}{}", char_repeat(' ', pad), show_perm));
729 }
730 line.push_str(fmt.perm.close);
731 indent.write(&mut line);
734 {
735 line.push_str(if is_last_child {
737 fmt.padding.join_last
738 } else {
739 fmt.padding.join_middle
740 });
741 line.push_str(fmt.padding.join_default);
742 line.push_str(if tree.children.is_empty() {
743 fmt.padding.join_default
744 } else {
745 fmt.padding.join_haschild
746 });
747 line.push_str(fmt.padding.join_default);
748 line.push_str(fmt.padding.join_default);
749 }
750 line.push_str(&fmt.print_tag(tree.tag, &tree.name));
751 let protector = protected_tags.get(&tree.tag);
752 line.push_str(fmt.print_protector(protector));
753 acc.push(line);
755 let nb_children = tree.children.len();
756 for (i, child) in tree.children.iter().enumerate() {
757 indent.increment(fmt, is_last_child);
758 print_aux(child, padding, fmt, indent, protected_tags, i + 1 == nb_children, acc);
759 indent.decrement(fmt);
760 }
761 }
762 }
763}
764
765const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt {
766 wrapper: DisplayFmtWrapper {
767 top: '─',
768 bot: '─',
769 warning_text: "Warning: this tree is indicative only. Some tags may have been hidden.",
770 },
771 perm: DisplayFmtPermission { open: "|", sep: "|", close: "|", uninit: "----", range_sep: ".." },
772 padding: DisplayFmtPadding {
773 join_middle: "├",
774 join_last: "└",
775 indent_middle: "│ ",
776 indent_last: " ",
777 join_haschild: "┬",
778 join_default: "─",
779 },
780 accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" },
781};
782
783impl<'tcx> Tree {
784 pub fn print_tree(
786 &self,
787 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
788 show_unnamed: bool,
789 ) -> InterpResult<'tcx> {
790 let mut indenter = DisplayIndent::new();
791 let ranges = self.rperms.iter_all().map(|(range, _perms)| range).collect::<Vec<_>>();
792 if let Some(repr) = DisplayRepr::from(self, show_unnamed) {
793 repr.print(
794 &DEFAULT_FORMATTER,
795 &mut indenter,
796 protected_tags,
797 ranges,
798 !show_unnamed,
799 );
800 }
801 interp_ok(())
802 }
803}