1 use rustc_hir::def_id::DefId;
3 use rustc_middle::mir::*;
4 use rustc_middle::ty::TyCtxt;
5 use rustc_session::config::MirSpanview;
6 use rustc_span::{BytePos, Pos, Span, SyntaxContext};
9 use std::io::{self, Write};
11 pub const TOOLTIP_INDENT: &str = " ";
13 const CARET: char = '\u{2038}'; // Unicode `CARET`
14 const ANNOTATION_LEFT_BRACKET: char = '\u{298a}'; // Unicode `Z NOTATION RIGHT BINDING BRACKET
15 const ANNOTATION_RIGHT_BRACKET: char = '\u{2989}'; // Unicode `Z NOTATION LEFT BINDING BRACKET`
16 const NEW_LINE_SPAN: &str = "</span>\n<span class=\"line\">";
17 const HEADER: &str = r#"<!DOCTYPE html>
20 <title>coverage_of_if_else - Code Regions</title>
23 counter-increment: line;
26 content: counter(line) ": ";
27 font-family: Menlo, Monaco, monospace;
30 display: inline-block;
33 -webkit-user-select: none;
37 background-color: #222222;
38 font-family: Menlo, Monaco, monospace;
40 border-bottom: 2px solid #222222;
42 display: inline-block;
45 background-color: #55bbff;
49 background-color: #ee7756;
53 --index: calc(var(--layer) - 1);
54 padding-top: calc(var(--index) * 0.15em);
56 hue-rotate(calc(var(--index) * 25deg))
57 saturate(calc(100% - (var(--index) * 2%)))
58 brightness(calc(100% - (var(--index) * 1.5%)));
62 font-family: monospace;
65 -webkit-user-select: none;
67 body:active .annotation {
68 /* requires holding mouse down anywhere on the page */
69 display: inline-block;
71 span:hover .annotation {
72 /* requires hover over a span ONLY on its first line */
73 display: inline-block;
79 const FOOTER: &str = r#"
83 /// Metadata to highlight the span of a MIR BasicBlock, Statement, or Terminator.
84 pub struct SpanViewable {
90 /// Write a spanview HTML+CSS file to analyze MIR element spans.
91 pub fn write_mir_fn_spanview<'tcx, W>(
94 spanview: MirSpanview,
100 let def_id = body.source.def_id();
101 let body_span = hir_body(tcx, def_id).value.span;
102 let mut span_viewables = Vec::new();
103 for (bb, data) in body.basic_blocks().iter_enumerated() {
105 MirSpanview::Statement => {
106 for (i, statement) in data.statements.iter().enumerate() {
107 if let Some(span_viewable) =
108 statement_span_viewable(tcx, body_span, bb, i, statement)
110 span_viewables.push(span_viewable);
113 if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
114 span_viewables.push(span_viewable);
117 MirSpanview::Terminator => {
118 if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
119 span_viewables.push(span_viewable);
122 MirSpanview::Block => {
123 if let Some(span_viewable) = block_span_viewable(tcx, body_span, bb, data) {
124 span_viewables.push(span_viewable);
129 write_spanview_document(tcx, def_id, span_viewables, w)?;
133 /// Generate a spanview HTML+CSS document for the given local function `def_id`, and a pre-generated
134 /// list `SpanViewable`s.
135 pub fn write_spanview_document<'tcx, W>(
138 mut span_viewables: Vec<SpanViewable>,
144 let fn_span = fn_span(tcx, def_id);
145 let mut from_pos = fn_span.lo();
146 let end_pos = fn_span.hi();
147 let source_map = tcx.sess.source_map();
148 let start = source_map.lookup_char_pos(from_pos);
149 let indent_to_initial_start_col = " ".repeat(start.col.to_usize());
151 "fn_span source is:\n{}{}",
152 indent_to_initial_start_col,
153 source_map.span_to_snippet(fn_span).expect("function should have printable source")
155 writeln!(w, "{}", HEADER)?;
158 r#"<div class="code" style="counter-reset: line {}"><span class="line">{}"#,
160 indent_to_initial_start_col,
162 span_viewables.sort_unstable_by(|a, b| {
165 if a.lo() == b.lo() {
166 // Sort hi() in reverse order so shorter spans are attempted after longer spans.
167 // This should give shorter spans a higher "layer", so they are not covered by
169 b.hi().partial_cmp(&a.hi())
171 a.lo().partial_cmp(&b.lo())
175 let mut ordered_viewables = &span_viewables[..];
176 const LOWEST_VIEWABLE_LAYER: usize = 1;
178 while ordered_viewables.len() > 0 {
180 "calling write_next_viewable with from_pos={}, end_pos={}, and viewables len={}",
183 ordered_viewables.len()
185 let (next_from_pos, next_ordered_viewables) = write_next_viewable_with_overlaps(
191 LOWEST_VIEWABLE_LAYER,
195 "DONE calling write_next_viewable, with new from_pos={}, \
196 and remaining viewables len={}",
197 next_from_pos.to_usize(),
198 next_ordered_viewables.len()
201 from_pos != next_from_pos || ordered_viewables.len() != next_ordered_viewables.len(),
202 "write_next_viewable_with_overlaps() must make a state change"
204 from_pos = next_from_pos;
205 if next_ordered_viewables.len() != ordered_viewables.len() {
206 ordered_viewables = next_ordered_viewables;
210 if from_pos < end_pos {
211 write_coverage_gap(tcx, from_pos, end_pos, w)?;
213 write!(w, r#"</span></div>"#)?;
214 writeln!(w, "{}", FOOTER)?;
218 /// Format a string showing the start line and column, and end line and column within a file.
219 pub fn source_range_no_file<'tcx>(tcx: TyCtxt<'tcx>, span: &Span) -> String {
220 let source_map = tcx.sess.source_map();
221 let start = source_map.lookup_char_pos(span.lo());
222 let end = source_map.lookup_char_pos(span.hi());
223 format!("{}:{}-{}:{}", start.line, start.col.to_usize() + 1, end.line, end.col.to_usize() + 1)
226 pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str {
227 use StatementKind::*;
228 match statement.kind {
229 Assign(..) => "Assign",
230 FakeRead(..) => "FakeRead",
231 SetDiscriminant { .. } => "SetDiscriminant",
232 StorageLive(..) => "StorageLive",
233 StorageDead(..) => "StorageDead",
234 LlvmInlineAsm(..) => "LlvmInlineAsm",
235 Retag(..) => "Retag",
236 AscribeUserType(..) => "AscribeUserType",
237 Coverage(..) => "Coverage",
242 pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str {
243 use TerminatorKind::*;
245 Goto { .. } => "Goto",
246 SwitchInt { .. } => "SwitchInt",
250 Unreachable => "Unreachable",
251 Drop { .. } => "Drop",
252 DropAndReplace { .. } => "DropAndReplace",
253 Call { .. } => "Call",
254 Assert { .. } => "Assert",
255 Yield { .. } => "Yield",
256 GeneratorDrop => "GeneratorDrop",
257 FalseEdge { .. } => "FalseEdge",
258 FalseUnwind { .. } => "FalseUnwind",
259 InlineAsm { .. } => "InlineAsm",
263 fn statement_span_viewable<'tcx>(
268 statement: &Statement<'tcx>,
269 ) -> Option<SpanViewable> {
270 let span = statement.source_info.span;
271 if !body_span.contains(span) {
274 let id = format!("{}[{}]", bb.index(), i);
275 let tooltip = tooltip(tcx, &id, span, vec![statement.clone()], &None);
276 Some(SpanViewable { span, id, tooltip })
279 fn terminator_span_viewable<'tcx>(
283 data: &BasicBlockData<'tcx>,
284 ) -> Option<SpanViewable> {
285 let term = data.terminator();
286 let span = term.source_info.span;
287 if !body_span.contains(span) {
290 let id = format!("{}:{}", bb.index(), terminator_kind_name(term));
291 let tooltip = tooltip(tcx, &id, span, vec![], &data.terminator);
292 Some(SpanViewable { span, id, tooltip })
295 fn block_span_viewable<'tcx>(
299 data: &BasicBlockData<'tcx>,
300 ) -> Option<SpanViewable> {
301 let span = compute_block_span(data, body_span);
302 if !body_span.contains(span) {
305 let id = format!("{}", bb.index());
306 let tooltip = tooltip(tcx, &id, span, data.statements.clone(), &data.terminator);
307 Some(SpanViewable { span, id, tooltip })
310 fn compute_block_span<'tcx>(data: &BasicBlockData<'tcx>, body_span: Span) -> Span {
311 let mut span = data.terminator().source_info.span;
312 for statement_span in data.statements.iter().map(|statement| statement.source_info.span) {
313 // Only combine Spans from the root context, and within the function's body_span.
314 if statement_span.ctxt() == SyntaxContext::root() && body_span.contains(statement_span) {
315 span = span.to(statement_span);
321 /// Recursively process each ordered span. Spans that overlap will have progressively varying
322 /// styles, such as increased padding for each overlap. Non-overlapping adjacent spans will
323 /// have alternating style choices, to help distinguish between them if, visually adjacent.
324 /// The `layer` is incremented for each overlap, and the `alt` bool alternates between true
325 /// and false, for each adjacent non-overlapping span. Source code between the spans (code
326 /// that is not in any coverage region) has neutral styling.
327 fn write_next_viewable_with_overlaps<'tcx, 'b, W>(
329 mut from_pos: BytePos,
331 ordered_viewables: &'b [SpanViewable],
335 ) -> io::Result<(BytePos, &'b [SpanViewable])>
339 let debug_indent = " ".repeat(layer);
340 let (viewable, mut remaining_viewables) =
341 ordered_viewables.split_first().expect("ordered_viewables should have some");
343 if from_pos < viewable.span.lo() {
345 "{}advance from_pos to next SpanViewable (from from_pos={} to viewable.span.lo()={} \
346 of {:?}), with to_pos={}",
349 viewable.span.lo().to_usize(),
353 let hi = cmp::min(viewable.span.lo(), to_pos);
354 write_coverage_gap(tcx, from_pos, hi, w)?;
356 if from_pos < viewable.span.lo() {
358 "{}EARLY RETURN: stopped before getting to next SpanViewable, at {}",
362 return Ok((from_pos, ordered_viewables));
366 if from_pos < viewable.span.hi() {
367 // Set to_pos to the end of this `viewable` to ensure the recursive calls stop writing
368 // with room to print the tail.
369 to_pos = cmp::min(viewable.span.hi(), to_pos);
371 "{}update to_pos (if not closer) to viewable.span.hi()={}; to_pos is now {}",
373 viewable.span.hi().to_usize(),
378 let mut subalt = false;
379 while remaining_viewables.len() > 0 && remaining_viewables[0].span.overlaps(viewable.span) {
380 let overlapping_viewable = &remaining_viewables[0];
381 debug!("{}overlapping_viewable.span={:?}", debug_indent, overlapping_viewable.span);
384 trim_span(viewable.span, from_pos, cmp::min(overlapping_viewable.span.lo(), to_pos));
385 let mut some_html_snippet = if from_pos <= viewable.span.hi() || viewable.span.is_empty() {
386 // `viewable` is not yet fully rendered, so start writing the span, up to either the
387 // `to_pos` or the next `overlapping_viewable`, whichever comes first.
389 "{}make html_snippet (may not write it if early exit) for partial span {:?} \
390 of viewable.span {:?}",
391 debug_indent, span, viewable.span
393 from_pos = span.hi();
394 make_html_snippet(tcx, span, Some(&viewable))
399 // Defer writing the HTML snippet (until after early return checks) ONLY for empty spans.
400 // An empty Span with Some(html_snippet) is probably a tail marker. If there is an early
401 // exit, there should be another opportunity to write the tail marker.
402 if !span.is_empty() {
403 if let Some(ref html_snippet) = some_html_snippet {
405 "{}write html_snippet for that partial span of viewable.span {:?}",
406 debug_indent, viewable.span
408 write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
410 some_html_snippet = None;
413 if from_pos < overlapping_viewable.span.lo() {
415 "{}EARLY RETURN: from_pos={} has not yet reached the \
416 overlapping_viewable.span {:?}",
419 overlapping_viewable.span
421 // must have reached `to_pos` before reaching the start of the
422 // `overlapping_viewable.span`
423 return Ok((from_pos, ordered_viewables));
426 if from_pos == to_pos
427 && !(from_pos == overlapping_viewable.span.lo() && overlapping_viewable.span.is_empty())
430 "{}EARLY RETURN: from_pos=to_pos={} and overlapping_viewable.span {:?} is not \
431 empty, or not from_pos",
434 overlapping_viewable.span
436 // `to_pos` must have occurred before the overlapping viewable. Return
437 // `ordered_viewables` so we can continue rendering the `viewable`, from after the
439 return Ok((from_pos, ordered_viewables));
442 if let Some(ref html_snippet) = some_html_snippet {
444 "{}write html_snippet for that partial span of viewable.span {:?}",
445 debug_indent, viewable.span
447 write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
451 "{}recursively calling write_next_viewable with from_pos={}, to_pos={}, \
452 and viewables len={}",
456 remaining_viewables.len()
458 // Write the overlaps (and the overlaps' overlaps, if any) up to `to_pos`.
459 let (next_from_pos, next_remaining_viewables) = write_next_viewable_with_overlaps(
463 &remaining_viewables,
469 "{}DONE recursively calling write_next_viewable, with new from_pos={}, and remaining \
472 next_from_pos.to_usize(),
473 next_remaining_viewables.len()
476 from_pos != next_from_pos
477 || remaining_viewables.len() != next_remaining_viewables.len(),
478 "write_next_viewable_with_overlaps() must make a state change"
480 from_pos = next_from_pos;
481 if next_remaining_viewables.len() != remaining_viewables.len() {
482 remaining_viewables = next_remaining_viewables;
486 if from_pos <= viewable.span.hi() {
487 let span = trim_span(viewable.span, from_pos, to_pos);
489 "{}After overlaps, writing (end span?) {:?} of viewable.span {:?}",
490 debug_indent, span, viewable.span
492 if let Some(ref html_snippet) = make_html_snippet(tcx, span, Some(&viewable)) {
493 from_pos = span.hi();
494 write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
497 debug!("{}RETURN: No more overlap", debug_indent);
500 if from_pos < viewable.span.hi() { ordered_viewables } else { remaining_viewables },
505 fn write_coverage_gap<'tcx, W>(
514 let span = Span::with_root_ctxt(lo, hi);
515 if let Some(ref html_snippet) = make_html_snippet(tcx, span, None) {
516 write_span(html_snippet, "", false, 0, w)
532 let maybe_alt_class = if layer > 0 {
533 if alt { " odd" } else { " even" }
537 let maybe_title_attr = if !tooltip.is_empty() {
538 format!(" title=\"{}\"", escape_attr(tooltip))
543 write!(w, "<span>")?;
545 for (i, line) in html_snippet.lines().enumerate() {
547 write!(w, "{}", NEW_LINE_SPAN)?;
551 r#"<span class="code{}" style="--layer: {}"{}>{}</span>"#,
552 maybe_alt_class, layer, maybe_title_attr, line
555 // Check for and translate trailing newlines, because `str::lines()` ignores them
556 if html_snippet.ends_with('\n') {
557 write!(w, "{}", NEW_LINE_SPAN)?;
560 write!(w, "</span>")?;
565 fn make_html_snippet<'tcx>(
568 some_viewable: Option<&SpanViewable>,
569 ) -> Option<String> {
570 let source_map = tcx.sess.source_map();
571 let snippet = source_map
572 .span_to_snippet(span)
573 .unwrap_or_else(|err| bug!("span_to_snippet error for span {:?}: {:?}", span, err));
574 let html_snippet = if let Some(viewable) = some_viewable {
575 let is_head = span.lo() == viewable.span.lo();
576 let is_tail = span.hi() == viewable.span.hi();
577 let mut labeled_snippet = if is_head {
578 format!(r#"<span class="annotation">{}{}</span>"#, viewable.id, ANNOTATION_LEFT_BRACKET)
583 if is_head && is_tail {
584 labeled_snippet.push(CARET);
587 labeled_snippet.push_str(&escape_html(&snippet));
590 labeled_snippet.push_str(&format!(
591 r#"<span class="annotation">{}{}</span>"#,
592 ANNOTATION_RIGHT_BRACKET, viewable.id
597 escape_html(&snippet)
599 if html_snippet.is_empty() { None } else { Some(html_snippet) }
606 statements: Vec<Statement<'tcx>>,
607 terminator: &Option<Terminator<'tcx>>,
609 let source_map = tcx.sess.source_map();
610 let mut text = Vec::new();
611 text.push(format!("{}: {}:", spanview_id, &source_map.span_to_string(span)));
612 for statement in statements {
613 let source_range = source_range_no_file(tcx, &statement.source_info.span);
618 statement_kind_name(&statement),
619 format!("{:?}", statement)
622 if let Some(term) = terminator {
623 let source_range = source_range_no_file(tcx, &term.source_info.span);
628 terminator_kind_name(term),
635 fn trim_span(span: Span, from_pos: BytePos, to_pos: BytePos) -> Span {
636 trim_span_hi(trim_span_lo(span, from_pos), to_pos)
639 fn trim_span_lo(span: Span, from_pos: BytePos) -> Span {
640 if from_pos <= span.lo() { span } else { span.with_lo(cmp::min(span.hi(), from_pos)) }
643 fn trim_span_hi(span: Span, to_pos: BytePos) -> Span {
644 if to_pos >= span.hi() { span } else { span.with_hi(cmp::max(span.lo(), to_pos)) }
647 fn fn_span<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Span {
649 tcx.hir().local_def_id_to_hir_id(def_id.as_local().expect("expected DefId is local"));
650 let fn_decl_span = tcx.hir().span(hir_id);
651 let body_span = hir_body(tcx, def_id).value.span;
652 debug_assert_eq!(fn_decl_span.ctxt(), body_span.ctxt());
653 fn_decl_span.to(body_span)
656 fn hir_body<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx rustc_hir::Body<'tcx> {
657 let hir_node = tcx.hir().get_if_local(def_id).expect("expected DefId is local");
658 let fn_body_id = hir::map::associated_body(hir_node).expect("HIR node is a function with body");
659 tcx.hir().body(fn_body_id)
662 fn escape_html(s: &str) -> String {
663 s.replace("&", "&").replace("<", "<").replace(">", ">")
666 fn escape_attr(s: &str) -> String {
667 s.replace("&", "&")
668 .replace("\"", """)
669 .replace("'", "'")
670 .replace("<", "<")
671 .replace(">", ">")