Skip to main content

rustc_mir_dataflow/framework/
graphviz.rs

1//! A helpful diagram for debugging dataflow problems.
2
3use std::borrow::Cow;
4use std::ffi::OsString;
5use std::path::PathBuf;
6use std::sync::LazyLock;
7use std::{io, ops, str};
8
9use regex::Regex;
10use rustc_attr_ir::{BorrowckGraphvizFormatKind, RustcMirKind, find_attr};
11use rustc_graphviz as dot;
12use rustc_index::bit_set::DenseBitSet;
13use rustc_middle::mir::{
14    self, BasicBlock, Body, Location, MirDumper, TerminatorEdges, graphviz_safe_def_name, traversal,
15};
16use rustc_middle::ty::TyCtxt;
17use rustc_middle::ty::print::with_no_trimmed_paths;
18use rustc_span::def_id::DefId;
19use tracing::debug;
20
21use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
22use super::{Analysis, Direction, Results, ResultsCursor, ResultsVisitor, visit_results};
23
24/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
25/// `rustc_mir` attributes and `-Z dump-mir-dataflow`. The `Result` in and the `Results` out are
26/// the same.
27pub(super) fn write_graphviz_results<'tcx, A>(
28    tcx: TyCtxt<'tcx>,
29    body: &Body<'tcx>,
30    results: &Results<'tcx, A>,
31    pass_name: Option<&'static str>,
32) -> std::io::Result<()>
33where
34    A: Analysis<'tcx>,
35    A::Domain: DebugWithContext<A>,
36{
37    use std::fs;
38    use std::io::Write;
39
40    let def_id = body.source.def_id();
41    let attrs = RustcMirAttrs::parse(tcx, def_id);
42
43    let mut file = match attrs.output_path(A::NAME) {
44        Some(path) => {
45            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/7bbda45cb1ec0a378d91959646a8fa904be3192a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs:45",
                        "rustc_mir_dataflow::framework::graphviz",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/7bbda45cb1ec0a378d91959646a8fa904be3192a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs"),
                        ::tracing_core::__macro_support::Option::Some(45u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_dataflow::framework::graphviz"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("printing dataflow results for {0:?} to {1}",
                                                    def_id, path.display()) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("printing dataflow results for {:?} to {}", def_id, path.display());
46            if let Some(parent) = path.parent() {
47                fs::create_dir_all(parent)?;
48            }
49            fs::File::create_buffered(&path)?
50        }
51
52        None => {
53            let Some(dumper) = MirDumper::new(tcx, A::NAME, body) else {
54                return Ok(());
55            };
56            let disambiguator = &pass_name.unwrap_or("-----");
57            dumper.set_disambiguator(disambiguator).create_dump_file("dot", body)?
58        }
59    };
60
61    let style = attrs.formatter.unwrap_or(OutputStyle::AfterOnly);
62
63    let mut buf = Vec::new();
64
65    let graphviz = Formatter::new(body, results, style);
66    let mut render_opts =
67        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())]))vec![dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())];
68    if tcx.sess.opts.unstable_opts.graphviz_dark_mode {
69        render_opts.push(dot::RenderOption::DarkTheme);
70    }
71    {
    let _guard = NoTrimmedGuard::new();
    dot::render_opts(&graphviz, &mut buf, &render_opts)
}with_no_trimmed_paths!(dot::render_opts(&graphviz, &mut buf, &render_opts))?;
72    file.write_all(&buf)
73}
74
75#[derive(#[automatically_derived]
impl ::core::default::Default for RustcMirAttrs {
    #[inline]
    fn default() -> Self {
        Self {
            basename_and_suffix: ::core::default::Default::default(),
            formatter: ::core::default::Default::default(),
        }
    }
}Default)]
76struct RustcMirAttrs {
77    basename_and_suffix: Option<PathBuf>,
78    formatter: Option<OutputStyle>,
79}
80
81impl RustcMirAttrs {
82    fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Self {
83        let mut ret = RustcMirAttrs::default();
84
85        if let Some(rustc_mir_attrs) = {
    {
        'done:
            {
            for i in ::rustc_attr_ir::HasAttrs::get_attrs(def_id, &tcx) {
                #[allow(unused_imports)]
                use ::rustc_attr_ir::AttributeKind::*;
                let i: &::rustc_attr_ir::Attribute = i;
                match i {
                    ::rustc_attr_ir::Attribute::Parsed(RustcMir(kind)) => {
                        break 'done Some(kind);
                    }
                    ::rustc_attr_ir::Attribute::Unparsed(..) =>
                        {}
                        #[deny(unreachable_patterns)]
                        _ => {}
                }
            }
            None
        }
    }
}find_attr!(tcx, def_id, RustcMir(kind) => kind) {
86            for attr in rustc_mir_attrs {
87                match attr {
88                    RustcMirKind::BorrowckGraphvizPostflow { path } => {
89                        ret.basename_and_suffix = Some(path.clone());
90                    }
91                    RustcMirKind::BorrowckGraphvizFormat { format } => {
92                        ret.formatter = match format {
93                            BorrowckGraphvizFormatKind::TwoPhase => {
94                                Some(OutputStyle::BeforeAndAfter)
95                            }
96                        };
97                    }
98                    _ => (),
99                };
100            }
101        }
102
103        ret
104    }
105
106    /// Returns the path where dataflow results should be written, or `None`
107    /// `borrowck_graphviz_postflow` was not specified.
108    ///
109    /// This performs the following transformation to the argument of `borrowck_graphviz_postflow`:
110    ///
111    /// "path/suffix.dot" -> "path/analysis_name_suffix.dot"
112    fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
113        let mut ret = self.basename_and_suffix.as_ref().cloned()?;
114        let suffix = ret.file_name().unwrap(); // Checked when parsing attrs
115
116        let mut file_name: OsString = analysis_name.into();
117        file_name.push("_");
118        file_name.push(suffix);
119        ret.set_file_name(file_name);
120
121        Some(ret)
122    }
123}
124
125#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for OutputStyle { }
#[automatically_derived]
impl ::core::clone::Clone for OutputStyle {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for OutputStyle { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for OutputStyle {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                OutputStyle::AfterOnly => "AfterOnly",
                OutputStyle::BeforeAndAfter => "BeforeAndAfter",
            })
    }
}Debug, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for OutputStyle { }
#[automatically_derived]
impl ::core::cmp::PartialEq for OutputStyle {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for OutputStyle { }Eq)]
126enum OutputStyle {
127    AfterOnly,
128    BeforeAndAfter,
129}
130
131impl OutputStyle {
132    fn num_state_columns(&self) -> usize {
133        match self {
134            Self::AfterOnly => 1,
135            Self::BeforeAndAfter => 2,
136        }
137    }
138}
139
140struct Formatter<'mir, 'tcx, A>
141where
142    A: Analysis<'tcx>,
143{
144    body: &'mir Body<'tcx>,
145    results: &'mir Results<'tcx, A>,
146    style: OutputStyle,
147    reachable: DenseBitSet<BasicBlock>,
148}
149
150impl<'mir, 'tcx, A> Formatter<'mir, 'tcx, A>
151where
152    A: Analysis<'tcx>,
153{
154    fn new(body: &'mir Body<'tcx>, results: &'mir Results<'tcx, A>, style: OutputStyle) -> Self {
155        let reachable = traversal::reachable_as_bitset(body);
156        Formatter { body, results, style, reachable }
157    }
158}
159
160/// A pair of a basic block and an index into that basic blocks `successors`.
161#[derive(#[automatically_derived]
impl ::core::marker::Copy for CfgEdge { }Copy, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for CfgEdge { }
#[automatically_derived]
impl ::core::clone::Clone for CfgEdge {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<BasicBlock>;
        let _: ::core::clone::AssertParamIsClone<usize>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for CfgEdge { }
#[automatically_derived]
impl ::core::cmp::PartialEq for CfgEdge {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.source == other.source && self.index == other.index
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CfgEdge {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<BasicBlock>;
        let _: ::core::cmp::AssertParamIsEq<usize>;
    }
}Eq, #[automatically_derived]
impl ::core::fmt::Debug for CfgEdge {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "CfgEdge",
            "source", &self.source, "index", &&self.index)
    }
}Debug)]
162struct CfgEdge {
163    source: BasicBlock,
164    index: usize,
165}
166
167fn dataflow_successors(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
168    body[bb]
169        .terminator()
170        .successors()
171        .enumerate()
172        .map(|(index, _)| CfgEdge { source: bb, index })
173        .collect()
174}
175
176impl<'tcx, A> dot::Labeller<'_> for Formatter<'_, 'tcx, A>
177where
178    A: Analysis<'tcx>,
179    A::Domain: DebugWithContext<A>,
180{
181    type Node = BasicBlock;
182    type Edge = CfgEdge;
183
184    fn graph_id(&self) -> dot::Id<'_> {
185        let name = graphviz_safe_def_name(self.body.source.def_id());
186        dot::Id::new(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("graph_for_def_id_{0}", name))
    })format!("graph_for_def_id_{name}")).unwrap()
187    }
188
189    fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
190        dot::Id::new(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("bb_{0}", n.index()))
    })format!("bb_{}", n.index())).unwrap()
191    }
192
193    fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
194        let diffs = StateDiffCollector::run(self.body, *block, self.results, self.style);
195
196        let mut fmt = BlockFormatter {
197            cursor: ResultsCursor::new_borrowing(self.body, self.results),
198            style: self.style,
199            bg: Background::Light,
200        };
201        let label = fmt.write_node_label(*block, diffs).unwrap();
202
203        dot::LabelText::html(String::from_utf8(label).unwrap())
204    }
205
206    fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
207        Some(dot::LabelText::label("none"))
208    }
209
210    fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
211        let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
212        dot::LabelText::label(label.clone())
213    }
214}
215
216impl<'tcx, A> dot::GraphWalk<'_> for Formatter<'_, 'tcx, A>
217where
218    A: Analysis<'tcx>,
219{
220    type Node = BasicBlock;
221    type Edge = CfgEdge;
222
223    fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
224        self.body
225            .basic_blocks
226            .indices()
227            .filter(|&idx| self.reachable.contains(idx))
228            .collect::<Vec<_>>()
229            .into()
230    }
231
232    fn edges(&self) -> dot::Edges<'_, Self::Edge> {
233        self.body
234            .basic_blocks
235            .indices()
236            .flat_map(|bb| dataflow_successors(self.body, bb))
237            .collect::<Vec<_>>()
238            .into()
239    }
240
241    fn source(&self, edge: &Self::Edge) -> Self::Node {
242        edge.source
243    }
244
245    fn target(&self, edge: &Self::Edge) -> Self::Node {
246        self.body[edge.source].terminator().successors().nth(edge.index).unwrap()
247    }
248}
249
250struct BlockFormatter<'mir, 'tcx, A>
251where
252    A: Analysis<'tcx>,
253{
254    cursor: ResultsCursor<'mir, 'tcx, A>,
255    bg: Background,
256    style: OutputStyle,
257}
258
259impl<'tcx, A> BlockFormatter<'_, 'tcx, A>
260where
261    A: Analysis<'tcx>,
262    A::Domain: DebugWithContext<A>,
263{
264    const HEADER_COLOR: &'static str = "#a0a0a0";
265
266    fn toggle_background(&mut self) -> Background {
267        let bg = self.bg;
268        self.bg = !bg;
269        bg
270    }
271
272    fn write_node_label(
273        &mut self,
274        block: BasicBlock,
275        diffs: StateDiffCollector<'_, 'tcx, A>,
276    ) -> io::Result<Vec<u8>> {
277        use std::io::Write;
278
279        //   Sample output:
280        //   +-+-----------------------------------------------+
281        // A |                      bb4                        |
282        //   +-+----------------------------------+------------+
283        // B |                MIR                 |   STATE    |
284        //   +-+----------------------------------+------------+
285        // C | | (on start)                       | {_0,_2,_3} |
286        //   +-+----------------------------------+------------+
287        // D |0| StorageLive(_7)                  |            |
288        //   +-+----------------------------------+------------+
289        //   |1| StorageLive(_8)                  |            |
290        //   +-+----------------------------------+------------+
291        //   |2| _8 = &mut _1                     | +_8        |
292        //   +-+----------------------------------+------------+
293        // E |T| _4 = const Foo::twiddle(move _2) | -_2        |
294        //   +-+----------------------------------+------------+
295        // F | | (on unwind)                      | {_0,_3,_8} |
296        //   +-+----------------------------------+------------+
297        //   | | (on successful return)           | +_4        |
298        //   +-+----------------------------------+------------+
299
300        // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their
301        // children. This is because `xdot` seemed to have a hard time correctly propagating
302        // attributes. Make sure to test the output before trying to remove the redundancy.
303        // Notably, `align` was found to have no effect when applied only to <table>.
304
305        let mut v = ::alloc::vec::Vec::new()vec![];
306        let w = &mut v;
307
308        let table_fmt = " border=\"1\" cellborder=\"1\" cellspacing=\"0\" cellpadding=\"3\" sides=\"rb\""concat!(
309            " border=\"1\"",
310            " cellborder=\"1\"",
311            " cellspacing=\"0\"",
312            " cellpadding=\"3\"",
313            " sides=\"rb\"",
314        );
315        w.write_fmt(format_args!("<table{0}>", table_fmt))write!(w, r#"<table{table_fmt}>"#)?;
316
317        // A + B: Block header
318        match self.style {
319            OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
320            OutputStyle::BeforeAndAfter => {
321                self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
322            }
323        }
324
325        // C: State at start of block
326        self.bg = Background::Light;
327        self.cursor.seek_to_block_start(block);
328        let block_start_state = self.cursor.get().clone();
329        self.write_row_with_full_state(w, "", "(on start)")?;
330
331        // D + E: Statement and terminator transfer functions
332        self.write_statements_and_terminator(w, block, diffs)?;
333
334        // F: State at end of block
335
336        let terminator = self.cursor.body()[block].terminator();
337
338        // Write the full dataflow state immediately after the terminator if it differs from the
339        // state at block entry.
340        self.cursor.seek_to_block_end(block);
341        if self.cursor.get() != &block_start_state || A::Direction::IS_BACKWARD {
342            let after_terminator_name = match terminator.kind {
343                mir::TerminatorKind::Call { target: Some(_), .. } => "(on unwind)",
344                _ => "(on end)",
345            };
346
347            self.write_row_with_full_state(w, "", after_terminator_name)?;
348        }
349
350        // Write any changes caused by terminator-specific effects.
351        //
352        // FIXME: These should really be printed as part of each outgoing edge rather than the node
353        // for the basic block itself. That way, we could display terminator-specific effects for
354        // backward dataflow analyses as well as effects for `SwitchInt` terminators.
355
356        match terminator.edges() {
357            TerminatorEdges::AssignOnReturn { return_, place, .. } if !return_.is_empty() => {
358                let label = match place {
359                    mir::CallReturnPlaces::Call(_) | mir::CallReturnPlaces::InlineAsm(_) => {
360                        "(on successful return)"
361                    }
362                    mir::CallReturnPlaces::Yield(_) => "(on yield resume)",
363                };
364
365                self.write_row(w, "", label, |this, w, fmt| {
366                    let state_before_effect = this.cursor.get().clone();
367                    this.cursor.apply_custom_effect(|analysis, state| {
368                        analysis.apply_call_return_effect(state, block, place);
369                    });
370
371                    w.write_fmt(format_args!("<td balign=\"left\" colspan=\"{0}\" {1} align=\"left\">{2}</td>",
        this.style.num_state_columns(), fmt,
        diff_pretty(this.cursor.get(), &state_before_effect,
            this.cursor.analysis())))write!(
372                        w,
373                        r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
374                        colspan = this.style.num_state_columns(),
375                        fmt = fmt,
376                        diff = diff_pretty(
377                            this.cursor.get(),
378                            &state_before_effect,
379                            this.cursor.analysis()
380                        ),
381                    )
382                })?;
383            }
384
385            _ => {}
386        }
387
388        w.write_fmt(format_args!("</table>"))write!(w, "</table>")?;
389
390        Ok(v)
391    }
392
393    fn write_block_header_simple(
394        &mut self,
395        w: &mut impl io::Write,
396        block: BasicBlock,
397    ) -> io::Result<()> {
398        //   +-------------------------------------------------+
399        // A |                      bb4                        |
400        //   +-----------------------------------+-------------+
401        // B |                MIR                |    STATE    |
402        //   +-+---------------------------------+-------------+
403        //   | |              ...                |             |
404
405        // A
406        w.write_fmt(format_args!("<tr><td colspan=\"3\" sides=\"tl\">bb{0}</td></tr>",
        block.index()))write!(
407            w,
408            concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
409            block_id = block.index(),
410        )?;
411
412        // B
413        w.write_fmt(format_args!("<tr><td colspan=\"2\" {0}>MIR</td><td {0}>STATE</td></tr>",
        ::alloc::__export::must_use({
                ::alloc::fmt::format(format_args!("bgcolor=\"{0}\" sides=\"tl\"",
                        Self::HEADER_COLOR))
            })))write!(
414            w,
415            concat!(
416                "<tr>",
417                r#"<td colspan="2" {fmt}>MIR</td>"#,
418                r#"<td {fmt}>STATE</td>"#,
419                "</tr>",
420            ),
421            fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
422        )
423    }
424
425    fn write_block_header_with_state_columns(
426        &mut self,
427        w: &mut impl io::Write,
428        block: BasicBlock,
429        state_column_names: &[&str],
430    ) -> io::Result<()> {
431        //   +------------------------------------+-------------+
432        // A |                bb4                 |    STATE    |
433        //   +------------------------------------+------+------+
434        // B |                MIR                 |  GEN | KILL |
435        //   +-+----------------------------------+------+------+
436        //   | |              ...                 |      |      |
437
438        // A
439        w.write_fmt(format_args!("<tr><td {0} colspan=\"2\">bb{2}</td><td {0} colspan=\"{1}\">STATE</td></tr>",
        "sides=\"tl\"", state_column_names.len(), block.index()))write!(
440            w,
441            concat!(
442                "<tr>",
443                r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
444                r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
445                "</tr>",
446            ),
447            fmt = "sides=\"tl\"",
448            num_state_cols = state_column_names.len(),
449            block_id = block.index(),
450        )?;
451
452        // B
453        let fmt = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("bgcolor=\"{0}\" sides=\"tl\"",
                Self::HEADER_COLOR))
    })format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
454        w.write_fmt(format_args!("<tr><td colspan=\"2\" {0}>MIR</td>", fmt))write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
455
456        for name in state_column_names {
457            w.write_fmt(format_args!("<td {0}>{1}</td>", fmt, name))write!(w, "<td {fmt}>{name}</td>")?;
458        }
459
460        w.write_fmt(format_args!("</tr>"))write!(w, "</tr>")
461    }
462
463    fn write_statements_and_terminator(
464        &mut self,
465        w: &mut impl io::Write,
466        block: BasicBlock,
467        diffs: StateDiffCollector<'_, 'tcx, A>,
468    ) -> io::Result<()> {
469        let mut diffs_before = diffs.before.map(|v| v.into_iter());
470        let mut diffs_after = diffs.after.into_iter();
471
472        let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
473            if A::Direction::IS_FORWARD { it.next().unwrap() } else { it.next_back().unwrap() }
474        };
475
476        for (i, statement) in self.cursor.body()[block].statements.iter().enumerate() {
477            let statement_str = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", statement))
    })format!("{statement:?}");
478            let index_str = ::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", i)) })format!("{i}");
479
480            let after = next_in_dataflow_order(&mut diffs_after);
481            let before = diffs_before.as_mut().map(next_in_dataflow_order);
482
483            self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
484                if let Some(before) = before {
485                    w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, before))write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
486                }
487
488                w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, after))write!(w, r#"<td {fmt} align="left">{after}</td>"#)
489            })?;
490        }
491
492        let after = next_in_dataflow_order(&mut diffs_after);
493        let before = diffs_before.as_mut().map(next_in_dataflow_order);
494
495        if !diffs_after.is_empty() {
    ::core::panicking::panic("assertion failed: diffs_after.is_empty()")
};assert!(diffs_after.is_empty());
496        if !diffs_before.as_ref().is_none_or(ExactSizeIterator::is_empty) {
    ::core::panicking::panic("assertion failed: diffs_before.as_ref().is_none_or(ExactSizeIterator::is_empty)")
};assert!(diffs_before.as_ref().is_none_or(ExactSizeIterator::is_empty));
497
498        let terminator = self.cursor.body()[block].terminator();
499        let mut terminator_str = String::new();
500        terminator.kind.fmt_head(&mut terminator_str).unwrap();
501
502        self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
503            if let Some(before) = before {
504                w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, before))write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
505            }
506
507            w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, after))write!(w, r#"<td {fmt} align="left">{after}</td>"#)
508        })
509    }
510
511    /// Write a row with the given index and MIR, using the function argument to fill in the
512    /// "STATE" column(s).
513    fn write_row<W: io::Write>(
514        &mut self,
515        w: &mut W,
516        i: &str,
517        mir: &str,
518        f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
519    ) -> io::Result<()> {
520        let bg = self.toggle_background();
521        let valign = if mir.starts_with("(on ") && mir != "(on start)" { "bottom" } else { "top" };
522
523        let fmt = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("valign=\"{0}\" sides=\"tl\" {1}",
                valign, bg.attr()))
    })format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
524
525        w.write_fmt(format_args!("<tr><td {1} align=\"right\">{0}</td><td {1} align=\"left\">{2}</td>",
        i, fmt, dot::escape_html(mir)))write!(
526            w,
527            concat!(
528                "<tr>",
529                r#"<td {fmt} align="right">{i}</td>"#,
530                r#"<td {fmt} align="left">{mir}</td>"#,
531            ),
532            i = i,
533            fmt = fmt,
534            mir = dot::escape_html(mir),
535        )?;
536
537        f(self, w, &fmt)?;
538        w.write_fmt(format_args!("</tr>"))write!(w, "</tr>")
539    }
540
541    fn write_row_with_full_state(
542        &mut self,
543        w: &mut impl io::Write,
544        i: &str,
545        mir: &str,
546    ) -> io::Result<()> {
547        self.write_row(w, i, mir, |this, w, fmt| {
548            let state = this.cursor.get();
549            let analysis = this.cursor.analysis();
550
551            // FIXME: The full state vector can be quite long. It would be nice to split on commas
552            // and use some text wrapping algorithm.
553            w.write_fmt(format_args!("<td colspan=\"{0}\" {1} align=\"left\">{2}</td>",
        this.style.num_state_columns(), fmt,
        dot::escape_html(&::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0:?}",
                                DebugWithAdapter { this: state, ctxt: analysis }))
                    }))))write!(
554                w,
555                r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
556                colspan = this.style.num_state_columns(),
557                fmt = fmt,
558                state = dot::escape_html(&format!(
559                    "{:?}",
560                    DebugWithAdapter { this: state, ctxt: analysis }
561                )),
562            )
563        })
564    }
565}
566
567struct StateDiffCollector<'a, 'tcx, A: Analysis<'tcx>> {
568    analysis: &'a A,
569    prev_state: A::Domain,
570    before: Option<Vec<String>>,
571    after: Vec<String>,
572}
573
574impl<'a, 'tcx, A: Analysis<'tcx>> StateDiffCollector<'a, 'tcx, A> {
575    fn run(
576        body: &Body<'tcx>,
577        block: BasicBlock,
578        results: &'a Results<'tcx, A>,
579        style: OutputStyle,
580    ) -> Self
581    where
582        A::Domain: DebugWithContext<A>,
583    {
584        let mut collector = StateDiffCollector {
585            analysis: &results.analysis,
586            prev_state: results.entry_states[block].clone(),
587            after: ::alloc::vec::Vec::new()vec![],
588            before: (style == OutputStyle::BeforeAndAfter).then_some(::alloc::vec::Vec::new()vec![]),
589        };
590
591        visit_results(body, std::iter::once(block), results, &mut collector);
592        collector
593    }
594}
595
596impl<'a, 'tcx, A> ResultsVisitor<'tcx, A> for StateDiffCollector<'a, 'tcx, A>
597where
598    A: Analysis<'tcx>,
599    A::Domain: DebugWithContext<A>,
600{
601    fn visit_after_early_statement_effect(
602        &mut self,
603        state: &A::Domain,
604        _statement: &mir::Statement<'tcx>,
605        _location: Location,
606    ) {
607        if let Some(before) = self.before.as_mut() {
608            before.push(diff_pretty(state, &self.prev_state, self.analysis));
609            self.prev_state.clone_from(state)
610        }
611    }
612
613    fn visit_after_primary_statement_effect(
614        &mut self,
615        state: &A::Domain,
616        _statement: &mir::Statement<'tcx>,
617        _location: Location,
618    ) {
619        self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
620        self.prev_state.clone_from(state)
621    }
622
623    fn visit_after_early_terminator_effect(
624        &mut self,
625        state: &A::Domain,
626        _terminator: &mir::Terminator<'tcx>,
627        _location: Location,
628    ) {
629        if let Some(before) = self.before.as_mut() {
630            before.push(diff_pretty(state, &self.prev_state, self.analysis));
631            self.prev_state.clone_from(state)
632        }
633    }
634
635    fn visit_after_primary_terminator_effect(
636        &mut self,
637        state: &A::Domain,
638        _terminator: &mir::Terminator<'tcx>,
639        _location: Location,
640    ) {
641        self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
642        self.prev_state.clone_from(state)
643    }
644}
645
646fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
647where
648    T: DebugWithContext<C>,
649{
650    if new == old {
651        return String::new();
652    }
653
654    static RE: LazyLock<regex::Regex> = LazyLock::new(|| Regex::new("\t?\u{001f}([+-])").unwrap());
655
656    let raw_diff = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:#?}",
                DebugDiffWithAdapter { new, old, ctxt }))
    })format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
657    let raw_diff = dot::escape_html(&raw_diff);
658
659    // Replace newlines in the `Debug` output with `<br/>`
660    let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
661
662    let mut inside_font_tag = false;
663    let html_diff = RE.replace_all(&raw_diff, |captures: &regex::Captures<'_>| {
664        let mut ret = String::new();
665        if inside_font_tag {
666            ret.push_str(r#"</font>"#);
667        }
668
669        let tag = match &captures[1] {
670            "+" => r#"<font color="darkgreen">+"#,
671            "-" => r#"<font color="red">-"#,
672            _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
673        };
674
675        inside_font_tag = true;
676        ret.push_str(tag);
677        ret
678    });
679
680    let Cow::Owned(mut html_diff) = html_diff else {
681        return raw_diff;
682    };
683
684    if inside_font_tag {
685        html_diff.push_str("</font>");
686    }
687
688    html_diff
689}
690
691/// The background color used for zebra-striping the table.
692#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Background { }
#[automatically_derived]
impl ::core::clone::Clone for Background {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Background { }Copy)]
693enum Background {
694    Light,
695    Dark,
696}
697
698impl Background {
699    fn attr(self) -> &'static str {
700        match self {
701            Self::Dark => "bgcolor=\"#f0f0f0\"",
702            Self::Light => "",
703        }
704    }
705}
706
707impl ops::Not for Background {
708    type Output = Self;
709
710    fn not(self) -> Self {
711        match self {
712            Self::Light => Self::Dark,
713            Self::Dark => Self::Light,
714        }
715    }
716}