blob: 7c6d9a5c0f38cbc3b015abfc872fbddec26c16d6 [file] [log] [blame]
Alex Lorenze82d89c2014-08-22 22:56:03 +00001//===- 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/SmallString.h"
16#include "llvm/Support/LineIterator.h"
17
18using namespace llvm;
19
20void SourceCoverageView::renderLine(raw_ostream &OS, StringRef Line,
21 ArrayRef<HighlightRange> Ranges) {
22 if (Ranges.empty()) {
23 OS << Line << "\n";
24 return;
25 }
26 if (Line.empty())
27 Line = " ";
28
29 unsigned PrevColumnStart = 0;
30 unsigned Start = 1;
31 for (const auto &Range : Ranges) {
32 if (PrevColumnStart == Range.ColumnStart)
33 continue;
34
35 // Show the unhighlighted part
36 unsigned ColumnStart = PrevColumnStart = Range.ColumnStart;
37 OS << Line.substr(Start - 1, ColumnStart - Start);
38
39 // Show the highlighted part
40 auto Color = Range.Kind == HighlightRange::NotCovered ? raw_ostream::RED
41 : raw_ostream::CYAN;
42 OS.changeColor(Color, false, true);
43 unsigned ColumnEnd = std::min(Range.ColumnEnd, (unsigned)Line.size() + 1);
44 OS << Line.substr(ColumnStart - 1, ColumnEnd - ColumnStart);
45 Start = ColumnEnd;
46 OS.resetColor();
47 }
48
49 // Show the rest of the line
50 OS << Line.substr(Start - 1, Line.size() - Start + 1);
51 OS << "\n";
52}
53
54void SourceCoverageView::renderOffset(raw_ostream &OS, unsigned I) {
55 for (unsigned J = 0; J < I; ++J)
56 OS << " |";
57}
58
59void SourceCoverageView::renderViewDivider(unsigned Offset, unsigned Length,
60 raw_ostream &OS) {
61 for (unsigned J = 1; J < Offset; ++J)
62 OS << " |";
63 if (Offset != 0)
64 OS.indent(2);
65 for (unsigned I = 0; I < Length; ++I)
66 OS << "-";
67}
68
69void
70SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS,
71 const LineCoverageInfo &Line) {
72 if (!Line.isMapped()) {
73 OS.indent(LineCoverageColumnWidth) << '|';
74 return;
75 }
76 SmallString<32> Buffer;
77 raw_svector_ostream BufferOS(Buffer);
78 BufferOS << Line.ExecutionCount;
79 auto Str = BufferOS.str();
80 // Trim
81 Str = Str.substr(0, std::min(Str.size(), (size_t)LineCoverageColumnWidth));
82 // Align to the right
83 OS.indent(LineCoverageColumnWidth - Str.size());
84 colored_ostream(OS, raw_ostream::MAGENTA,
85 Line.hasMultipleRegions() && Options.Colors)
86 << Str;
87 OS << '|';
88}
89
90void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS,
91 unsigned LineNo) {
92 SmallString<32> Buffer;
93 raw_svector_ostream BufferOS(Buffer);
94 BufferOS << LineNo;
95 auto Str = BufferOS.str();
96 // Trim and align to the right
97 Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
98 OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
99}
100
101void SourceCoverageView::renderRegionMarkers(raw_ostream &OS,
102 ArrayRef<RegionMarker> Regions) {
103 SmallString<32> Buffer;
104 raw_svector_ostream BufferOS(Buffer);
105
106 unsigned PrevColumn = 1;
107 for (const auto &Region : Regions) {
108 // Skip to the new region
109 if (Region.Column > PrevColumn)
110 OS.indent(Region.Column - PrevColumn);
111 PrevColumn = Region.Column + 1;
112 BufferOS << Region.ExecutionCount;
113 StringRef Str = BufferOS.str();
114 // Trim the execution count
115 Str = Str.substr(0, std::min(Str.size(), (size_t)7));
116 PrevColumn += Str.size();
117 OS << '^' << Str;
118 Buffer.clear();
119 }
120 OS << "\n";
Justin Bogner0b3614f2014-09-15 22:12:28 +0000121
122 if (Options.Debug) {
123 for (const auto &Region : Regions) {
124 errs() << "Marker at " << Region.Line << ":" << Region.Column << " = "
125 << Region.ExecutionCount << "\n";
126 }
127 }
Alex Lorenze82d89c2014-08-22 22:56:03 +0000128}
129
130/// \brief Insert a new highlighting range into the line's highlighting ranges
131/// Return line's new highlighting ranges in result.
132static void insertHighlightRange(
133 ArrayRef<SourceCoverageView::HighlightRange> Ranges,
134 SourceCoverageView::HighlightRange RangeToInsert,
135 SmallVectorImpl<SourceCoverageView::HighlightRange> &Result) {
136 Result.clear();
137 size_t I = 0;
138 auto E = Ranges.size();
139 for (; I < E; ++I) {
140 if (RangeToInsert.ColumnStart < Ranges[I].ColumnEnd) {
141 const auto &Range = Ranges[I];
142 bool NextRangeContainsInserted = false;
143 // If the next range starts before the inserted range, move the end of the
144 // next range to the start of the inserted range.
145 if (Range.ColumnStart < RangeToInsert.ColumnStart) {
146 if (RangeToInsert.ColumnStart != Range.ColumnStart)
147 Result.push_back(SourceCoverageView::HighlightRange(
148 Range.Line, Range.ColumnStart, RangeToInsert.ColumnStart,
149 Range.Kind));
150 // If the next range also ends after the inserted range, keep this range
151 // and create a new range that starts at the inserted range and ends
152 // at the next range later.
153 if (Range.ColumnEnd > RangeToInsert.ColumnEnd)
154 NextRangeContainsInserted = true;
155 }
156 if (!NextRangeContainsInserted) {
157 ++I;
158 // Ignore ranges that are contained in inserted range
159 while (I < E && RangeToInsert.contains(Ranges[I]))
160 ++I;
161 }
162 break;
163 }
164 Result.push_back(Ranges[I]);
165 }
166 Result.push_back(RangeToInsert);
167 // If the next range starts before the inserted range end, move the start
168 // of the next range to the end of the inserted range.
169 if (I < E && Ranges[I].ColumnStart < RangeToInsert.ColumnEnd) {
170 const auto &Range = Ranges[I];
171 if (RangeToInsert.ColumnEnd != Range.ColumnEnd)
172 Result.push_back(SourceCoverageView::HighlightRange(
173 Range.Line, RangeToInsert.ColumnEnd, Range.ColumnEnd, Range.Kind));
174 ++I;
175 }
176 // Add the remaining ranges that are located after the inserted range
177 for (; I < E; ++I)
178 Result.push_back(Ranges[I]);
179}
180
181void SourceCoverageView::sortChildren() {
182 for (auto &I : Children)
183 I->sortChildren();
184 std::sort(Children.begin(), Children.end(),
185 [](const std::unique_ptr<SourceCoverageView> &LHS,
186 const std::unique_ptr<SourceCoverageView> &RHS) {
187 return LHS->ExpansionRegion < RHS->ExpansionRegion;
188 });
189}
190
191SourceCoverageView::HighlightRange
192SourceCoverageView::getExpansionHighlightRange() const {
193 return HighlightRange(ExpansionRegion.LineStart, ExpansionRegion.ColumnStart,
194 ExpansionRegion.ColumnEnd, HighlightRange::Expanded);
195}
196
197template <typename T>
198ArrayRef<T> gatherLineItems(size_t &CurrentIdx, const std::vector<T> &Items,
199 unsigned LineNo) {
200 auto PrevIdx = CurrentIdx;
201 auto E = Items.size();
202 while (CurrentIdx < E && Items[CurrentIdx].Line == LineNo)
203 ++CurrentIdx;
204 return ArrayRef<T>(Items.data() + PrevIdx, CurrentIdx - PrevIdx);
205}
206
207ArrayRef<std::unique_ptr<SourceCoverageView>>
208gatherLineSubViews(size_t &CurrentIdx,
209 ArrayRef<std::unique_ptr<SourceCoverageView>> Items,
210 unsigned LineNo) {
211 auto PrevIdx = CurrentIdx;
212 auto E = Items.size();
213 while (CurrentIdx < E &&
214 Items[CurrentIdx]->getSubViewsExpansionLine() == LineNo)
215 ++CurrentIdx;
Justin Bogner3f81d492014-09-10 06:06:07 +0000216 return Items.slice(PrevIdx, CurrentIdx - PrevIdx);
Alex Lorenze82d89c2014-08-22 22:56:03 +0000217}
218
219void SourceCoverageView::render(raw_ostream &OS, unsigned Offset) {
220 // Make sure that the children are in sorted order.
221 sortChildren();
222
223 SmallVector<HighlightRange, 8> AdjustedLineHighlightRanges;
224 size_t CurrentChild = 0;
225 size_t CurrentHighlightRange = 0;
226 size_t CurrentRegionMarker = 0;
227
228 line_iterator Lines(File);
229 // Advance the line iterator to the first line.
Justin Bogner7dad93b2014-09-15 03:41:04 +0000230 while (Lines.line_number() < LineOffset)
Alex Lorenze82d89c2014-08-22 22:56:03 +0000231 ++Lines;
232
233 // The width of the leading columns
234 unsigned CombinedColumnWidth =
235 (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
236 (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
237 // The width of the line that is used to divide between the view and the
238 // subviews.
239 unsigned DividerWidth = CombinedColumnWidth + 4;
240
Justin Bogner7dad93b2014-09-15 03:41:04 +0000241 for (size_t I = 0, E = LineStats.size(); I < E; ++I) {
242 unsigned LineNo = I + LineOffset;
Alex Lorenze82d89c2014-08-22 22:56:03 +0000243
244 // Gather the child subviews that are visible on this line.
245 auto LineSubViews = gatherLineSubViews(CurrentChild, Children, LineNo);
246
247 renderOffset(OS, Offset);
248 if (Options.ShowLineStats)
249 renderLineCoverageColumn(OS, LineStats[I]);
250 if (Options.ShowLineNumbers)
251 renderLineNumberColumn(OS, LineNo);
252
253 // Gather highlighting ranges.
254 auto LineHighlightRanges =
255 gatherLineItems(CurrentHighlightRange, HighlightRanges, LineNo);
256 auto LineRanges = LineHighlightRanges;
257 // Highlight the expansion range if there is an expansion subview on this
258 // line.
259 if (!LineSubViews.empty() && LineSubViews.front()->isExpansionSubView() &&
260 Options.Colors) {
261 insertHighlightRange(LineHighlightRanges,
262 LineSubViews.front()->getExpansionHighlightRange(),
263 AdjustedLineHighlightRanges);
264 LineRanges = AdjustedLineHighlightRanges;
265 }
266
267 // Display the source code for the current line.
268 StringRef Line = *Lines;
269 // Check if the line is empty, as line_iterator skips blank lines.
270 if (LineNo < Lines.line_number())
271 Line = "";
272 else if (!Lines.is_at_eof())
273 ++Lines;
274 renderLine(OS, Line, LineRanges);
275
276 // Show the region markers.
277 bool ShowMarkers = !Options.ShowLineStatsOrRegionMarkers ||
278 LineStats[I].hasMultipleRegions();
279 auto LineMarkers = gatherLineItems(CurrentRegionMarker, Markers, LineNo);
280 if (ShowMarkers && !LineMarkers.empty()) {
281 renderOffset(OS, Offset);
282 OS.indent(CombinedColumnWidth);
283 renderRegionMarkers(OS, LineMarkers);
284 }
285
286 // Show the line's expanded child subviews.
287 bool FirstChildExpansion = true;
288 if (LineSubViews.empty())
289 continue;
290 unsigned NewOffset = Offset + 1;
291 renderViewDivider(NewOffset, DividerWidth, OS);
292 OS << "\n";
293 for (const auto &Child : LineSubViews) {
294 // If this subview shows a function instantiation, render the function's
295 // name.
296 if (Child->isInstantiationSubView()) {
297 renderOffset(OS, NewOffset);
298 OS << ' ';
299 Options.colored_ostream(OS, raw_ostream::CYAN) << Child->FunctionName
300 << ":";
301 OS << "\n";
302 } else {
303 if (!FirstChildExpansion) {
304 // Re-render the current line and highlight the expansion range for
305 // this
306 // subview.
307 insertHighlightRange(LineHighlightRanges,
308 Child->getExpansionHighlightRange(),
309 AdjustedLineHighlightRanges);
310 renderOffset(OS, Offset);
311 OS.indent(CombinedColumnWidth + (Offset == 0 ? 0 : 1));
312 renderLine(OS, Line, AdjustedLineHighlightRanges);
313 renderViewDivider(NewOffset, DividerWidth, OS);
314 OS << "\n";
315 } else
316 FirstChildExpansion = false;
317 }
318 // Render the child subview
319 Child->render(OS, NewOffset);
320 renderViewDivider(NewOffset, DividerWidth, OS);
321 OS << "\n";
322 }
323 }
324}
325
Justin Bogner0b3614f2014-09-15 22:12:28 +0000326void SourceCoverageView::setUpVisibleRange(SourceCoverageDataManager &Data) {
Justin Bogner7dad93b2014-09-15 03:41:04 +0000327 auto CountedRegions = Data.getSourceRegions();
328 if (!CountedRegions.size())
329 return;
330
Justin Bogner0b3614f2014-09-15 22:12:28 +0000331 unsigned Start = CountedRegions.front().LineStart, End = 0;
Justin Bogner7dad93b2014-09-15 03:41:04 +0000332 for (const auto &CR : CountedRegions) {
Justin Bogner0b3614f2014-09-15 22:12:28 +0000333 Start = std::min(Start, CR.LineStart);
334 End = std::max(End, CR.LineEnd);
335 }
336 LineOffset = Start;
337 LineStats.resize(End - Start + 1);
338}
339
340void
341SourceCoverageView::createLineCoverageInfo(SourceCoverageDataManager &Data) {
342 auto CountedRegions = Data.getSourceRegions();
343 for (const auto &CR : CountedRegions) {
Justin Bognere53be062014-09-09 05:32:18 +0000344 if (CR.Kind == coverage::CounterMappingRegion::SkippedRegion) {
345 // Reset the line stats for skipped regions.
346 for (unsigned Line = CR.LineStart; Line <= CR.LineEnd;
347 ++Line)
Justin Bogner7dad93b2014-09-15 03:41:04 +0000348 LineStats[Line - LineOffset] = LineCoverageInfo();
Justin Bognere53be062014-09-09 05:32:18 +0000349 continue;
350 }
Justin Bogner7dad93b2014-09-15 03:41:04 +0000351 LineStats[CR.LineStart - LineOffset].addRegionStartCount(CR.ExecutionCount);
Justin Bognere53be062014-09-09 05:32:18 +0000352 for (unsigned Line = CR.LineStart + 1; Line <= CR.LineEnd; ++Line)
Justin Bogner7dad93b2014-09-15 03:41:04 +0000353 LineStats[Line - LineOffset].addRegionCount(CR.ExecutionCount);
Alex Lorenze82d89c2014-08-22 22:56:03 +0000354 }
355}
356
357void
358SourceCoverageView::createHighlightRanges(SourceCoverageDataManager &Data) {
Justin Bognere53be062014-09-09 05:32:18 +0000359 auto CountedRegions = Data.getSourceRegions();
Alex Lorenze82d89c2014-08-22 22:56:03 +0000360 std::vector<bool> AlreadyHighlighted;
Justin Bognere53be062014-09-09 05:32:18 +0000361 AlreadyHighlighted.resize(CountedRegions.size(), false);
Alex Lorenze82d89c2014-08-22 22:56:03 +0000362
Justin Bognere53be062014-09-09 05:32:18 +0000363 for (size_t I = 0, S = CountedRegions.size(); I < S; ++I) {
364 const auto &CR = CountedRegions[I];
365 if (CR.Kind == coverage::CounterMappingRegion::SkippedRegion ||
366 CR.ExecutionCount != 0)
Alex Lorenze82d89c2014-08-22 22:56:03 +0000367 continue;
368 if (AlreadyHighlighted[I])
369 continue;
370 for (size_t J = 0; J < S; ++J) {
Justin Bognere53be062014-09-09 05:32:18 +0000371 if (CR.contains(CountedRegions[J])) {
Alex Lorenze82d89c2014-08-22 22:56:03 +0000372 AlreadyHighlighted[J] = true;
373 }
374 }
Justin Bognere53be062014-09-09 05:32:18 +0000375 if (CR.LineStart == CR.LineEnd) {
Alex Lorenze82d89c2014-08-22 22:56:03 +0000376 HighlightRanges.push_back(HighlightRange(
Justin Bognere53be062014-09-09 05:32:18 +0000377 CR.LineStart, CR.ColumnStart, CR.ColumnEnd));
Alex Lorenze82d89c2014-08-22 22:56:03 +0000378 continue;
379 }
380 HighlightRanges.push_back(
Justin Bognere53be062014-09-09 05:32:18 +0000381 HighlightRange(CR.LineStart, CR.ColumnStart,
Alex Lorenze82d89c2014-08-22 22:56:03 +0000382 std::numeric_limits<unsigned>::max()));
383 HighlightRanges.push_back(
Justin Bognere53be062014-09-09 05:32:18 +0000384 HighlightRange(CR.LineEnd, 1, CR.ColumnEnd));
385 for (unsigned Line = CR.LineStart + 1; Line < CR.LineEnd;
Alex Lorenze82d89c2014-08-22 22:56:03 +0000386 ++Line) {
387 HighlightRanges.push_back(
388 HighlightRange(Line, 1, std::numeric_limits<unsigned>::max()));
389 }
390 }
391
392 std::sort(HighlightRanges.begin(), HighlightRanges.end());
393
394 if (Options.Debug) {
395 for (const auto &Range : HighlightRanges) {
396 outs() << "Highlighted line " << Range.Line << ", " << Range.ColumnStart
397 << " -> ";
398 if (Range.ColumnEnd == std::numeric_limits<unsigned>::max()) {
399 outs() << "?\n";
400 } else {
401 outs() << Range.ColumnEnd << "\n";
402 }
403 }
404 }
405}
406
407void SourceCoverageView::createRegionMarkers(SourceCoverageDataManager &Data) {
Justin Bogner7dad93b2014-09-15 03:41:04 +0000408 for (const auto &CR : Data.getSourceRegions())
409 if (CR.Kind != coverage::CounterMappingRegion::SkippedRegion)
Justin Bognere53be062014-09-09 05:32:18 +0000410 Markers.push_back(
411 RegionMarker(CR.LineStart, CR.ColumnStart, CR.ExecutionCount));
Alex Lorenze82d89c2014-08-22 22:56:03 +0000412}
413
414void SourceCoverageView::load(SourceCoverageDataManager &Data) {
Justin Bogner0b3614f2014-09-15 22:12:28 +0000415 setUpVisibleRange(Data);
Alex Lorenze82d89c2014-08-22 22:56:03 +0000416 if (Options.ShowLineStats)
417 createLineCoverageInfo(Data);
418 if (Options.Colors)
419 createHighlightRanges(Data);
420 if (Options.ShowRegionMarkers)
421 createRegionMarkers(Data);
422}