use rustc_hir::def_id::DefId; use rustc_middle::hir; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; use rustc_session::config::MirSpanview; use rustc_span::{BytePos, Pos, Span, SyntaxContext}; use std::cmp; use std::io::{self, Write}; pub const TOOLTIP_INDENT: &str = " "; const CARET: char = '\u{2038}'; // Unicode `CARET` const ANNOTATION_LEFT_BRACKET: char = '\u{298a}'; // Unicode `Z NOTATION RIGHT BINDING BRACKET const ANNOTATION_RIGHT_BRACKET: char = '\u{2989}'; // Unicode `Z NOTATION LEFT BINDING BRACKET` const NEW_LINE_SPAN: &str = "\n"; const HEADER: &str = r#" "#; const START_BODY: &str = r#" "#; const FOOTER: &str = r#" "#; const STYLE_SECTION: &str = r#""#; /// Metadata to highlight the span of a MIR BasicBlock, Statement, or Terminator. #[derive(Clone, Debug)] pub struct SpanViewable { pub bb: BasicBlock, pub span: Span, pub id: String, pub tooltip: String, } /// Write a spanview HTML+CSS file to analyze MIR element spans. pub fn write_mir_fn_spanview<'tcx, W>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, spanview: MirSpanview, title: &str, w: &mut W, ) -> io::Result<()> where W: Write, { let def_id = body.source.def_id(); let hir_body = hir_body(tcx, def_id); if hir_body.is_none() { return Ok(()); } let body_span = hir_body.unwrap().value.span; let mut span_viewables = Vec::new(); for (bb, data) in body.basic_blocks.iter_enumerated() { match spanview { MirSpanview::Statement => { for (i, statement) in data.statements.iter().enumerate() { if let Some(span_viewable) = statement_span_viewable(tcx, body_span, bb, i, statement) { span_viewables.push(span_viewable); } } if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) { span_viewables.push(span_viewable); } } MirSpanview::Terminator => { if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) { span_viewables.push(span_viewable); } } MirSpanview::Block => { if let Some(span_viewable) = block_span_viewable(tcx, body_span, bb, data) { span_viewables.push(span_viewable); } } } } write_document(tcx, fn_span(tcx, def_id), span_viewables, title, w)?; Ok(()) } /// Generate a spanview HTML+CSS document for the given local function `def_id`, and a pre-generated /// list `SpanViewable`s. pub fn write_document<'tcx, W>( tcx: TyCtxt<'tcx>, spanview_span: Span, mut span_viewables: Vec, title: &str, w: &mut W, ) -> io::Result<()> where W: Write, { let mut from_pos = spanview_span.lo(); let end_pos = spanview_span.hi(); let source_map = tcx.sess.source_map(); let start = source_map.lookup_char_pos(from_pos); let indent_to_initial_start_col = " ".repeat(start.col.to_usize()); debug!( "spanview_span={:?}; source is:\n{}{}", spanview_span, indent_to_initial_start_col, source_map.span_to_snippet(spanview_span).expect("function should have printable source") ); writeln!(w, "{}", HEADER)?; writeln!(w, "{}", title)?; writeln!(w, "{}", STYLE_SECTION)?; writeln!(w, "{}", START_BODY)?; write!( w, r#"
{}"#, start.line - 1, indent_to_initial_start_col, )?; span_viewables.sort_unstable_by(|a, b| { let a = a.span; let b = b.span; if a.lo() == b.lo() { // Sort hi() in reverse order so shorter spans are attempted after longer spans. // This should give shorter spans a higher "layer", so they are not covered by // the longer spans. b.hi().partial_cmp(&a.hi()) } else { a.lo().partial_cmp(&b.lo()) } .unwrap() }); let mut ordered_viewables = &span_viewables[..]; const LOWEST_VIEWABLE_LAYER: usize = 1; let mut alt = false; while ordered_viewables.len() > 0 { debug!( "calling write_next_viewable with from_pos={}, end_pos={}, and viewables len={}", from_pos.to_usize(), end_pos.to_usize(), ordered_viewables.len() ); let curr_id = &ordered_viewables[0].id; let (next_from_pos, next_ordered_viewables) = write_next_viewable_with_overlaps( tcx, from_pos, end_pos, ordered_viewables, alt, LOWEST_VIEWABLE_LAYER, w, )?; debug!( "DONE calling write_next_viewable, with new from_pos={}, \ and remaining viewables len={}", next_from_pos.to_usize(), next_ordered_viewables.len() ); assert!( from_pos != next_from_pos || ordered_viewables.len() != next_ordered_viewables.len(), "write_next_viewable_with_overlaps() must make a state change" ); from_pos = next_from_pos; if next_ordered_viewables.len() != ordered_viewables.len() { ordered_viewables = next_ordered_viewables; if let Some(next_ordered_viewable) = ordered_viewables.first() { if &next_ordered_viewable.id != curr_id { alt = !alt; } } } } if from_pos < end_pos { write_coverage_gap(tcx, from_pos, end_pos, w)?; } writeln!(w, r#"
"#)?; writeln!(w, "{}", FOOTER)?; Ok(()) } /// Format a string showing the start line and column, and end line and column within a file. pub fn source_range_no_file(tcx: TyCtxt<'_>, span: Span) -> String { let source_map = tcx.sess.source_map(); let start = source_map.lookup_char_pos(span.lo()); let end = source_map.lookup_char_pos(span.hi()); format!("{}:{}-{}:{}", start.line, start.col.to_usize() + 1, end.line, end.col.to_usize() + 1) } pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str { use StatementKind::*; match statement.kind { Assign(..) => "Assign", FakeRead(..) => "FakeRead", SetDiscriminant { .. } => "SetDiscriminant", Deinit(..) => "Deinit", StorageLive(..) => "StorageLive", StorageDead(..) => "StorageDead", Retag(..) => "Retag", AscribeUserType(..) => "AscribeUserType", Coverage(..) => "Coverage", Intrinsic(..) => "Intrinsic", Nop => "Nop", } } pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str { use TerminatorKind::*; match term.kind { Goto { .. } => "Goto", SwitchInt { .. } => "SwitchInt", Resume => "Resume", Abort => "Abort", Return => "Return", Unreachable => "Unreachable", Drop { .. } => "Drop", DropAndReplace { .. } => "DropAndReplace", Call { .. } => "Call", Assert { .. } => "Assert", Yield { .. } => "Yield", GeneratorDrop => "GeneratorDrop", FalseEdge { .. } => "FalseEdge", FalseUnwind { .. } => "FalseUnwind", InlineAsm { .. } => "InlineAsm", } } fn statement_span_viewable<'tcx>( tcx: TyCtxt<'tcx>, body_span: Span, bb: BasicBlock, i: usize, statement: &Statement<'tcx>, ) -> Option { let span = statement.source_info.span; if !body_span.contains(span) { return None; } let id = format!("{}[{}]", bb.index(), i); let tooltip = tooltip(tcx, &id, span, vec![statement.clone()], &None); Some(SpanViewable { bb, span, id, tooltip }) } fn terminator_span_viewable<'tcx>( tcx: TyCtxt<'tcx>, body_span: Span, bb: BasicBlock, data: &BasicBlockData<'tcx>, ) -> Option { let term = data.terminator(); let span = term.source_info.span; if !body_span.contains(span) { return None; } let id = format!("{}:{}", bb.index(), terminator_kind_name(term)); let tooltip = tooltip(tcx, &id, span, vec![], &data.terminator); Some(SpanViewable { bb, span, id, tooltip }) } fn block_span_viewable<'tcx>( tcx: TyCtxt<'tcx>, body_span: Span, bb: BasicBlock, data: &BasicBlockData<'tcx>, ) -> Option { let span = compute_block_span(data, body_span); if !body_span.contains(span) { return None; } let id = format!("{}", bb.index()); let tooltip = tooltip(tcx, &id, span, data.statements.clone(), &data.terminator); Some(SpanViewable { bb, span, id, tooltip }) } fn compute_block_span(data: &BasicBlockData<'_>, body_span: Span) -> Span { let mut span = data.terminator().source_info.span; for statement_span in data.statements.iter().map(|statement| statement.source_info.span) { // Only combine Spans from the root context, and within the function's body_span. if statement_span.ctxt() == SyntaxContext::root() && body_span.contains(statement_span) { span = span.to(statement_span); } } span } /// Recursively process each ordered span. Spans that overlap will have progressively varying /// styles, such as increased padding for each overlap. Non-overlapping adjacent spans will /// have alternating style choices, to help distinguish between them if, visually adjacent. /// The `layer` is incremented for each overlap, and the `alt` bool alternates between true /// and false, for each adjacent non-overlapping span. Source code between the spans (code /// that is not in any coverage region) has neutral styling. fn write_next_viewable_with_overlaps<'tcx, 'b, W>( tcx: TyCtxt<'tcx>, mut from_pos: BytePos, mut to_pos: BytePos, ordered_viewables: &'b [SpanViewable], alt: bool, layer: usize, w: &mut W, ) -> io::Result<(BytePos, &'b [SpanViewable])> where W: Write, { let debug_indent = " ".repeat(layer); let (viewable, mut remaining_viewables) = ordered_viewables.split_first().expect("ordered_viewables should have some"); if from_pos < viewable.span.lo() { debug!( "{}advance from_pos to next SpanViewable (from from_pos={} to viewable.span.lo()={} \ of {:?}), with to_pos={}", debug_indent, from_pos.to_usize(), viewable.span.lo().to_usize(), viewable.span, to_pos.to_usize() ); let hi = cmp::min(viewable.span.lo(), to_pos); write_coverage_gap(tcx, from_pos, hi, w)?; from_pos = hi; if from_pos < viewable.span.lo() { debug!( "{}EARLY RETURN: stopped before getting to next SpanViewable, at {}", debug_indent, from_pos.to_usize() ); return Ok((from_pos, ordered_viewables)); } } if from_pos < viewable.span.hi() { // Set to_pos to the end of this `viewable` to ensure the recursive calls stop writing // with room to print the tail. to_pos = cmp::min(viewable.span.hi(), to_pos); debug!( "{}update to_pos (if not closer) to viewable.span.hi()={}; to_pos is now {}", debug_indent, viewable.span.hi().to_usize(), to_pos.to_usize() ); } let mut subalt = false; while remaining_viewables.len() > 0 && remaining_viewables[0].span.overlaps(viewable.span) { let overlapping_viewable = &remaining_viewables[0]; debug!("{}overlapping_viewable.span={:?}", debug_indent, overlapping_viewable.span); let span = trim_span(viewable.span, from_pos, cmp::min(overlapping_viewable.span.lo(), to_pos)); let mut some_html_snippet = if from_pos <= viewable.span.hi() || viewable.span.is_empty() { // `viewable` is not yet fully rendered, so start writing the span, up to either the // `to_pos` or the next `overlapping_viewable`, whichever comes first. debug!( "{}make html_snippet (may not write it if early exit) for partial span {:?} \ of viewable.span {:?}", debug_indent, span, viewable.span ); from_pos = span.hi(); make_html_snippet(tcx, span, Some(&viewable)) } else { None }; // Defer writing the HTML snippet (until after early return checks) ONLY for empty spans. // An empty Span with Some(html_snippet) is probably a tail marker. If there is an early // exit, there should be another opportunity to write the tail marker. if !span.is_empty() { if let Some(ref html_snippet) = some_html_snippet { debug!( "{}write html_snippet for that partial span of viewable.span {:?}", debug_indent, viewable.span ); write_span(html_snippet, &viewable.tooltip, alt, layer, w)?; } some_html_snippet = None; } if from_pos < overlapping_viewable.span.lo() { debug!( "{}EARLY RETURN: from_pos={} has not yet reached the \ overlapping_viewable.span {:?}", debug_indent, from_pos.to_usize(), overlapping_viewable.span ); // must have reached `to_pos` before reaching the start of the // `overlapping_viewable.span` return Ok((from_pos, ordered_viewables)); } if from_pos == to_pos && !(from_pos == overlapping_viewable.span.lo() && overlapping_viewable.span.is_empty()) { debug!( "{}EARLY RETURN: from_pos=to_pos={} and overlapping_viewable.span {:?} is not \ empty, or not from_pos", debug_indent, to_pos.to_usize(), overlapping_viewable.span ); // `to_pos` must have occurred before the overlapping viewable. Return // `ordered_viewables` so we can continue rendering the `viewable`, from after the // `to_pos`. return Ok((from_pos, ordered_viewables)); } if let Some(ref html_snippet) = some_html_snippet { debug!( "{}write html_snippet for that partial span of viewable.span {:?}", debug_indent, viewable.span ); write_span(html_snippet, &viewable.tooltip, alt, layer, w)?; } debug!( "{}recursively calling write_next_viewable with from_pos={}, to_pos={}, \ and viewables len={}", debug_indent, from_pos.to_usize(), to_pos.to_usize(), remaining_viewables.len() ); // Write the overlaps (and the overlaps' overlaps, if any) up to `to_pos`. let curr_id = &remaining_viewables[0].id; let (next_from_pos, next_remaining_viewables) = write_next_viewable_with_overlaps( tcx, from_pos, to_pos, &remaining_viewables, subalt, layer + 1, w, )?; debug!( "{}DONE recursively calling write_next_viewable, with new from_pos={}, and remaining \ viewables len={}", debug_indent, next_from_pos.to_usize(), next_remaining_viewables.len() ); assert!( from_pos != next_from_pos || remaining_viewables.len() != next_remaining_viewables.len(), "write_next_viewable_with_overlaps() must make a state change" ); from_pos = next_from_pos; if next_remaining_viewables.len() != remaining_viewables.len() { remaining_viewables = next_remaining_viewables; if let Some(next_ordered_viewable) = remaining_viewables.first() { if &next_ordered_viewable.id != curr_id { subalt = !subalt; } } } } if from_pos <= viewable.span.hi() { let span = trim_span(viewable.span, from_pos, to_pos); debug!( "{}After overlaps, writing (end span?) {:?} of viewable.span {:?}", debug_indent, span, viewable.span ); if let Some(ref html_snippet) = make_html_snippet(tcx, span, Some(&viewable)) { from_pos = span.hi(); write_span(html_snippet, &viewable.tooltip, alt, layer, w)?; } } debug!("{}RETURN: No more overlap", debug_indent); Ok(( from_pos, if from_pos < viewable.span.hi() { ordered_viewables } else { remaining_viewables }, )) } #[inline(always)] fn write_coverage_gap(tcx: TyCtxt<'_>, lo: BytePos, hi: BytePos, w: &mut W) -> io::Result<()> where W: Write, { let span = Span::with_root_ctxt(lo, hi); if let Some(ref html_snippet) = make_html_snippet(tcx, span, None) { write_span(html_snippet, "", false, 0, w) } else { Ok(()) } } fn write_span( html_snippet: &str, tooltip: &str, alt: bool, layer: usize, w: &mut W, ) -> io::Result<()> where W: Write, { let maybe_alt_class = if layer > 0 { if alt { " odd" } else { " even" } } else { "" }; let maybe_title_attr = if !tooltip.is_empty() { format!(" title=\"{}\"", escape_attr(tooltip)) } else { "".to_owned() }; if layer == 1 { write!(w, "")?; } for (i, line) in html_snippet.lines().enumerate() { if i > 0 { write!(w, "{}", NEW_LINE_SPAN)?; } write!( w, r#"{}"#, maybe_alt_class, layer, maybe_title_attr, line )?; } // Check for and translate trailing newlines, because `str::lines()` ignores them if html_snippet.ends_with('\n') { write!(w, "{}", NEW_LINE_SPAN)?; } if layer == 1 { write!(w, "")?; } Ok(()) } fn make_html_snippet( tcx: TyCtxt<'_>, span: Span, some_viewable: Option<&SpanViewable>, ) -> Option { let source_map = tcx.sess.source_map(); let snippet = source_map .span_to_snippet(span) .unwrap_or_else(|err| bug!("span_to_snippet error for span {:?}: {:?}", span, err)); let html_snippet = if let Some(viewable) = some_viewable { let is_head = span.lo() == viewable.span.lo(); let is_tail = span.hi() == viewable.span.hi(); let mut labeled_snippet = if is_head { format!(r#"{}{}"#, viewable.id, ANNOTATION_LEFT_BRACKET) } else { "".to_owned() }; if span.is_empty() { if is_head && is_tail { labeled_snippet.push(CARET); } } else { labeled_snippet.push_str(&escape_html(&snippet)); }; if is_tail { labeled_snippet.push_str(&format!( r#"{}{}"#, ANNOTATION_RIGHT_BRACKET, viewable.id )); } labeled_snippet } else { escape_html(&snippet) }; if html_snippet.is_empty() { None } else { Some(html_snippet) } } fn tooltip<'tcx>( tcx: TyCtxt<'tcx>, spanview_id: &str, span: Span, statements: Vec>, terminator: &Option>, ) -> String { let source_map = tcx.sess.source_map(); let mut text = Vec::new(); text.push(format!("{}: {}:", spanview_id, &source_map.span_to_embeddable_string(span))); for statement in statements { let source_range = source_range_no_file(tcx, statement.source_info.span); text.push(format!( "\n{}{}: {}: {:?}", TOOLTIP_INDENT, source_range, statement_kind_name(&statement), statement )); } if let Some(term) = terminator { let source_range = source_range_no_file(tcx, term.source_info.span); text.push(format!( "\n{}{}: {}: {:?}", TOOLTIP_INDENT, source_range, terminator_kind_name(term), term.kind )); } text.join("") } fn trim_span(span: Span, from_pos: BytePos, to_pos: BytePos) -> Span { trim_span_hi(trim_span_lo(span, from_pos), to_pos) } fn trim_span_lo(span: Span, from_pos: BytePos) -> Span { if from_pos <= span.lo() { span } else { span.with_lo(cmp::min(span.hi(), from_pos)) } } fn trim_span_hi(span: Span, to_pos: BytePos) -> Span { if to_pos >= span.hi() { span } else { span.with_hi(cmp::max(span.lo(), to_pos)) } } fn fn_span(tcx: TyCtxt<'_>, def_id: DefId) -> Span { let fn_decl_span = tcx.def_span(def_id); if let Some(body_span) = hir_body(tcx, def_id).map(|hir_body| hir_body.value.span) { if fn_decl_span.eq_ctxt(body_span) { fn_decl_span.to(body_span) } else { body_span } } else { fn_decl_span } } fn hir_body(tcx: TyCtxt<'_>, def_id: DefId) -> Option<&rustc_hir::Body<'_>> { let hir_node = tcx.hir().get_if_local(def_id).expect("expected DefId is local"); hir::map::associated_body(hir_node).map(|fn_body_id| tcx.hir().body(fn_body_id)) } fn escape_html(s: &str) -> String { s.replace('&', "&").replace('<', "<").replace('>', ">") } fn escape_attr(s: &str) -> String { s.replace('&', "&") .replace('\"', """) .replace('\'', "'") .replace('<', "<") .replace('>', ">") }