]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir/src/util/spanview.rs
Add new `-Z dump-mir-spanview` option
[rust.git] / compiler / rustc_mir / src / util / spanview.rs
1 use rustc_hir::def_id::DefId;
2 use rustc_middle::hir;
3 use rustc_middle::mir::*;
4 use rustc_middle::ty::TyCtxt;
5 use rustc_session::config::MirSpanview;
6 use rustc_span::{BytePos, Pos, Span};
7
8 use std::io::{self, Write};
9 use std::iter::Peekable;
10
11 pub const TOOLTIP_INDENT: &str = "    ";
12
13 const NEW_LINE_SPAN: &str = "</span>\n<span class=\"line\">";
14 const HEADER: &str = r#"<!DOCTYPE html>
15 <html>
16 <head>
17     <title>coverage_of_if_else - Code Regions</title>
18     <style>
19     .line {
20         counter-increment: line;
21     }
22     .line:before {
23         content: counter(line) ": ";
24         font-family: Menlo, Monaco, monospace;
25         font-style: italic;
26         width: 3.8em;
27         display: inline-block;
28         text-align: right;
29         filter: opacity(50%);
30         -webkit-user-select: none;
31     }
32     .code {
33         color: #dddddd;
34         background-color: #222222;
35         font-family: Menlo, Monaco, monospace;
36         line-height: 1.4em;
37         border-bottom: 2px solid #222222;
38         white-space: pre;
39         display: inline-block;
40     }
41     .odd {
42         background-color: #55bbff;
43         color: #223311;
44     }
45     .even {
46         background-color: #ee7756;
47         color: #551133;
48     }
49     .code {
50         --index: calc(var(--layer) - 1);
51         padding-top: calc(var(--index) * 0.15em);
52         filter:
53             hue-rotate(calc(var(--index) * 25deg))
54             saturate(calc(100% - (var(--index) * 2%)))
55             brightness(calc(100% - (var(--index) * 1.5%)));
56     }
57     .annotation {
58         color: #4444ff;
59         font-family: monospace;
60         font-style: italic;
61         display: none;
62         -webkit-user-select: none;
63     }
64     body:active .annotation {
65         /* requires holding mouse down anywhere on the page */
66         display: inline-block;
67     }
68     span:hover .annotation {
69         /* requires hover over a span ONLY on its first line */
70         display: inline-block;
71     }
72     </style>
73 </head>
74 <body>"#;
75
76 const FOOTER: &str = r#"
77 </body>
78 </html>"#;
79
80 /// Metadata to highlight the span of a MIR BasicBlock, Statement, or Terminator.
81 pub struct SpanViewable {
82     pub span: Span,
83     pub title: String,
84     pub tooltip: String,
85 }
86
87 /// Write a spanview HTML+CSS file to analyze MIR element spans.
88 pub fn write_mir_fn_spanview<'tcx, W>(
89     tcx: TyCtxt<'tcx>,
90     def_id: DefId,
91     body: &Body<'tcx>,
92     spanview: MirSpanview,
93     w: &mut W,
94 ) -> io::Result<()>
95 where
96     W: Write,
97 {
98     let body_span = hir_body(tcx, def_id).value.span;
99     let mut span_viewables = Vec::new();
100     for (bb, data) in body.basic_blocks().iter_enumerated() {
101         match spanview {
102             MirSpanview::Statement => {
103                 for (i, statement) in data.statements.iter().enumerate() {
104                     if let Some(span_viewable) =
105                         statement_span_viewable(tcx, body_span, bb, i, statement)
106                     {
107                         span_viewables.push(span_viewable);
108                     }
109                 }
110                 if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
111                     span_viewables.push(span_viewable);
112                 }
113             }
114             MirSpanview::Terminator => {
115                 if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
116                     span_viewables.push(span_viewable);
117                 }
118             }
119             MirSpanview::Block => {
120                 if let Some(span_viewable) = block_span_viewable(tcx, body_span, bb, data) {
121                     span_viewables.push(span_viewable);
122                 }
123             }
124         }
125     }
126     write_spanview_document(tcx, def_id, span_viewables, w)?;
127     Ok(())
128 }
129
130 /// Generate a spanview HTML+CSS document for the given local function `def_id`, and a pre-generated
131 /// list `SpanViewable`s.
132 pub fn write_spanview_document<'tcx, W>(
133     tcx: TyCtxt<'tcx>,
134     def_id: DefId,
135     mut span_viewables: Vec<SpanViewable>,
136     w: &mut W,
137 ) -> io::Result<()>
138 where
139     W: Write,
140 {
141     let fn_span = fn_span(tcx, def_id);
142     writeln!(w, "{}", HEADER)?;
143     let mut next_pos = fn_span.lo();
144     let end_pos = fn_span.hi();
145     let source_map = tcx.sess.source_map();
146     let start = source_map.lookup_char_pos(next_pos);
147     write!(
148         w,
149         r#"<div class="code" style="counter-reset: line {}"><span class="line">{}"#,
150         start.line - 1,
151         " ".repeat(start.col.to_usize())
152     )?;
153     span_viewables.sort_unstable_by(|a, b| {
154         let a = a.span;
155         let b = b.span;
156         if a.lo() == b.lo() {
157             // Sort hi() in reverse order so shorter spans are attempted after longer spans.
158             // This should give shorter spans a higher "layer", so they are not covered by
159             // the longer spans.
160             b.hi().partial_cmp(&a.hi())
161         } else {
162             a.lo().partial_cmp(&b.lo())
163         }
164         .unwrap()
165     });
166     let mut ordered_span_viewables = span_viewables.iter().peekable();
167     let mut alt = false;
168     while ordered_span_viewables.peek().is_some() {
169         next_pos = write_span_viewables(tcx, next_pos, &mut ordered_span_viewables, false, 1, w)?;
170         alt = !alt;
171     }
172     if next_pos < end_pos {
173         write_coverage_gap(tcx, next_pos, end_pos, w)?;
174     }
175     write!(w, r#"</span></div>"#)?;
176     writeln!(w, "{}", FOOTER)?;
177     Ok(())
178 }
179
180 /// Format a string showing the start line and column, and end line and column within a file.
181 pub fn source_range_no_file<'tcx>(tcx: TyCtxt<'tcx>, span: &Span) -> String {
182     let source_map = tcx.sess.source_map();
183     let start = source_map.lookup_char_pos(span.lo());
184     let end = source_map.lookup_char_pos(span.hi());
185     format!("{}:{}-{}:{}", start.line, start.col.to_usize() + 1, end.line, end.col.to_usize() + 1)
186 }
187
188 pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str {
189     use StatementKind::*;
190     match statement.kind {
191         Assign(..) => "Assign",
192         FakeRead(..) => "FakeRead",
193         SetDiscriminant { .. } => "SetDiscriminant",
194         StorageLive(..) => "StorageLive",
195         StorageDead(..) => "StorageDead",
196         LlvmInlineAsm(..) => "LlvmInlineAsm",
197         Retag(..) => "Retag",
198         AscribeUserType(..) => "AscribeUserType",
199         Coverage(..) => "Coverage",
200         Nop => "Nop",
201     }
202 }
203
204 pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str {
205     use TerminatorKind::*;
206     match term.kind {
207         Goto { .. } => "Goto",
208         SwitchInt { .. } => "SwitchInt",
209         Resume => "Resume",
210         Abort => "Abort",
211         Return => "Return",
212         Unreachable => "Unreachable",
213         Drop { .. } => "Drop",
214         DropAndReplace { .. } => "DropAndReplace",
215         Call { .. } => "Call",
216         Assert { .. } => "Assert",
217         Yield { .. } => "Yield",
218         GeneratorDrop => "GeneratorDrop",
219         FalseEdge { .. } => "FalseEdge",
220         FalseUnwind { .. } => "FalseUnwind",
221         InlineAsm { .. } => "InlineAsm",
222     }
223 }
224
225 fn statement_span_viewable<'tcx>(
226     tcx: TyCtxt<'tcx>,
227     body_span: Span,
228     bb: BasicBlock,
229     i: usize,
230     statement: &Statement<'tcx>,
231 ) -> Option<SpanViewable> {
232     let span = statement.source_info.span;
233     if !body_span.contains(span) {
234         return None;
235     }
236     let title = format!("bb{}[{}]", bb.index(), i);
237     let tooltip = tooltip(tcx, &title, span, vec![statement.clone()], &None);
238     Some(SpanViewable { span, title, tooltip })
239 }
240
241 fn terminator_span_viewable<'tcx>(
242     tcx: TyCtxt<'tcx>,
243     body_span: Span,
244     bb: BasicBlock,
245     data: &BasicBlockData<'tcx>,
246 ) -> Option<SpanViewable> {
247     let term = data.terminator();
248     let span = term.source_info.span;
249     if !body_span.contains(span) {
250         return None;
251     }
252     let title = format!("bb{}`{}`", bb.index(), terminator_kind_name(term));
253     let tooltip = tooltip(tcx, &title, span, vec![], &data.terminator);
254     Some(SpanViewable { span, title, tooltip })
255 }
256
257 fn block_span_viewable<'tcx>(
258     tcx: TyCtxt<'tcx>,
259     body_span: Span,
260     bb: BasicBlock,
261     data: &BasicBlockData<'tcx>,
262 ) -> Option<SpanViewable> {
263     let span = compute_block_span(data, body_span);
264     if !body_span.contains(span) {
265         return None;
266     }
267     let title = format!("bb{}", bb.index());
268     let tooltip = tooltip(tcx, &title, span, data.statements.clone(), &data.terminator);
269     Some(SpanViewable { span, title, tooltip })
270 }
271
272 fn compute_block_span<'tcx>(data: &BasicBlockData<'tcx>, body_span: Span) -> Span {
273     let mut span = data.terminator().source_info.span;
274     for statement_span in data.statements.iter().map(|statement| statement.source_info.span) {
275         // Only combine Spans from the function's body_span.
276         if body_span.contains(statement_span) {
277             span = span.to(statement_span);
278         }
279     }
280     span
281 }
282
283 /// Recursively process each ordered span. Spans that overlap will have progressively varying
284 /// styles, such as increased padding for each overlap. Non-overlapping adjacent spans will
285 /// have alternating style choices, to help distinguish between them if, visually adjacent.
286 /// The `layer` is incremented for each overlap, and the `alt` bool alternates between true
287 /// and false, for each adjacent non-overlapping span. Source code between the spans (code
288 /// that is not in any coverage region) has neutral styling.
289 fn write_span_viewables<'tcx, 'b, W>(
290     tcx: TyCtxt<'tcx>,
291     next_pos: BytePos,
292     ordered_span_viewables: &mut Peekable<impl Iterator<Item = &'b SpanViewable>>,
293     alt: bool,
294     layer: usize,
295     w: &mut W,
296 ) -> io::Result<BytePos>
297 where
298     W: Write,
299 {
300     let span_viewable =
301         ordered_span_viewables.next().expect("ordered_span_viewables should have some");
302     if next_pos < span_viewable.span.lo() {
303         write_coverage_gap(tcx, next_pos, span_viewable.span.lo(), w)?;
304     }
305     let mut remaining_span = span_viewable.span;
306     let mut subalt = false;
307     loop {
308         let next_span_viewable = match ordered_span_viewables.peek() {
309             None => break,
310             Some(span_viewable) => *span_viewable,
311         };
312         if !next_span_viewable.span.overlaps(remaining_span) {
313             break;
314         }
315         write_span(
316             tcx,
317             remaining_span.until(next_span_viewable.span),
318             Some(span_viewable),
319             alt,
320             layer,
321             w,
322         )?;
323         let next_pos = write_span_viewables(
324             tcx,
325             next_span_viewable.span.lo(),
326             ordered_span_viewables,
327             subalt,
328             layer + 1,
329             w,
330         )?;
331         subalt = !subalt;
332         if next_pos < remaining_span.hi() {
333             remaining_span = remaining_span.with_lo(next_pos);
334         } else {
335             return Ok(next_pos);
336         }
337     }
338     write_span(tcx, remaining_span, Some(span_viewable), alt, layer, w)
339 }
340
341 fn write_coverage_gap<'tcx, W>(
342     tcx: TyCtxt<'tcx>,
343     lo: BytePos,
344     hi: BytePos,
345     w: &mut W,
346 ) -> io::Result<BytePos>
347 where
348     W: Write,
349 {
350     write_span(tcx, Span::with_root_ctxt(lo, hi), None, false, 0, w)
351 }
352
353 fn write_span<'tcx, W>(
354     tcx: TyCtxt<'tcx>,
355     span: Span,
356     span_viewable: Option<&SpanViewable>,
357     alt: bool,
358     layer: usize,
359     w: &mut W,
360 ) -> io::Result<BytePos>
361 where
362     W: Write,
363 {
364     let source_map = tcx.sess.source_map();
365     let snippet = source_map
366         .span_to_snippet(span)
367         .unwrap_or_else(|err| bug!("span_to_snippet error for span {:?}: {:?}", span, err));
368     let labeled_snippet = if let Some(SpanViewable { title, .. }) = span_viewable {
369         if span.is_empty() {
370             format!(r#"<span class="annotation">@{}</span>"#, title)
371         } else {
372             format!(r#"<span class="annotation">@{}:</span> {}"#, title, escape_html(&snippet))
373         }
374     } else {
375         snippet
376     };
377     let maybe_alt = if layer > 0 {
378         if alt { " odd" } else { " even" }
379     } else {
380         ""
381     };
382     let maybe_tooltip = if let Some(SpanViewable { tooltip, .. }) = span_viewable {
383         format!(" title=\"{}\"", escape_attr(tooltip))
384     } else {
385         "".to_owned()
386     };
387     if layer == 1 {
388         write!(w, "<span>")?;
389     }
390     for (i, line) in labeled_snippet.lines().enumerate() {
391         if i > 0 {
392             write!(w, "{}", NEW_LINE_SPAN)?;
393         }
394         write!(
395             w,
396             r#"<span class="code{}" style="--layer: {}"{}>{}</span>"#,
397             maybe_alt, layer, maybe_tooltip, line
398         )?;
399     }
400     if layer == 1 {
401         write!(w, "</span>")?;
402     }
403     Ok(span.hi())
404 }
405
406 fn tooltip<'tcx>(
407     tcx: TyCtxt<'tcx>,
408     title: &str,
409     span: Span,
410     statements: Vec<Statement<'tcx>>,
411     terminator: &Option<Terminator<'tcx>>,
412 ) -> String {
413     let source_map = tcx.sess.source_map();
414     let mut text = Vec::new();
415     text.push(format!("{}: {}:", title, &source_map.span_to_string(span)));
416     for statement in statements {
417         let source_range = source_range_no_file(tcx, &statement.source_info.span);
418         text.push(format!(
419             "\n{}{}: {}: {}",
420             TOOLTIP_INDENT,
421             source_range,
422             statement_kind_name(&statement),
423             format!("{:?}", statement)
424         ));
425     }
426     if let Some(term) = terminator {
427         let source_range = source_range_no_file(tcx, &term.source_info.span);
428         text.push(format!(
429             "\n{}{}: {}: {:?}",
430             TOOLTIP_INDENT,
431             source_range,
432             terminator_kind_name(term),
433             term.kind
434         ));
435     }
436     text.join("")
437 }
438
439 fn fn_span<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Span {
440     let hir_id =
441         tcx.hir().local_def_id_to_hir_id(def_id.as_local().expect("expected DefId is local"));
442     tcx.hir().span(hir_id)
443 }
444
445 fn hir_body<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx rustc_hir::Body<'tcx> {
446     let hir_node = tcx.hir().get_if_local(def_id).expect("expected DefId is local");
447     let fn_body_id = hir::map::associated_body(hir_node).expect("HIR node is a function with body");
448     tcx.hir().body(fn_body_id)
449 }
450
451 fn escape_html(s: &str) -> String {
452     s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
453 }
454
455 fn escape_attr(s: &str) -> String {
456     s.replace("&", "&amp;")
457         .replace("\"", "&quot;")
458         .replace("'", "&#39;")
459         .replace("<", "&lt;")
460         .replace(">", "&gt;")
461 }