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 wildcard_root: S,
493}
494struct DisplayFmtAccess {
511 yes: S,
513 no: S,
516 meh: S,
519}
520
521struct DisplayFmt {
523 wrapper: DisplayFmtWrapper,
524 perm: DisplayFmtPermission,
525 padding: DisplayFmtPadding,
526 accessed: DisplayFmtAccess,
527}
528impl DisplayFmt {
529 fn print_perm(&self, perm: Option<LocationState>) -> String {
533 if let Some(perm) = perm {
534 format!(
535 "{ac}{st}",
536 ac = if perm.accessed() { self.accessed.yes } else { self.accessed.no },
537 st = perm.permission().short_name(),
538 )
539 } else {
540 format!("{}{}", self.accessed.meh, self.perm.uninit)
541 }
542 }
543
544 fn print_tag(&self, tag: BorTag, name: &Option<String>) -> String {
547 let printable_tag = tag.get();
548 if let Some(name) = name {
549 format!("<{printable_tag}={name}>")
550 } else {
551 format!("<{printable_tag}>")
552 }
553 }
554
555 fn print_protector(&self, protector: Option<&ProtectorKind>) -> &'static str {
557 protector
558 .map(|p| {
559 match *p {
560 ProtectorKind::WeakProtector => " Weakly protected",
561 ProtectorKind::StrongProtector => " Strongly protected",
562 }
563 })
564 .unwrap_or("")
565 }
566
567 fn print_exposed(&self, exposed: bool) -> S {
569 if exposed { " (exposed)" } else { "" }
570 }
571}
572
573struct DisplayIndent {
575 curr: String,
576}
577impl DisplayIndent {
578 fn new() -> Self {
579 Self { curr: " ".to_string() }
580 }
581
582 fn increment(&mut self, formatter: &DisplayFmt, is_last: bool) {
586 self.curr.push_str(if is_last {
587 formatter.padding.indent_last
588 } else {
589 formatter.padding.indent_middle
590 });
591 }
592
593 fn decrement(&mut self, formatter: &DisplayFmt) {
595 for _ in 0..formatter.padding.indent_last.len() {
596 let _ = self.curr.pop();
597 }
598 }
599
600 fn write(&self, s: &mut String) {
602 s.push_str(&self.curr);
603 }
604}
605
606fn char_repeat(c: char, n: usize) -> String {
608 std::iter::once(c).cycle().take(n).collect::<String>()
609}
610
611struct DisplayRepr {
615 tag: BorTag,
616 name: Option<String>,
617 exposed: bool,
618 rperm: Vec<Option<LocationState>>,
619 children: Vec<DisplayRepr>,
620}
621
622impl DisplayRepr {
623 fn from(tree: &Tree, root: UniIndex, show_unnamed: bool) -> Option<Self> {
624 let mut v = Vec::new();
625 extraction_aux(tree, root, show_unnamed, &mut v);
626 let Some(root) = v.pop() else {
627 if show_unnamed {
628 unreachable!(
629 "This allocation contains no tags, not even a root. This should not happen."
630 );
631 }
632 return None;
633 };
634 assert!(v.is_empty());
635 return Some(root);
636
637 fn extraction_aux(
638 tree: &Tree,
639 idx: UniIndex,
640 show_unnamed: bool,
641 acc: &mut Vec<DisplayRepr>,
642 ) {
643 let node = tree.nodes.get(idx).unwrap();
644 let name = node.debug_info.name.clone();
645 let exposed = node.is_exposed;
646 let children_sorted = {
647 let mut children = node.children.iter().cloned().collect::<Vec<_>>();
648 children.sort_by_key(|idx| tree.nodes.get(*idx).unwrap().tag);
649 children
650 };
651 if !show_unnamed && name.is_none() {
652 for child_idx in children_sorted {
654 extraction_aux(tree, child_idx, show_unnamed, acc);
655 }
656 } else {
657 let rperm = tree
659 .locations
660 .iter_all()
661 .map(move |(_offset, loc)| {
662 let perm = loc.perms.get(idx);
663 perm.cloned()
664 })
665 .collect::<Vec<_>>();
666 let mut children = Vec::new();
667 for child_idx in children_sorted {
668 extraction_aux(tree, child_idx, show_unnamed, &mut children);
669 }
670 acc.push(DisplayRepr { tag: node.tag, name, rperm, children, exposed });
671 }
672 }
673 }
674 fn print(
675 main_root: &Option<DisplayRepr>,
676 wildcard_subtrees: &[DisplayRepr],
677 fmt: &DisplayFmt,
678 indenter: &mut DisplayIndent,
679 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
680 ranges: Vec<Range<u64>>,
681 print_warning: bool,
682 ) {
683 let mut block = Vec::new();
684 let (range_header, range_padding) = {
688 let mut header_top = String::new();
689 header_top.push_str("0..");
690 let mut padding = Vec::new();
691 for (i, range) in ranges.iter().enumerate() {
692 if i > 0 {
693 header_top.push_str(fmt.perm.range_sep);
694 }
695 let s = range.end.to_string();
696 let l = s.chars().count() + fmt.perm.range_sep.chars().count();
697 {
698 let target_len =
699 fmt.perm.uninit.chars().count() + fmt.accessed.yes.chars().count() + 1;
700 let tot_len = target_len.max(l);
701 let header_top_pad_len = target_len.saturating_sub(l);
702 let body_pad_len = tot_len.saturating_sub(target_len);
703 header_top.push_str(&format!("{}{}", char_repeat(' ', header_top_pad_len), s));
704 padding.push(body_pad_len);
705 }
706 }
707 ([header_top], padding)
708 };
709 for s in range_header {
710 block.push(s);
711 }
712 if let Some(root) = main_root {
714 print_aux(
715 root,
716 &range_padding,
717 fmt,
718 indenter,
719 protected_tags,
720 true, false, &mut block,
723 );
724 }
725 for tree in wildcard_subtrees.iter() {
726 let mut gap_line = String::new();
727 gap_line.push_str(fmt.perm.open);
728 for (i, &pad) in range_padding.iter().enumerate() {
729 if i > 0 {
730 gap_line.push_str(fmt.perm.sep);
731 }
732 gap_line.push_str(&format!("{}{}", char_repeat(' ', pad), " "));
733 }
734 gap_line.push_str(fmt.perm.close);
735 block.push(gap_line);
736
737 print_aux(
738 tree,
739 &range_padding,
740 fmt,
741 indenter,
742 protected_tags,
743 true, true, &mut block,
746 );
747 }
748 {
750 let wr = &fmt.wrapper;
751 let max_width = {
752 let block_width = block.iter().map(|s| s.chars().count()).max().unwrap();
753 if print_warning {
754 block_width.max(wr.warning_text.chars().count())
755 } else {
756 block_width
757 }
758 };
759 eprintln!("{}", char_repeat(wr.top, max_width));
760 if print_warning {
761 eprintln!("{}", wr.warning_text,);
762 }
763 for line in block {
764 eprintln!("{line}");
765 }
766 eprintln!("{}", char_repeat(wr.bot, max_width));
767 }
768
769 fn print_aux(
771 tree: &DisplayRepr,
772 padding: &[usize],
773 fmt: &DisplayFmt,
774 indent: &mut DisplayIndent,
775 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
776 is_last_child: bool,
777 is_wildcard_root: bool,
778 acc: &mut Vec<String>,
779 ) {
780 let mut line = String::new();
781 line.push_str(fmt.perm.open);
784 for (i, (perm, &pad)) in tree.rperm.iter().zip(padding.iter()).enumerate() {
785 if i > 0 {
786 line.push_str(fmt.perm.sep);
787 }
788 let show_perm = fmt.print_perm(*perm);
789 line.push_str(&format!("{}{}", char_repeat(' ', pad), show_perm));
790 }
791 line.push_str(fmt.perm.close);
792 indent.write(&mut line);
795 {
796 line.push_str(if is_wildcard_root {
798 fmt.padding.wildcard_root
799 } else if is_last_child {
800 fmt.padding.join_last
801 } else {
802 fmt.padding.join_middle
803 });
804 line.push_str(fmt.padding.join_default);
805 line.push_str(if tree.children.is_empty() {
806 fmt.padding.join_default
807 } else {
808 fmt.padding.join_haschild
809 });
810 line.push_str(fmt.padding.join_default);
811 line.push_str(fmt.padding.join_default);
812 }
813 line.push_str(&fmt.print_tag(tree.tag, &tree.name));
814 let protector = protected_tags.get(&tree.tag);
815 line.push_str(fmt.print_protector(protector));
816 line.push_str(fmt.print_exposed(tree.exposed));
817 acc.push(line);
819 let nb_children = tree.children.len();
820 for (i, child) in tree.children.iter().enumerate() {
821 indent.increment(fmt, is_last_child);
822 print_aux(
823 child,
824 padding,
825 fmt,
826 indent,
827 protected_tags,
828 i + 1 == nb_children,
829 false,
830 acc,
831 );
832 indent.decrement(fmt);
833 }
834 }
835 }
836}
837
838const DEFAULT_FORMATTER: DisplayFmt = DisplayFmt {
839 wrapper: DisplayFmtWrapper {
840 top: '─',
841 bot: '─',
842 warning_text: "Warning: this tree is indicative only. Some tags may have been hidden.",
843 },
844 perm: DisplayFmtPermission { open: "|", sep: "|", close: "|", uninit: "----", range_sep: ".." },
845 padding: DisplayFmtPadding {
846 join_middle: "├",
847 join_last: "└",
848 indent_middle: "│ ",
849 indent_last: " ",
850 join_haschild: "┬",
851 join_default: "─",
852 wildcard_root: "*",
853 },
854 accessed: DisplayFmtAccess { yes: " ", no: "?", meh: "-" },
855};
856
857impl<'tcx> Tree {
858 pub fn print_tree(
860 &self,
861 protected_tags: &FxHashMap<BorTag, ProtectorKind>,
862 show_unnamed: bool,
863 ) -> InterpResult<'tcx> {
864 let mut indenter = DisplayIndent::new();
865 let ranges = self.locations.iter_all().map(|(range, _loc)| range).collect::<Vec<_>>();
866 let main_tree = DisplayRepr::from(self, self.roots[0], show_unnamed);
867 let wildcard_subtrees = self.roots[1..]
868 .iter()
869 .filter_map(|root| DisplayRepr::from(self, *root, show_unnamed))
870 .collect::<Vec<_>>();
871
872 if main_tree.is_none() && wildcard_subtrees.is_empty() {
873 eprintln!(
874 "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags."
875 );
876 }
877
878 DisplayRepr::print(
879 &main_tree,
880 wildcard_subtrees.as_slice(),
881 &DEFAULT_FORMATTER,
882 &mut indenter,
883 protected_tags,
884 ranges,
885 !show_unnamed,
886 );
887 interp_ok(())
888 }
889}