1use std::borrow::Cow;
4use std::ffi::OsString;
5use std::path::PathBuf;
6use std::sync::OnceLock;
7use std::{io, ops, str};
8
9use regex::Regex;
10use rustc_graphviz as dot;
11use rustc_hir::attrs::{AttributeKind, BorrowckGraphvizFormatKind, RustcMirKind};
12use rustc_hir::find_attr;
13use rustc_index::bit_set::DenseBitSet;
14use rustc_middle::mir::{
15 self, BasicBlock, Body, Location, MirDumper, graphviz_safe_def_name, traversal,
16};
17use rustc_middle::ty::TyCtxt;
18use rustc_middle::ty::print::with_no_trimmed_paths;
19use rustc_span::def_id::DefId;
20use tracing::debug;
21
22use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
23use super::{
24 Analysis, CallReturnPlaces, Direction, Results, ResultsCursor, ResultsVisitor, visit_results,
25};
26
27pub(super) fn write_graphviz_results<'tcx, A>(
31 tcx: TyCtxt<'tcx>,
32 body: &Body<'tcx>,
33 results: &Results<'tcx, A>,
34 pass_name: Option<&'static str>,
35) -> std::io::Result<()>
36where
37 A: Analysis<'tcx>,
38 A::Domain: DebugWithContext<A>,
39{
40 use std::fs;
41 use std::io::Write;
42
43 let def_id = body.source.def_id();
44 let attrs = RustcMirAttrs::parse(tcx, def_id);
45
46 let file = try {
47 match attrs.output_path(A::NAME) {
48 Some(path) => {
49 {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_dataflow/src/framework/graphviz.rs:49",
"rustc_mir_dataflow::framework::graphviz",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_dataflow/src/framework/graphviz.rs"),
::tracing_core::__macro_support::Option::Some(49u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("printing dataflow results for {0:?} to {1}",
def_id, path.display()) as &dyn Value))])
});
} else { ; }
};debug!("printing dataflow results for {:?} to {}", def_id, path.display());
50 if let Some(parent) = path.parent() {
51 fs::create_dir_all(parent)?;
52 }
53 fs::File::create_buffered(&path)?
54 }
55
56 None => {
57 let Some(dumper) = MirDumper::new(tcx, A::NAME, body) else {
58 return Ok(());
59 };
60 let disambiguator = &pass_name.unwrap_or("-----");
61 dumper.set_disambiguator(disambiguator).create_dump_file("dot", body)?
62 }
63 }
64 };
65 let mut file = match file {
66 Ok(f) => f,
67 Err(e) => return Err(e),
68 };
69
70 let style = attrs.formatter.unwrap_or(OutputStyle::AfterOnly);
71
72 let mut buf = Vec::new();
73
74 let graphviz = Formatter::new(body, results, style);
75 let mut render_opts =
76 <[_]>::into_vec(::alloc::boxed::box_new([dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())]))vec![dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())];
77 if tcx.sess.opts.unstable_opts.graphviz_dark_mode {
78 render_opts.push(dot::RenderOption::DarkTheme);
79 }
80 let r = {
let _guard = NoTrimmedGuard::new();
dot::render_opts(&graphviz, &mut buf, &render_opts)
}with_no_trimmed_paths!(dot::render_opts(&graphviz, &mut buf, &render_opts));
81
82 let lhs = try {
83 r?;
84 file.write_all(&buf)?;
85 };
86
87 lhs
88}
89
90#[derive(#[automatically_derived]
impl ::core::default::Default for RustcMirAttrs {
#[inline]
fn default() -> RustcMirAttrs {
RustcMirAttrs {
basename_and_suffix: ::core::default::Default::default(),
formatter: ::core::default::Default::default(),
}
}
}Default)]
91struct RustcMirAttrs {
92 basename_and_suffix: Option<PathBuf>,
93 formatter: Option<OutputStyle>,
94}
95
96impl RustcMirAttrs {
97 fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Self {
98 let mut ret = RustcMirAttrs::default();
99
100 let attrs = tcx.get_all_attrs(def_id);
101 if let Some(rustc_mir_attrs) = {
'done:
{
for i in attrs {
let i: &rustc_hir::Attribute = i;
match i {
rustc_hir::Attribute::Parsed(AttributeKind::RustcMir(kind)) =>
{
break 'done Some(kind);
}
_ => {}
}
}
None
}
}find_attr!(attrs, AttributeKind::RustcMir(kind) => kind) {
102 for attr in rustc_mir_attrs {
103 match attr {
104 RustcMirKind::BorrowckGraphvizPostflow { path } => {
105 ret.basename_and_suffix = Some(path.clone());
106 }
107 RustcMirKind::BorrowckGraphvizFormat { format } => {
108 ret.formatter = match format {
109 BorrowckGraphvizFormatKind::TwoPhase => {
110 Some(OutputStyle::BeforeAndAfter)
111 }
112 };
113 }
114 _ => (),
115 };
116 }
117 }
118
119 ret
120 }
121
122 fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
129 let mut ret = self.basename_and_suffix.as_ref().cloned()?;
130 let suffix = ret.file_name().unwrap(); let mut file_name: OsString = analysis_name.into();
133 file_name.push("_");
134 file_name.push(suffix);
135 ret.set_file_name(file_name);
136
137 Some(ret)
138 }
139}
140
141#[derive(#[automatically_derived]
impl ::core::clone::Clone for OutputStyle {
#[inline]
fn clone(&self) -> OutputStyle { *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::cmp::PartialEq for OutputStyle {
#[inline]
fn eq(&self, other: &OutputStyle) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for OutputStyle {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_receiver_is_total_eq(&self) {}
}Eq)]
142enum OutputStyle {
143 AfterOnly,
144 BeforeAndAfter,
145}
146
147impl OutputStyle {
148 fn num_state_columns(&self) -> usize {
149 match self {
150 Self::AfterOnly => 1,
151 Self::BeforeAndAfter => 2,
152 }
153 }
154}
155
156struct Formatter<'mir, 'tcx, A>
157where
158 A: Analysis<'tcx>,
159{
160 body: &'mir Body<'tcx>,
161 results: &'mir Results<'tcx, A>,
162 style: OutputStyle,
163 reachable: DenseBitSet<BasicBlock>,
164}
165
166impl<'mir, 'tcx, A> Formatter<'mir, 'tcx, A>
167where
168 A: Analysis<'tcx>,
169{
170 fn new(body: &'mir Body<'tcx>, results: &'mir Results<'tcx, A>, style: OutputStyle) -> Self {
171 let reachable = traversal::reachable_as_bitset(body);
172 Formatter { body, results, style, reachable }
173 }
174}
175
176#[derive(#[automatically_derived]
impl ::core::marker::Copy for CfgEdge { }Copy, #[automatically_derived]
impl ::core::clone::Clone for CfgEdge {
#[inline]
fn clone(&self) -> CfgEdge {
let _: ::core::clone::AssertParamIsClone<BasicBlock>;
let _: ::core::clone::AssertParamIsClone<usize>;
*self
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for CfgEdge {
#[inline]
fn eq(&self, other: &CfgEdge) -> 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_receiver_is_total_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)]
178struct CfgEdge {
179 source: BasicBlock,
180 index: usize,
181}
182
183fn dataflow_successors(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
184 body[bb]
185 .terminator()
186 .successors()
187 .enumerate()
188 .map(|(index, _)| CfgEdge { source: bb, index })
189 .collect()
190}
191
192impl<'tcx, A> dot::Labeller<'_> for Formatter<'_, 'tcx, A>
193where
194 A: Analysis<'tcx>,
195 A::Domain: DebugWithContext<A>,
196{
197 type Node = BasicBlock;
198 type Edge = CfgEdge;
199
200 fn graph_id(&self) -> dot::Id<'_> {
201 let name = graphviz_safe_def_name(self.body.source.def_id());
202 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()
203 }
204
205 fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
206 dot::Id::new(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("bb_{0}", n.index()))
})format!("bb_{}", n.index())).unwrap()
207 }
208
209 fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
210 let diffs = StateDiffCollector::run(self.body, *block, self.results, self.style);
211
212 let mut fmt = BlockFormatter {
213 cursor: ResultsCursor::new_borrowing(self.body, self.results),
214 style: self.style,
215 bg: Background::Light,
216 };
217 let label = fmt.write_node_label(*block, diffs).unwrap();
218
219 dot::LabelText::html(String::from_utf8(label).unwrap())
220 }
221
222 fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
223 Some(dot::LabelText::label("none"))
224 }
225
226 fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
227 let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
228 dot::LabelText::label(label.clone())
229 }
230}
231
232impl<'tcx, A> dot::GraphWalk<'_> for Formatter<'_, 'tcx, A>
233where
234 A: Analysis<'tcx>,
235{
236 type Node = BasicBlock;
237 type Edge = CfgEdge;
238
239 fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
240 self.body
241 .basic_blocks
242 .indices()
243 .filter(|&idx| self.reachable.contains(idx))
244 .collect::<Vec<_>>()
245 .into()
246 }
247
248 fn edges(&self) -> dot::Edges<'_, Self::Edge> {
249 self.body
250 .basic_blocks
251 .indices()
252 .flat_map(|bb| dataflow_successors(self.body, bb))
253 .collect::<Vec<_>>()
254 .into()
255 }
256
257 fn source(&self, edge: &Self::Edge) -> Self::Node {
258 edge.source
259 }
260
261 fn target(&self, edge: &Self::Edge) -> Self::Node {
262 self.body[edge.source].terminator().successors().nth(edge.index).unwrap()
263 }
264}
265
266struct BlockFormatter<'mir, 'tcx, A>
267where
268 A: Analysis<'tcx>,
269{
270 cursor: ResultsCursor<'mir, 'tcx, A>,
271 bg: Background,
272 style: OutputStyle,
273}
274
275impl<'tcx, A> BlockFormatter<'_, 'tcx, A>
276where
277 A: Analysis<'tcx>,
278 A::Domain: DebugWithContext<A>,
279{
280 const HEADER_COLOR: &'static str = "#a0a0a0";
281
282 fn toggle_background(&mut self) -> Background {
283 let bg = self.bg;
284 self.bg = !bg;
285 bg
286 }
287
288 fn write_node_label(
289 &mut self,
290 block: BasicBlock,
291 diffs: StateDiffCollector<A::Domain>,
292 ) -> io::Result<Vec<u8>> {
293 use std::io::Write;
294
295 let mut v = ::alloc::vec::Vec::new()vec![];
322 let w = &mut v;
323
324 let table_fmt = " border=\"1\" cellborder=\"1\" cellspacing=\"0\" cellpadding=\"3\" sides=\"rb\""concat!(
325 " border=\"1\"",
326 " cellborder=\"1\"",
327 " cellspacing=\"0\"",
328 " cellpadding=\"3\"",
329 " sides=\"rb\"",
330 );
331 w.write_fmt(format_args!("<table{0}>", table_fmt))write!(w, r#"<table{table_fmt}>"#)?;
332
333 match self.style {
335 OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
336 OutputStyle::BeforeAndAfter => {
337 self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
338 }
339 }
340
341 self.bg = Background::Light;
343 self.cursor.seek_to_block_start(block);
344 let block_start_state = self.cursor.get().clone();
345 self.write_row_with_full_state(w, "", "(on start)")?;
346
347 self.write_statements_and_terminator(w, block, diffs)?;
349
350 let terminator = self.cursor.body()[block].terminator();
353
354 self.cursor.seek_to_block_end(block);
357 if self.cursor.get() != &block_start_state || A::Direction::IS_BACKWARD {
358 let after_terminator_name = match terminator.kind {
359 mir::TerminatorKind::Call { target: Some(_), .. } => "(on unwind)",
360 _ => "(on end)",
361 };
362
363 self.write_row_with_full_state(w, "", after_terminator_name)?;
364 }
365
366 match terminator.kind {
372 mir::TerminatorKind::Call { destination, .. } => {
373 self.write_row(w, "", "(on successful return)", |this, w, fmt| {
374 let state_on_unwind = this.cursor.get().clone();
375 this.cursor.apply_custom_effect(|analysis, state| {
376 analysis.apply_call_return_effect(
377 state,
378 block,
379 CallReturnPlaces::Call(destination),
380 );
381 });
382
383 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_on_unwind,
this.cursor.analysis())))write!(
384 w,
385 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
386 colspan = this.style.num_state_columns(),
387 fmt = fmt,
388 diff = diff_pretty(
389 this.cursor.get(),
390 &state_on_unwind,
391 this.cursor.analysis()
392 ),
393 )
394 })?;
395 }
396
397 mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
398 self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
399 let state_on_coroutine_drop = this.cursor.get().clone();
400 this.cursor.apply_custom_effect(|analysis, state| {
401 analysis.apply_call_return_effect(
402 state,
403 resume,
404 CallReturnPlaces::Yield(resume_arg),
405 );
406 });
407
408 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_on_coroutine_drop,
this.cursor.analysis())))write!(
409 w,
410 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
411 colspan = this.style.num_state_columns(),
412 fmt = fmt,
413 diff = diff_pretty(
414 this.cursor.get(),
415 &state_on_coroutine_drop,
416 this.cursor.analysis()
417 ),
418 )
419 })?;
420 }
421
422 mir::TerminatorKind::InlineAsm { ref targets, ref operands, .. }
423 if !targets.is_empty() =>
424 {
425 self.write_row(w, "", "(on successful return)", |this, w, fmt| {
426 let state_on_unwind = this.cursor.get().clone();
427 this.cursor.apply_custom_effect(|analysis, state| {
428 analysis.apply_call_return_effect(
429 state,
430 block,
431 CallReturnPlaces::InlineAsm(operands),
432 );
433 });
434
435 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_on_unwind,
this.cursor.analysis())))write!(
436 w,
437 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
438 colspan = this.style.num_state_columns(),
439 fmt = fmt,
440 diff = diff_pretty(
441 this.cursor.get(),
442 &state_on_unwind,
443 this.cursor.analysis()
444 ),
445 )
446 })?;
447 }
448
449 _ => {}
450 };
451
452 w.write_fmt(format_args!("</table>"))write!(w, "</table>")?;
453
454 Ok(v)
455 }
456
457 fn write_block_header_simple(
458 &mut self,
459 w: &mut impl io::Write,
460 block: BasicBlock,
461 ) -> io::Result<()> {
462 w.write_fmt(format_args!("<tr><td colspan=\"3\" sides=\"tl\">bb{0}</td></tr>",
block.index()))write!(
471 w,
472 concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
473 block_id = block.index(),
474 )?;
475
476 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!(
478 w,
479 concat!(
480 "<tr>",
481 r#"<td colspan="2" {fmt}>MIR</td>"#,
482 r#"<td {fmt}>STATE</td>"#,
483 "</tr>",
484 ),
485 fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
486 )
487 }
488
489 fn write_block_header_with_state_columns(
490 &mut self,
491 w: &mut impl io::Write,
492 block: BasicBlock,
493 state_column_names: &[&str],
494 ) -> io::Result<()> {
495 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!(
504 w,
505 concat!(
506 "<tr>",
507 r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
508 r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
509 "</tr>",
510 ),
511 fmt = "sides=\"tl\"",
512 num_state_cols = state_column_names.len(),
513 block_id = block.index(),
514 )?;
515
516 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);
518 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,)?;
519
520 for name in state_column_names {
521 w.write_fmt(format_args!("<td {0}>{1}</td>", fmt, name))write!(w, "<td {fmt}>{name}</td>")?;
522 }
523
524 w.write_fmt(format_args!("</tr>"))write!(w, "</tr>")
525 }
526
527 fn write_statements_and_terminator(
528 &mut self,
529 w: &mut impl io::Write,
530 block: BasicBlock,
531 diffs: StateDiffCollector<A::Domain>,
532 ) -> io::Result<()> {
533 let mut diffs_before = diffs.before.map(|v| v.into_iter());
534 let mut diffs_after = diffs.after.into_iter();
535
536 let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
537 if A::Direction::IS_FORWARD { it.next().unwrap() } else { it.next_back().unwrap() }
538 };
539
540 for (i, statement) in self.cursor.body()[block].statements.iter().enumerate() {
541 let statement_str = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", statement))
})format!("{statement:?}");
542 let index_str = ::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", i)) })format!("{i}");
543
544 let after = next_in_dataflow_order(&mut diffs_after);
545 let before = diffs_before.as_mut().map(next_in_dataflow_order);
546
547 self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
548 if let Some(before) = before {
549 w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, before))write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
550 }
551
552 w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, after))write!(w, r#"<td {fmt} align="left">{after}</td>"#)
553 })?;
554 }
555
556 let after = next_in_dataflow_order(&mut diffs_after);
557 let before = diffs_before.as_mut().map(next_in_dataflow_order);
558
559 if !diffs_after.is_empty() {
::core::panicking::panic("assertion failed: diffs_after.is_empty()")
};assert!(diffs_after.is_empty());
560 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));
561
562 let terminator = self.cursor.body()[block].terminator();
563 let mut terminator_str = String::new();
564 terminator.kind.fmt_head(&mut terminator_str).unwrap();
565
566 self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
567 if let Some(before) = before {
568 w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, before))write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
569 }
570
571 w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, after))write!(w, r#"<td {fmt} align="left">{after}</td>"#)
572 })
573 }
574
575 fn write_row<W: io::Write>(
578 &mut self,
579 w: &mut W,
580 i: &str,
581 mir: &str,
582 f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
583 ) -> io::Result<()> {
584 let bg = self.toggle_background();
585 let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
586
587 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());
588
589 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!(
590 w,
591 concat!(
592 "<tr>",
593 r#"<td {fmt} align="right">{i}</td>"#,
594 r#"<td {fmt} align="left">{mir}</td>"#,
595 ),
596 i = i,
597 fmt = fmt,
598 mir = dot::escape_html(mir),
599 )?;
600
601 f(self, w, &fmt)?;
602 w.write_fmt(format_args!("</tr>"))write!(w, "</tr>")
603 }
604
605 fn write_row_with_full_state(
606 &mut self,
607 w: &mut impl io::Write,
608 i: &str,
609 mir: &str,
610 ) -> io::Result<()> {
611 self.write_row(w, i, mir, |this, w, fmt| {
612 let state = this.cursor.get();
613 let analysis = this.cursor.analysis();
614
615 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!(
618 w,
619 r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
620 colspan = this.style.num_state_columns(),
621 fmt = fmt,
622 state = dot::escape_html(&format!(
623 "{:?}",
624 DebugWithAdapter { this: state, ctxt: analysis }
625 )),
626 )
627 })
628 }
629}
630
631struct StateDiffCollector<D> {
632 prev_state: D,
633 before: Option<Vec<String>>,
634 after: Vec<String>,
635}
636
637impl<D> StateDiffCollector<D> {
638 fn run<'tcx, A>(
639 body: &Body<'tcx>,
640 block: BasicBlock,
641 results: &Results<'tcx, A>,
642 style: OutputStyle,
643 ) -> Self
644 where
645 A: Analysis<'tcx, Domain = D>,
646 D: DebugWithContext<A>,
647 {
648 let mut collector = StateDiffCollector {
649 prev_state: results.analysis.bottom_value(body),
650 after: ::alloc::vec::Vec::new()vec![],
651 before: (style == OutputStyle::BeforeAndAfter).then_some(::alloc::vec::Vec::new()vec![]),
652 };
653
654 visit_results(body, std::iter::once(block), results, &mut collector);
655 collector
656 }
657}
658
659impl<'tcx, A> ResultsVisitor<'tcx, A> for StateDiffCollector<A::Domain>
660where
661 A: Analysis<'tcx>,
662 A::Domain: DebugWithContext<A>,
663{
664 fn visit_block_start(&mut self, state: &A::Domain) {
665 if A::Direction::IS_FORWARD {
666 self.prev_state.clone_from(state);
667 }
668 }
669
670 fn visit_block_end(&mut self, state: &A::Domain) {
671 if A::Direction::IS_BACKWARD {
672 self.prev_state.clone_from(state);
673 }
674 }
675
676 fn visit_after_early_statement_effect(
677 &mut self,
678 analysis: &A,
679 state: &A::Domain,
680 _statement: &mir::Statement<'tcx>,
681 _location: Location,
682 ) {
683 if let Some(before) = self.before.as_mut() {
684 before.push(diff_pretty(state, &self.prev_state, analysis));
685 self.prev_state.clone_from(state)
686 }
687 }
688
689 fn visit_after_primary_statement_effect(
690 &mut self,
691 analysis: &A,
692 state: &A::Domain,
693 _statement: &mir::Statement<'tcx>,
694 _location: Location,
695 ) {
696 self.after.push(diff_pretty(state, &self.prev_state, analysis));
697 self.prev_state.clone_from(state)
698 }
699
700 fn visit_after_early_terminator_effect(
701 &mut self,
702 analysis: &A,
703 state: &A::Domain,
704 _terminator: &mir::Terminator<'tcx>,
705 _location: Location,
706 ) {
707 if let Some(before) = self.before.as_mut() {
708 before.push(diff_pretty(state, &self.prev_state, analysis));
709 self.prev_state.clone_from(state)
710 }
711 }
712
713 fn visit_after_primary_terminator_effect(
714 &mut self,
715 analysis: &A,
716 state: &A::Domain,
717 _terminator: &mir::Terminator<'tcx>,
718 _location: Location,
719 ) {
720 self.after.push(diff_pretty(state, &self.prev_state, analysis));
721 self.prev_state.clone_from(state)
722 }
723}
724
725macro_rules! regex {
726 ($re:literal $(,)?) => {{
727 static RE: OnceLock<regex::Regex> = OnceLock::new();
728 RE.get_or_init(|| Regex::new($re).unwrap())
729 }};
730}
731
732fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
733where
734 T: DebugWithContext<C>,
735{
736 if new == old {
737 return String::new();
738 }
739
740 let re = {
static RE: OnceLock<regex::Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new("\t?\u{001f}([+-])").unwrap())
}regex!("\t?\u{001f}([+-])");
741
742 let raw_diff = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:#?}",
DebugDiffWithAdapter { new, old, ctxt }))
})format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
743 let raw_diff = dot::escape_html(&raw_diff);
744
745 let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
747
748 let mut inside_font_tag = false;
749 let html_diff = re.replace_all(&raw_diff, |captures: ®ex::Captures<'_>| {
750 let mut ret = String::new();
751 if inside_font_tag {
752 ret.push_str(r#"</font>"#);
753 }
754
755 let tag = match &captures[1] {
756 "+" => r#"<font color="darkgreen">+"#,
757 "-" => r#"<font color="red">-"#,
758 _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
759 };
760
761 inside_font_tag = true;
762 ret.push_str(tag);
763 ret
764 });
765
766 let Cow::Owned(mut html_diff) = html_diff else {
767 return raw_diff;
768 };
769
770 if inside_font_tag {
771 html_diff.push_str("</font>");
772 }
773
774 html_diff
775}
776
777#[derive(#[automatically_derived]
impl ::core::clone::Clone for Background {
#[inline]
fn clone(&self) -> Background { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Background { }Copy)]
779enum Background {
780 Light,
781 Dark,
782}
783
784impl Background {
785 fn attr(self) -> &'static str {
786 match self {
787 Self::Dark => "bgcolor=\"#f0f0f0\"",
788 Self::Light => "",
789 }
790 }
791}
792
793impl ops::Not for Background {
794 type Output = Self;
795
796 fn not(self) -> Self {
797 match self {
798 Self::Light => Self::Dark,
799 Self::Dark => Self::Light,
800 }
801 }
802}