1use std::borrow::Cow;
4use std::ffi::OsString;
5use std::path::PathBuf;
6use std::sync::OnceLock;
7use std::{io, ops, str};
8
9use regex::Regex;
10use rustc_index::bit_set::DenseBitSet;
11use rustc_middle::mir::{
12 self, BasicBlock, Body, Location, MirDumper, graphviz_safe_def_name, traversal,
13};
14use rustc_middle::ty::TyCtxt;
15use rustc_middle::ty::print::with_no_trimmed_paths;
16use rustc_span::def_id::DefId;
17use rustc_span::{Symbol, sym};
18use tracing::debug;
19use {rustc_ast as ast, rustc_graphviz as dot};
20
21use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
22use super::{
23 Analysis, CallReturnPlaces, Direction, Results, ResultsCursor, ResultsVisitor, visit_results,
24};
25use crate::errors::{
26 DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter,
27};
28
29pub(super) fn write_graphviz_results<'tcx, A>(
33 tcx: TyCtxt<'tcx>,
34 body: &Body<'tcx>,
35 results: &Results<'tcx, A>,
36 pass_name: Option<&'static str>,
37) -> std::io::Result<()>
38where
39 A: Analysis<'tcx>,
40 A::Domain: DebugWithContext<A>,
41{
42 use std::fs;
43 use std::io::Write;
44
45 let def_id = body.source.def_id();
46 let Ok(attrs) = RustcMirAttrs::parse(tcx, def_id) else {
47 return Ok(());
49 };
50
51 let file = try {
52 match attrs.output_path(A::NAME) {
53 Some(path) => {
54 debug!("printing dataflow results for {:?} to {}", def_id, path.display());
55 if let Some(parent) = path.parent() {
56 fs::create_dir_all(parent)?;
57 }
58 fs::File::create_buffered(&path)?
59 }
60
61 None => {
62 let Some(dumper) = MirDumper::new(tcx, A::NAME, body) else {
63 return Ok(());
64 };
65 let disambiguator = &pass_name.unwrap_or("-----");
66 dumper.set_disambiguator(disambiguator).create_dump_file("dot", body)?
67 }
68 }
69 };
70 let mut file = match file {
71 Ok(f) => f,
72 Err(e) => return Err(e),
73 };
74
75 let style = match attrs.formatter {
76 Some(sym::two_phase) => OutputStyle::BeforeAndAfter,
77 _ => OutputStyle::AfterOnly,
78 };
79
80 let mut buf = Vec::new();
81
82 let graphviz = Formatter::new(body, results, style);
83 let mut render_opts =
84 vec![dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())];
85 if tcx.sess.opts.unstable_opts.graphviz_dark_mode {
86 render_opts.push(dot::RenderOption::DarkTheme);
87 }
88 let r = with_no_trimmed_paths!(dot::render_opts(&graphviz, &mut buf, &render_opts));
89
90 let lhs = try {
91 r?;
92 file.write_all(&buf)?;
93 };
94
95 lhs
96}
97
98#[derive(Default)]
99struct RustcMirAttrs {
100 basename_and_suffix: Option<PathBuf>,
101 formatter: Option<Symbol>,
102}
103
104impl RustcMirAttrs {
105 fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Result<Self, ()> {
106 let mut result = Ok(());
107 let mut ret = RustcMirAttrs::default();
108
109 let rustc_mir_attrs = tcx
110 .get_attrs(def_id, sym::rustc_mir)
111 .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter()));
112
113 for attr in rustc_mir_attrs {
114 let attr_result = match attr.name() {
115 Some(name @ sym::borrowck_graphviz_postflow) => {
116 Self::set_field(&mut ret.basename_and_suffix, tcx, name, &attr, |s| {
117 let path = PathBuf::from(s.to_string());
118 match path.file_name() {
119 Some(_) => Ok(path),
120 None => {
121 tcx.dcx().emit_err(PathMustEndInFilename { span: attr.span() });
122 Err(())
123 }
124 }
125 })
126 }
127 Some(name @ sym::borrowck_graphviz_format) => {
128 Self::set_field(&mut ret.formatter, tcx, name, &attr, |s| match s {
129 sym::two_phase => Ok(s),
130 _ => {
131 tcx.dcx().emit_err(UnknownFormatter { span: attr.span() });
132 Err(())
133 }
134 })
135 }
136 _ => Ok(()),
137 };
138
139 result = result.and(attr_result);
140 }
141
142 result.map(|()| ret)
143 }
144
145 fn set_field<T>(
146 field: &mut Option<T>,
147 tcx: TyCtxt<'_>,
148 name: Symbol,
149 attr: &ast::MetaItemInner,
150 mapper: impl FnOnce(Symbol) -> Result<T, ()>,
151 ) -> Result<(), ()> {
152 if field.is_some() {
153 tcx.dcx().emit_err(DuplicateValuesFor { span: attr.span(), name });
154
155 return Err(());
156 }
157
158 if let Some(s) = attr.value_str() {
159 *field = Some(mapper(s)?);
160 Ok(())
161 } else {
162 tcx.dcx()
163 .emit_err(RequiresAnArgument { span: attr.span(), name: attr.name().unwrap() });
164 Err(())
165 }
166 }
167
168 fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
175 let mut ret = self.basename_and_suffix.as_ref().cloned()?;
176 let suffix = ret.file_name().unwrap(); let mut file_name: OsString = analysis_name.into();
179 file_name.push("_");
180 file_name.push(suffix);
181 ret.set_file_name(file_name);
182
183 Some(ret)
184 }
185}
186
187#[derive(Clone, Copy, Debug, PartialEq, Eq)]
188enum OutputStyle {
189 AfterOnly,
190 BeforeAndAfter,
191}
192
193impl OutputStyle {
194 fn num_state_columns(&self) -> usize {
195 match self {
196 Self::AfterOnly => 1,
197 Self::BeforeAndAfter => 2,
198 }
199 }
200}
201
202struct Formatter<'mir, 'tcx, A>
203where
204 A: Analysis<'tcx>,
205{
206 body: &'mir Body<'tcx>,
207 results: &'mir Results<'tcx, A>,
208 style: OutputStyle,
209 reachable: DenseBitSet<BasicBlock>,
210}
211
212impl<'mir, 'tcx, A> Formatter<'mir, 'tcx, A>
213where
214 A: Analysis<'tcx>,
215{
216 fn new(body: &'mir Body<'tcx>, results: &'mir Results<'tcx, A>, style: OutputStyle) -> Self {
217 let reachable = traversal::reachable_as_bitset(body);
218 Formatter { body, results, style, reachable }
219 }
220}
221
222#[derive(Copy, Clone, PartialEq, Eq, Debug)]
224struct CfgEdge {
225 source: BasicBlock,
226 index: usize,
227}
228
229fn dataflow_successors(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
230 body[bb]
231 .terminator()
232 .successors()
233 .enumerate()
234 .map(|(index, _)| CfgEdge { source: bb, index })
235 .collect()
236}
237
238impl<'tcx, A> dot::Labeller<'_> for Formatter<'_, 'tcx, A>
239where
240 A: Analysis<'tcx>,
241 A::Domain: DebugWithContext<A>,
242{
243 type Node = BasicBlock;
244 type Edge = CfgEdge;
245
246 fn graph_id(&self) -> dot::Id<'_> {
247 let name = graphviz_safe_def_name(self.body.source.def_id());
248 dot::Id::new(format!("graph_for_def_id_{name}")).unwrap()
249 }
250
251 fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
252 dot::Id::new(format!("bb_{}", n.index())).unwrap()
253 }
254
255 fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
256 let diffs = StateDiffCollector::run(self.body, *block, self.results, self.style);
257
258 let mut fmt = BlockFormatter {
259 cursor: ResultsCursor::new_borrowing(self.body, self.results),
260 style: self.style,
261 bg: Background::Light,
262 };
263 let label = fmt.write_node_label(*block, diffs).unwrap();
264
265 dot::LabelText::html(String::from_utf8(label).unwrap())
266 }
267
268 fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
269 Some(dot::LabelText::label("none"))
270 }
271
272 fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
273 let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
274 dot::LabelText::label(label.clone())
275 }
276}
277
278impl<'tcx, A> dot::GraphWalk<'_> for Formatter<'_, 'tcx, A>
279where
280 A: Analysis<'tcx>,
281{
282 type Node = BasicBlock;
283 type Edge = CfgEdge;
284
285 fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
286 self.body
287 .basic_blocks
288 .indices()
289 .filter(|&idx| self.reachable.contains(idx))
290 .collect::<Vec<_>>()
291 .into()
292 }
293
294 fn edges(&self) -> dot::Edges<'_, Self::Edge> {
295 self.body
296 .basic_blocks
297 .indices()
298 .flat_map(|bb| dataflow_successors(self.body, bb))
299 .collect::<Vec<_>>()
300 .into()
301 }
302
303 fn source(&self, edge: &Self::Edge) -> Self::Node {
304 edge.source
305 }
306
307 fn target(&self, edge: &Self::Edge) -> Self::Node {
308 self.body[edge.source].terminator().successors().nth(edge.index).unwrap()
309 }
310}
311
312struct BlockFormatter<'mir, 'tcx, A>
313where
314 A: Analysis<'tcx>,
315{
316 cursor: ResultsCursor<'mir, 'tcx, A>,
317 bg: Background,
318 style: OutputStyle,
319}
320
321impl<'tcx, A> BlockFormatter<'_, 'tcx, A>
322where
323 A: Analysis<'tcx>,
324 A::Domain: DebugWithContext<A>,
325{
326 const HEADER_COLOR: &'static str = "#a0a0a0";
327
328 fn toggle_background(&mut self) -> Background {
329 let bg = self.bg;
330 self.bg = !bg;
331 bg
332 }
333
334 fn write_node_label(
335 &mut self,
336 block: BasicBlock,
337 diffs: StateDiffCollector<A::Domain>,
338 ) -> io::Result<Vec<u8>> {
339 use std::io::Write;
340
341 let mut v = vec![];
368 let w = &mut v;
369
370 let table_fmt = concat!(
371 " border=\"1\"",
372 " cellborder=\"1\"",
373 " cellspacing=\"0\"",
374 " cellpadding=\"3\"",
375 " sides=\"rb\"",
376 );
377 write!(w, r#"<table{table_fmt}>"#)?;
378
379 match self.style {
381 OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
382 OutputStyle::BeforeAndAfter => {
383 self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
384 }
385 }
386
387 self.bg = Background::Light;
389 self.cursor.seek_to_block_start(block);
390 let block_start_state = self.cursor.get().clone();
391 self.write_row_with_full_state(w, "", "(on start)")?;
392
393 self.write_statements_and_terminator(w, block, diffs)?;
395
396 let terminator = self.cursor.body()[block].terminator();
399
400 self.cursor.seek_to_block_end(block);
403 if self.cursor.get() != &block_start_state || A::Direction::IS_BACKWARD {
404 let after_terminator_name = match terminator.kind {
405 mir::TerminatorKind::Call { target: Some(_), .. } => "(on unwind)",
406 _ => "(on end)",
407 };
408
409 self.write_row_with_full_state(w, "", after_terminator_name)?;
410 }
411
412 match terminator.kind {
418 mir::TerminatorKind::Call { destination, .. } => {
419 self.write_row(w, "", "(on successful return)", |this, w, fmt| {
420 let state_on_unwind = this.cursor.get().clone();
421 this.cursor.apply_custom_effect(|analysis, state| {
422 analysis.apply_call_return_effect(
423 state,
424 block,
425 CallReturnPlaces::Call(destination),
426 );
427 });
428
429 write!(
430 w,
431 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
432 colspan = this.style.num_state_columns(),
433 fmt = fmt,
434 diff = diff_pretty(
435 this.cursor.get(),
436 &state_on_unwind,
437 this.cursor.analysis()
438 ),
439 )
440 })?;
441 }
442
443 mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
444 self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
445 let state_on_coroutine_drop = this.cursor.get().clone();
446 this.cursor.apply_custom_effect(|analysis, state| {
447 analysis.apply_call_return_effect(
448 state,
449 resume,
450 CallReturnPlaces::Yield(resume_arg),
451 );
452 });
453
454 write!(
455 w,
456 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
457 colspan = this.style.num_state_columns(),
458 fmt = fmt,
459 diff = diff_pretty(
460 this.cursor.get(),
461 &state_on_coroutine_drop,
462 this.cursor.analysis()
463 ),
464 )
465 })?;
466 }
467
468 mir::TerminatorKind::InlineAsm { ref targets, ref operands, .. }
469 if !targets.is_empty() =>
470 {
471 self.write_row(w, "", "(on successful return)", |this, w, fmt| {
472 let state_on_unwind = this.cursor.get().clone();
473 this.cursor.apply_custom_effect(|analysis, state| {
474 analysis.apply_call_return_effect(
475 state,
476 block,
477 CallReturnPlaces::InlineAsm(operands),
478 );
479 });
480
481 write!(
482 w,
483 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
484 colspan = this.style.num_state_columns(),
485 fmt = fmt,
486 diff = diff_pretty(
487 this.cursor.get(),
488 &state_on_unwind,
489 this.cursor.analysis()
490 ),
491 )
492 })?;
493 }
494
495 _ => {}
496 };
497
498 write!(w, "</table>")?;
499
500 Ok(v)
501 }
502
503 fn write_block_header_simple(
504 &mut self,
505 w: &mut impl io::Write,
506 block: BasicBlock,
507 ) -> io::Result<()> {
508 write!(
517 w,
518 concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
519 block_id = block.index(),
520 )?;
521
522 write!(
524 w,
525 concat!(
526 "<tr>",
527 r#"<td colspan="2" {fmt}>MIR</td>"#,
528 r#"<td {fmt}>STATE</td>"#,
529 "</tr>",
530 ),
531 fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
532 )
533 }
534
535 fn write_block_header_with_state_columns(
536 &mut self,
537 w: &mut impl io::Write,
538 block: BasicBlock,
539 state_column_names: &[&str],
540 ) -> io::Result<()> {
541 write!(
550 w,
551 concat!(
552 "<tr>",
553 r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
554 r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
555 "</tr>",
556 ),
557 fmt = "sides=\"tl\"",
558 num_state_cols = state_column_names.len(),
559 block_id = block.index(),
560 )?;
561
562 let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
564 write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
565
566 for name in state_column_names {
567 write!(w, "<td {fmt}>{name}</td>")?;
568 }
569
570 write!(w, "</tr>")
571 }
572
573 fn write_statements_and_terminator(
574 &mut self,
575 w: &mut impl io::Write,
576 block: BasicBlock,
577 diffs: StateDiffCollector<A::Domain>,
578 ) -> io::Result<()> {
579 let mut diffs_before = diffs.before.map(|v| v.into_iter());
580 let mut diffs_after = diffs.after.into_iter();
581
582 let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
583 if A::Direction::IS_FORWARD { it.next().unwrap() } else { it.next_back().unwrap() }
584 };
585
586 for (i, statement) in self.cursor.body()[block].statements.iter().enumerate() {
587 let statement_str = format!("{statement:?}");
588 let index_str = format!("{i}");
589
590 let after = next_in_dataflow_order(&mut diffs_after);
591 let before = diffs_before.as_mut().map(next_in_dataflow_order);
592
593 self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
594 if let Some(before) = before {
595 write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
596 }
597
598 write!(w, r#"<td {fmt} align="left">{after}</td>"#)
599 })?;
600 }
601
602 let after = next_in_dataflow_order(&mut diffs_after);
603 let before = diffs_before.as_mut().map(next_in_dataflow_order);
604
605 assert!(diffs_after.is_empty());
606 assert!(diffs_before.as_ref().is_none_or(ExactSizeIterator::is_empty));
607
608 let terminator = self.cursor.body()[block].terminator();
609 let mut terminator_str = String::new();
610 terminator.kind.fmt_head(&mut terminator_str).unwrap();
611
612 self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
613 if let Some(before) = before {
614 write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
615 }
616
617 write!(w, r#"<td {fmt} align="left">{after}</td>"#)
618 })
619 }
620
621 fn write_row<W: io::Write>(
624 &mut self,
625 w: &mut W,
626 i: &str,
627 mir: &str,
628 f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
629 ) -> io::Result<()> {
630 let bg = self.toggle_background();
631 let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
632
633 let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
634
635 write!(
636 w,
637 concat!(
638 "<tr>",
639 r#"<td {fmt} align="right">{i}</td>"#,
640 r#"<td {fmt} align="left">{mir}</td>"#,
641 ),
642 i = i,
643 fmt = fmt,
644 mir = dot::escape_html(mir),
645 )?;
646
647 f(self, w, &fmt)?;
648 write!(w, "</tr>")
649 }
650
651 fn write_row_with_full_state(
652 &mut self,
653 w: &mut impl io::Write,
654 i: &str,
655 mir: &str,
656 ) -> io::Result<()> {
657 self.write_row(w, i, mir, |this, w, fmt| {
658 let state = this.cursor.get();
659 let analysis = this.cursor.analysis();
660
661 write!(
664 w,
665 r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
666 colspan = this.style.num_state_columns(),
667 fmt = fmt,
668 state = dot::escape_html(&format!(
669 "{:?}",
670 DebugWithAdapter { this: state, ctxt: analysis }
671 )),
672 )
673 })
674 }
675}
676
677struct StateDiffCollector<D> {
678 prev_state: D,
679 before: Option<Vec<String>>,
680 after: Vec<String>,
681}
682
683impl<D> StateDiffCollector<D> {
684 fn run<'tcx, A>(
685 body: &Body<'tcx>,
686 block: BasicBlock,
687 results: &Results<'tcx, A>,
688 style: OutputStyle,
689 ) -> Self
690 where
691 A: Analysis<'tcx, Domain = D>,
692 D: DebugWithContext<A>,
693 {
694 let mut collector = StateDiffCollector {
695 prev_state: results.analysis.bottom_value(body),
696 after: vec![],
697 before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
698 };
699
700 visit_results(body, std::iter::once(block), results, &mut collector);
701 collector
702 }
703}
704
705impl<'tcx, A> ResultsVisitor<'tcx, A> for StateDiffCollector<A::Domain>
706where
707 A: Analysis<'tcx>,
708 A::Domain: DebugWithContext<A>,
709{
710 fn visit_block_start(&mut self, state: &A::Domain) {
711 if A::Direction::IS_FORWARD {
712 self.prev_state.clone_from(state);
713 }
714 }
715
716 fn visit_block_end(&mut self, state: &A::Domain) {
717 if A::Direction::IS_BACKWARD {
718 self.prev_state.clone_from(state);
719 }
720 }
721
722 fn visit_after_early_statement_effect(
723 &mut self,
724 analysis: &A,
725 state: &A::Domain,
726 _statement: &mir::Statement<'tcx>,
727 _location: Location,
728 ) {
729 if let Some(before) = self.before.as_mut() {
730 before.push(diff_pretty(state, &self.prev_state, analysis));
731 self.prev_state.clone_from(state)
732 }
733 }
734
735 fn visit_after_primary_statement_effect(
736 &mut self,
737 analysis: &A,
738 state: &A::Domain,
739 _statement: &mir::Statement<'tcx>,
740 _location: Location,
741 ) {
742 self.after.push(diff_pretty(state, &self.prev_state, analysis));
743 self.prev_state.clone_from(state)
744 }
745
746 fn visit_after_early_terminator_effect(
747 &mut self,
748 analysis: &A,
749 state: &A::Domain,
750 _terminator: &mir::Terminator<'tcx>,
751 _location: Location,
752 ) {
753 if let Some(before) = self.before.as_mut() {
754 before.push(diff_pretty(state, &self.prev_state, analysis));
755 self.prev_state.clone_from(state)
756 }
757 }
758
759 fn visit_after_primary_terminator_effect(
760 &mut self,
761 analysis: &A,
762 state: &A::Domain,
763 _terminator: &mir::Terminator<'tcx>,
764 _location: Location,
765 ) {
766 self.after.push(diff_pretty(state, &self.prev_state, analysis));
767 self.prev_state.clone_from(state)
768 }
769}
770
771macro_rules! regex {
772 ($re:literal $(,)?) => {{
773 static RE: OnceLock<regex::Regex> = OnceLock::new();
774 RE.get_or_init(|| Regex::new($re).unwrap())
775 }};
776}
777
778fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
779where
780 T: DebugWithContext<C>,
781{
782 if new == old {
783 return String::new();
784 }
785
786 let re = regex!("\t?\u{001f}([+-])");
787
788 let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
789 let raw_diff = dot::escape_html(&raw_diff);
790
791 let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
793
794 let mut inside_font_tag = false;
795 let html_diff = re.replace_all(&raw_diff, |captures: ®ex::Captures<'_>| {
796 let mut ret = String::new();
797 if inside_font_tag {
798 ret.push_str(r#"</font>"#);
799 }
800
801 let tag = match &captures[1] {
802 "+" => r#"<font color="darkgreen">+"#,
803 "-" => r#"<font color="red">-"#,
804 _ => unreachable!(),
805 };
806
807 inside_font_tag = true;
808 ret.push_str(tag);
809 ret
810 });
811
812 let Cow::Owned(mut html_diff) = html_diff else {
813 return raw_diff;
814 };
815
816 if inside_font_tag {
817 html_diff.push_str("</font>");
818 }
819
820 html_diff
821}
822
823#[derive(Clone, Copy)]
825enum Background {
826 Light,
827 Dark,
828}
829
830impl Background {
831 fn attr(self) -> &'static str {
832 match self {
833 Self::Dark => "bgcolor=\"#f0f0f0\"",
834 Self::Light => "",
835 }
836 }
837}
838
839impl ops::Not for Background {
840 type Output = Self;
841
842 fn not(self) -> Self {
843 match self {
844 Self::Light => Self::Dark,
845 Self::Dark => Self::Light,
846 }
847 }
848}