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};
8 use std::io::{self, Write};
9 use std::iter::Peekable;
11 pub const TOOLTIP_INDENT: &str = " ";
13 const NEW_LINE_SPAN: &str = "</span>\n<span class=\"line\">";
14 const HEADER: &str = r#"<!DOCTYPE html>
17 <title>coverage_of_if_else - Code Regions</title>
20 counter-increment: line;
23 content: counter(line) ": ";
24 font-family: Menlo, Monaco, monospace;
27 display: inline-block;
30 -webkit-user-select: none;
34 background-color: #222222;
35 font-family: Menlo, Monaco, monospace;
37 border-bottom: 2px solid #222222;
39 display: inline-block;
42 background-color: #55bbff;
46 background-color: #ee7756;
50 --index: calc(var(--layer) - 1);
51 padding-top: calc(var(--index) * 0.15em);
53 hue-rotate(calc(var(--index) * 25deg))
54 saturate(calc(100% - (var(--index) * 2%)))
55 brightness(calc(100% - (var(--index) * 1.5%)));
59 font-family: monospace;
62 -webkit-user-select: none;
64 body:active .annotation {
65 /* requires holding mouse down anywhere on the page */
66 display: inline-block;
68 span:hover .annotation {
69 /* requires hover over a span ONLY on its first line */
70 display: inline-block;
76 const FOOTER: &str = r#"
80 /// Metadata to highlight the span of a MIR BasicBlock, Statement, or Terminator.
81 pub struct SpanViewable {
87 /// Write a spanview HTML+CSS file to analyze MIR element spans.
88 pub fn write_mir_fn_spanview<'tcx, W>(
92 spanview: MirSpanview,
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() {
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)
107 span_viewables.push(span_viewable);
110 if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
111 span_viewables.push(span_viewable);
114 MirSpanview::Terminator => {
115 if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
116 span_viewables.push(span_viewable);
119 MirSpanview::Block => {
120 if let Some(span_viewable) = block_span_viewable(tcx, body_span, bb, data) {
121 span_viewables.push(span_viewable);
126 write_spanview_document(tcx, def_id, span_viewables, w)?;
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>(
135 mut span_viewables: Vec<SpanViewable>,
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);
149 r#"<div class="code" style="counter-reset: line {}"><span class="line">{}"#,
151 " ".repeat(start.col.to_usize())
153 span_viewables.sort_unstable_by(|a, b| {
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
160 b.hi().partial_cmp(&a.hi())
162 a.lo().partial_cmp(&b.lo())
166 let mut ordered_span_viewables = span_viewables.iter().peekable();
168 while ordered_span_viewables.peek().is_some() {
169 next_pos = write_span_viewables(tcx, next_pos, &mut ordered_span_viewables, false, 1, w)?;
172 if next_pos < end_pos {
173 write_coverage_gap(tcx, next_pos, end_pos, w)?;
175 write!(w, r#"</span></div>"#)?;
176 writeln!(w, "{}", FOOTER)?;
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)
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",
204 pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str {
205 use TerminatorKind::*;
207 Goto { .. } => "Goto",
208 SwitchInt { .. } => "SwitchInt",
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",
225 fn statement_span_viewable<'tcx>(
230 statement: &Statement<'tcx>,
231 ) -> Option<SpanViewable> {
232 let span = statement.source_info.span;
233 if !body_span.contains(span) {
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 })
241 fn terminator_span_viewable<'tcx>(
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) {
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 })
257 fn block_span_viewable<'tcx>(
261 data: &BasicBlockData<'tcx>,
262 ) -> Option<SpanViewable> {
263 let span = compute_block_span(data, body_span);
264 if !body_span.contains(span) {
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 })
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);
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>(
292 ordered_span_viewables: &mut Peekable<impl Iterator<Item = &'b SpanViewable>>,
296 ) -> io::Result<BytePos>
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)?;
305 let mut remaining_span = span_viewable.span;
306 let mut subalt = false;
308 let next_span_viewable = match ordered_span_viewables.peek() {
310 Some(span_viewable) => *span_viewable,
312 if !next_span_viewable.span.overlaps(remaining_span) {
317 remaining_span.until(next_span_viewable.span),
323 let next_pos = write_span_viewables(
325 next_span_viewable.span.lo(),
326 ordered_span_viewables,
332 if next_pos < remaining_span.hi() {
333 remaining_span = remaining_span.with_lo(next_pos);
338 write_span(tcx, remaining_span, Some(span_viewable), alt, layer, w)
341 fn write_coverage_gap<'tcx, W>(
346 ) -> io::Result<BytePos>
350 write_span(tcx, Span::with_root_ctxt(lo, hi), None, false, 0, w)
353 fn write_span<'tcx, W>(
356 span_viewable: Option<&SpanViewable>,
360 ) -> io::Result<BytePos>
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 {
370 format!(r#"<span class="annotation">@{}</span>"#, title)
372 format!(r#"<span class="annotation">@{}:</span> {}"#, title, escape_html(&snippet))
377 let maybe_alt = if layer > 0 {
378 if alt { " odd" } else { " even" }
382 let maybe_tooltip = if let Some(SpanViewable { tooltip, .. }) = span_viewable {
383 format!(" title=\"{}\"", escape_attr(tooltip))
388 write!(w, "<span>")?;
390 for (i, line) in labeled_snippet.lines().enumerate() {
392 write!(w, "{}", NEW_LINE_SPAN)?;
396 r#"<span class="code{}" style="--layer: {}"{}>{}</span>"#,
397 maybe_alt, layer, maybe_tooltip, line
401 write!(w, "</span>")?;
410 statements: Vec<Statement<'tcx>>,
411 terminator: &Option<Terminator<'tcx>>,
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);
422 statement_kind_name(&statement),
423 format!("{:?}", statement)
426 if let Some(term) = terminator {
427 let source_range = source_range_no_file(tcx, &term.source_info.span);
432 terminator_kind_name(term),
439 fn fn_span<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Span {
441 tcx.hir().local_def_id_to_hir_id(def_id.as_local().expect("expected DefId is local"));
442 tcx.hir().span(hir_id)
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)
451 fn escape_html(s: &str) -> String {
452 s.replace("&", "&").replace("<", "<").replace(">", ">")
455 fn escape_attr(s: &str) -> String {
456 s.replace("&", "&")
457 .replace("\"", """)
458 .replace("'", "'")
459 .replace("<", "<")
460 .replace(">", ">")