1use std::fmt;
2use std::ops::Range;
3
4use rustc_data_structures::fx::FxHashMap;
5use rustc_span::Span;
6
7use crate::borrow_tracker::tree_borrows::perms::{PermTransition, Permission};
8use crate::borrow_tracker::tree_borrows::tree::LocationState;
9use crate::borrow_tracker::tree_borrows::unimap::UniIndex;
10use crate::borrow_tracker::{AccessKind, ProtectorKind};
11use crate::*;
12
13#[derive(Clone, Copy, Debug)]
16pub enum AccessCause {
17 Explicit(AccessKind),
18 Reborrow(AccessKind),
19 Dealloc,
20 FnExit(AccessKind),
21}
22
23impl fmt::Display for AccessCause {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Self::Explicit(kind) => write!(f, "{kind}"),
27 Self::Reborrow(_) => write!(f, "reborrow"),
28 Self::Dealloc => write!(f, "deallocation"),
29 Self::FnExit(_) => unreachable!("protector accesses can never be the source of UB"),
34 }
35 }
36}
37
38impl AccessCause {
39 fn print_as_access(self, is_foreign: bool) -> String {
40 let rel = if is_foreign { "foreign" } else { "child" };
41 match self {
42 Self::Explicit(kind) => format!("{rel} {kind}"),
43 Self::Reborrow(kind) => format!("reborrow (acting as a {rel} {kind})"),
44 Self::Dealloc => format!("deallocation (acting as a {rel} write access)"),
45 Self::FnExit(kind) => format!("protector release (acting as a {rel} {kind})"),
46 }
47 }
48}
49
50#[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)]
83pub struct DiagnosticInfo {
84 pub alloc_id: AllocId,
85 pub span: Span,
86 pub transition_range: Range<u64>,
89 pub access_range: Option<AllocRange>,
91 pub access_cause: AccessCause,
92}
93impl DiagnosticInfo {
94 pub fn create_event(&self, transition: PermTransition, is_foreign: bool) -> Event {
96 Event {
97 transition,
98 is_foreign,
99 access_cause: self.access_cause,
100 access_range: self.access_range,
101 transition_range: self.transition_range.clone(),
102 span: self.span,
103 }
104 }
105}
106#[derive(Clone, Debug)]
111pub struct History {
112 tag: BorTag,
113 created: (Span, Permission),
114 events: Vec<Event>,
115}
116
117#[derive(Debug, Clone, Default)]
119pub struct HistoryData {
120 pub events: Vec<(Option<Span>, String)>, }
122
123impl History {
124 pub fn push(&mut self, event: Event) {
126 self.events.push(event);
127 }
128}
129
130impl HistoryData {
131 fn extend(&mut self, new_history: History, tag_name: &'static str, show_initial_state: bool) {
133 let History { tag, created, events } = new_history;
134 let this = format!("the {tag_name} tag {tag:?}");
135 let msg_initial_state = format!(", in the initial state {}", created.1);
136 let msg_creation = format!(
137 "{this} was created here{maybe_msg_initial_state}",
138 maybe_msg_initial_state = if show_initial_state { &msg_initial_state } else { "" },
139 );
140
141 self.events.push((Some(created.0), msg_creation));
142 for &Event {
143 transition,
144 is_foreign,
145 access_cause,
146 access_range,
147 span,
148 transition_range: _,
149 } in &events
150 {
151 let access = access_cause.print_as_access(is_foreign);
154 let access_range_text = match access_range {
155 Some(r) => format!("at offsets {r}"),
156 None => format!("on every location previously accessed by this tag"),
157 };
158 self.events.push((
159 Some(span),
160 format!(
161 "{this} later transitioned to {endpoint} due to a {access} {access_range_text}",
162 endpoint = transition.endpoint()
163 ),
164 ));
165 self.events
166 .push((None, format!("this transition corresponds to {}", transition.summary())));
167 }
168 }
169}
170
171#[derive(Clone, Debug)]
174pub struct NodeDebugInfo {
175 pub tag: BorTag,
177 pub name: Option<String>,
182 pub history: History,
190}
191
192impl NodeDebugInfo {
193 pub fn new(tag: BorTag, initial: Permission, span: Span) -> Self {
196 let history = History { tag, created: (span, initial), events: Vec::new() };
197 Self { tag, name: None, history }
198 }
199
200 pub fn add_name(&mut self, name: &str) {
203 if let Some(prev_name) = &mut self.name {
204 prev_name.push_str(", ");
205 prev_name.push_str(name);
206 } else {
207 self.name = Some(String::from(name));
208 }
209 }
210}
211
212impl fmt::Display for NodeDebugInfo {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 if let Some(ref name) = self.name {
215 write!(f, "{tag:?} ({name})", tag = self.tag)
216 } else {
217 write!(f, "{tag:?}", tag = self.tag)
218 }
219 }
220}
221
222impl<'tcx> Tree {
223 fn nth_parent(&self, tag: BorTag, nth_parent: u8) -> Option<BorTag> {
228 let mut idx = self.tag_mapping.get(&tag).unwrap();
229 for _ in 0..nth_parent {
230 let node = self.nodes.get(idx).unwrap();
231 idx = node.parent?;
232 }
233 Some(self.nodes.get(idx).unwrap().tag)
234 }
235
236 pub fn give_pointer_debug_name(
238 &mut self,
239 tag: BorTag,
240 nth_parent: u8,
241 name: &str,
242 ) -> InterpResult<'tcx> {
243 let tag = self.nth_parent(tag, nth_parent).unwrap();
244 let idx = self.tag_mapping.get(&tag).unwrap();
245 if let Some(node) = self.nodes.get_mut(idx) {
246 node.debug_info.add_name(name);
247 } else {
248 eprintln!("Tag {tag:?} (to be named '{name}') not found!");
249 }
250 interp_ok(())
251 }
252
253 pub fn is_allocation_of(&self, tag: BorTag) -> bool {
255 self.tag_mapping.contains_key(&tag)
256 }
257}
258
259#[derive(Debug, Clone, Copy)]
260pub(super) enum TransitionError {
261 ChildAccessForbidden(Permission),
266 ProtectedDisabled(Permission),
272 ProtectedDealloc,
275}
276
277impl History {
278 fn forget(&self) -> Self {
280 History { events: Vec::new(), created: self.created, tag: self.tag }
281 }
282
283 fn extract_relevant(&self, error_offset: u64, error_kind: TransitionError) -> Self {
286 History {
287 events: self
288 .events
289 .iter()
290 .filter(|e| e.transition_range.contains(&error_offset))
291 .filter(|e| e.transition.is_relevant(error_kind))
292 .cloned()
293 .collect::<Vec<_>>(),
294 created: self.created,
295 tag: self.tag,
296 }
297 }
298}
299
300pub(super) struct TbError<'node> {
302 pub error_kind: TransitionError,
304 pub access_info: &'node DiagnosticInfo,
306 pub conflicting_node_info: &'node NodeDebugInfo,
311 pub accessed_node_info: Option<&'node NodeDebugInfo>,
315}
316
317impl TbError<'_> {
318 pub fn build<'tcx>(self) -> InterpErrorKind<'tcx> {
320 use TransitionError::*;
321 let cause = self.access_info.access_cause;
322 let error_offset = self.access_info.transition_range.start;
323 let accessed = self.accessed_node_info;
324 let accessed_str =
325 self.accessed_node_info.map(|v| format!("{v}")).unwrap_or_else(|| "<wildcard>".into());
326 let conflicting = self.conflicting_node_info;
327 let accessed_is_conflicting = accessed.map(|a| a.tag) == Some(conflicting.tag);
336 let title = format!(
337 "{cause} through {accessed_str} at {alloc_id}[{error_offset:#x}] is forbidden",
338 alloc_id = self.access_info.alloc_id
339 );
340 let (title, details, conflicting_tag_name) = match self.error_kind {
341 ChildAccessForbidden(perm) => {
342 let conflicting_tag_name =
343 if accessed_is_conflicting { "accessed" } else { "conflicting" };
344 let mut details = Vec::new();
345 if !accessed_is_conflicting {
346 details.push(format!(
347 "the accessed tag {accessed_str} is a child of the conflicting tag {conflicting}"
348 ));
349 }
350 let access = cause.print_as_access(false);
351 details.push(format!(
352 "the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids this {access}"
353 ));
354 (title, details, conflicting_tag_name)
355 }
356 ProtectedDisabled(before_disabled) => {
357 let conflicting_tag_name = "protected";
358 let access = cause.print_as_access(true);
359 let details = vec![
360 format!(
361 "the accessed tag {accessed_str} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
362 ),
363 format!(
364 "this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled"
365 ),
366 format!("protected tags must never be Disabled"),
367 ];
368 (title, details, conflicting_tag_name)
369 }
370 ProtectedDealloc => {
371 let conflicting_tag_name = "strongly protected";
372 let details = vec![
373 format!(
374 "the allocation of the accessed tag {accessed_str} also contains the {conflicting_tag_name} tag {conflicting}"
375 ),
376 format!("the {conflicting_tag_name} tag {conflicting} disallows deallocations"),
377 ];
378 (title, details, conflicting_tag_name)
379 }
380 };
381 let mut history = HistoryData::default();
382 if let Some(accessed_info) = self.accessed_node_info
383 && !accessed_is_conflicting
384 {
385 history.extend(accessed_info.history.forget(), "accessed", false);
386 }
387 history.extend(
388 self.conflicting_node_info.history.extract_relevant(error_offset, self.error_kind),
389 conflicting_tag_name,
390 true,
391 );
392 err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
393 }
394}
395
396pub fn no_valid_exposed_references_error<'tcx>(
399 DiagnosticInfo { alloc_id, transition_range, access_cause, .. }: &DiagnosticInfo,
400) -> InterpErrorKind<'tcx> {
401 let title = format!(
402 "{access_cause} through <wildcard> at {alloc_id:?}[{offset:#x}] is forbidden",
403 offset = transition_range.start
404 );
405 let details = vec![format!("there are no exposed tags which may perform this access here")];
406 let history = HistoryData::default();
407 err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
408}
409
410type S = &'static str;
411struct DisplayFmtWrapper {
431 top: char,
433 bot: char,
435 warning_text: S,
437}
438
439struct DisplayFmtPermission {
457 open: S,
459 sep: S,
461 close: S,
463 uninit: S,
467 range_sep: S,
469}
470
471struct DisplayFmtPadding {
495 join_middle: S,
497 join_last: S,
499 join_haschild: S,
501 join_default: S,
504 indent_middle: S,
506 indent_last: S,
508 wildcard_root: S,
510}
511struct DisplayFmtAccess {
528 yes: S,
530 no: S,
533 meh: S,
536}
537
538struct DisplayFmt {
540 wrapper: DisplayFmtWrapper,
541 perm: DisplayFmtPermission,
542 padding: DisplayFmtPadding,
543 accessed: DisplayFmtAccess,
544}
545impl DisplayFmt {
546 fn print_perm(&self, perm: Option<LocationState>) -> String {
550 if let Some(perm) = perm {
551 format!(
552 "{ac}{st}",
553 ac = if perm.accessed() { self.accessed.yes } else { self.accessed.no },
554 st = perm.permission().short_name(),
555 )
556 } else {
557 format!("{}{}", self.accessed.meh, self.perm.uninit)
558 }
559 }
560
561 fn print_tag(&self, tag: BorTag, name: &Option<String>) -> String {
564 let printable_tag = tag.get();
565 if let Some(name) = name {
566 format!("<{printable_tag}={name}>")
567 } else {
568 format!("<{printable_tag}>")
569 }
570 }
571
572 fn print_protector(&self, protector: Option<&ProtectorKind>) -> &'static str {
574 protector
575 .map(|p| {
576 match *p {
577 ProtectorKind::WeakProtector => " Weakly protected",
578 ProtectorKind::StrongProtector => " Strongly protected",
579 }
580 })
581 .unwrap_or("")
582 }
583
584 fn print_exposed(&self, exposed: bool) -> S {
586 if exposed { " (exposed)" } else { "" }
587 }
588}
589
590struct DisplayIndent {
592 curr: String,
593}
594impl DisplayIndent {
595 fn new() -> Self {
596 Self { curr: " ".to_string() }
597 }
598
599 fn increment(&mut self, formatter: &DisplayFmt, is_last: bool) {
603 self.curr.push_str(if is_last {
604 formatter.padding.indent_last
605 } else {
606 formatter.padding.indent_middle
607 });
608 }
609
610 fn decrement(&mut self, formatter: &DisplayFmt) {
612 for _ in 0..formatter.padding.indent_last.len() {
613 let _ = self.curr.pop();
614 }
615 }
616
617 fn write(&self, s: &mut String) {
619 s.push_str(&self.curr);
620 }
621}
622
623fn char_repeat(c: char, n: usize) -> String {
625 std::iter::once(c).cycle().take(n).collect::<String>()
626}
627
628struct DisplayRepr {
632 tag: BorTag,
633 name: Option<String>,
634 exposed: bool,
635 rperm: Vec<Option<LocationState>>,
636 children: Vec<DisplayRepr>,
637}
638
639impl DisplayRepr {
640 fn from(tree: &Tree, root: UniIndex, show_unnamed: bool) -> Option<Self> {
641 let mut v = Vec::new();
642 extraction_aux(tree, root, show_unnamed, &mut v);
643 let Some(root) = v.pop() else {
644 if show_unnamed {
645 unreachable!(
646 "This allocation contains no tags, not even a root. This should not happen."
647 );
648 }
649 return None;
650 };
651 assert!(v.is_empty());
652 return Some(root);
653
654 fn extraction_aux(
655 tree: &Tree,
656 idx: UniIndex,
657 show_unnamed: bool,
658 acc: &mut Vec<DisplayRepr>,
659 ) {
660 let node = tree.nodes.get(idx).unwrap();
661 let name = node.debug_info.name.clone();
662 let exposed = node.is_exposed;
663 let children_sorted = {
664 let mut children = node.children.iter().cloned().collect::<Vec<_>>();
665 children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag);
666 children
667 };
668 if !show_unnamed && name.is_none() {
669 for child_idx in children_sorted {
671 extraction_aux(tree, child_idx, show_unnamed, acc);
672 }
673 } else {
674 let rperm = tree
676 .locations
677 .iter_all()
678 .map(move |(_offset, loc)| {
679 let perm = loc.perms.get(idx);
680 perm.cloned()
681 })
682 .collect::<Vec<_>>();
683 let mut children = Vec::new();
684 for child_idx in children_sorted {
685 extraction_aux(tree, child_idx, show_unnamed, &mut children);
686 }
687 acc.push(DisplayRepr { tag: node.tag, name, rperm, children, exposed });
688 }
689 }
690 }
691 fn print(
692 main_root: &Option<DisplayRepr>,
693 wildcard_subtrees: &[DisplayRepr],
694 fmt: &DisplayFmt,
695 indenter: &mut DisplayIndent,
696 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
697 ranges: Vec<Range<u64>>,
698 print_warning: bool,
699 ) {
700 let mut block = Vec::new();
701 let (range_header, range_padding) = {
705 let mut header_top = String::new();
706 header_top.push_str("0..");
707 let mut padding = Vec::new();
708 for (i, range) in ranges.iter().enumerate() {
709 if i > 0 {
710 header_top.push_str(fmt.perm.range_sep);
711 }
712 let s = range.end.to_string();
713 let l = s.chars().count() + fmt.perm.range_sep.chars().count();
714 {
715 let target_len =
716 fmt.perm.uninit.chars().count() + fmt.accessed.yes.chars().count() + 1;
717 let tot_len = target_len.max(l);
718 let header_top_pad_len = target_len.saturating_sub(l);
719 let body_pad_len = tot_len.saturating_sub(target_len);
720 header_top.push_str(&format!("{}{}", char_repeat(' ', header_top_pad_len), s));
721 padding.push(body_pad_len);
722 }
723 }
724 ([header_top], padding)
725 };
726 for s in range_header {
727 block.push(s);
728 }
729 if let Some(root) = main_root {
731 print_aux(
732 root,
733 &range_padding,
734 fmt,
735 indenter,
736 protected_tags,
737 true, false, &mut block,
740 );
741 }
742 for tree in wildcard_subtrees.iter() {
743 let mut gap_line = String::new();
744 gap_line.push_str(fmt.perm.open);
745 for (i, &pad) in range_padding.iter().enumerate() {
746 if i > 0 {
747 gap_line.push_str(fmt.perm.sep);
748 }
749 gap_line.push_str(&format!("{}{}", char_repeat(' ', pad), " "));
750 }
751 gap_line.push_str(fmt.perm.close);
752 block.push(gap_line);
753
754 print_aux(
755 tree,
756 &range_padding,
757 fmt,
758 indenter,
759 protected_tags,
760 true, true, &mut block,
763 );
764 }
765 {
767 let wr = &fmt.wrapper;
768 let max_width = {
769 let block_width = block.iter().map(|s| s.chars().count()).max().unwrap();
770 if print_warning {
771 block_width.max(wr.warning_text.chars().count())
772 } else {
773 block_width
774 }
775 };
776 eprintln!("{}", char_repeat(wr.top, max_width));
777 if print_warning {
778 eprintln!("{}", wr.warning_text,);
779 }
780 for line in block {
781 eprintln!("{line}");
782 }
783 eprintln!("{}", char_repeat(wr.bot, max_width));
784 }
785
786 fn print_aux(
788 tree: &DisplayRepr,
789 padding: &[usize],
790 fmt: &DisplayFmt,
791 indent: &mut DisplayIndent,
792 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
793 is_last_child: bool,
794 is_wildcard_root: bool,
795 acc: &mut Vec<String>,
796 ) {
797 let mut line = String::new();
798 line.push_str(fmt.perm.open);
801 for (i, (perm, &pad)) in tree.rperm.iter().zip(padding.iter()).enumerate() {
802 if i > 0 {
803 line.push_str(fmt.perm.sep);
804 }
805 let show_perm = fmt.print_perm(*perm);
806 line.push_str(&format!("{}{}", char_repeat(' ', pad), show_perm));
807 }
808 line.push_str(fmt.perm.close);
809 indent.write(&mut line);
812 {
813 line.push_str(if is_wildcard_root {
815 fmt.padding.wildcard_root
816 } else if is_last_child {
817 fmt.padding.join_last
818 } else {
819 fmt.padding.join_middle
820 });
821 line.push_str(fmt.padding.join_default);
822 line.push_str(if tree.children.is_empty() {
823 fmt.padding.join_default
824 } else {
825 fmt.padding.join_haschild
826 });
827 line.push_str(fmt.padding.join_default);
828 line.push_str(fmt.padding.join_default);
829 }
830 line.push_str(&fmt.print_tag(tree.tag, &tree.name));
831 let protector = protected_tags.get(&tree.tag);
832 line.push_str(fmt.print_protector(protector));
833 line.push_str(fmt.print_exposed(tree.exposed));
834 acc.push(line);
836 let nb_children = tree.children.len();
837 for (i, child) in tree.children.iter().enumerate() {
838 indent.increment(fmt, is_last_child);
839 print_aux(
840 child,
841 padding,
842 fmt,
843 indent,
844 protected_tags,
845 i + 1 == nb_children,
846 false,
847 acc,
848 );
849 indent.decrement(fmt);
850 }
851 }
852 }
853}
854
855const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt {
856 wrapper: DisplayFmtWrapper {
857 top: '─',
858 bot: '─',
859 warning_text: "Warning: this tree is indicative only. Some tags may have been hidden.",
860 },
861 perm: DisplayFmtPermission { open: "|", sep: "|", close: "|", uninit: "----", range_sep: ".." },
862 padding: DisplayFmtPadding {
863 join_middle: "├",
864 join_last: "└",
865 indent_middle: "│ ",
866 indent_last: " ",
867 join_haschild: "┬",
868 join_default: "─",
869 wildcard_root: "*",
870 },
871 accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" },
872};
873
874impl<'tcx> Tree {
875 pub fn print_tree(
877 &self,
878 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
879 show_unnamed: bool,
880 ) -> InterpResult<'tcx> {
881 let mut indenter = DisplayIndent::new();
882 let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
883 let main_tree = DisplayRepr::from(self, self.roots[0], show_unnamed);
884 let wildcard_subtrees = self.roots[1..]
885 .iter()
886 .filter_map(|root| DisplayRepr::from(self, *root, show_unnamed))
887 .collect::<Vec<_>>();
888
889 if main_tree.is_none() && wildcard_subtrees.is_empty() {
890 eprintln!(
891 "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags."
892 );
893 }
894
895 DisplayRepr::print(
896 &main_tree,
897 wildcard_subtrees.as_slice(),
898 &DEFAULT_FORMATTER,
899 &mut indenter,
900 protected_tags,
901 ranges,
902 !show_unnamed,
903 );
904 interp_ok(())
905 }
906}