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 const START_BODY
: &str = r
#"</head>
22 const FOOTER
: &str = r
#"</body>
25 const STYLE_SECTION
: &str = r
#"<style>
27 counter-increment: line;
30 content: counter(line) ": ";
31 font-family: Menlo, Monaco, monospace;
34 display: inline-block;
37 -webkit-user-select: none;
41 background-color: #222222;
42 font-family: Menlo, Monaco, monospace;
44 border-bottom: 2px solid #222222;
46 display: inline-block;
49 background-color: #55bbff;
53 background-color: #ee7756;
57 --index: calc(var(--layer) - 1);
58 padding-top: calc(var(--index) * 0.15em);
60 hue-rotate(calc(var(--index) * 25deg))
61 saturate(calc(100% - (var(--index) * 2%)))
62 brightness(calc(100% - (var(--index) * 1.5%)));
66 font-family: monospace;
69 -webkit-user-select: none;
71 body:active .annotation {
72 /* requires holding mouse down anywhere on the page */
73 display: inline-block;
75 span:hover .annotation {
76 /* requires hover over a span ONLY on its first line */
77 display: inline-block;
81 /// Metadata to highlight the span of a MIR BasicBlock, Statement, or Terminator.
82 #[derive(Clone, Debug)]
83 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
,
101 let def_id
= body
.source
.def_id();
102 let hir_body
= hir_body(tcx
, def_id
);
103 if hir_body
.is_none() {
106 let body_span
= hir_body
.unwrap().value
.span
;
107 let mut span_viewables
= Vec
::new();
108 for (bb
, data
) in body
.basic_blocks().iter_enumerated() {
110 MirSpanview
::Statement
=> {
111 for (i
, statement
) in data
.statements
.iter().enumerate() {
112 if let Some(span_viewable
) =
113 statement_span_viewable(tcx
, body_span
, bb
, i
, statement
)
115 span_viewables
.push(span_viewable
);
118 if let Some(span_viewable
) = terminator_span_viewable(tcx
, body_span
, bb
, data
) {
119 span_viewables
.push(span_viewable
);
122 MirSpanview
::Terminator
=> {
123 if let Some(span_viewable
) = terminator_span_viewable(tcx
, body_span
, bb
, data
) {
124 span_viewables
.push(span_viewable
);
127 MirSpanview
::Block
=> {
128 if let Some(span_viewable
) = block_span_viewable(tcx
, body_span
, bb
, data
) {
129 span_viewables
.push(span_viewable
);
134 write_document(tcx
, fn_span(tcx
, def_id
), span_viewables
, title
, w
)?
;
138 /// Generate a spanview HTML+CSS document for the given local function `def_id`, and a pre-generated
139 /// list `SpanViewable`s.
140 pub fn write_document
<'tcx
, W
>(
143 mut span_viewables
: Vec
<SpanViewable
>,
150 let mut from_pos
= spanview_span
.lo();
151 let end_pos
= spanview_span
.hi();
152 let source_map
= tcx
.sess
.source_map();
153 let start
= source_map
.lookup_char_pos(from_pos
);
154 let indent_to_initial_start_col
= " ".repeat(start
.col
.to_usize());
156 "spanview_span={:?}; source is:\n{}{}",
158 indent_to_initial_start_col
,
159 source_map
.span_to_snippet(spanview_span
).expect("function should have printable source")
161 writeln
!(w
, "{}", HEADER
)?
;
162 writeln
!(w
, "<title>{}</title>", title
)?
;
163 writeln
!(w
, "{}", STYLE_SECTION
)?
;
164 writeln
!(w
, "{}", START_BODY
)?
;
167 r
#"<div class="code" style="counter-reset: line {}"><span class="line">{}"#,
169 indent_to_initial_start_col
,
171 span_viewables
.sort_unstable_by(|a
, b
| {
174 if a
.lo() == b
.lo() {
175 // Sort hi() in reverse order so shorter spans are attempted after longer spans.
176 // This should give shorter spans a higher "layer", so they are not covered by
178 b
.hi().partial_cmp(&a
.hi())
180 a
.lo().partial_cmp(&b
.lo())
184 let mut ordered_viewables
= &span_viewables
[..];
185 const LOWEST_VIEWABLE_LAYER
: usize = 1;
187 while ordered_viewables
.len() > 0 {
189 "calling write_next_viewable with from_pos={}, end_pos={}, and viewables len={}",
192 ordered_viewables
.len()
194 let curr_id
= &ordered_viewables
[0].id
;
195 let (next_from_pos
, next_ordered_viewables
) = write_next_viewable_with_overlaps(
201 LOWEST_VIEWABLE_LAYER
,
205 "DONE calling write_next_viewable, with new from_pos={}, \
206 and remaining viewables len={}",
207 next_from_pos
.to_usize(),
208 next_ordered_viewables
.len()
211 from_pos
!= next_from_pos
|| ordered_viewables
.len() != next_ordered_viewables
.len(),
212 "write_next_viewable_with_overlaps() must make a state change"
214 from_pos
= next_from_pos
;
215 if next_ordered_viewables
.len() != ordered_viewables
.len() {
216 ordered_viewables
= next_ordered_viewables
;
217 if let Some(next_ordered_viewable
) = ordered_viewables
.first() {
218 if &next_ordered_viewable
.id
!= curr_id
{
224 if from_pos
< end_pos
{
225 write_coverage_gap(tcx
, from_pos
, end_pos
, w
)?
;
227 writeln
!(w
, r
#"</span></div>"#)?;
228 writeln
!(w
, "{}", FOOTER
)?
;
232 /// Format a string showing the start line and column, and end line and column within a file.
233 pub fn source_range_no_file
<'tcx
>(tcx
: TyCtxt
<'tcx
>, span
: &Span
) -> String
{
234 let source_map
= tcx
.sess
.source_map();
235 let start
= source_map
.lookup_char_pos(span
.lo());
236 let end
= source_map
.lookup_char_pos(span
.hi());
237 format
!("{}:{}-{}:{}", start
.line
, start
.col
.to_usize() + 1, end
.line
, end
.col
.to_usize() + 1)
240 pub fn statement_kind_name(statement
: &Statement
<'_
>) -> &'
static str {
241 use StatementKind
::*;
242 match statement
.kind
{
243 Assign(..) => "Assign",
244 FakeRead(..) => "FakeRead",
245 SetDiscriminant { .. }
=> "SetDiscriminant",
246 StorageLive(..) => "StorageLive",
247 StorageDead(..) => "StorageDead",
248 LlvmInlineAsm(..) => "LlvmInlineAsm",
249 Retag(..) => "Retag",
250 AscribeUserType(..) => "AscribeUserType",
251 Coverage(..) => "Coverage",
252 CopyNonOverlapping(..) => "CopyNonOverlapping",
257 pub fn terminator_kind_name(term
: &Terminator
<'_
>) -> &'
static str {
258 use TerminatorKind
::*;
260 Goto { .. }
=> "Goto",
261 SwitchInt { .. }
=> "SwitchInt",
265 Unreachable
=> "Unreachable",
266 Drop { .. }
=> "Drop",
267 DropAndReplace { .. }
=> "DropAndReplace",
268 Call { .. }
=> "Call",
269 Assert { .. }
=> "Assert",
270 Yield { .. }
=> "Yield",
271 GeneratorDrop
=> "GeneratorDrop",
272 FalseEdge { .. }
=> "FalseEdge",
273 FalseUnwind { .. }
=> "FalseUnwind",
274 InlineAsm { .. }
=> "InlineAsm",
278 fn statement_span_viewable
<'tcx
>(
283 statement
: &Statement
<'tcx
>,
284 ) -> Option
<SpanViewable
> {
285 let span
= statement
.source_info
.span
;
286 if !body_span
.contains(span
) {
289 let id
= format
!("{}[{}]", bb
.index(), i
);
290 let tooltip
= tooltip(tcx
, &id
, span
, vec
![statement
.clone()], &None
);
291 Some(SpanViewable { bb, span, id, tooltip }
)
294 fn terminator_span_viewable
<'tcx
>(
298 data
: &BasicBlockData
<'tcx
>,
299 ) -> Option
<SpanViewable
> {
300 let term
= data
.terminator();
301 let span
= term
.source_info
.span
;
302 if !body_span
.contains(span
) {
305 let id
= format
!("{}:{}", bb
.index(), terminator_kind_name(term
));
306 let tooltip
= tooltip(tcx
, &id
, span
, vec
![], &data
.terminator
);
307 Some(SpanViewable { bb, span, id, tooltip }
)
310 fn block_span_viewable
<'tcx
>(
314 data
: &BasicBlockData
<'tcx
>,
315 ) -> Option
<SpanViewable
> {
316 let span
= compute_block_span(data
, body_span
);
317 if !body_span
.contains(span
) {
320 let id
= format
!("{}", bb
.index());
321 let tooltip
= tooltip(tcx
, &id
, span
, data
.statements
.clone(), &data
.terminator
);
322 Some(SpanViewable { bb, span, id, tooltip }
)
325 fn compute_block_span
<'tcx
>(data
: &BasicBlockData
<'tcx
>, body_span
: Span
) -> Span
{
326 let mut span
= data
.terminator().source_info
.span
;
327 for statement_span
in data
.statements
.iter().map(|statement
| statement
.source_info
.span
) {
328 // Only combine Spans from the root context, and within the function's body_span.
329 if statement_span
.ctxt() == SyntaxContext
::root() && body_span
.contains(statement_span
) {
330 span
= span
.to(statement_span
);
336 /// Recursively process each ordered span. Spans that overlap will have progressively varying
337 /// styles, such as increased padding for each overlap. Non-overlapping adjacent spans will
338 /// have alternating style choices, to help distinguish between them if, visually adjacent.
339 /// The `layer` is incremented for each overlap, and the `alt` bool alternates between true
340 /// and false, for each adjacent non-overlapping span. Source code between the spans (code
341 /// that is not in any coverage region) has neutral styling.
342 fn write_next_viewable_with_overlaps
<'tcx
, 'b
, W
>(
344 mut from_pos
: BytePos
,
346 ordered_viewables
: &'b
[SpanViewable
],
350 ) -> io
::Result
<(BytePos
, &'b
[SpanViewable
])>
354 let debug_indent
= " ".repeat(layer
);
355 let (viewable
, mut remaining_viewables
) =
356 ordered_viewables
.split_first().expect("ordered_viewables should have some");
358 if from_pos
< viewable
.span
.lo() {
360 "{}advance from_pos to next SpanViewable (from from_pos={} to viewable.span.lo()={} \
361 of {:?}), with to_pos={}",
364 viewable
.span
.lo().to_usize(),
368 let hi
= cmp
::min(viewable
.span
.lo(), to_pos
);
369 write_coverage_gap(tcx
, from_pos
, hi
, w
)?
;
371 if from_pos
< viewable
.span
.lo() {
373 "{}EARLY RETURN: stopped before getting to next SpanViewable, at {}",
377 return Ok((from_pos
, ordered_viewables
));
381 if from_pos
< viewable
.span
.hi() {
382 // Set to_pos to the end of this `viewable` to ensure the recursive calls stop writing
383 // with room to print the tail.
384 to_pos
= cmp
::min(viewable
.span
.hi(), to_pos
);
386 "{}update to_pos (if not closer) to viewable.span.hi()={}; to_pos is now {}",
388 viewable
.span
.hi().to_usize(),
393 let mut subalt
= false;
394 while remaining_viewables
.len() > 0 && remaining_viewables
[0].span
.overlaps(viewable
.span
) {
395 let overlapping_viewable
= &remaining_viewables
[0];
396 debug
!("{}overlapping_viewable.span={:?}", debug_indent
, overlapping_viewable
.span
);
399 trim_span(viewable
.span
, from_pos
, cmp
::min(overlapping_viewable
.span
.lo(), to_pos
));
400 let mut some_html_snippet
= if from_pos
<= viewable
.span
.hi() || viewable
.span
.is_empty() {
401 // `viewable` is not yet fully rendered, so start writing the span, up to either the
402 // `to_pos` or the next `overlapping_viewable`, whichever comes first.
404 "{}make html_snippet (may not write it if early exit) for partial span {:?} \
405 of viewable.span {:?}",
406 debug_indent
, span
, viewable
.span
408 from_pos
= span
.hi();
409 make_html_snippet(tcx
, span
, Some(&viewable
))
414 // Defer writing the HTML snippet (until after early return checks) ONLY for empty spans.
415 // An empty Span with Some(html_snippet) is probably a tail marker. If there is an early
416 // exit, there should be another opportunity to write the tail marker.
417 if !span
.is_empty() {
418 if let Some(ref html_snippet
) = some_html_snippet
{
420 "{}write html_snippet for that partial span of viewable.span {:?}",
421 debug_indent
, viewable
.span
423 write_span(html_snippet
, &viewable
.tooltip
, alt
, layer
, w
)?
;
425 some_html_snippet
= None
;
428 if from_pos
< overlapping_viewable
.span
.lo() {
430 "{}EARLY RETURN: from_pos={} has not yet reached the \
431 overlapping_viewable.span {:?}",
434 overlapping_viewable
.span
436 // must have reached `to_pos` before reaching the start of the
437 // `overlapping_viewable.span`
438 return Ok((from_pos
, ordered_viewables
));
441 if from_pos
== to_pos
442 && !(from_pos
== overlapping_viewable
.span
.lo() && overlapping_viewable
.span
.is_empty())
445 "{}EARLY RETURN: from_pos=to_pos={} and overlapping_viewable.span {:?} is not \
446 empty, or not from_pos",
449 overlapping_viewable
.span
451 // `to_pos` must have occurred before the overlapping viewable. Return
452 // `ordered_viewables` so we can continue rendering the `viewable`, from after the
454 return Ok((from_pos
, ordered_viewables
));
457 if let Some(ref html_snippet
) = some_html_snippet
{
459 "{}write html_snippet for that partial span of viewable.span {:?}",
460 debug_indent
, viewable
.span
462 write_span(html_snippet
, &viewable
.tooltip
, alt
, layer
, w
)?
;
466 "{}recursively calling write_next_viewable with from_pos={}, to_pos={}, \
467 and viewables len={}",
471 remaining_viewables
.len()
473 // Write the overlaps (and the overlaps' overlaps, if any) up to `to_pos`.
474 let curr_id
= &remaining_viewables
[0].id
;
475 let (next_from_pos
, next_remaining_viewables
) = write_next_viewable_with_overlaps(
479 &remaining_viewables
,
485 "{}DONE recursively calling write_next_viewable, with new from_pos={}, and remaining \
488 next_from_pos
.to_usize(),
489 next_remaining_viewables
.len()
492 from_pos
!= next_from_pos
493 || remaining_viewables
.len() != next_remaining_viewables
.len(),
494 "write_next_viewable_with_overlaps() must make a state change"
496 from_pos
= next_from_pos
;
497 if next_remaining_viewables
.len() != remaining_viewables
.len() {
498 remaining_viewables
= next_remaining_viewables
;
499 if let Some(next_ordered_viewable
) = remaining_viewables
.first() {
500 if &next_ordered_viewable
.id
!= curr_id
{
506 if from_pos
<= viewable
.span
.hi() {
507 let span
= trim_span(viewable
.span
, from_pos
, to_pos
);
509 "{}After overlaps, writing (end span?) {:?} of viewable.span {:?}",
510 debug_indent
, span
, viewable
.span
512 if let Some(ref html_snippet
) = make_html_snippet(tcx
, span
, Some(&viewable
)) {
513 from_pos
= span
.hi();
514 write_span(html_snippet
, &viewable
.tooltip
, alt
, layer
, w
)?
;
517 debug
!("{}RETURN: No more overlap", debug_indent
);
520 if from_pos
< viewable
.span
.hi() { ordered_viewables }
else { remaining_viewables }
,
525 fn write_coverage_gap
<'tcx
, W
>(
534 let span
= Span
::with_root_ctxt(lo
, hi
);
535 if let Some(ref html_snippet
) = make_html_snippet(tcx
, span
, None
) {
536 write_span(html_snippet
, "", false, 0, w
)
552 let maybe_alt_class
= if layer
> 0 {
553 if alt { " odd" }
else { " even" }
557 let maybe_title_attr
= if !tooltip
.is_empty() {
558 format
!(" title=\"{}\"", escape_attr(tooltip
))
563 write
!(w
, "<span>")?
;
565 for (i
, line
) in html_snippet
.lines().enumerate() {
567 write
!(w
, "{}", NEW_LINE_SPAN
)?
;
571 r
#"<span class="code{}" style="--layer: {}"{}>{}</span>"#,
572 maybe_alt_class
, layer
, maybe_title_attr
, line
575 // Check for and translate trailing newlines, because `str::lines()` ignores them
576 if html_snippet
.ends_with('
\n'
) {
577 write
!(w
, "{}", NEW_LINE_SPAN
)?
;
580 write
!(w
, "</span>")?
;
585 fn make_html_snippet
<'tcx
>(
588 some_viewable
: Option
<&SpanViewable
>,
589 ) -> Option
<String
> {
590 let source_map
= tcx
.sess
.source_map();
591 let snippet
= source_map
592 .span_to_snippet(span
)
593 .unwrap_or_else(|err
| bug
!("span_to_snippet error for span {:?}: {:?}", span
, err
));
594 let html_snippet
= if let Some(viewable
) = some_viewable
{
595 let is_head
= span
.lo() == viewable
.span
.lo();
596 let is_tail
= span
.hi() == viewable
.span
.hi();
597 let mut labeled_snippet
= if is_head
{
598 format
!(r
#"<span class="annotation">{}{}</span>"#, viewable.id, ANNOTATION_LEFT_BRACKET)
603 if is_head
&& is_tail
{
604 labeled_snippet
.push(CARET
);
607 labeled_snippet
.push_str(&escape_html(&snippet
));
610 labeled_snippet
.push_str(&format
!(
611 r
#"<span class="annotation">{}{}</span>"#,
612 ANNOTATION_RIGHT_BRACKET
, viewable
.id
617 escape_html(&snippet
)
619 if html_snippet
.is_empty() { None }
else { Some(html_snippet) }
626 statements
: Vec
<Statement
<'tcx
>>,
627 terminator
: &Option
<Terminator
<'tcx
>>,
629 let source_map
= tcx
.sess
.source_map();
630 let mut text
= Vec
::new();
631 text
.push(format
!("{}: {}:", spanview_id
, &source_map
.span_to_embeddable_string(span
)));
632 for statement
in statements
{
633 let source_range
= source_range_no_file(tcx
, &statement
.source_info
.span
);
638 statement_kind_name(&statement
),
639 format
!("{:?}", statement
)
642 if let Some(term
) = terminator
{
643 let source_range
= source_range_no_file(tcx
, &term
.source_info
.span
);
648 terminator_kind_name(term
),
655 fn trim_span(span
: Span
, from_pos
: BytePos
, to_pos
: BytePos
) -> Span
{
656 trim_span_hi(trim_span_lo(span
, from_pos
), to_pos
)
659 fn trim_span_lo(span
: Span
, from_pos
: BytePos
) -> Span
{
660 if from_pos
<= span
.lo() { span }
else { span.with_lo(cmp::min(span.hi(), from_pos)) }
663 fn trim_span_hi(span
: Span
, to_pos
: BytePos
) -> Span
{
664 if to_pos
>= span
.hi() { span }
else { span.with_hi(cmp::max(span.lo(), to_pos)) }
667 fn fn_span
<'tcx
>(tcx
: TyCtxt
<'tcx
>, def_id
: DefId
) -> Span
{
669 tcx
.hir().local_def_id_to_hir_id(def_id
.as_local().expect("expected DefId is local"));
670 let fn_decl_span
= tcx
.hir().span(hir_id
);
671 if let Some(body_span
) = hir_body(tcx
, def_id
).map(|hir_body
| hir_body
.value
.span
) {
672 if fn_decl_span
.ctxt() == body_span
.ctxt() { fn_decl_span.to(body_span) }
else { body_span }
678 fn hir_body
<'tcx
>(tcx
: TyCtxt
<'tcx
>, def_id
: DefId
) -> Option
<&'tcx rustc_hir
::Body
<'tcx
>> {
679 let hir_node
= tcx
.hir().get_if_local(def_id
).expect("expected DefId is local");
680 hir
::map
::associated_body(hir_node
).map(|fn_body_id
| tcx
.hir().body(fn_body_id
))
683 fn escape_html(s
: &str) -> String
{
684 s
.replace("&", "&").replace("<", "<").replace(">", ">")
687 fn escape_attr(s
: &str) -> String
{
688 s
.replace("&", "&")
689 .replace("\"", """)
690 .replace("'", "'")
691 .replace("<", "<")
692 .replace(">", ">")