1use 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
24pub(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 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(); 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#[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 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 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 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 self.write_statements_and_terminator(w, block, diffs)?;
333
334 let terminator = self.cursor.body()[block].terminator();
337
338 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 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 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 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 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 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 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 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 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: ®ex::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#[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}