1//===- SourceCoverageView.cpp - Code coverage view for source code --------===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This class implements rendering for code coverage of source code.
11//
12//===----------------------------------------------------------------------===//
13
14#include "SourceCoverageView.h"
15#include "llvm/ADT/Optional.h"
16#include "llvm/ADT/SmallString.h"
17#include "llvm/Support/LineIterator.h"
18
19using namespace llvm;
20
21void SourceCoverageView::renderLine(
22    raw_ostream &OS, StringRef Line, int64_t LineNumber,
23    const coverage::CoverageSegment *WrappedSegment,
24    ArrayRef<const coverage::CoverageSegment *> Segments,
25    unsigned ExpansionCol) {
26  Optional<raw_ostream::Colors> Highlight;
27  SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
28
29  // The first segment overlaps from a previous line, so we treat it specially.
30  if (WrappedSegment && WrappedSegment->HasCount && WrappedSegment->Count == 0)
31    Highlight = raw_ostream::RED;
32
33  // Output each segment of the line, possibly highlighted.
34  unsigned Col = 1;
35  for (const auto *S : Segments) {
36    unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);
37    colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
38                    Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
39        << Line.substr(Col - 1, End - Col);
40    if (Options.Debug && Highlight)
41      HighlightedRanges.push_back(std::make_pair(Col, End));
42    Col = End;
43    if (Col == ExpansionCol)
44      Highlight = raw_ostream::CYAN;
45    else if (S->HasCount && S->Count == 0)
46      Highlight = raw_ostream::RED;
47    else
48      Highlight = None;
49  }
50
51  // Show the rest of the line
52  colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
53                  Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
54      << Line.substr(Col - 1, Line.size() - Col + 1);
55  OS << "\n";
56
57  if (Options.Debug) {
58    for (const auto &Range : HighlightedRanges)
59      errs() << "Highlighted line " << LineNumber << ", " << Range.first
60             << " -> " << Range.second << "\n";
61    if (Highlight)
62      errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";
63  }
64}
65
66void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) {
67  for (unsigned I = 0; I < Level; ++I)
68    OS << "  |";
69}
70
71void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length,
72                                           raw_ostream &OS) {
73  assert(Level != 0 && "Cannot render divider at top level");
74  renderIndent(OS, Level - 1);
75  OS.indent(2);
76  for (unsigned I = 0; I < Length; ++I)
77    OS << "-";
78}
79
80void
81SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS,
82                                             const LineCoverageInfo &Line) {
83  if (!Line.isMapped()) {
84    OS.indent(LineCoverageColumnWidth) << '|';
85    return;
86  }
87  SmallString<32> Buffer;
88  raw_svector_ostream BufferOS(Buffer);
89  BufferOS << Line.ExecutionCount;
90  auto Str = BufferOS.str();
91  // Trim
92  Str = Str.substr(0, std::min(Str.size(), (size_t)LineCoverageColumnWidth));
93  // Align to the right
94  OS.indent(LineCoverageColumnWidth - Str.size());
95  colored_ostream(OS, raw_ostream::MAGENTA,
96                  Line.hasMultipleRegions() && Options.Colors)
97      << Str;
98  OS << '|';
99}
100
101void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS,
102                                                unsigned LineNo) {
103  SmallString<32> Buffer;
104  raw_svector_ostream BufferOS(Buffer);
105  BufferOS << LineNo;
106  auto Str = BufferOS.str();
107  // Trim and align to the right
108  Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
109  OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
110}
111
112void SourceCoverageView::renderRegionMarkers(
113    raw_ostream &OS, ArrayRef<const coverage::CoverageSegment *> Segments) {
114  SmallString<32> Buffer;
115  raw_svector_ostream BufferOS(Buffer);
116
117  unsigned PrevColumn = 1;
118  for (const auto *S : Segments) {
119    if (!S->IsRegionEntry)
120      continue;
121    // Skip to the new region
122    if (S->Col > PrevColumn)
123      OS.indent(S->Col - PrevColumn);
124    PrevColumn = S->Col + 1;
125    BufferOS << S->Count;
126    StringRef Str = BufferOS.str();
127    // Trim the execution count
128    Str = Str.substr(0, std::min(Str.size(), (size_t)7));
129    PrevColumn += Str.size();
130    OS << '^' << Str;
131    Buffer.clear();
132  }
133  OS << "\n";
134
135  if (Options.Debug)
136    for (const auto *S : Segments)
137      errs() << "Marker at " << S->Line << ":" << S->Col << " = " << S->Count
138             << (S->IsRegionEntry ? "\n" : " (pop)\n");
139}
140
141void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
142                                unsigned IndentLevel) {
143  // The width of the leading columns
144  unsigned CombinedColumnWidth =
145      (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
146      (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
147  // The width of the line that is used to divide between the view and the
148  // subviews.
149  unsigned DividerWidth = CombinedColumnWidth + 4;
150
151  // We need the expansions and instantiations sorted so we can go through them
152  // while we iterate lines.
153  std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end());
154  std::sort(InstantiationSubViews.begin(), InstantiationSubViews.end());
155  auto NextESV = ExpansionSubViews.begin();
156  auto EndESV = ExpansionSubViews.end();
157  auto NextISV = InstantiationSubViews.begin();
158  auto EndISV = InstantiationSubViews.end();
159
160  // Get the coverage information for the file.
161  auto NextSegment = CoverageInfo.begin();
162  auto EndSegment = CoverageInfo.end();
163
164  unsigned FirstLine = NextSegment != EndSegment ? NextSegment->Line : 0;
165  const coverage::CoverageSegment *WrappedSegment = nullptr;
166  SmallVector<const coverage::CoverageSegment *, 8> LineSegments;
167  for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof(); ++LI) {
168    // If we aren't rendering the whole file, we need to filter out the prologue
169    // and epilogue.
170    if (!WholeFile) {
171      if (NextSegment == EndSegment)
172        break;
173      else if (LI.line_number() < FirstLine)
174        continue;
175    }
176
177    // Collect the coverage information relevant to this line.
178    if (LineSegments.size())
179      WrappedSegment = LineSegments.back();
180    LineSegments.clear();
181    while (NextSegment != EndSegment && NextSegment->Line == LI.line_number())
182      LineSegments.push_back(&*NextSegment++);
183
184    // Calculate a count to be for the line as a whole.
185    LineCoverageInfo LineCount;
186    if (WrappedSegment && WrappedSegment->HasCount)
187      LineCount.addRegionCount(WrappedSegment->Count);
188    for (const auto *S : LineSegments)
189      if (S->HasCount && S->IsRegionEntry)
190          LineCount.addRegionStartCount(S->Count);
191
192    // Render the line prefix.
193    renderIndent(OS, IndentLevel);
194    if (Options.ShowLineStats)
195      renderLineCoverageColumn(OS, LineCount);
196    if (Options.ShowLineNumbers)
197      renderLineNumberColumn(OS, LI.line_number());
198
199    // If there are expansion subviews, we want to highlight the first one.
200    unsigned ExpansionColumn = 0;
201    if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&
202        Options.Colors)
203      ExpansionColumn = NextESV->getStartCol();
204
205    // Display the source code for the current line.
206    renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
207               ExpansionColumn);
208
209    // Show the region markers.
210    if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers ||
211                                      LineCount.hasMultipleRegions()) &&
212        !LineSegments.empty()) {
213      renderIndent(OS, IndentLevel);
214      OS.indent(CombinedColumnWidth);
215      renderRegionMarkers(OS, LineSegments);
216    }
217
218    // Show the expansions and instantiations for this line.
219    unsigned NestedIndent = IndentLevel + 1;
220    bool RenderedSubView = false;
221    for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
222         ++NextESV) {
223      renderViewDivider(NestedIndent, DividerWidth, OS);
224      OS << "\n";
225      if (RenderedSubView) {
226        // Re-render the current line and highlight the expansion range for
227        // this subview.
228        ExpansionColumn = NextESV->getStartCol();
229        renderIndent(OS, IndentLevel);
230        OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
231        renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
232                   ExpansionColumn);
233        renderViewDivider(NestedIndent, DividerWidth, OS);
234        OS << "\n";
235      }
236      // Render the child subview
237      if (Options.Debug)
238        errs() << "Expansion at line " << NextESV->getLine() << ", "
239               << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
240               << "\n";
241      NextESV->View->render(OS, false, NestedIndent);
242      RenderedSubView = true;
243    }
244    for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
245      renderViewDivider(NestedIndent, DividerWidth, OS);
246      OS << "\n";
247      renderIndent(OS, NestedIndent);
248      OS << ' ';
249      Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
250                                                     << ":";
251      OS << "\n";
252      NextISV->View->render(OS, false, NestedIndent);
253      RenderedSubView = true;
254    }
255    if (RenderedSubView) {
256      renderViewDivider(NestedIndent, DividerWidth, OS);
257      OS << "\n";
258    }
259  }
260}
261