xref: /freebsd/contrib/llvm-project/llvm/tools/llvm-cov/CoverageReport.cpp (revision a90b9d0159070121c221b966469c3e36d912bf82)
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/SmallString.h"
16 #include "llvm/Support/Format.h"
17 #include "llvm/Support/Path.h"
18 #include "llvm/Support/ThreadPool.h"
19 #include "llvm/Support/Threading.h"
20 #include <numeric>
21 
22 using namespace llvm;
23 
24 namespace {
25 
26 /// Helper struct which prints trimmed and aligned columns.
27 struct Column {
28   enum TrimKind { NoTrim, WidthTrim, RightTrim };
29 
30   enum AlignmentKind { LeftAlignment, RightAlignment };
31 
32   StringRef Str;
33   unsigned Width;
34   TrimKind Trim;
35   AlignmentKind Alignment;
36 
37   Column(StringRef Str, unsigned Width)
38       : Str(Str), Width(Width), Trim(WidthTrim), Alignment(LeftAlignment) {}
39 
40   Column &set(TrimKind Value) {
41     Trim = Value;
42     return *this;
43   }
44 
45   Column &set(AlignmentKind Value) {
46     Alignment = Value;
47     return *this;
48   }
49 
50   void render(raw_ostream &OS) const {
51     if (Str.size() <= Width) {
52       if (Alignment == RightAlignment) {
53         OS.indent(Width - Str.size());
54         OS << Str;
55         return;
56       }
57       OS << Str;
58       OS.indent(Width - Str.size());
59       return;
60     }
61 
62     switch (Trim) {
63     case NoTrim:
64       OS << Str;
65       break;
66     case WidthTrim:
67       OS << Str.substr(0, Width);
68       break;
69     case RightTrim:
70       OS << Str.substr(0, Width - 3) << "...";
71       break;
72     }
73   }
74 };
75 
76 raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {
77   Value.render(OS);
78   return OS;
79 }
80 
81 Column column(StringRef Str, unsigned Width) { return Column(Str, Width); }
82 
83 template <typename T>
84 Column column(StringRef Str, unsigned Width, const T &Value) {
85   return Column(Str, Width).set(Value);
86 }
87 
88 // Specify the default column widths.
89 size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 16, 16, 10,
90                               12, 18, 10, 12, 18, 10, 20, 21, 10};
91 size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8, 20, 8, 8};
92 
93 /// Adjust column widths to fit long file paths and function names.
94 void adjustColumnWidths(ArrayRef<StringRef> Files,
95                         ArrayRef<StringRef> Functions) {
96   for (StringRef Filename : Files)
97     FileReportColumns[0] = std::max(FileReportColumns[0], Filename.size());
98   for (StringRef Funcname : Functions)
99     FunctionReportColumns[0] =
100         std::max(FunctionReportColumns[0], Funcname.size());
101 }
102 
103 /// Prints a horizontal divider long enough to cover the given column
104 /// widths.
105 void renderDivider(ArrayRef<size_t> ColumnWidths, raw_ostream &OS) {
106   size_t Length = std::accumulate(ColumnWidths.begin(), ColumnWidths.end(), 0);
107   for (size_t I = 0; I < Length; ++I)
108     OS << '-';
109 }
110 
111 /// Return the color which correponds to the coverage percentage of a
112 /// certain metric.
113 template <typename T>
114 raw_ostream::Colors determineCoveragePercentageColor(const T &Info) {
115   if (Info.isFullyCovered())
116     return raw_ostream::GREEN;
117   return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW
118                                           : raw_ostream::RED;
119 }
120 
121 /// Get the number of redundant path components in each path in \p Paths.
122 unsigned getNumRedundantPathComponents(ArrayRef<std::string> Paths) {
123   // To start, set the number of redundant path components to the maximum
124   // possible value.
125   SmallVector<StringRef, 8> FirstPathComponents{sys::path::begin(Paths[0]),
126                                                 sys::path::end(Paths[0])};
127   unsigned NumRedundant = FirstPathComponents.size();
128 
129   for (unsigned I = 1, E = Paths.size(); NumRedundant > 0 && I < E; ++I) {
130     StringRef Path = Paths[I];
131     for (const auto &Component :
132          enumerate(make_range(sys::path::begin(Path), sys::path::end(Path)))) {
133       // Do not increase the number of redundant components: that would remove
134       // useful parts of already-visited paths.
135       if (Component.index() >= NumRedundant)
136         break;
137 
138       // Lower the number of redundant components when there's a mismatch
139       // between the first path, and the path under consideration.
140       if (FirstPathComponents[Component.index()] != Component.value()) {
141         NumRedundant = Component.index();
142         break;
143       }
144     }
145   }
146 
147   return NumRedundant;
148 }
149 
150 /// Determine the length of the longest redundant prefix of the paths in
151 /// \p Paths.
152 unsigned getRedundantPrefixLen(ArrayRef<std::string> Paths) {
153   // If there's at most one path, no path components are redundant.
154   if (Paths.size() <= 1)
155     return 0;
156 
157   unsigned PrefixLen = 0;
158   unsigned NumRedundant = getNumRedundantPathComponents(Paths);
159   auto Component = sys::path::begin(Paths[0]);
160   for (unsigned I = 0; I < NumRedundant; ++I) {
161     auto LastComponent = Component;
162     ++Component;
163     PrefixLen += Component - LastComponent;
164   }
165   return PrefixLen;
166 }
167 
168 /// Determine the length of the longest redundant prefix of the substrs starts
169 /// from \p LCP in \p Paths. \p Paths can't be empty. If there's only one
170 /// element in \p Paths, the length of the substr is returned. Note this is
171 /// differnet from the behavior of the function above.
172 unsigned getRedundantPrefixLen(ArrayRef<StringRef> Paths, unsigned LCP) {
173   assert(!Paths.empty() && "Paths must have at least one element");
174 
175   auto Iter = Paths.begin();
176   auto IterE = Paths.end();
177   auto Prefix = Iter->substr(LCP);
178   while (++Iter != IterE) {
179     auto Other = Iter->substr(LCP);
180     auto Len = std::min(Prefix.size(), Other.size());
181     for (std::size_t I = 0; I < Len; ++I) {
182       if (Prefix[I] != Other[I]) {
183         Prefix = Prefix.substr(0, I);
184         break;
185       }
186     }
187   }
188 
189   for (auto I = Prefix.size(); --I != SIZE_MAX;) {
190     if (Prefix[I] == '/' || Prefix[I] == '\\')
191       return I + 1;
192   }
193 
194   return Prefix.size();
195 }
196 
197 } // end anonymous namespace
198 
199 namespace llvm {
200 
201 void CoverageReport::render(const FileCoverageSummary &File,
202                             raw_ostream &OS) const {
203   auto FileCoverageColor =
204       determineCoveragePercentageColor(File.RegionCoverage);
205   auto FuncCoverageColor =
206       determineCoveragePercentageColor(File.FunctionCoverage);
207   auto InstantiationCoverageColor =
208       determineCoveragePercentageColor(File.InstantiationCoverage);
209   auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage);
210   SmallString<256> FileName = File.Name;
211   sys::path::native(FileName);
212 
213   // remove_dots will remove trailing slash, so we need to check before it.
214   auto IsDir = FileName.ends_with(sys::path::get_separator());
215   sys::path::remove_dots(FileName, /*remove_dot_dot=*/true);
216   if (IsDir)
217     FileName += sys::path::get_separator();
218 
219   OS << column(FileName, FileReportColumns[0], Column::NoTrim);
220 
221   if (Options.ShowRegionSummary) {
222     OS << format("%*u", FileReportColumns[1],
223                  (unsigned)File.RegionCoverage.getNumRegions());
224     Options.colored_ostream(OS, FileCoverageColor)
225         << format("%*u", FileReportColumns[2],
226                   (unsigned)(File.RegionCoverage.getNumRegions() -
227                              File.RegionCoverage.getCovered()));
228     if (File.RegionCoverage.getNumRegions())
229       Options.colored_ostream(OS, FileCoverageColor)
230           << format("%*.2f", FileReportColumns[3] - 1,
231                     File.RegionCoverage.getPercentCovered())
232           << '%';
233     else
234       OS << column("-", FileReportColumns[3], Column::RightAlignment);
235   }
236 
237   OS << format("%*u", FileReportColumns[4],
238                (unsigned)File.FunctionCoverage.getNumFunctions());
239   OS << format("%*u", FileReportColumns[5],
240                (unsigned)(File.FunctionCoverage.getNumFunctions() -
241                           File.FunctionCoverage.getExecuted()));
242   if (File.FunctionCoverage.getNumFunctions())
243     Options.colored_ostream(OS, FuncCoverageColor)
244         << format("%*.2f", FileReportColumns[6] - 1,
245                   File.FunctionCoverage.getPercentCovered())
246         << '%';
247   else
248     OS << column("-", FileReportColumns[6], Column::RightAlignment);
249 
250   if (Options.ShowInstantiationSummary) {
251     OS << format("%*u", FileReportColumns[7],
252                  (unsigned)File.InstantiationCoverage.getNumFunctions());
253     OS << format("%*u", FileReportColumns[8],
254                  (unsigned)(File.InstantiationCoverage.getNumFunctions() -
255                             File.InstantiationCoverage.getExecuted()));
256     if (File.InstantiationCoverage.getNumFunctions())
257       Options.colored_ostream(OS, InstantiationCoverageColor)
258           << format("%*.2f", FileReportColumns[9] - 1,
259                     File.InstantiationCoverage.getPercentCovered())
260           << '%';
261     else
262       OS << column("-", FileReportColumns[9], Column::RightAlignment);
263   }
264 
265   OS << format("%*u", FileReportColumns[10],
266                (unsigned)File.LineCoverage.getNumLines());
267   Options.colored_ostream(OS, LineCoverageColor) << format(
268       "%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() -
269                                                File.LineCoverage.getCovered()));
270   if (File.LineCoverage.getNumLines())
271     Options.colored_ostream(OS, LineCoverageColor)
272         << format("%*.2f", FileReportColumns[12] - 1,
273                   File.LineCoverage.getPercentCovered())
274         << '%';
275   else
276     OS << column("-", FileReportColumns[12], Column::RightAlignment);
277 
278   if (Options.ShowBranchSummary) {
279     OS << format("%*u", FileReportColumns[13],
280                  (unsigned)File.BranchCoverage.getNumBranches());
281     Options.colored_ostream(OS, LineCoverageColor)
282         << format("%*u", FileReportColumns[14],
283                   (unsigned)(File.BranchCoverage.getNumBranches() -
284                              File.BranchCoverage.getCovered()));
285     if (File.BranchCoverage.getNumBranches())
286       Options.colored_ostream(OS, LineCoverageColor)
287           << format("%*.2f", FileReportColumns[15] - 1,
288                     File.BranchCoverage.getPercentCovered())
289           << '%';
290     else
291       OS << column("-", FileReportColumns[15], Column::RightAlignment);
292   }
293 
294   if (Options.ShowMCDCSummary) {
295     OS << format("%*u", FileReportColumns[16],
296                  (unsigned)File.MCDCCoverage.getNumPairs());
297     Options.colored_ostream(OS, LineCoverageColor)
298         << format("%*u", FileReportColumns[17],
299                   (unsigned)(File.MCDCCoverage.getNumPairs() -
300                              File.MCDCCoverage.getCoveredPairs()));
301     if (File.MCDCCoverage.getNumPairs())
302       Options.colored_ostream(OS, LineCoverageColor)
303           << format("%*.2f", FileReportColumns[18] - 1,
304                     File.MCDCCoverage.getPercentCovered())
305           << '%';
306     else
307       OS << column("-", FileReportColumns[18], Column::RightAlignment);
308   }
309 
310   OS << "\n";
311 }
312 
313 void CoverageReport::render(const FunctionCoverageSummary &Function,
314                             const DemangleCache &DC,
315                             raw_ostream &OS) const {
316   auto FuncCoverageColor =
317       determineCoveragePercentageColor(Function.RegionCoverage);
318   auto LineCoverageColor =
319       determineCoveragePercentageColor(Function.LineCoverage);
320   OS << column(DC.demangle(Function.Name), FunctionReportColumns[0],
321                Column::RightTrim)
322      << format("%*u", FunctionReportColumns[1],
323                (unsigned)Function.RegionCoverage.getNumRegions());
324   Options.colored_ostream(OS, FuncCoverageColor)
325       << format("%*u", FunctionReportColumns[2],
326                 (unsigned)(Function.RegionCoverage.getNumRegions() -
327                            Function.RegionCoverage.getCovered()));
328   Options.colored_ostream(
329       OS, determineCoveragePercentageColor(Function.RegionCoverage))
330       << format("%*.2f", FunctionReportColumns[3] - 1,
331                 Function.RegionCoverage.getPercentCovered())
332       << '%';
333   OS << format("%*u", FunctionReportColumns[4],
334                (unsigned)Function.LineCoverage.getNumLines());
335   Options.colored_ostream(OS, LineCoverageColor)
336       << format("%*u", FunctionReportColumns[5],
337                 (unsigned)(Function.LineCoverage.getNumLines() -
338                            Function.LineCoverage.getCovered()));
339   Options.colored_ostream(
340       OS, determineCoveragePercentageColor(Function.LineCoverage))
341       << format("%*.2f", FunctionReportColumns[6] - 1,
342                 Function.LineCoverage.getPercentCovered())
343       << '%';
344   if (Options.ShowBranchSummary) {
345     OS << format("%*u", FunctionReportColumns[7],
346                  (unsigned)Function.BranchCoverage.getNumBranches());
347     Options.colored_ostream(OS, LineCoverageColor)
348         << format("%*u", FunctionReportColumns[8],
349                   (unsigned)(Function.BranchCoverage.getNumBranches() -
350                              Function.BranchCoverage.getCovered()));
351     Options.colored_ostream(
352         OS, determineCoveragePercentageColor(Function.BranchCoverage))
353         << format("%*.2f", FunctionReportColumns[9] - 1,
354                   Function.BranchCoverage.getPercentCovered())
355         << '%';
356   }
357   if (Options.ShowMCDCSummary) {
358     OS << format("%*u", FunctionReportColumns[10],
359                  (unsigned)Function.MCDCCoverage.getNumPairs());
360     Options.colored_ostream(OS, LineCoverageColor)
361         << format("%*u", FunctionReportColumns[11],
362                   (unsigned)(Function.MCDCCoverage.getNumPairs() -
363                              Function.MCDCCoverage.getCoveredPairs()));
364     Options.colored_ostream(
365         OS, determineCoveragePercentageColor(Function.MCDCCoverage))
366         << format("%*.2f", FunctionReportColumns[12] - 1,
367                   Function.MCDCCoverage.getPercentCovered())
368         << '%';
369   }
370   OS << "\n";
371 }
372 
373 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
374                                            const DemangleCache &DC,
375                                            raw_ostream &OS) {
376   bool isFirst = true;
377   for (StringRef Filename : Files) {
378     auto Functions = Coverage.getCoveredFunctions(Filename);
379 
380     if (isFirst)
381       isFirst = false;
382     else
383       OS << "\n";
384 
385     std::vector<StringRef> Funcnames;
386     for (const auto &F : Functions)
387       Funcnames.emplace_back(DC.demangle(F.Name));
388     adjustColumnWidths({}, Funcnames);
389 
390     OS << "File '" << Filename << "':\n";
391     OS << column("Name", FunctionReportColumns[0])
392        << column("Regions", FunctionReportColumns[1], Column::RightAlignment)
393        << column("Miss", FunctionReportColumns[2], Column::RightAlignment)
394        << column("Cover", FunctionReportColumns[3], Column::RightAlignment)
395        << column("Lines", FunctionReportColumns[4], Column::RightAlignment)
396        << column("Miss", FunctionReportColumns[5], Column::RightAlignment)
397        << column("Cover", FunctionReportColumns[6], Column::RightAlignment);
398     if (Options.ShowBranchSummary)
399       OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment)
400          << column("Miss", FunctionReportColumns[8], Column::RightAlignment)
401          << column("Cover", FunctionReportColumns[9], Column::RightAlignment);
402     if (Options.ShowMCDCSummary)
403       OS << column("MC/DC Conditions", FunctionReportColumns[10],
404                    Column::RightAlignment)
405          << column("Miss", FunctionReportColumns[11], Column::RightAlignment)
406          << column("Cover", FunctionReportColumns[12], Column::RightAlignment);
407     OS << "\n";
408     renderDivider(FunctionReportColumns, OS);
409     OS << "\n";
410     FunctionCoverageSummary Totals("TOTAL");
411     for (const auto &F : Functions) {
412       auto Function = FunctionCoverageSummary::get(Coverage, F);
413       ++Totals.ExecutionCount;
414       Totals.RegionCoverage += Function.RegionCoverage;
415       Totals.LineCoverage += Function.LineCoverage;
416       Totals.BranchCoverage += Function.BranchCoverage;
417       Totals.MCDCCoverage += Function.MCDCCoverage;
418       render(Function, DC, OS);
419     }
420     if (Totals.ExecutionCount) {
421       renderDivider(FunctionReportColumns, OS);
422       OS << "\n";
423       render(Totals, DC, OS);
424     }
425   }
426 }
427 
428 void CoverageReport::prepareSingleFileReport(const StringRef Filename,
429     const coverage::CoverageMapping *Coverage,
430     const CoverageViewOptions &Options, const unsigned LCP,
431     FileCoverageSummary *FileReport, const CoverageFilter *Filters) {
432   for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {
433     std::vector<FunctionCoverageSummary> InstantiationSummaries;
434     for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
435       if (!Filters->matches(*Coverage, *F))
436         continue;
437       auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);
438       FileReport->addInstantiation(InstantiationSummary);
439       InstantiationSummaries.push_back(InstantiationSummary);
440     }
441     if (InstantiationSummaries.empty())
442       continue;
443 
444     auto GroupSummary =
445         FunctionCoverageSummary::get(Group, InstantiationSummaries);
446 
447     if (Options.Debug)
448       outs() << "InstantiationGroup: " << GroupSummary.Name << " with "
449              << "size = " << Group.size() << "\n";
450 
451     FileReport->addFunction(GroupSummary);
452   }
453 }
454 
455 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(
456     const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals,
457     ArrayRef<std::string> Files, const CoverageViewOptions &Options,
458     const CoverageFilter &Filters) {
459   unsigned LCP = getRedundantPrefixLen(Files);
460 
461   ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);
462   if (Options.NumThreads == 0) {
463     // If NumThreads is not specified, create one thread for each input, up to
464     // the number of hardware cores.
465     S = heavyweight_hardware_concurrency(Files.size());
466     S.Limit = true;
467   }
468   ThreadPool Pool(S);
469 
470   std::vector<FileCoverageSummary> FileReports;
471   FileReports.reserve(Files.size());
472 
473   for (StringRef Filename : Files) {
474     FileReports.emplace_back(Filename.drop_front(LCP));
475     Pool.async(&CoverageReport::prepareSingleFileReport, Filename,
476                &Coverage, Options, LCP, &FileReports.back(), &Filters);
477   }
478   Pool.wait();
479 
480   for (const auto &FileReport : FileReports)
481     Totals += FileReport;
482 
483   return FileReports;
484 }
485 
486 void CoverageReport::renderFileReports(
487     raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const {
488   std::vector<std::string> UniqueSourceFiles;
489   for (StringRef SF : Coverage.getUniqueSourceFiles()) {
490     // Apply ignore source files filters.
491     if (!IgnoreFilenameFilters.matchesFilename(SF))
492       UniqueSourceFiles.emplace_back(SF.str());
493   }
494   renderFileReports(OS, UniqueSourceFiles);
495 }
496 
497 void CoverageReport::renderFileReports(
498     raw_ostream &OS, ArrayRef<std::string> Files) const {
499   renderFileReports(OS, Files, CoverageFiltersMatchAll());
500 }
501 
502 void CoverageReport::renderFileReports(
503     raw_ostream &OS, ArrayRef<std::string> Files,
504     const CoverageFiltersMatchAll &Filters) const {
505   FileCoverageSummary Totals("TOTAL");
506   auto FileReports =
507       prepareFileReports(Coverage, Totals, Files, Options, Filters);
508   renderFileReports(OS, FileReports, Totals, Filters.empty());
509 }
510 
511 void CoverageReport::renderFileReports(
512     raw_ostream &OS, const std::vector<FileCoverageSummary> &FileReports,
513     const FileCoverageSummary &Totals, bool ShowEmptyFiles) const {
514   std::vector<StringRef> Filenames;
515   Filenames.reserve(FileReports.size());
516   for (const FileCoverageSummary &FCS : FileReports)
517     Filenames.emplace_back(FCS.Name);
518   adjustColumnWidths(Filenames, {});
519 
520   OS << column("Filename", FileReportColumns[0]);
521   if (Options.ShowRegionSummary)
522     OS << column("Regions", FileReportColumns[1], Column::RightAlignment)
523        << column("Missed Regions", FileReportColumns[2], Column::RightAlignment)
524        << column("Cover", FileReportColumns[3], Column::RightAlignment);
525   OS << column("Functions", FileReportColumns[4], Column::RightAlignment)
526      << column("Missed Functions", FileReportColumns[5], Column::RightAlignment)
527      << column("Executed", FileReportColumns[6], Column::RightAlignment);
528   if (Options.ShowInstantiationSummary)
529     OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment)
530        << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment)
531        << column("Executed", FileReportColumns[9], Column::RightAlignment);
532   OS << column("Lines", FileReportColumns[10], Column::RightAlignment)
533      << column("Missed Lines", FileReportColumns[11], Column::RightAlignment)
534      << column("Cover", FileReportColumns[12], Column::RightAlignment);
535   if (Options.ShowBranchSummary)
536     OS << column("Branches", FileReportColumns[13], Column::RightAlignment)
537        << column("Missed Branches", FileReportColumns[14],
538                  Column::RightAlignment)
539        << column("Cover", FileReportColumns[15], Column::RightAlignment);
540   if (Options.ShowMCDCSummary)
541     OS << column("MC/DC Conditions", FileReportColumns[16],
542                  Column::RightAlignment)
543        << column("Missed Conditions", FileReportColumns[17],
544                  Column::RightAlignment)
545        << column("Cover", FileReportColumns[18], Column::RightAlignment);
546   OS << "\n";
547   renderDivider(FileReportColumns, OS);
548   OS << "\n";
549 
550   std::vector<const FileCoverageSummary *> EmptyFiles;
551   for (const FileCoverageSummary &FCS : FileReports) {
552     if (FCS.FunctionCoverage.getNumFunctions())
553       render(FCS, OS);
554     else
555       EmptyFiles.push_back(&FCS);
556   }
557 
558   if (!EmptyFiles.empty() && ShowEmptyFiles) {
559     OS << "\n"
560        << "Files which contain no functions:\n";
561 
562     for (auto FCS : EmptyFiles)
563       render(*FCS, OS);
564   }
565 
566   renderDivider(FileReportColumns, OS);
567   OS << "\n";
568   render(Totals, OS);
569 }
570 
571 Expected<FileCoverageSummary> DirectoryCoverageReport::prepareDirectoryReports(
572     ArrayRef<std::string> SourceFiles) {
573   std::vector<StringRef> Files(SourceFiles.begin(), SourceFiles.end());
574 
575   unsigned RootLCP = getRedundantPrefixLen(Files, 0);
576   auto LCPath = Files.front().substr(0, RootLCP);
577 
578   ThreadPoolStrategy PoolS = hardware_concurrency(Options.NumThreads);
579   if (Options.NumThreads == 0) {
580     PoolS = heavyweight_hardware_concurrency(Files.size());
581     PoolS.Limit = true;
582   }
583   ThreadPool Pool(PoolS);
584 
585   TPool = &Pool;
586   LCPStack = {RootLCP};
587   FileCoverageSummary RootTotals(LCPath);
588   if (auto E = prepareSubDirectoryReports(Files, &RootTotals))
589     return {std::move(E)};
590   return {std::move(RootTotals)};
591 }
592 
593 /// Filter out files in LCPStack.back(), group others by subdirectory name
594 /// and recurse on them. After returning from all subdirectories, call
595 /// generateSubDirectoryReport(). \p Files must be non-empty. The
596 /// FileCoverageSummary of this directory will be added to \p Totals.
597 Error DirectoryCoverageReport::prepareSubDirectoryReports(
598     const ArrayRef<StringRef> &Files, FileCoverageSummary *Totals) {
599   assert(!Files.empty() && "Files must have at least one element");
600 
601   auto LCP = LCPStack.back();
602   auto LCPath = Files.front().substr(0, LCP).str();
603 
604   // Use ordered map to keep entries in order.
605   SubFileReports SubFiles;
606   SubDirReports SubDirs;
607   for (auto &&File : Files) {
608     auto SubPath = File.substr(LCPath.size());
609     SmallVector<char, 128> NativeSubPath;
610     sys::path::native(SubPath, NativeSubPath);
611     StringRef NativeSubPathRef(NativeSubPath.data(), NativeSubPath.size());
612 
613     auto I = sys::path::begin(NativeSubPathRef);
614     auto E = sys::path::end(NativeSubPathRef);
615     assert(I != E && "Such case should have been filtered out in the caller");
616 
617     auto Name = SubPath.substr(0, I->size());
618     if (++I == E) {
619       auto Iter = SubFiles.insert_or_assign(Name, SubPath).first;
620       // Makes files reporting overlap with subdir reporting.
621       TPool->async(&CoverageReport::prepareSingleFileReport, File, &Coverage,
622                    Options, LCP, &Iter->second, &Filters);
623     } else {
624       SubDirs[Name].second.push_back(File);
625     }
626   }
627 
628   // Call recursively on subdirectories.
629   for (auto &&KV : SubDirs) {
630     auto &V = KV.second;
631     if (V.second.size() == 1) {
632       // If there's only one file in that subdirectory, we don't bother to
633       // recurse on it further.
634       V.first.Name = V.second.front().substr(LCP);
635       TPool->async(&CoverageReport::prepareSingleFileReport, V.second.front(),
636                    &Coverage, Options, LCP, &V.first, &Filters);
637     } else {
638       auto SubDirLCP = getRedundantPrefixLen(V.second, LCP);
639       V.first.Name = V.second.front().substr(LCP, SubDirLCP);
640       LCPStack.push_back(LCP + SubDirLCP);
641       if (auto E = prepareSubDirectoryReports(V.second, &V.first))
642         return E;
643     }
644   }
645 
646   TPool->wait();
647 
648   FileCoverageSummary CurrentTotals(LCPath);
649   for (auto &&KV : SubFiles)
650     CurrentTotals += KV.second;
651   for (auto &&KV : SubDirs)
652     CurrentTotals += KV.second.first;
653   *Totals += CurrentTotals;
654 
655   if (auto E = generateSubDirectoryReport(
656           std::move(SubFiles), std::move(SubDirs), std::move(CurrentTotals)))
657     return E;
658 
659   LCPStack.pop_back();
660   return Error::success();
661 }
662 
663 } // end namespace llvm
664