llvm-cov: Fix an issue with showing regions but not counts
[oota-llvm.git] / tools / llvm-cov / SourceCoverageView.cpp
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/SmallString.h"
16 #include "llvm/Support/LineIterator.h"
17
18 using namespace llvm;
19
20 void 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
54 void SourceCoverageView::renderOffset(raw_ostream &OS, unsigned I) {
55   for (unsigned J = 0; J < I; ++J)
56     OS << "  |";
57 }
58
59 void 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
69 void
70 SourceCoverageView::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
90 void 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
101 void 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";
121
122   if (Options.Debug) {
123     for (const auto &Region : Regions) {
124       errs() << "Marker at " << Region.Line << ":" << Region.Column << " = "
125              << Region.ExecutionCount << "\n";
126     }
127   }
128 }
129
130 /// \brief Insert a new highlighting range into the line's highlighting ranges
131 /// Return line's new highlighting ranges in result.
132 static 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
181 void 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
191 SourceCoverageView::HighlightRange
192 SourceCoverageView::getExpansionHighlightRange() const {
193   return HighlightRange(ExpansionRegion.LineStart, ExpansionRegion.ColumnStart,
194                         ExpansionRegion.ColumnEnd, HighlightRange::Expanded);
195 }
196
197 template <typename T>
198 ArrayRef<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
207 ArrayRef<std::unique_ptr<SourceCoverageView>>
208 gatherLineSubViews(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;
216   return Items.slice(PrevIdx, CurrentIdx - PrevIdx);
217 }
218
219 void 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.
230   while (Lines.line_number() < LineOffset)
231     ++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
241   for (size_t I = 0, E = LineStats.size(); I < E; ++I) {
242     unsigned LineNo = I + LineOffset;
243
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
326 void SourceCoverageView::setUpVisibleRange(SourceCoverageDataManager &Data) {
327   auto CountedRegions = Data.getSourceRegions();
328   if (!CountedRegions.size())
329     return;
330
331   unsigned Start = CountedRegions.front().LineStart, End = 0;
332   for (const auto &CR : CountedRegions) {
333     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
340 void
341 SourceCoverageView::createLineCoverageInfo(SourceCoverageDataManager &Data) {
342   auto CountedRegions = Data.getSourceRegions();
343   for (const auto &CR : CountedRegions) {
344     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)
348         LineStats[Line - LineOffset] = LineCoverageInfo();
349       continue;
350     }
351     LineStats[CR.LineStart - LineOffset].addRegionStartCount(CR.ExecutionCount);
352     for (unsigned Line = CR.LineStart + 1; Line <= CR.LineEnd; ++Line)
353       LineStats[Line - LineOffset].addRegionCount(CR.ExecutionCount);
354   }
355 }
356
357 void
358 SourceCoverageView::createHighlightRanges(SourceCoverageDataManager &Data) {
359   auto CountedRegions = Data.getSourceRegions();
360   std::vector<bool> AlreadyHighlighted;
361   AlreadyHighlighted.resize(CountedRegions.size(), false);
362
363   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)
367       continue;
368     if (AlreadyHighlighted[I])
369       continue;
370     for (size_t J = 0; J < S; ++J) {
371       if (CR.contains(CountedRegions[J])) {
372         AlreadyHighlighted[J] = true;
373       }
374     }
375     if (CR.LineStart == CR.LineEnd) {
376       HighlightRanges.push_back(HighlightRange(
377           CR.LineStart, CR.ColumnStart, CR.ColumnEnd));
378       continue;
379     }
380     HighlightRanges.push_back(
381         HighlightRange(CR.LineStart, CR.ColumnStart,
382                        std::numeric_limits<unsigned>::max()));
383     HighlightRanges.push_back(
384         HighlightRange(CR.LineEnd, 1, CR.ColumnEnd));
385     for (unsigned Line = CR.LineStart + 1; Line < CR.LineEnd;
386          ++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
407 void SourceCoverageView::createRegionMarkers(SourceCoverageDataManager &Data) {
408   for (const auto &CR : Data.getSourceRegions())
409     if (CR.Kind != coverage::CounterMappingRegion::SkippedRegion)
410       Markers.push_back(
411           RegionMarker(CR.LineStart, CR.ColumnStart, CR.ExecutionCount));
412 }
413
414 void SourceCoverageView::load(SourceCoverageDataManager &Data) {
415   setUpVisibleRange(Data);
416   if (Options.ShowLineStats)
417     createLineCoverageInfo(Data);
418   if (Options.Colors)
419     createHighlightRanges(Data);
420   if (Options.ShowRegionMarkers)
421     createRegionMarkers(Data);
422 }