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