1use std::fmt;
2use std::ops::Range;
3
4use rustc_data_structures::fx::FxHashMap;
5use rustc_span::{Span, SpanData};
6
7use crate::borrow_tracker::tree_borrows::perms::{PermTransition, Permission};
8use crate::borrow_tracker::tree_borrows::tree::LocationState;
9use crate::borrow_tracker::tree_borrows::unimap::UniIndex;
10use crate::borrow_tracker::{AccessKind, ProtectorKind};
11use crate::*;
12
13#[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(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: Option<&'node NodeDebugInfo>,
298}
299
300impl TbError<'_> {
301 pub fn build<'tcx>(self) -> InterpErrorKind<'tcx> {
303 use TransitionError::*;
304 let cause = self.access_cause;
305 let accessed = self.accessed_info;
306 let accessed_str =
307 self.accessed_info.map(|v| format!("{v}")).unwrap_or_else(|| "<wildcard>".into());
308 let conflicting = self.conflicting_info;
309 let accessed_is_conflicting = accessed.map(|a| a.tag) == Some(conflicting.tag);
318 let title = format!(
319 "{cause} through {accessed_str} at {alloc_id:?}[{offset:#x}] is forbidden",
320 alloc_id = self.alloc_id,
321 offset = self.error_offset
322 );
323 let (title, details, conflicting_tag_name) = match self.error_kind {
324 ChildAccessForbidden(perm) => {
325 let conflicting_tag_name =
326 if accessed_is_conflicting { "accessed" } else { "conflicting" };
327 let mut details = Vec::new();
328 if !accessed_is_conflicting {
329 details.push(format!(
330 "the accessed tag {accessed_str} is a child of the conflicting tag {conflicting}"
331 ));
332 }
333 let access = cause.print_as_access(false);
334 details.push(format!(
335 "the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids this {access}"
336 ));
337 (title, details, conflicting_tag_name)
338 }
339 ProtectedDisabled(before_disabled) => {
340 let conflicting_tag_name = "protected";
341 let access = cause.print_as_access(true);
342 let details = vec![
343 format!(
344 "the accessed tag {accessed_str} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
345 ),
346 format!(
347 "this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled"
348 ),
349 format!("protected tags must never be Disabled"),
350 ];
351 (title, details, conflicting_tag_name)
352 }
353 ProtectedDealloc => {
354 let conflicting_tag_name = "strongly protected";
355 let details = vec![
356 format!(
357 "the allocation of the accessed tag {accessed_str} also contains the {conflicting_tag_name} tag {conflicting}"
358 ),
359 format!("the {conflicting_tag_name} tag {conflicting} disallows deallocations"),
360 ];
361 (title, details, conflicting_tag_name)
362 }
363 };
364 let mut history = HistoryData::default();
365 if let Some(accessed_info) = self.accessed_info
366 && !accessed_is_conflicting
367 {
368 history.extend(accessed_info.history.forget(), "accessed", false);
369 }
370 history.extend(
371 self.conflicting_info.history.extract_relevant(self.error_offset, self.error_kind),
372 conflicting_tag_name,
373 true,
374 );
375 err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
376 }
377}
378
379pub fn no_valid_exposed_references_error<'tcx>(
382 alloc_id: AllocId,
383 offset: u64,
384 access_cause: AccessCause,
385) -> InterpErrorKind<'tcx> {
386 let title =
387 format!("{access_cause} through <wildcard> at {alloc_id:?}[{offset:#x}] is forbidden");
388 let details = vec![format!("there are no exposed tags which may perform this access here")];
389 let history = HistoryData::default();
390 err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
391}
392
393type S = &'static str;
394struct DisplayFmtWrapper {
414 top: char,
416 bot: char,
418 warning_text: S,
420}
421
422struct DisplayFmtPermission {
440 open: S,
442 sep: S,
444 close: S,
446 uninit: S,
450 range_sep: S,
452}
453
454struct DisplayFmtPadding {
478 join_middle: S,
480 join_last: S,
482 join_haschild: S,
484 join_default: S,
487 indent_middle: S,
489 indent_last: S,
491}
492struct DisplayFmtAccess {
509 yes: S,
511 no: S,
514 meh: S,
517}
518
519struct DisplayFmt {
521 wrapper: DisplayFmtWrapper,
522 perm: DisplayFmtPermission,
523 padding: DisplayFmtPadding,
524 accessed: DisplayFmtAccess,
525}
526impl DisplayFmt {
527 fn print_perm(&self, perm: Option<LocationState>) -> String {
531 if let Some(perm) = perm {
532 format!(
533 "{ac}{st}",
534 ac = if perm.accessed() { self.accessed.yes } else { self.accessed.no },
535 st = perm.permission().short_name(),
536 )
537 } else {
538 format!("{}{}", self.accessed.meh, self.perm.uninit)
539 }
540 }
541
542 fn print_tag(&self, tag: BorTag, name: &Option<String>) -> String {
545 let printable_tag = tag.get();
546 if let Some(name) = name {
547 format!("<{printable_tag}={name}>")
548 } else {
549 format!("<{printable_tag}>")
550 }
551 }
552
553 fn print_protector(&self, protector: Option<&ProtectorKind>) -> &'static str {
555 protector
556 .map(|p| {
557 match *p {
558 ProtectorKind::WeakProtector => " Weakly protected",
559 ProtectorKind::StrongProtector => " Strongly protected",
560 }
561 })
562 .unwrap_or("")
563 }
564}
565
566struct DisplayIndent {
568 curr: String,
569}
570impl DisplayIndent {
571 fn new() -> Self {
572 Self { curr: " ".to_string() }
573 }
574
575 fn increment(&mut self, formatter: &DisplayFmt, is_last: bool) {
579 self.curr.push_str(if is_last {
580 formatter.padding.indent_last
581 } else {
582 formatter.padding.indent_middle
583 });
584 }
585
586 fn decrement(&mut self, formatter: &DisplayFmt) {
588 for _ in 0..formatter.padding.indent_last.len() {
589 let _ = self.curr.pop();
590 }
591 }
592
593 fn write(&self, s: &mut String) {
595 s.push_str(&self.curr);
596 }
597}
598
599fn char_repeat(c: char, n: usize) -> String {
601 std::iter::once(c).cycle().take(n).collect::<String>()
602}
603
604struct DisplayRepr {
608 tag: BorTag,
609 name: Option<String>,
610 rperm: Vec<Option<LocationState>>,
611 children: Vec<DisplayRepr>,
612}
613
614impl DisplayRepr {
615 fn from(tree: &Tree, show_unnamed: bool) -> Option<Self> {
616 let mut v = Vec::new();
617 extraction_aux(tree, tree.root, show_unnamed, &mut v);
618 let Some(root) = v.pop() else {
619 if show_unnamed {
620 unreachable!(
621 "This allocation contains no tags, not even a root. This should not happen."
622 );
623 }
624 eprintln!(
625 "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags."
626 );
627 return None;
628 };
629 assert!(v.is_empty());
630 return Some(root);
631
632 fn extraction_aux(
633 tree: &Tree,
634 idx: UniIndex,
635 show_unnamed: bool,
636 acc: &mut Vec<DisplayRepr>,
637 ) {
638 let node = tree.nodes.get(idx).unwrap();
639 let name = node.debug_info.name.clone();
640 let children_sorted = {
641 let mut children = node.children.iter().cloned().collect::<Vec<_>>();
642 children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag);
643 children
644 };
645 if !show_unnamed && name.is_none() {
646 for child_idx in children_sorted {
648 extraction_aux(tree, child_idx, show_unnamed, acc);
649 }
650 } else {
651 let rperm = tree
653 .locations
654 .iter_all()
655 .map(move |(_offset, loc)| {
656 let perm = loc.perms.get(idx);
657 perm.cloned()
658 })
659 .collect::<Vec<_>>();
660 let mut children = Vec::new();
661 for child_idx in children_sorted {
662 extraction_aux(tree, child_idx, show_unnamed, &mut children);
663 }
664 acc.push(DisplayRepr { tag: node.tag, name, rperm, children });
665 }
666 }
667 }
668 fn print(
669 &self,
670 fmt: &DisplayFmt,
671 indenter: &mut DisplayIndent,
672 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
673 ranges: Vec<Range<u64>>,
674 print_warning: bool,
675 ) {
676 let mut block = Vec::new();
677 let (range_header, range_padding) = {
681 let mut header_top = String::new();
682 header_top.push_str("0..");
683 let mut padding = Vec::new();
684 for (i, range) in ranges.iter().enumerate() {
685 if i > 0 {
686 header_top.push_str(fmt.perm.range_sep);
687 }
688 let s = range.end.to_string();
689 let l = s.chars().count() + fmt.perm.range_sep.chars().count();
690 {
691 let target_len =
692 fmt.perm.uninit.chars().count() + fmt.accessed.yes.chars().count() + 1;
693 let tot_len = target_len.max(l);
694 let header_top_pad_len = target_len.saturating_sub(l);
695 let body_pad_len = tot_len.saturating_sub(target_len);
696 header_top.push_str(&format!("{}{}", char_repeat(' ', header_top_pad_len), s));
697 padding.push(body_pad_len);
698 }
699 }
700 ([header_top], padding)
701 };
702 for s in range_header {
703 block.push(s);
704 }
705 print_aux(
707 self,
708 &range_padding,
709 fmt,
710 indenter,
711 protected_tags,
712 true, &mut block,
714 );
715 {
717 let wr = &fmt.wrapper;
718 let max_width = {
719 let block_width = block.iter().map(|s| s.chars().count()).max().unwrap();
720 if print_warning {
721 block_width.max(wr.warning_text.chars().count())
722 } else {
723 block_width
724 }
725 };
726 eprintln!("{}", char_repeat(wr.top, max_width));
727 if print_warning {
728 eprintln!("{}", wr.warning_text,);
729 }
730 for line in block {
731 eprintln!("{line}");
732 }
733 eprintln!("{}", char_repeat(wr.bot, max_width));
734 }
735
736 fn print_aux(
738 tree: &DisplayRepr,
739 padding: &[usize],
740 fmt: &DisplayFmt,
741 indent: &mut DisplayIndent,
742 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
743 is_last_child: bool,
744 acc: &mut Vec<String>,
745 ) {
746 let mut line = String::new();
747 line.push_str(fmt.perm.open);
750 for (i, (perm, &pad)) in tree.rperm.iter().zip(padding.iter()).enumerate() {
751 if i > 0 {
752 line.push_str(fmt.perm.sep);
753 }
754 let show_perm = fmt.print_perm(*perm);
755 line.push_str(&format!("{}{}", char_repeat(' ', pad), show_perm));
756 }
757 line.push_str(fmt.perm.close);
758 indent.write(&mut line);
761 {
762 line.push_str(if is_last_child {
764 fmt.padding.join_last
765 } else {
766 fmt.padding.join_middle
767 });
768 line.push_str(fmt.padding.join_default);
769 line.push_str(if tree.children.is_empty() {
770 fmt.padding.join_default
771 } else {
772 fmt.padding.join_haschild
773 });
774 line.push_str(fmt.padding.join_default);
775 line.push_str(fmt.padding.join_default);
776 }
777 line.push_str(&fmt.print_tag(tree.tag, &tree.name));
778 let protector = protected_tags.get(&tree.tag);
779 line.push_str(fmt.print_protector(protector));
780 acc.push(line);
782 let nb_children = tree.children.len();
783 for (i, child) in tree.children.iter().enumerate() {
784 indent.increment(fmt, is_last_child);
785 print_aux(child, padding, fmt, indent, protected_tags, i + 1 == nb_children, acc);
786 indent.decrement(fmt);
787 }
788 }
789 }
790}
791
792const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt {
793 wrapper: DisplayFmtWrapper {
794 top: '─',
795 bot: '─',
796 warning_text: "Warning: this tree is indicative only. Some tags may have been hidden.",
797 },
798 perm: DisplayFmtPermission { open: "|", sep: "|", close: "|", uninit: "----", range_sep: ".." },
799 padding: DisplayFmtPadding {
800 join_middle: "├",
801 join_last: "└",
802 indent_middle: "│ ",
803 indent_last: " ",
804 join_haschild: "┬",
805 join_default: "─",
806 },
807 accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" },
808};
809
810impl<'tcx> Tree {
811 pub fn print_tree(
813 &self,
814 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
815 show_unnamed: bool,
816 ) -> InterpResult<'tcx> {
817 let mut indenter = DisplayIndent::new();
818 let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
819 if let Some(repr) = DisplayRepr::from(self, show_unnamed) {
820 repr.print(
821 &DEFAULT_FORMATTER,
822 &mut indenter,
823 protected_tags,
824 ranges,
825 !show_unnamed,
826 );
827 }
828 interp_ok(())
829 }
830}