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)]
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)]
122pub struct HistoryData {
123 pub events: Vec<(Option<SpanData>, String)>, }
125
126impl History {
127 pub fn push(&mut self, event: Event) {
129 self.events.push(event);
130 }
131}
132
133impl HistoryData {
134 fn extend(&mut self, new_history: History, tag_name: &'static str, show_initial_state: bool) {
138 let History { tag, created, events } = new_history;
139 let this = format!("the {tag_name} tag {tag:?}");
140 let msg_initial_state = format!(", in the initial state {}", created.1);
141 let msg_creation = format!(
142 "{this} was created here{maybe_msg_initial_state}",
143 maybe_msg_initial_state = if show_initial_state { &msg_initial_state } else { "" },
144 );
145
146 self.events.push((Some(created.0.data()), msg_creation));
147 for &Event {
148 transition,
149 is_foreign,
150 access_cause,
151 access_range,
152 span,
153 transition_range: _,
154 } in &events
155 {
156 let access = access_cause.print_as_access(is_foreign);
159 let access_range_text = match access_range {
160 Some(r) => format!("at offsets {r:?}"),
161 None => format!("on every location previously accessed by this tag"),
162 };
163 self.events.push((
164 Some(span.data()),
165 format!(
166 "{this} later transitioned to {endpoint} due to a {access} {access_range_text}",
167 endpoint = transition.endpoint()
168 ),
169 ));
170 self.events
171 .push((None, format!("this transition corresponds to {}", transition.summary())));
172 }
173 }
174}
175
176#[derive(Clone, Debug)]
179pub struct NodeDebugInfo {
180 pub tag: BorTag,
182 pub name: Option<String>,
187 pub history: History,
195}
196
197impl NodeDebugInfo {
198 pub fn new(tag: BorTag, initial: Permission, span: Span) -> Self {
201 let history = History { tag, created: (span, initial), events: Vec::new() };
202 Self { tag, name: None, history }
203 }
204
205 pub fn add_name(&mut self, name: &str) {
208 if let Some(prev_name) = &mut self.name {
209 prev_name.push_str(", ");
210 prev_name.push_str(name);
211 } else {
212 self.name = Some(String::from(name));
213 }
214 }
215}
216
217impl fmt::Display for NodeDebugInfo {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 if let Some(ref name) = self.name {
220 write!(f, "{tag:?} ({name})", tag = self.tag)
221 } else {
222 write!(f, "{tag:?}", tag = self.tag)
223 }
224 }
225}
226
227impl<'tcx> Tree {
228 fn nth_parent(&self, tag: BorTag, nth_parent: u8) -> Option<BorTag> {
233 let mut idx = self.tag_mapping.get(&tag).unwrap();
234 for _ in 0..nth_parent {
235 let node = self.nodes.get(idx).unwrap();
236 idx = node.parent?;
237 }
238 Some(self.nodes.get(idx).unwrap().tag)
239 }
240
241 pub fn give_pointer_debug_name(
243 &mut self,
244 tag: BorTag,
245 nth_parent: u8,
246 name: &str,
247 ) -> InterpResult<'tcx> {
248 let tag = self.nth_parent(tag, nth_parent).unwrap();
249 let idx = self.tag_mapping.get(&tag).unwrap();
250 if let Some(node) = self.nodes.get_mut(idx) {
251 node.debug_info.add_name(name);
252 } else {
253 eprintln!("Tag {tag:?} (to be named '{name}') not found!");
254 }
255 interp_ok(())
256 }
257
258 pub fn is_allocation_of(&self, tag: BorTag) -> bool {
260 self.tag_mapping.contains_key(&tag)
261 }
262}
263
264#[derive(Debug, Clone, Copy)]
265pub(super) enum TransitionError {
266 ChildAccessForbidden(Permission),
271 ProtectedDisabled(Permission),
277 ProtectedDealloc,
280}
281
282impl History {
283 fn forget(&self) -> Self {
285 History { events: Vec::new(), created: self.created, tag: self.tag }
286 }
287
288 fn extract_relevant(&self, error_offset: u64, error_kind: TransitionError) -> Self {
291 History {
292 events: self
293 .events
294 .iter()
295 .filter(|e| e.transition_range.contains(&error_offset))
296 .filter(|e| e.transition.is_relevant(error_kind))
297 .cloned()
298 .collect::<Vec<_>>(),
299 created: self.created,
300 tag: self.tag,
301 }
302 }
303}
304
305pub(super) struct TbError<'node> {
307 pub error_kind: TransitionError,
309 pub access_info: &'node DiagnosticInfo,
311 pub conflicting_node_info: &'node NodeDebugInfo,
316 pub accessed_node_info: Option<&'node NodeDebugInfo>,
320}
321
322impl TbError<'_> {
323 pub fn build<'tcx>(self) -> InterpErrorKind<'tcx> {
325 use TransitionError::*;
326 let cause = self.access_info.access_cause;
327 let error_offset = self.access_info.transition_range.start;
328 let accessed = self.accessed_node_info;
329 let accessed_str =
330 self.accessed_node_info.map(|v| format!("{v}")).unwrap_or_else(|| "<wildcard>".into());
331 let conflicting = self.conflicting_node_info;
332 let accessed_is_conflicting = accessed.map(|a| a.tag) == Some(conflicting.tag);
341 let title = format!(
342 "{cause} through {accessed_str} at {alloc_id:?}[{error_offset:#x}] is forbidden",
343 alloc_id = self.access_info.alloc_id
344 );
345 let (title, details, conflicting_tag_name) = match self.error_kind {
346 ChildAccessForbidden(perm) => {
347 let conflicting_tag_name =
348 if accessed_is_conflicting { "accessed" } else { "conflicting" };
349 let mut details = Vec::new();
350 if !accessed_is_conflicting {
351 details.push(format!(
352 "the accessed tag {accessed_str} is a child of the conflicting tag {conflicting}"
353 ));
354 }
355 let access = cause.print_as_access(false);
356 details.push(format!(
357 "the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids this {access}"
358 ));
359 (title, details, conflicting_tag_name)
360 }
361 ProtectedDisabled(before_disabled) => {
362 let conflicting_tag_name = "protected";
363 let access = cause.print_as_access(true);
364 let details = vec![
365 format!(
366 "the accessed tag {accessed_str} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)"
367 ),
368 format!(
369 "this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled"
370 ),
371 format!("protected tags must never be Disabled"),
372 ];
373 (title, details, conflicting_tag_name)
374 }
375 ProtectedDealloc => {
376 let conflicting_tag_name = "strongly protected";
377 let details = vec![
378 format!(
379 "the allocation of the accessed tag {accessed_str} also contains the {conflicting_tag_name} tag {conflicting}"
380 ),
381 format!("the {conflicting_tag_name} tag {conflicting} disallows deallocations"),
382 ];
383 (title, details, conflicting_tag_name)
384 }
385 };
386 let mut history = HistoryData::default();
387 if let Some(accessed_info) = self.accessed_node_info
388 && !accessed_is_conflicting
389 {
390 history.extend(accessed_info.history.forget(), "accessed", false);
391 }
392 history.extend(
393 self.conflicting_node_info.history.extract_relevant(error_offset, self.error_kind),
394 conflicting_tag_name,
395 true,
396 );
397 err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
398 }
399}
400
401pub fn no_valid_exposed_references_error<'tcx>(
404 DiagnosticInfo { alloc_id, transition_range, access_cause, .. }: &DiagnosticInfo,
405) -> InterpErrorKind<'tcx> {
406 let title = format!(
407 "{access_cause} through <wildcard> at {alloc_id:?}[{offset:#x}] is forbidden",
408 offset = transition_range.start
409 );
410 let details = vec![format!("there are no exposed tags which may perform this access here")];
411 let history = HistoryData::default();
412 err_machine_stop!(TerminationInfo::TreeBorrowsUb { title, details, history })
413}
414
415type S = &'static str;
416struct DisplayFmtWrapper {
436 top: char,
438 bot: char,
440 warning_text: S,
442}
443
444struct DisplayFmtPermission {
462 open: S,
464 sep: S,
466 close: S,
468 uninit: S,
472 range_sep: S,
474}
475
476struct DisplayFmtPadding {
500 join_middle: S,
502 join_last: S,
504 join_haschild: S,
506 join_default: S,
509 indent_middle: S,
511 indent_last: S,
513 wildcard_root: S,
515}
516struct DisplayFmtAccess {
533 yes: S,
535 no: S,
538 meh: S,
541}
542
543struct DisplayFmt {
545 wrapper: DisplayFmtWrapper,
546 perm: DisplayFmtPermission,
547 padding: DisplayFmtPadding,
548 accessed: DisplayFmtAccess,
549}
550impl DisplayFmt {
551 fn print_perm(&self, perm: Option<LocationState>) -> String {
555 if let Some(perm) = perm {
556 format!(
557 "{ac}{st}",
558 ac = if perm.accessed() { self.accessed.yes } else { self.accessed.no },
559 st = perm.permission().short_name(),
560 )
561 } else {
562 format!("{}{}", self.accessed.meh, self.perm.uninit)
563 }
564 }
565
566 fn print_tag(&self, tag: BorTag, name: &Option<String>) -> String {
569 let printable_tag = tag.get();
570 if let Some(name) = name {
571 format!("<{printable_tag}={name}>")
572 } else {
573 format!("<{printable_tag}>")
574 }
575 }
576
577 fn print_protector(&self, protector: Option<&ProtectorKind>) -> &'static str {
579 protector
580 .map(|p| {
581 match *p {
582 ProtectorKind::WeakProtector => " Weakly protected",
583 ProtectorKind::StrongProtector => " Strongly protected",
584 }
585 })
586 .unwrap_or("")
587 }
588
589 fn print_exposed(&self, exposed: bool) -> S {
591 if exposed { " (exposed)" } else { "" }
592 }
593}
594
595struct DisplayIndent {
597 curr: String,
598}
599impl DisplayIndent {
600 fn new() -> Self {
601 Self { curr: " ".to_string() }
602 }
603
604 fn increment(&mut self, formatter: &DisplayFmt, is_last: bool) {
608 self.curr.push_str(if is_last {
609 formatter.padding.indent_last
610 } else {
611 formatter.padding.indent_middle
612 });
613 }
614
615 fn decrement(&mut self, formatter: &DisplayFmt) {
617 for _ in 0..formatter.padding.indent_last.len() {
618 let _ = self.curr.pop();
619 }
620 }
621
622 fn write(&self, s: &mut String) {
624 s.push_str(&self.curr);
625 }
626}
627
628fn char_repeat(c: char, n: usize) -> String {
630 std::iter::once(c).cycle().take(n).collect::<String>()
631}
632
633struct DisplayRepr {
637 tag: BorTag,
638 name: Option<String>,
639 exposed: bool,
640 rperm: Vec<Option<LocationState>>,
641 children: Vec<DisplayRepr>,
642}
643
644impl DisplayRepr {
645 fn from(tree: &Tree, root: UniIndex, show_unnamed: bool) -> Option<Self> {
646 let mut v = Vec::new();
647 extraction_aux(tree, root, show_unnamed, &mut v);
648 let Some(root) = v.pop() else {
649 if show_unnamed {
650 unreachable!(
651 "This allocation contains no tags, not even a root. This should not happen."
652 );
653 }
654 return None;
655 };
656 assert!(v.is_empty());
657 return Some(root);
658
659 fn extraction_aux(
660 tree: &Tree,
661 idx: UniIndex,
662 show_unnamed: bool,
663 acc: &mut Vec<DisplayRepr>,
664 ) {
665 let node = tree.nodes.get(idx).unwrap();
666 let name = node.debug_info.name.clone();
667 let exposed = node.is_exposed;
668 let children_sorted = {
669 let mut children = node.children.iter().cloned().collect::<Vec<_>>();
670 children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag);
671 children
672 };
673 if !show_unnamed && name.is_none() {
674 for child_idx in children_sorted {
676 extraction_aux(tree, child_idx, show_unnamed, acc);
677 }
678 } else {
679 let rperm = tree
681 .locations
682 .iter_all()
683 .map(move |(_offset, loc)| {
684 let perm = loc.perms.get(idx);
685 perm.cloned()
686 })
687 .collect::<Vec<_>>();
688 let mut children = Vec::new();
689 for child_idx in children_sorted {
690 extraction_aux(tree, child_idx, show_unnamed, &mut children);
691 }
692 acc.push(DisplayRepr { tag: node.tag, name, rperm, children, exposed });
693 }
694 }
695 }
696 fn print(
697 main_root: &Option<DisplayRepr>,
698 wildcard_subtrees: &[DisplayRepr],
699 fmt: &DisplayFmt,
700 indenter: &mut DisplayIndent,
701 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
702 ranges: Vec<Range<u64>>,
703 print_warning: bool,
704 ) {
705 let mut block = Vec::new();
706 let (range_header, range_padding) = {
710 let mut header_top = String::new();
711 header_top.push_str("0..");
712 let mut padding = Vec::new();
713 for (i, range) in ranges.iter().enumerate() {
714 if i > 0 {
715 header_top.push_str(fmt.perm.range_sep);
716 }
717 let s = range.end.to_string();
718 let l = s.chars().count() + fmt.perm.range_sep.chars().count();
719 {
720 let target_len =
721 fmt.perm.uninit.chars().count() + fmt.accessed.yes.chars().count() + 1;
722 let tot_len = target_len.max(l);
723 let header_top_pad_len = target_len.saturating_sub(l);
724 let body_pad_len = tot_len.saturating_sub(target_len);
725 header_top.push_str(&format!("{}{}", char_repeat(' ', header_top_pad_len), s));
726 padding.push(body_pad_len);
727 }
728 }
729 ([header_top], padding)
730 };
731 for s in range_header {
732 block.push(s);
733 }
734 if let Some(root) = main_root {
736 print_aux(
737 root,
738 &range_padding,
739 fmt,
740 indenter,
741 protected_tags,
742 true, false, &mut block,
745 );
746 }
747 for tree in wildcard_subtrees.iter() {
748 let mut gap_line = String::new();
749 gap_line.push_str(fmt.perm.open);
750 for (i, &pad) in range_padding.iter().enumerate() {
751 if i > 0 {
752 gap_line.push_str(fmt.perm.sep);
753 }
754 gap_line.push_str(&format!("{}{}", char_repeat(' ', pad), " "));
755 }
756 gap_line.push_str(fmt.perm.close);
757 block.push(gap_line);
758
759 print_aux(
760 tree,
761 &range_padding,
762 fmt,
763 indenter,
764 protected_tags,
765 true, true, &mut block,
768 );
769 }
770 {
772 let wr = &fmt.wrapper;
773 let max_width = {
774 let block_width = block.iter().map(|s| s.chars().count()).max().unwrap();
775 if print_warning {
776 block_width.max(wr.warning_text.chars().count())
777 } else {
778 block_width
779 }
780 };
781 eprintln!("{}", char_repeat(wr.top, max_width));
782 if print_warning {
783 eprintln!("{}", wr.warning_text,);
784 }
785 for line in block {
786 eprintln!("{line}");
787 }
788 eprintln!("{}", char_repeat(wr.bot, max_width));
789 }
790
791 fn print_aux(
793 tree: &DisplayRepr,
794 padding: &[usize],
795 fmt: &DisplayFmt,
796 indent: &mut DisplayIndent,
797 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
798 is_last_child: bool,
799 is_wildcard_root: bool,
800 acc: &mut Vec<String>,
801 ) {
802 let mut line = String::new();
803 line.push_str(fmt.perm.open);
806 for (i, (perm, &pad)) in tree.rperm.iter().zip(padding.iter()).enumerate() {
807 if i > 0 {
808 line.push_str(fmt.perm.sep);
809 }
810 let show_perm = fmt.print_perm(*perm);
811 line.push_str(&format!("{}{}", char_repeat(' ', pad), show_perm));
812 }
813 line.push_str(fmt.perm.close);
814 indent.write(&mut line);
817 {
818 line.push_str(if is_wildcard_root {
820 fmt.padding.wildcard_root
821 } else if is_last_child {
822 fmt.padding.join_last
823 } else {
824 fmt.padding.join_middle
825 });
826 line.push_str(fmt.padding.join_default);
827 line.push_str(if tree.children.is_empty() {
828 fmt.padding.join_default
829 } else {
830 fmt.padding.join_haschild
831 });
832 line.push_str(fmt.padding.join_default);
833 line.push_str(fmt.padding.join_default);
834 }
835 line.push_str(&fmt.print_tag(tree.tag, &tree.name));
836 let protector = protected_tags.get(&tree.tag);
837 line.push_str(fmt.print_protector(protector));
838 line.push_str(fmt.print_exposed(tree.exposed));
839 acc.push(line);
841 let nb_children = tree.children.len();
842 for (i, child) in tree.children.iter().enumerate() {
843 indent.increment(fmt, is_last_child);
844 print_aux(
845 child,
846 padding,
847 fmt,
848 indent,
849 protected_tags,
850 i + 1 == nb_children,
851 false,
852 acc,
853 );
854 indent.decrement(fmt);
855 }
856 }
857 }
858}
859
860const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt {
861 wrapper: DisplayFmtWrapper {
862 top: '─',
863 bot: '─',
864 warning_text: "Warning: this tree is indicative only. Some tags may have been hidden.",
865 },
866 perm: DisplayFmtPermission { open: "|", sep: "|", close: "|", uninit: "----", range_sep: ".." },
867 padding: DisplayFmtPadding {
868 join_middle: "├",
869 join_last: "└",
870 indent_middle: "│ ",
871 indent_last: " ",
872 join_haschild: "┬",
873 join_default: "─",
874 wildcard_root: "*",
875 },
876 accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" },
877};
878
879impl<'tcx> Tree {
880 pub fn print_tree(
882 &self,
883 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
884 show_unnamed: bool,
885 ) -> InterpResult<'tcx> {
886 let mut indenter = DisplayIndent::new();
887 let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
888 let main_tree = DisplayRepr::from(self, self.roots[0], show_unnamed);
889 let wildcard_subtrees = self.roots[1..]
890 .iter()
891 .filter_map(|root| DisplayRepr::from(self, *root, show_unnamed))
892 .collect::<Vec<_>>();
893
894 if main_tree.is_none() && wildcard_subtrees.is_empty() {
895 eprintln!(
896 "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags."
897 );
898 }
899
900 DisplayRepr::print(
901 &main_tree,
902 wildcard_subtrees.as_slice(),
903 &DEFAULT_FORMATTER,
904 &mut indenter,
905 protected_tags,
906 ranges,
907 !show_unnamed,
908 );
909 interp_ok(())
910 }
911}