1 //===- CoverageReport.cpp - Code coverage report -------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This class implements rendering of a code coverage report.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "CoverageReport.h"
14 #include "RenderingSupport.h"
15 #include "llvm/ADT/DenseMap.h"
16 #include "llvm/ADT/SmallString.h"
17 #include "llvm/Support/Format.h"
18 #include "llvm/Support/Path.h"
19 #include "llvm/Support/ThreadPool.h"
20 #include "llvm/Support/Threading.h"
21 #include <numeric>
22 
23 using namespace llvm;
24 
25 namespace {
26 
27 /// Helper struct which prints trimmed and aligned columns.
28 struct Column {
29   enum TrimKind { NoTrim, WidthTrim, RightTrim };
30 
31   enum AlignmentKind { LeftAlignment, RightAlignment };
32 
33   StringRef Str;
34   unsigned Width;
35   TrimKind Trim;
36   AlignmentKind Alignment;
37 
38   Column(StringRef Str, unsigned Width)
39       : Str(Str), Width(Width), Trim(WidthTrim), Alignment(LeftAlignment) {}
40 
41   Column &set(TrimKind Value) {
42     Trim = Value;
43     return *this;
44   }
45 
46   Column &set(AlignmentKind Value) {
47     Alignment = Value;
48     return *this;
49   }
50 
51   void render(raw_ostream &OS) const {
52     if (Str.size() <= Width) {
53       if (Alignment == RightAlignment) {
54         OS.indent(Width - Str.size());
55         OS << Str;
56         return;
57       }
58       OS << Str;
59       OS.indent(Width - Str.size());
60       return;
61     }
62 
63     switch (Trim) {
64     case NoTrim:
65       OS << Str;
66       break;
67     case WidthTrim:
68       OS << Str.substr(0, Width);
69       break;
70     case RightTrim:
71       OS << Str.substr(0, Width - 3) << "...";
72       break;
73     }
74   }
75 };
76 
77 raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {
78   Value.render(OS);
79   return OS;
80 }
81 
82 Column column(StringRef Str, unsigned Width) { return Column(Str, Width); }
83 
84 template <typename T>
85 Column column(StringRef Str, unsigned Width, const T &Value) {
86   return Column(Str, Width).set(Value);
87 }
88 
89 // Specify the default column widths.
90 size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 16,
91                               16, 10, 12, 18, 10, 12, 18, 10};
92 size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8};
93 
94 /// Adjust column widths to fit long file paths and function names.
95 void adjustColumnWidths(ArrayRef<StringRef> Files,
96                         ArrayRef<StringRef> Functions) {
97   for (StringRef Filename : Files)
98     FileReportColumns[0] = std::max(FileReportColumns[0], Filename.size());
99   for (StringRef Funcname : Functions)
100     FunctionReportColumns[0] =
101         std::max(FunctionReportColumns[0], Funcname.size());
102 }
103 
104 /// Prints a horizontal divider long enough to cover the given column
105 /// widths.
106 void renderDivider(ArrayRef<size_t> ColumnWidths, raw_ostream &OS) {
107   size_t Length = std::accumulate(ColumnWidths.begin(), ColumnWidths.end(), 0);
108   for (size_t I = 0; I < Length; ++I)
109     OS << '-';
110 }
111 
112 /// Return the color which correponds to the coverage percentage of a
113 /// certain metric.
114 template <typename T>
115 raw_ostream::Colors determineCoveragePercentageColor(const T &Info) {
116   if (Info.isFullyCovered())
117     return raw_ostream::GREEN;
118   return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW
119                                           : raw_ostream::RED;
120 }
121 
122 /// Get the number of redundant path components in each path in \p Paths.
123 unsigned getNumRedundantPathComponents(ArrayRef<std::string> Paths) {
124   // To start, set the number of redundant path components to the maximum
125   // possible value.
126   SmallVector<StringRef, 8> FirstPathComponents{sys::path::begin(Paths[0]),
127                                                 sys::path::end(Paths[0])};
128   unsigned NumRedundant = FirstPathComponents.size();
129 
130   for (unsigned I = 1, E = Paths.size(); NumRedundant > 0 && I < E; ++I) {
131     StringRef Path = Paths[I];
132     for (const auto &Component :
133          enumerate(make_range(sys::path::begin(Path), sys::path::end(Path)))) {
134       // Do not increase the number of redundant components: that would remove
135       // useful parts of already-visited paths.
136       if (Component.index() >= NumRedundant)
137         break;
138 
139       // Lower the number of redundant components when there's a mismatch
140       // between the first path, and the path under consideration.
141       if (FirstPathComponents[Component.index()] != Component.value()) {
142         NumRedundant = Component.index();
143         break;
144       }
145     }
146   }
147 
148   return NumRedundant;
149 }
150 
151 /// Determine the length of the longest redundant prefix of the paths in
152 /// \p Paths.
153 unsigned getRedundantPrefixLen(ArrayRef<std::string> Paths) {
154   // If there's at most one path, no path components are redundant.
155   if (Paths.size() <= 1)
156     return 0;
157 
158   unsigned PrefixLen = 0;
159   unsigned NumRedundant = getNumRedundantPathComponents(Paths);
160   auto Component = sys::path::begin(Paths[0]);
161   for (unsigned I = 0; I < NumRedundant; ++I) {
162     auto LastComponent = Component;
163     ++Component;
164     PrefixLen += Component - LastComponent;
165   }
166   return PrefixLen;
167 }
168 
169 } // end anonymous namespace
170 
171 namespace llvm {
172 
173 void CoverageReport::render(const FileCoverageSummary &File,
174                             raw_ostream &OS) const {
175   auto FileCoverageColor =
176       determineCoveragePercentageColor(File.RegionCoverage);
177   auto FuncCoverageColor =
178       determineCoveragePercentageColor(File.FunctionCoverage);
179   auto InstantiationCoverageColor =
180       determineCoveragePercentageColor(File.InstantiationCoverage);
181   auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage);
182   SmallString<256> FileName = File.Name;
183   sys::path::remove_dots(FileName, /*remove_dot_dot=*/true);
184   sys::path::native(FileName);
185   OS << column(FileName, FileReportColumns[0], Column::NoTrim);
186 
187   if (Options.ShowRegionSummary) {
188     OS << format("%*u", FileReportColumns[1],
189                  (unsigned)File.RegionCoverage.getNumRegions());
190     Options.colored_ostream(OS, FileCoverageColor)
191         << format("%*u", FileReportColumns[2],
192                   (unsigned)(File.RegionCoverage.getNumRegions() -
193                              File.RegionCoverage.getCovered()));
194     if (File.RegionCoverage.getNumRegions())
195       Options.colored_ostream(OS, FileCoverageColor)
196           << format("%*.2f", FileReportColumns[3] - 1,
197                     File.RegionCoverage.getPercentCovered())
198           << '%';
199     else
200       OS << column("-", FileReportColumns[3], Column::RightAlignment);
201   }
202 
203   OS << format("%*u", FileReportColumns[4],
204                (unsigned)File.FunctionCoverage.getNumFunctions());
205   OS << format("%*u", FileReportColumns[5],
206                (unsigned)(File.FunctionCoverage.getNumFunctions() -
207                           File.FunctionCoverage.getExecuted()));
208   if (File.FunctionCoverage.getNumFunctions())
209     Options.colored_ostream(OS, FuncCoverageColor)
210         << format("%*.2f", FileReportColumns[6] - 1,
211                   File.FunctionCoverage.getPercentCovered())
212         << '%';
213   else
214     OS << column("-", FileReportColumns[6], Column::RightAlignment);
215 
216   if (Options.ShowInstantiationSummary) {
217     OS << format("%*u", FileReportColumns[7],
218                  (unsigned)File.InstantiationCoverage.getNumFunctions());
219     OS << format("%*u", FileReportColumns[8],
220                  (unsigned)(File.InstantiationCoverage.getNumFunctions() -
221                             File.InstantiationCoverage.getExecuted()));
222     if (File.InstantiationCoverage.getNumFunctions())
223       Options.colored_ostream(OS, InstantiationCoverageColor)
224           << format("%*.2f", FileReportColumns[9] - 1,
225                     File.InstantiationCoverage.getPercentCovered())
226           << '%';
227     else
228       OS << column("-", FileReportColumns[9], Column::RightAlignment);
229   }
230 
231   OS << format("%*u", FileReportColumns[10],
232                (unsigned)File.LineCoverage.getNumLines());
233   Options.colored_ostream(OS, LineCoverageColor) << format(
234       "%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() -
235                                                File.LineCoverage.getCovered()));
236   if (File.LineCoverage.getNumLines())
237     Options.colored_ostream(OS, LineCoverageColor)
238         << format("%*.2f", FileReportColumns[12] - 1,
239                   File.LineCoverage.getPercentCovered())
240         << '%';
241   else
242     OS << column("-", FileReportColumns[12], Column::RightAlignment);
243 
244   if (Options.ShowBranchSummary) {
245     OS << format("%*u", FileReportColumns[13],
246                  (unsigned)File.BranchCoverage.getNumBranches());
247     Options.colored_ostream(OS, LineCoverageColor)
248         << format("%*u", FileReportColumns[14],
249                   (unsigned)(File.BranchCoverage.getNumBranches() -
250                              File.BranchCoverage.getCovered()));
251     if (File.BranchCoverage.getNumBranches())
252       Options.colored_ostream(OS, LineCoverageColor)
253           << format("%*.2f", FileReportColumns[15] - 1,
254                     File.BranchCoverage.getPercentCovered())
255           << '%';
256     else
257       OS << column("-", FileReportColumns[15], Column::RightAlignment);
258   }
259 
260   OS << "\n";
261 }
262 
263 void CoverageReport::render(const FunctionCoverageSummary &Function,
264                             const DemangleCache &DC,
265                             raw_ostream &OS) const {
266   auto FuncCoverageColor =
267       determineCoveragePercentageColor(Function.RegionCoverage);
268   auto LineCoverageColor =
269       determineCoveragePercentageColor(Function.LineCoverage);
270   OS << column(DC.demangle(Function.Name), FunctionReportColumns[0],
271                Column::RightTrim)
272      << format("%*u", FunctionReportColumns[1],
273                (unsigned)Function.RegionCoverage.getNumRegions());
274   Options.colored_ostream(OS, FuncCoverageColor)
275       << format("%*u", FunctionReportColumns[2],
276                 (unsigned)(Function.RegionCoverage.getNumRegions() -
277                            Function.RegionCoverage.getCovered()));
278   Options.colored_ostream(
279       OS, determineCoveragePercentageColor(Function.RegionCoverage))
280       << format("%*.2f", FunctionReportColumns[3] - 1,
281                 Function.RegionCoverage.getPercentCovered())
282       << '%';
283   OS << format("%*u", FunctionReportColumns[4],
284                (unsigned)Function.LineCoverage.getNumLines());
285   Options.colored_ostream(OS, LineCoverageColor)
286       << format("%*u", FunctionReportColumns[5],
287                 (unsigned)(Function.LineCoverage.getNumLines() -
288                            Function.LineCoverage.getCovered()));
289   Options.colored_ostream(
290       OS, determineCoveragePercentageColor(Function.LineCoverage))
291       << format("%*.2f", FunctionReportColumns[6] - 1,
292                 Function.LineCoverage.getPercentCovered())
293       << '%';
294   if (Options.ShowBranchSummary) {
295     OS << format("%*u", FunctionReportColumns[7],
296                  (unsigned)Function.BranchCoverage.getNumBranches());
297     Options.colored_ostream(OS, LineCoverageColor)
298         << format("%*u", FunctionReportColumns[8],
299                   (unsigned)(Function.BranchCoverage.getNumBranches() -
300                              Function.BranchCoverage.getCovered()));
301     Options.colored_ostream(
302         OS, determineCoveragePercentageColor(Function.BranchCoverage))
303         << format("%*.2f", FunctionReportColumns[9] - 1,
304                   Function.BranchCoverage.getPercentCovered())
305         << '%';
306   }
307   OS << "\n";
308 }
309 
310 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
311                                            const DemangleCache &DC,
312                                            raw_ostream &OS) {
313   bool isFirst = true;
314   for (StringRef Filename : Files) {
315     auto Functions = Coverage.getCoveredFunctions(Filename);
316 
317     if (isFirst)
318       isFirst = false;
319     else
320       OS << "\n";
321 
322     std::vector<StringRef> Funcnames;
323     for (const auto &F : Functions)
324       Funcnames.emplace_back(DC.demangle(F.Name));
325     adjustColumnWidths({}, Funcnames);
326 
327     OS << "File '" << Filename << "':\n";
328     OS << column("Name", FunctionReportColumns[0])
329        << column("Regions", FunctionReportColumns[1], Column::RightAlignment)
330        << column("Miss", FunctionReportColumns[2], Column::RightAlignment)
331        << column("Cover", FunctionReportColumns[3], Column::RightAlignment)
332        << column("Lines", FunctionReportColumns[4], Column::RightAlignment)
333        << column("Miss", FunctionReportColumns[5], Column::RightAlignment)
334        << column("Cover", FunctionReportColumns[6], Column::RightAlignment);
335     if (Options.ShowBranchSummary)
336       OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment)
337          << column("Miss", FunctionReportColumns[8], Column::RightAlignment)
338          << column("Cover", FunctionReportColumns[9], Column::RightAlignment);
339     OS << "\n";
340     renderDivider(FunctionReportColumns, OS);
341     OS << "\n";
342     FunctionCoverageSummary Totals("TOTAL");
343     for (const auto &F : Functions) {
344       auto Function = FunctionCoverageSummary::get(Coverage, F);
345       ++Totals.ExecutionCount;
346       Totals.RegionCoverage += Function.RegionCoverage;
347       Totals.LineCoverage += Function.LineCoverage;
348       Totals.BranchCoverage += Function.BranchCoverage;
349       render(Function, DC, OS);
350     }
351     if (Totals.ExecutionCount) {
352       renderDivider(FunctionReportColumns, OS);
353       OS << "\n";
354       render(Totals, DC, OS);
355     }
356   }
357 }
358 
359 void CoverageReport::prepareSingleFileReport(const StringRef Filename,
360     const coverage::CoverageMapping *Coverage,
361     const CoverageViewOptions &Options, const unsigned LCP,
362     FileCoverageSummary *FileReport, const CoverageFilter *Filters) {
363   for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {
364     std::vector<FunctionCoverageSummary> InstantiationSummaries;
365     for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
366       if (!Filters->matches(*Coverage, *F))
367         continue;
368       auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);
369       FileReport->addInstantiation(InstantiationSummary);
370       InstantiationSummaries.push_back(InstantiationSummary);
371     }
372     if (InstantiationSummaries.empty())
373       continue;
374 
375     auto GroupSummary =
376         FunctionCoverageSummary::get(Group, InstantiationSummaries);
377 
378     if (Options.Debug)
379       outs() << "InstantiationGroup: " << GroupSummary.Name << " with "
380              << "size = " << Group.size() << "\n";
381 
382     FileReport->addFunction(GroupSummary);
383   }
384 }
385 
386 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(
387     const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals,
388     ArrayRef<std::string> Files, const CoverageViewOptions &Options,
389     const CoverageFilter &Filters) {
390   unsigned LCP = getRedundantPrefixLen(Files);
391 
392   ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);
393   if (Options.NumThreads == 0) {
394     // If NumThreads is not specified, create one thread for each input, up to
395     // the number of hardware cores.
396     S = heavyweight_hardware_concurrency(Files.size());
397     S.Limit = true;
398   }
399   ThreadPool Pool(S);
400 
401   std::vector<FileCoverageSummary> FileReports;
402   FileReports.reserve(Files.size());
403 
404   for (StringRef Filename : Files) {
405     FileReports.emplace_back(Filename.drop_front(LCP));
406     Pool.async(&CoverageReport::prepareSingleFileReport, Filename,
407                &Coverage, Options, LCP, &FileReports.back(), &Filters);
408   }
409   Pool.wait();
410 
411   for (const auto &FileReport : FileReports)
412     Totals += FileReport;
413 
414   return FileReports;
415 }
416 
417 void CoverageReport::renderFileReports(
418     raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const {
419   std::vector<std::string> UniqueSourceFiles;
420   for (StringRef SF : Coverage.getUniqueSourceFiles()) {
421     // Apply ignore source files filters.
422     if (!IgnoreFilenameFilters.matchesFilename(SF))
423       UniqueSourceFiles.emplace_back(SF.str());
424   }
425   renderFileReports(OS, UniqueSourceFiles);
426 }
427 
428 void CoverageReport::renderFileReports(
429     raw_ostream &OS, ArrayRef<std::string> Files) const {
430   renderFileReports(OS, Files, CoverageFiltersMatchAll());
431 }
432 
433 void CoverageReport::renderFileReports(
434     raw_ostream &OS, ArrayRef<std::string> Files,
435     const CoverageFiltersMatchAll &Filters) const {
436   FileCoverageSummary Totals("TOTAL");
437   auto FileReports =
438       prepareFileReports(Coverage, Totals, Files, Options, Filters);
439 
440   std::vector<StringRef> Filenames;
441   Filenames.reserve(FileReports.size());
442   for (const FileCoverageSummary &FCS : FileReports)
443     Filenames.emplace_back(FCS.Name);
444   adjustColumnWidths(Filenames, {});
445 
446   OS << column("Filename", FileReportColumns[0]);
447   if (Options.ShowRegionSummary)
448     OS << column("Regions", FileReportColumns[1], Column::RightAlignment)
449        << column("Missed Regions", FileReportColumns[2], Column::RightAlignment)
450        << column("Cover", FileReportColumns[3], Column::RightAlignment);
451   OS << column("Functions", FileReportColumns[4], Column::RightAlignment)
452      << column("Missed Functions", FileReportColumns[5], Column::RightAlignment)
453      << column("Executed", FileReportColumns[6], Column::RightAlignment);
454   if (Options.ShowInstantiationSummary)
455     OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment)
456        << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment)
457        << column("Executed", FileReportColumns[9], Column::RightAlignment);
458   OS << column("Lines", FileReportColumns[10], Column::RightAlignment)
459      << column("Missed Lines", FileReportColumns[11], Column::RightAlignment)
460      << column("Cover", FileReportColumns[12], Column::RightAlignment);
461   if (Options.ShowBranchSummary)
462     OS << column("Branches", FileReportColumns[13], Column::RightAlignment)
463        << column("Missed Branches", FileReportColumns[14],
464                  Column::RightAlignment)
465        << column("Cover", FileReportColumns[15], Column::RightAlignment);
466   OS << "\n";
467   renderDivider(FileReportColumns, OS);
468   OS << "\n";
469 
470   bool EmptyFiles = false;
471   for (const FileCoverageSummary &FCS : FileReports) {
472     if (FCS.FunctionCoverage.getNumFunctions())
473       render(FCS, OS);
474     else
475       EmptyFiles = true;
476   }
477 
478   if (EmptyFiles && Filters.empty()) {
479     OS << "\n"
480        << "Files which contain no functions:\n";
481 
482     for (const FileCoverageSummary &FCS : FileReports)
483       if (!FCS.FunctionCoverage.getNumFunctions())
484         render(FCS, OS);
485   }
486 
487   renderDivider(FileReportColumns, OS);
488   OS << "\n";
489   render(Totals, OS);
490 }
491 
492 } // end namespace llvm
493