]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | //! A helpful diagram for debugging dataflow problems. |
2 | ||
1b1a35ee | 3 | use std::borrow::Cow; |
923072b8 | 4 | use std::sync::OnceLock; |
dfeec247 | 5 | use std::{io, ops, str}; |
e74abb32 | 6 | |
1b1a35ee | 7 | use regex::Regex; |
f035d41b | 8 | use rustc_graphviz as dot; |
c295e0f8 | 9 | use rustc_middle::mir::graphviz_safe_def_name; |
ba9703b0 | 10 | use rustc_middle::mir::{self, BasicBlock, Body, Location}; |
e74abb32 | 11 | |
1b1a35ee | 12 | use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext}; |
a2a8927a | 13 | use super::{Analysis, CallReturnPlaces, Direction, Results, ResultsRefCursor, ResultsVisitor}; |
e74abb32 | 14 | |
1b1a35ee XL |
15 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
16 | pub enum OutputStyle { | |
17 | AfterOnly, | |
18 | BeforeAndAfter, | |
19 | } | |
20 | ||
21 | impl OutputStyle { | |
22 | fn num_state_columns(&self) -> usize { | |
23 | match self { | |
24 | Self::AfterOnly => 1, | |
25 | Self::BeforeAndAfter => 2, | |
26 | } | |
27 | } | |
28 | } | |
29 | ||
e74abb32 XL |
30 | pub struct Formatter<'a, 'tcx, A> |
31 | where | |
32 | A: Analysis<'tcx>, | |
33 | { | |
34 | body: &'a Body<'tcx>, | |
1b1a35ee XL |
35 | results: &'a Results<'tcx, A>, |
36 | style: OutputStyle, | |
e74abb32 XL |
37 | } |
38 | ||
a2a8927a | 39 | impl<'a, 'tcx, A> Formatter<'a, 'tcx, A> |
e74abb32 XL |
40 | where |
41 | A: Analysis<'tcx>, | |
42 | { | |
29967ef6 XL |
43 | pub fn new(body: &'a Body<'tcx>, results: &'a Results<'tcx, A>, style: OutputStyle) -> Self { |
44 | Formatter { body, results, style } | |
e74abb32 XL |
45 | } |
46 | } | |
47 | ||
48 | /// A pair of a basic block and an index into that basic blocks `successors`. | |
49 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] | |
50 | pub struct CfgEdge { | |
51 | source: BasicBlock, | |
52 | index: usize, | |
53 | } | |
54 | ||
a2a8927a | 55 | fn dataflow_successors(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> { |
e74abb32 XL |
56 | body[bb] |
57 | .terminator() | |
58 | .successors() | |
59 | .enumerate() | |
60 | .map(|(index, _)| CfgEdge { source: bb, index }) | |
61 | .collect() | |
62 | } | |
63 | ||
a2a8927a | 64 | impl<'tcx, A> dot::Labeller<'_> for Formatter<'_, 'tcx, A> |
e74abb32 XL |
65 | where |
66 | A: Analysis<'tcx>, | |
1b1a35ee | 67 | A::Domain: DebugWithContext<A>, |
e74abb32 XL |
68 | { |
69 | type Node = BasicBlock; | |
70 | type Edge = CfgEdge; | |
71 | ||
72 | fn graph_id(&self) -> dot::Id<'_> { | |
29967ef6 | 73 | let name = graphviz_safe_def_name(self.body.source.def_id()); |
e74abb32 XL |
74 | dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap() |
75 | } | |
76 | ||
77 | fn node_id(&self, n: &Self::Node) -> dot::Id<'_> { | |
78 | dot::Id::new(format!("bb_{}", n.index())).unwrap() | |
79 | } | |
80 | ||
81 | fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> { | |
82 | let mut label = Vec::new(); | |
1b1a35ee XL |
83 | let mut fmt = BlockFormatter { |
84 | results: ResultsRefCursor::new(self.body, self.results), | |
85 | style: self.style, | |
86 | bg: Background::Light, | |
87 | }; | |
88 | ||
89 | fmt.write_node_label(&mut label, self.body, *block).unwrap(); | |
e74abb32 XL |
90 | dot::LabelText::html(String::from_utf8(label).unwrap()) |
91 | } | |
92 | ||
93 | fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> { | |
94 | Some(dot::LabelText::label("none")) | |
95 | } | |
96 | ||
97 | fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> { | |
dfeec247 | 98 | let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index]; |
e74abb32 XL |
99 | dot::LabelText::label(label.clone()) |
100 | } | |
101 | } | |
102 | ||
a2a8927a | 103 | impl<'a, 'tcx, A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A> |
e74abb32 XL |
104 | where |
105 | A: Analysis<'tcx>, | |
106 | { | |
107 | type Node = BasicBlock; | |
108 | type Edge = CfgEdge; | |
109 | ||
110 | fn nodes(&self) -> dot::Nodes<'_, Self::Node> { | |
f2b60f7d | 111 | self.body.basic_blocks.indices().collect::<Vec<_>>().into() |
e74abb32 XL |
112 | } |
113 | ||
114 | fn edges(&self) -> dot::Edges<'_, Self::Edge> { | |
115 | self.body | |
f2b60f7d | 116 | .basic_blocks |
e74abb32 | 117 | .indices() |
f9f354fc | 118 | .flat_map(|bb| dataflow_successors(self.body, bb)) |
e74abb32 XL |
119 | .collect::<Vec<_>>() |
120 | .into() | |
121 | } | |
122 | ||
123 | fn source(&self, edge: &Self::Edge) -> Self::Node { | |
124 | edge.source | |
125 | } | |
126 | ||
127 | fn target(&self, edge: &Self::Edge) -> Self::Node { | |
923072b8 | 128 | self.body[edge.source].terminator().successors().nth(edge.index).unwrap() |
e74abb32 XL |
129 | } |
130 | } | |
131 | ||
132 | struct BlockFormatter<'a, 'tcx, A> | |
133 | where | |
134 | A: Analysis<'tcx>, | |
135 | { | |
e74abb32 XL |
136 | results: ResultsRefCursor<'a, 'a, 'tcx, A>, |
137 | bg: Background, | |
1b1a35ee | 138 | style: OutputStyle, |
e74abb32 XL |
139 | } |
140 | ||
a2a8927a | 141 | impl<'a, 'tcx, A> BlockFormatter<'a, 'tcx, A> |
e74abb32 XL |
142 | where |
143 | A: Analysis<'tcx>, | |
1b1a35ee | 144 | A::Domain: DebugWithContext<A>, |
e74abb32 | 145 | { |
dfeec247 XL |
146 | const HEADER_COLOR: &'static str = "#a0a0a0"; |
147 | ||
e74abb32 XL |
148 | fn toggle_background(&mut self) -> Background { |
149 | let bg = self.bg; | |
150 | self.bg = !bg; | |
151 | bg | |
152 | } | |
153 | ||
154 | fn write_node_label( | |
155 | &mut self, | |
156 | w: &mut impl io::Write, | |
157 | body: &'a Body<'tcx>, | |
158 | block: BasicBlock, | |
159 | ) -> io::Result<()> { | |
160 | // Sample output: | |
161 | // +-+-----------------------------------------------+ | |
162 | // A | bb4 | | |
163 | // +-+----------------------------------+------------+ | |
164 | // B | MIR | STATE | | |
165 | // +-+----------------------------------+------------+ | |
166 | // C | | (on entry) | {_0,_2,_3} | | |
167 | // +-+----------------------------------+------------+ | |
168 | // D |0| StorageLive(_7) | | | |
169 | // +-+----------------------------------+------------+ | |
170 | // |1| StorageLive(_8) | | | |
171 | // +-+----------------------------------+------------+ | |
172 | // |2| _8 = &mut _1 | +_8 | | |
173 | // +-+----------------------------------+------------+ | |
174 | // E |T| _4 = const Foo::twiddle(move _2) | -_2 | | |
175 | // +-+----------------------------------+------------+ | |
176 | // F | | (on unwind) | {_0,_3,_8} | | |
177 | // +-+----------------------------------+------------+ | |
178 | // | | (on successful return) | +_4 | | |
179 | // +-+----------------------------------+------------+ | |
180 | ||
74b04a01 XL |
181 | // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their |
182 | // children. This is because `xdot` seemed to have a hard time correctly propagating | |
183 | // attributes. Make sure to test the output before trying to remove the redundancy. | |
184 | // Notably, `align` was found to have no effect when applied only to <table>. | |
185 | ||
186 | let table_fmt = concat!( | |
187 | " border=\"1\"", | |
188 | " cellborder=\"1\"", | |
189 | " cellspacing=\"0\"", | |
190 | " cellpadding=\"3\"", | |
191 | " sides=\"rb\"", | |
192 | ); | |
193 | write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?; | |
e74abb32 | 194 | |
dfeec247 | 195 | // A + B: Block header |
1b1a35ee XL |
196 | match self.style { |
197 | OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?, | |
198 | OutputStyle::BeforeAndAfter => { | |
199 | self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])? | |
200 | } | |
dfeec247 | 201 | } |
e74abb32 | 202 | |
f9f354fc | 203 | // C: State at start of block |
e74abb32 XL |
204 | self.bg = Background::Light; |
205 | self.results.seek_to_block_start(block); | |
1b1a35ee | 206 | let block_start_state = self.results.get().clone(); |
f9f354fc | 207 | self.write_row_with_full_state(w, "", "(on start)")?; |
e74abb32 | 208 | |
1b1a35ee XL |
209 | // D + E: Statement and terminator transfer functions |
210 | self.write_statements_and_terminator(w, body, block)?; | |
e74abb32 | 211 | |
f9f354fc | 212 | // F: State at end of block |
74b04a01 | 213 | |
1b1a35ee XL |
214 | let terminator = body[block].terminator(); |
215 | ||
74b04a01 XL |
216 | // Write the full dataflow state immediately after the terminator if it differs from the |
217 | // state at block entry. | |
f9f354fc | 218 | self.results.seek_to_block_end(block); |
923072b8 | 219 | if self.results.get() != &block_start_state || A::Direction::IS_BACKWARD { |
74b04a01 | 220 | let after_terminator_name = match terminator.kind { |
923072b8 | 221 | mir::TerminatorKind::Call { target: Some(_), .. } => "(on unwind)", |
f9f354fc | 222 | _ => "(on end)", |
74b04a01 XL |
223 | }; |
224 | ||
225 | self.write_row_with_full_state(w, "", after_terminator_name)?; | |
e74abb32 XL |
226 | } |
227 | ||
1b1a35ee XL |
228 | // Write any changes caused by terminator-specific effects. |
229 | // | |
230 | // FIXME: These should really be printed as part of each outgoing edge rather than the node | |
231 | // for the basic block itself. That way, we could display terminator-specific effects for | |
232 | // backward dataflow analyses as well as effects for `SwitchInt` terminators. | |
f9f354fc | 233 | match terminator.kind { |
923072b8 | 234 | mir::TerminatorKind::Call { destination, .. } => { |
f9f354fc | 235 | self.write_row(w, "", "(on successful return)", |this, w, fmt| { |
f9f354fc XL |
236 | let state_on_unwind = this.results.get().clone(); |
237 | this.results.apply_custom_effect(|analysis, state| { | |
a2a8927a XL |
238 | analysis.apply_call_return_effect( |
239 | state, | |
240 | block, | |
923072b8 | 241 | CallReturnPlaces::Call(destination), |
a2a8927a | 242 | ); |
f9f354fc XL |
243 | }); |
244 | ||
1b1a35ee XL |
245 | write!( |
246 | w, | |
247 | r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#, | |
248 | colspan = this.style.num_state_columns(), | |
249 | fmt = fmt, | |
250 | diff = diff_pretty( | |
251 | this.results.get(), | |
252 | &state_on_unwind, | |
253 | this.results.analysis() | |
254 | ), | |
255 | ) | |
f9f354fc XL |
256 | })?; |
257 | } | |
258 | ||
259 | mir::TerminatorKind::Yield { resume, resume_arg, .. } => { | |
260 | self.write_row(w, "", "(on yield resume)", |this, w, fmt| { | |
f9f354fc XL |
261 | let state_on_generator_drop = this.results.get().clone(); |
262 | this.results.apply_custom_effect(|analysis, state| { | |
263 | analysis.apply_yield_resume_effect(state, resume, resume_arg); | |
264 | }); | |
265 | ||
1b1a35ee | 266 | write!( |
f9f354fc | 267 | w, |
1b1a35ee XL |
268 | r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#, |
269 | colspan = this.style.num_state_columns(), | |
270 | fmt = fmt, | |
271 | diff = diff_pretty( | |
272 | this.results.get(), | |
273 | &state_on_generator_drop, | |
274 | this.results.analysis() | |
275 | ), | |
276 | ) | |
f9f354fc XL |
277 | })?; |
278 | } | |
279 | ||
a2a8927a XL |
280 | mir::TerminatorKind::InlineAsm { destination: Some(_), ref operands, .. } => { |
281 | self.write_row(w, "", "(on successful return)", |this, w, fmt| { | |
282 | let state_on_unwind = this.results.get().clone(); | |
283 | this.results.apply_custom_effect(|analysis, state| { | |
284 | analysis.apply_call_return_effect( | |
285 | state, | |
286 | block, | |
287 | CallReturnPlaces::InlineAsm(operands), | |
288 | ); | |
289 | }); | |
290 | ||
291 | write!( | |
292 | w, | |
293 | r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#, | |
294 | colspan = this.style.num_state_columns(), | |
295 | fmt = fmt, | |
296 | diff = diff_pretty( | |
297 | this.results.get(), | |
298 | &state_on_unwind, | |
299 | this.results.analysis() | |
300 | ), | |
301 | ) | |
302 | })?; | |
303 | } | |
304 | ||
f9f354fc | 305 | _ => {} |
74b04a01 XL |
306 | }; |
307 | ||
e74abb32 XL |
308 | write!(w, "</table>") |
309 | } | |
310 | ||
dfeec247 | 311 | fn write_block_header_simple( |
e74abb32 XL |
312 | &mut self, |
313 | w: &mut impl io::Write, | |
dfeec247 | 314 | block: BasicBlock, |
e74abb32 | 315 | ) -> io::Result<()> { |
dfeec247 XL |
316 | // +-------------------------------------------------+ |
317 | // A | bb4 | | |
318 | // +-----------------------------------+-------------+ | |
319 | // B | MIR | STATE | | |
320 | // +-+---------------------------------+-------------+ | |
321 | // | | ... | | | |
e74abb32 | 322 | |
dfeec247 XL |
323 | // A |
324 | write!( | |
325 | w, | |
326 | concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",), | |
327 | block_id = block.index(), | |
328 | )?; | |
e74abb32 | 329 | |
dfeec247 | 330 | // B |
e74abb32 XL |
331 | write!( |
332 | w, | |
dfeec247 XL |
333 | concat!( |
334 | "<tr>", | |
335 | r#"<td colspan="2" {fmt}>MIR</td>"#, | |
336 | r#"<td {fmt}>STATE</td>"#, | |
337 | "</tr>", | |
338 | ), | |
339 | fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR), | |
e74abb32 XL |
340 | ) |
341 | } | |
342 | ||
dfeec247 | 343 | fn write_block_header_with_state_columns( |
e74abb32 XL |
344 | &mut self, |
345 | w: &mut impl io::Write, | |
dfeec247 | 346 | block: BasicBlock, |
1b1a35ee | 347 | state_column_names: &[&str], |
e74abb32 | 348 | ) -> io::Result<()> { |
dfeec247 XL |
349 | // +------------------------------------+-------------+ |
350 | // A | bb4 | STATE | | |
351 | // +------------------------------------+------+------+ | |
352 | // B | MIR | GEN | KILL | | |
353 | // +-+----------------------------------+------+------+ | |
354 | // | | ... | | | | |
355 | ||
dfeec247 XL |
356 | // A |
357 | write!( | |
358 | w, | |
359 | concat!( | |
360 | "<tr>", | |
361 | r#"<td {fmt} colspan="2">bb{block_id}</td>"#, | |
362 | r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#, | |
363 | "</tr>", | |
364 | ), | |
365 | fmt = "sides=\"tl\"", | |
366 | num_state_cols = state_column_names.len(), | |
367 | block_id = block.index(), | |
368 | )?; | |
e74abb32 | 369 | |
dfeec247 XL |
370 | // B |
371 | let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR); | |
372 | write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?; | |
e74abb32 | 373 | |
dfeec247 XL |
374 | for name in state_column_names { |
375 | write!(w, "<td {fmt}>{name}</td>", fmt = fmt, name = name)?; | |
376 | } | |
e74abb32 | 377 | |
dfeec247 XL |
378 | write!(w, "</tr>") |
379 | } | |
380 | ||
1b1a35ee XL |
381 | fn write_statements_and_terminator( |
382 | &mut self, | |
383 | w: &mut impl io::Write, | |
384 | body: &'a Body<'tcx>, | |
385 | block: BasicBlock, | |
386 | ) -> io::Result<()> { | |
387 | let diffs = StateDiffCollector::run(body, block, self.results.results(), self.style); | |
388 | ||
389 | let mut befores = diffs.before.map(|v| v.into_iter()); | |
390 | let mut afters = diffs.after.into_iter(); | |
391 | ||
392 | let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| { | |
923072b8 | 393 | if A::Direction::IS_FORWARD { it.next().unwrap() } else { it.next_back().unwrap() } |
1b1a35ee XL |
394 | }; |
395 | ||
396 | for (i, statement) in body[block].statements.iter().enumerate() { | |
397 | let statement_str = format!("{:?}", statement); | |
398 | let index_str = format!("{}", i); | |
399 | ||
400 | let after = next_in_dataflow_order(&mut afters); | |
401 | let before = befores.as_mut().map(next_in_dataflow_order); | |
402 | ||
403 | self.write_row(w, &index_str, &statement_str, |_this, w, fmt| { | |
404 | if let Some(before) = before { | |
405 | write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?; | |
406 | } | |
407 | ||
408 | write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after) | |
409 | })?; | |
410 | } | |
411 | ||
412 | let after = next_in_dataflow_order(&mut afters); | |
413 | let before = befores.as_mut().map(next_in_dataflow_order); | |
414 | ||
415 | assert!(afters.is_empty()); | |
416 | assert!(befores.as_ref().map_or(true, ExactSizeIterator::is_empty)); | |
417 | ||
418 | let terminator = body[block].terminator(); | |
419 | let mut terminator_str = String::new(); | |
420 | terminator.kind.fmt_head(&mut terminator_str).unwrap(); | |
421 | ||
422 | self.write_row(w, "T", &terminator_str, |_this, w, fmt| { | |
423 | if let Some(before) = before { | |
424 | write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?; | |
425 | } | |
426 | ||
427 | write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after) | |
428 | }) | |
429 | } | |
430 | ||
dfeec247 XL |
431 | /// Write a row with the given index and MIR, using the function argument to fill in the |
432 | /// "STATE" column(s). | |
433 | fn write_row<W: io::Write>( | |
434 | &mut self, | |
435 | w: &mut W, | |
436 | i: &str, | |
437 | mir: &str, | |
438 | f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>, | |
439 | ) -> io::Result<()> { | |
440 | let bg = self.toggle_background(); | |
74b04a01 XL |
441 | let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" }; |
442 | ||
443 | let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr()); | |
e74abb32 XL |
444 | |
445 | write!( | |
446 | w, | |
dfeec247 XL |
447 | concat!( |
448 | "<tr>", | |
449 | r#"<td {fmt} align="right">{i}</td>"#, | |
450 | r#"<td {fmt} align="left">{mir}</td>"#, | |
451 | ), | |
e74abb32 | 452 | i = i, |
dfeec247 | 453 | fmt = fmt, |
e74abb32 XL |
454 | mir = dot::escape_html(mir), |
455 | )?; | |
456 | ||
dfeec247 XL |
457 | f(self, w, &fmt)?; |
458 | write!(w, "</tr>") | |
459 | } | |
460 | ||
461 | fn write_row_with_full_state( | |
462 | &mut self, | |
463 | w: &mut impl io::Write, | |
464 | i: &str, | |
465 | mir: &str, | |
466 | ) -> io::Result<()> { | |
467 | self.write_row(w, i, mir, |this, w, fmt| { | |
468 | let state = this.results.get(); | |
469 | let analysis = this.results.analysis(); | |
470 | ||
1b1a35ee XL |
471 | // FIXME: The full state vector can be quite long. It would be nice to split on commas |
472 | // and use some text wrapping algorithm. | |
e74abb32 XL |
473 | write!( |
474 | w, | |
1b1a35ee XL |
475 | r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#, |
476 | colspan = this.style.num_state_columns(), | |
dfeec247 | 477 | fmt = fmt, |
1b1a35ee XL |
478 | state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }), |
479 | ) | |
dfeec247 XL |
480 | }) |
481 | } | |
482 | } | |
483 | ||
1b1a35ee | 484 | struct StateDiffCollector<'a, 'tcx, A> |
dfeec247 XL |
485 | where |
486 | A: Analysis<'tcx>, | |
487 | { | |
1b1a35ee XL |
488 | analysis: &'a A, |
489 | prev_state: A::Domain, | |
490 | before: Option<Vec<String>>, | |
491 | after: Vec<String>, | |
dfeec247 XL |
492 | } |
493 | ||
a2a8927a | 494 | impl<'a, 'tcx, A> StateDiffCollector<'a, 'tcx, A> |
f9f354fc XL |
495 | where |
496 | A: Analysis<'tcx>, | |
1b1a35ee | 497 | A::Domain: DebugWithContext<A>, |
f9f354fc | 498 | { |
1b1a35ee XL |
499 | fn run( |
500 | body: &'a mir::Body<'tcx>, | |
501 | block: BasicBlock, | |
502 | results: &'a Results<'tcx, A>, | |
503 | style: OutputStyle, | |
504 | ) -> Self { | |
505 | let mut collector = StateDiffCollector { | |
506 | analysis: &results.analysis, | |
507 | prev_state: results.analysis.bottom_value(body), | |
508 | after: vec![], | |
509 | before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]), | |
510 | }; | |
dfeec247 | 511 | |
1b1a35ee XL |
512 | results.visit_with(body, std::iter::once(block), &mut collector); |
513 | collector | |
dfeec247 XL |
514 | } |
515 | } | |
516 | ||
a2a8927a | 517 | impl<'a, 'tcx, A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A> |
dfeec247 XL |
518 | where |
519 | A: Analysis<'tcx>, | |
1b1a35ee | 520 | A::Domain: DebugWithContext<A>, |
dfeec247 | 521 | { |
1b1a35ee | 522 | type FlowState = A::Domain; |
dfeec247 | 523 | |
1b1a35ee | 524 | fn visit_block_start( |
dfeec247 | 525 | &mut self, |
1b1a35ee | 526 | state: &Self::FlowState, |
a2a8927a | 527 | _block_data: &mir::BasicBlockData<'tcx>, |
1b1a35ee XL |
528 | _block: BasicBlock, |
529 | ) { | |
923072b8 | 530 | if A::Direction::IS_FORWARD { |
1b1a35ee | 531 | self.prev_state.clone_from(state); |
e74abb32 | 532 | } |
dfeec247 | 533 | } |
dfeec247 | 534 | |
1b1a35ee XL |
535 | fn visit_block_end( |
536 | &mut self, | |
537 | state: &Self::FlowState, | |
a2a8927a | 538 | _block_data: &mir::BasicBlockData<'tcx>, |
1b1a35ee XL |
539 | _block: BasicBlock, |
540 | ) { | |
923072b8 | 541 | if A::Direction::IS_BACKWARD { |
1b1a35ee XL |
542 | self.prev_state.clone_from(state); |
543 | } | |
544 | } | |
dfeec247 | 545 | |
1b1a35ee XL |
546 | fn visit_statement_before_primary_effect( |
547 | &mut self, | |
548 | state: &Self::FlowState, | |
a2a8927a | 549 | _statement: &mir::Statement<'tcx>, |
1b1a35ee XL |
550 | _location: Location, |
551 | ) { | |
552 | if let Some(before) = self.before.as_mut() { | |
553 | before.push(diff_pretty(state, &self.prev_state, self.analysis)); | |
554 | self.prev_state.clone_from(state) | |
555 | } | |
dfeec247 | 556 | } |
dfeec247 | 557 | |
1b1a35ee XL |
558 | fn visit_statement_after_primary_effect( |
559 | &mut self, | |
560 | state: &Self::FlowState, | |
a2a8927a | 561 | _statement: &mir::Statement<'tcx>, |
1b1a35ee XL |
562 | _location: Location, |
563 | ) { | |
564 | self.after.push(diff_pretty(state, &self.prev_state, self.analysis)); | |
565 | self.prev_state.clone_from(state) | |
dfeec247 XL |
566 | } |
567 | ||
1b1a35ee | 568 | fn visit_terminator_before_primary_effect( |
dfeec247 | 569 | &mut self, |
1b1a35ee | 570 | state: &Self::FlowState, |
a2a8927a | 571 | _terminator: &mir::Terminator<'tcx>, |
1b1a35ee XL |
572 | _location: Location, |
573 | ) { | |
574 | if let Some(before) = self.before.as_mut() { | |
575 | before.push(diff_pretty(state, &self.prev_state, self.analysis)); | |
576 | self.prev_state.clone_from(state) | |
e74abb32 | 577 | } |
dfeec247 | 578 | } |
dfeec247 | 579 | |
1b1a35ee XL |
580 | fn visit_terminator_after_primary_effect( |
581 | &mut self, | |
582 | state: &Self::FlowState, | |
a2a8927a | 583 | _terminator: &mir::Terminator<'tcx>, |
1b1a35ee XL |
584 | _location: Location, |
585 | ) { | |
586 | self.after.push(diff_pretty(state, &self.prev_state, self.analysis)); | |
587 | self.prev_state.clone_from(state) | |
588 | } | |
dfeec247 XL |
589 | } |
590 | ||
1b1a35ee XL |
591 | macro_rules! regex { |
592 | ($re:literal $(,)?) => {{ | |
923072b8 | 593 | static RE: OnceLock<regex::Regex> = OnceLock::new(); |
1b1a35ee XL |
594 | RE.get_or_init(|| Regex::new($re).unwrap()) |
595 | }}; | |
dfeec247 XL |
596 | } |
597 | ||
1b1a35ee | 598 | fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String |
dfeec247 | 599 | where |
1b1a35ee | 600 | T: DebugWithContext<C>, |
dfeec247 | 601 | { |
1b1a35ee XL |
602 | if new == old { |
603 | return String::new(); | |
dfeec247 XL |
604 | } |
605 | ||
1b1a35ee | 606 | let re = regex!("\t?\u{001f}([+-])"); |
dfeec247 | 607 | |
1b1a35ee | 608 | let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt }); |
dfeec247 | 609 | |
1b1a35ee XL |
610 | // Replace newlines in the `Debug` output with `<br/>` |
611 | let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#); | |
dfeec247 | 612 | |
1b1a35ee XL |
613 | let mut inside_font_tag = false; |
614 | let html_diff = re.replace_all(&raw_diff, |captures: ®ex::Captures<'_>| { | |
615 | let mut ret = String::new(); | |
616 | if inside_font_tag { | |
617 | ret.push_str(r#"</font>"#); | |
e74abb32 XL |
618 | } |
619 | ||
1b1a35ee XL |
620 | let tag = match &captures[1] { |
621 | "+" => r#"<font color="darkgreen">+"#, | |
622 | "-" => r#"<font color="red">-"#, | |
623 | _ => unreachable!(), | |
dfeec247 | 624 | }; |
e74abb32 | 625 | |
1b1a35ee XL |
626 | inside_font_tag = true; |
627 | ret.push_str(tag); | |
628 | ret | |
629 | }); | |
74b04a01 | 630 | |
5e7ed085 FG |
631 | let Cow::Owned(mut html_diff) = html_diff else { |
632 | return raw_diff; | |
1b1a35ee | 633 | }; |
e74abb32 | 634 | |
1b1a35ee XL |
635 | if inside_font_tag { |
636 | html_diff.push_str("</font>"); | |
e74abb32 XL |
637 | } |
638 | ||
1b1a35ee | 639 | html_diff |
e74abb32 XL |
640 | } |
641 | ||
642 | /// The background color used for zebra-striping the table. | |
643 | #[derive(Clone, Copy)] | |
644 | enum Background { | |
645 | Light, | |
646 | Dark, | |
647 | } | |
648 | ||
649 | impl Background { | |
650 | fn attr(self) -> &'static str { | |
651 | match self { | |
652 | Self::Dark => "bgcolor=\"#f0f0f0\"", | |
653 | Self::Light => "", | |
654 | } | |
655 | } | |
656 | } | |
657 | ||
658 | impl ops::Not for Background { | |
659 | type Output = Self; | |
660 | ||
661 | fn not(self) -> Self { | |
662 | match self { | |
663 | Self::Light => Self::Dark, | |
664 | Self::Dark => Self::Light, | |
665 | } | |
666 | } | |
667 | } |