rustc_mir_dataflow/framework/
graphviz.rs

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