xref: /llvm-project/llvm/tools/llvm-cov/CoverageReport.cpp (revision 618a22144db5e45da8c95dc22064103e1b5e5b71)
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, 16, 10,
91                               12, 18, 10, 12, 18, 10, 20, 21, 10};
92 size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8, 20, 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 /// Determine the length of the longest redundant prefix of the substrs starts
170 /// from \p LCP in \p Paths. \p Paths can't be empty. If there's only one
171 /// element in \p Paths, the length of the substr is returned. Note this is
172 /// differnet from the behavior of the function above.
173 unsigned getRedundantPrefixLen(ArrayRef<StringRef> Paths, unsigned LCP) {
174   assert(!Paths.empty() && "Paths must have at least one element");
175 
176   auto Iter = Paths.begin();
177   auto IterE = Paths.end();
178   auto Prefix = Iter->substr(LCP);
179   while (++Iter != IterE) {
180     auto Other = Iter->substr(LCP);
181     auto Len = std::min(Prefix.size(), Other.size());
182     for (std::size_t I = 0; I < Len; ++I) {
183       if (Prefix[I] != Other[I]) {
184         Prefix = Prefix.substr(0, I);
185         break;
186       }
187     }
188   }
189 
190   for (auto I = Prefix.size(); --I != SIZE_MAX;) {
191     if (Prefix[I] == '/' || Prefix[I] == '\\')
192       return I + 1;
193   }
194 
195   return Prefix.size();
196 }
197 
198 } // end anonymous namespace
199 
200 namespace llvm {
201 
202 void CoverageReport::render(const FileCoverageSummary &File,
203                             raw_ostream &OS) const {
204   auto FileCoverageColor =
205       determineCoveragePercentageColor(File.RegionCoverage);
206   auto FuncCoverageColor =
207       determineCoveragePercentageColor(File.FunctionCoverage);
208   auto InstantiationCoverageColor =
209       determineCoveragePercentageColor(File.InstantiationCoverage);
210   auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage);
211   SmallString<256> FileName = File.Name;
212   sys::path::native(FileName);
213 
214   // remove_dots will remove trailing slash, so we need to check before it.
215   auto IsDir = FileName.endswith(sys::path::get_separator());
216   sys::path::remove_dots(FileName, /*remove_dot_dot=*/true);
217   if (IsDir)
218     FileName += sys::path::get_separator();
219 
220   OS << column(FileName, FileReportColumns[0], Column::NoTrim);
221 
222   if (Options.ShowRegionSummary) {
223     OS << format("%*u", FileReportColumns[1],
224                  (unsigned)File.RegionCoverage.getNumRegions());
225     Options.colored_ostream(OS, FileCoverageColor)
226         << format("%*u", FileReportColumns[2],
227                   (unsigned)(File.RegionCoverage.getNumRegions() -
228                              File.RegionCoverage.getCovered()));
229     if (File.RegionCoverage.getNumRegions())
230       Options.colored_ostream(OS, FileCoverageColor)
231           << format("%*.2f", FileReportColumns[3] - 1,
232                     File.RegionCoverage.getPercentCovered())
233           << '%';
234     else
235       OS << column("-", FileReportColumns[3], Column::RightAlignment);
236   }
237 
238   OS << format("%*u", FileReportColumns[4],
239                (unsigned)File.FunctionCoverage.getNumFunctions());
240   OS << format("%*u", FileReportColumns[5],
241                (unsigned)(File.FunctionCoverage.getNumFunctions() -
242                           File.FunctionCoverage.getExecuted()));
243   if (File.FunctionCoverage.getNumFunctions())
244     Options.colored_ostream(OS, FuncCoverageColor)
245         << format("%*.2f", FileReportColumns[6] - 1,
246                   File.FunctionCoverage.getPercentCovered())
247         << '%';
248   else
249     OS << column("-", FileReportColumns[6], Column::RightAlignment);
250 
251   if (Options.ShowInstantiationSummary) {
252     OS << format("%*u", FileReportColumns[7],
253                  (unsigned)File.InstantiationCoverage.getNumFunctions());
254     OS << format("%*u", FileReportColumns[8],
255                  (unsigned)(File.InstantiationCoverage.getNumFunctions() -
256                             File.InstantiationCoverage.getExecuted()));
257     if (File.InstantiationCoverage.getNumFunctions())
258       Options.colored_ostream(OS, InstantiationCoverageColor)
259           << format("%*.2f", FileReportColumns[9] - 1,
260                     File.InstantiationCoverage.getPercentCovered())
261           << '%';
262     else
263       OS << column("-", FileReportColumns[9], Column::RightAlignment);
264   }
265 
266   OS << format("%*u", FileReportColumns[10],
267                (unsigned)File.LineCoverage.getNumLines());
268   Options.colored_ostream(OS, LineCoverageColor) << format(
269       "%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() -
270                                                File.LineCoverage.getCovered()));
271   if (File.LineCoverage.getNumLines())
272     Options.colored_ostream(OS, LineCoverageColor)
273         << format("%*.2f", FileReportColumns[12] - 1,
274                   File.LineCoverage.getPercentCovered())
275         << '%';
276   else
277     OS << column("-", FileReportColumns[12], Column::RightAlignment);
278 
279   if (Options.ShowBranchSummary) {
280     OS << format("%*u", FileReportColumns[13],
281                  (unsigned)File.BranchCoverage.getNumBranches());
282     Options.colored_ostream(OS, LineCoverageColor)
283         << format("%*u", FileReportColumns[14],
284                   (unsigned)(File.BranchCoverage.getNumBranches() -
285                              File.BranchCoverage.getCovered()));
286     if (File.BranchCoverage.getNumBranches())
287       Options.colored_ostream(OS, LineCoverageColor)
288           << format("%*.2f", FileReportColumns[15] - 1,
289                     File.BranchCoverage.getPercentCovered())
290           << '%';
291     else
292       OS << column("-", FileReportColumns[15], Column::RightAlignment);
293   }
294 
295   if (Options.ShowMCDCSummary) {
296     OS << format("%*u", FileReportColumns[16],
297                  (unsigned)File.MCDCCoverage.getNumPairs());
298     Options.colored_ostream(OS, LineCoverageColor)
299         << format("%*u", FileReportColumns[17],
300                   (unsigned)(File.MCDCCoverage.getNumPairs() -
301                              File.MCDCCoverage.getCoveredPairs()));
302     if (File.MCDCCoverage.getNumPairs())
303       Options.colored_ostream(OS, LineCoverageColor)
304           << format("%*.2f", FileReportColumns[18] - 1,
305                     File.MCDCCoverage.getPercentCovered())
306           << '%';
307     else
308       OS << column("-", FileReportColumns[18], Column::RightAlignment);
309   }
310 
311   OS << "\n";
312 }
313 
314 void CoverageReport::render(const FunctionCoverageSummary &Function,
315                             const DemangleCache &DC,
316                             raw_ostream &OS) const {
317   auto FuncCoverageColor =
318       determineCoveragePercentageColor(Function.RegionCoverage);
319   auto LineCoverageColor =
320       determineCoveragePercentageColor(Function.LineCoverage);
321   OS << column(DC.demangle(Function.Name), FunctionReportColumns[0],
322                Column::RightTrim)
323      << format("%*u", FunctionReportColumns[1],
324                (unsigned)Function.RegionCoverage.getNumRegions());
325   Options.colored_ostream(OS, FuncCoverageColor)
326       << format("%*u", FunctionReportColumns[2],
327                 (unsigned)(Function.RegionCoverage.getNumRegions() -
328                            Function.RegionCoverage.getCovered()));
329   Options.colored_ostream(
330       OS, determineCoveragePercentageColor(Function.RegionCoverage))
331       << format("%*.2f", FunctionReportColumns[3] - 1,
332                 Function.RegionCoverage.getPercentCovered())
333       << '%';
334   OS << format("%*u", FunctionReportColumns[4],
335                (unsigned)Function.LineCoverage.getNumLines());
336   Options.colored_ostream(OS, LineCoverageColor)
337       << format("%*u", FunctionReportColumns[5],
338                 (unsigned)(Function.LineCoverage.getNumLines() -
339                            Function.LineCoverage.getCovered()));
340   Options.colored_ostream(
341       OS, determineCoveragePercentageColor(Function.LineCoverage))
342       << format("%*.2f", FunctionReportColumns[6] - 1,
343                 Function.LineCoverage.getPercentCovered())
344       << '%';
345   if (Options.ShowBranchSummary) {
346     OS << format("%*u", FunctionReportColumns[7],
347                  (unsigned)Function.BranchCoverage.getNumBranches());
348     Options.colored_ostream(OS, LineCoverageColor)
349         << format("%*u", FunctionReportColumns[8],
350                   (unsigned)(Function.BranchCoverage.getNumBranches() -
351                              Function.BranchCoverage.getCovered()));
352     Options.colored_ostream(
353         OS, determineCoveragePercentageColor(Function.BranchCoverage))
354         << format("%*.2f", FunctionReportColumns[9] - 1,
355                   Function.BranchCoverage.getPercentCovered())
356         << '%';
357   }
358   if (Options.ShowMCDCSummary) {
359     OS << format("%*u", FunctionReportColumns[10],
360                  (unsigned)Function.MCDCCoverage.getNumPairs());
361     Options.colored_ostream(OS, LineCoverageColor)
362         << format("%*u", FunctionReportColumns[11],
363                   (unsigned)(Function.MCDCCoverage.getNumPairs() -
364                              Function.MCDCCoverage.getCoveredPairs()));
365     Options.colored_ostream(
366         OS, determineCoveragePercentageColor(Function.MCDCCoverage))
367         << format("%*.2f", FunctionReportColumns[12] - 1,
368                   Function.MCDCCoverage.getPercentCovered())
369         << '%';
370   }
371   OS << "\n";
372 }
373 
374 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
375                                            const DemangleCache &DC,
376                                            raw_ostream &OS) {
377   bool isFirst = true;
378   for (StringRef Filename : Files) {
379     auto Functions = Coverage.getCoveredFunctions(Filename);
380 
381     if (isFirst)
382       isFirst = false;
383     else
384       OS << "\n";
385 
386     std::vector<StringRef> Funcnames;
387     for (const auto &F : Functions)
388       Funcnames.emplace_back(DC.demangle(F.Name));
389     adjustColumnWidths({}, Funcnames);
390 
391     OS << "File '" << Filename << "':\n";
392     OS << column("Name", FunctionReportColumns[0])
393        << column("Regions", FunctionReportColumns[1], Column::RightAlignment)
394        << column("Miss", FunctionReportColumns[2], Column::RightAlignment)
395        << column("Cover", FunctionReportColumns[3], Column::RightAlignment)
396        << column("Lines", FunctionReportColumns[4], Column::RightAlignment)
397        << column("Miss", FunctionReportColumns[5], Column::RightAlignment)
398        << column("Cover", FunctionReportColumns[6], Column::RightAlignment);
399     if (Options.ShowBranchSummary)
400       OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment)
401          << column("Miss", FunctionReportColumns[8], Column::RightAlignment)
402          << column("Cover", FunctionReportColumns[9], Column::RightAlignment);
403     if (Options.ShowMCDCSummary)
404       OS << column("MC/DC Conditions", FunctionReportColumns[10],
405                    Column::RightAlignment)
406          << column("Miss", FunctionReportColumns[11], Column::RightAlignment)
407          << column("Cover", FunctionReportColumns[12], Column::RightAlignment);
408     OS << "\n";
409     renderDivider(FunctionReportColumns, OS);
410     OS << "\n";
411     FunctionCoverageSummary Totals("TOTAL");
412     for (const auto &F : Functions) {
413       auto Function = FunctionCoverageSummary::get(Coverage, F);
414       ++Totals.ExecutionCount;
415       Totals.RegionCoverage += Function.RegionCoverage;
416       Totals.LineCoverage += Function.LineCoverage;
417       Totals.BranchCoverage += Function.BranchCoverage;
418       Totals.MCDCCoverage += Function.MCDCCoverage;
419       render(Function, DC, OS);
420     }
421     if (Totals.ExecutionCount) {
422       renderDivider(FunctionReportColumns, OS);
423       OS << "\n";
424       render(Totals, DC, OS);
425     }
426   }
427 }
428 
429 void CoverageReport::prepareSingleFileReport(const StringRef Filename,
430     const coverage::CoverageMapping *Coverage,
431     const CoverageViewOptions &Options, const unsigned LCP,
432     FileCoverageSummary *FileReport, const CoverageFilter *Filters) {
433   for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {
434     std::vector<FunctionCoverageSummary> InstantiationSummaries;
435     for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
436       if (!Filters->matches(*Coverage, *F))
437         continue;
438       auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);
439       FileReport->addInstantiation(InstantiationSummary);
440       InstantiationSummaries.push_back(InstantiationSummary);
441     }
442     if (InstantiationSummaries.empty())
443       continue;
444 
445     auto GroupSummary =
446         FunctionCoverageSummary::get(Group, InstantiationSummaries);
447 
448     if (Options.Debug)
449       outs() << "InstantiationGroup: " << GroupSummary.Name << " with "
450              << "size = " << Group.size() << "\n";
451 
452     FileReport->addFunction(GroupSummary);
453   }
454 }
455 
456 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(
457     const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals,
458     ArrayRef<std::string> Files, const CoverageViewOptions &Options,
459     const CoverageFilter &Filters) {
460   unsigned LCP = getRedundantPrefixLen(Files);
461 
462   ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);
463   if (Options.NumThreads == 0) {
464     // If NumThreads is not specified, create one thread for each input, up to
465     // the number of hardware cores.
466     S = heavyweight_hardware_concurrency(Files.size());
467     S.Limit = true;
468   }
469   ThreadPool Pool(S);
470 
471   std::vector<FileCoverageSummary> FileReports;
472   FileReports.reserve(Files.size());
473 
474   for (StringRef Filename : Files) {
475     FileReports.emplace_back(Filename.drop_front(LCP));
476     Pool.async(&CoverageReport::prepareSingleFileReport, Filename,
477                &Coverage, Options, LCP, &FileReports.back(), &Filters);
478   }
479   Pool.wait();
480 
481   for (const auto &FileReport : FileReports)
482     Totals += FileReport;
483 
484   return FileReports;
485 }
486 
487 void CoverageReport::renderFileReports(
488     raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const {
489   std::vector<std::string> UniqueSourceFiles;
490   for (StringRef SF : Coverage.getUniqueSourceFiles()) {
491     // Apply ignore source files filters.
492     if (!IgnoreFilenameFilters.matchesFilename(SF))
493       UniqueSourceFiles.emplace_back(SF.str());
494   }
495   renderFileReports(OS, UniqueSourceFiles);
496 }
497 
498 void CoverageReport::renderFileReports(
499     raw_ostream &OS, ArrayRef<std::string> Files) const {
500   renderFileReports(OS, Files, CoverageFiltersMatchAll());
501 }
502 
503 void CoverageReport::renderFileReports(
504     raw_ostream &OS, ArrayRef<std::string> Files,
505     const CoverageFiltersMatchAll &Filters) const {
506   FileCoverageSummary Totals("TOTAL");
507   auto FileReports =
508       prepareFileReports(Coverage, Totals, Files, Options, Filters);
509   renderFileReports(OS, FileReports, Totals, Filters.empty());
510 }
511 
512 void CoverageReport::renderFileReports(
513     raw_ostream &OS, const std::vector<FileCoverageSummary> &FileReports,
514     const FileCoverageSummary &Totals, bool ShowEmptyFiles) const {
515   std::vector<StringRef> Filenames;
516   Filenames.reserve(FileReports.size());
517   for (const FileCoverageSummary &FCS : FileReports)
518     Filenames.emplace_back(FCS.Name);
519   adjustColumnWidths(Filenames, {});
520 
521   OS << column("Filename", FileReportColumns[0]);
522   if (Options.ShowRegionSummary)
523     OS << column("Regions", FileReportColumns[1], Column::RightAlignment)
524        << column("Missed Regions", FileReportColumns[2], Column::RightAlignment)
525        << column("Cover", FileReportColumns[3], Column::RightAlignment);
526   OS << column("Functions", FileReportColumns[4], Column::RightAlignment)
527      << column("Missed Functions", FileReportColumns[5], Column::RightAlignment)
528      << column("Executed", FileReportColumns[6], Column::RightAlignment);
529   if (Options.ShowInstantiationSummary)
530     OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment)
531        << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment)
532        << column("Executed", FileReportColumns[9], Column::RightAlignment);
533   OS << column("Lines", FileReportColumns[10], Column::RightAlignment)
534      << column("Missed Lines", FileReportColumns[11], Column::RightAlignment)
535      << column("Cover", FileReportColumns[12], Column::RightAlignment);
536   if (Options.ShowBranchSummary)
537     OS << column("Branches", FileReportColumns[13], Column::RightAlignment)
538        << column("Missed Branches", FileReportColumns[14],
539                  Column::RightAlignment)
540        << column("Cover", FileReportColumns[15], Column::RightAlignment);
541   if (Options.ShowMCDCSummary)
542     OS << column("MC/DC Conditions", FileReportColumns[16],
543                  Column::RightAlignment)
544        << column("Missed Conditions", FileReportColumns[17],
545                  Column::RightAlignment)
546        << column("Cover", FileReportColumns[18], Column::RightAlignment);
547   OS << "\n";
548   renderDivider(FileReportColumns, OS);
549   OS << "\n";
550 
551   std::vector<const FileCoverageSummary *> EmptyFiles;
552   for (const FileCoverageSummary &FCS : FileReports) {
553     if (FCS.FunctionCoverage.getNumFunctions())
554       render(FCS, OS);
555     else
556       EmptyFiles.push_back(&FCS);
557   }
558 
559   if (!EmptyFiles.empty() && ShowEmptyFiles) {
560     OS << "\n"
561        << "Files which contain no functions:\n";
562 
563     for (auto FCS : EmptyFiles)
564       render(*FCS, OS);
565   }
566 
567   renderDivider(FileReportColumns, OS);
568   OS << "\n";
569   render(Totals, OS);
570 }
571 
572 Expected<FileCoverageSummary> DirectoryCoverageReport::prepareDirectoryReports(
573     ArrayRef<std::string> SourceFiles) {
574   std::vector<StringRef> Files(SourceFiles.begin(), SourceFiles.end());
575 
576   unsigned RootLCP = getRedundantPrefixLen(Files, 0);
577   auto LCPath = Files.front().substr(0, RootLCP);
578 
579   ThreadPoolStrategy PoolS = hardware_concurrency(Options.NumThreads);
580   if (Options.NumThreads == 0) {
581     PoolS = heavyweight_hardware_concurrency(Files.size());
582     PoolS.Limit = true;
583   }
584   ThreadPool Pool(PoolS);
585 
586   TPool = &Pool;
587   LCPStack = {RootLCP};
588   FileCoverageSummary RootTotals(LCPath);
589   if (auto E = prepareSubDirectoryReports(Files, &RootTotals))
590     return {std::move(E)};
591   return {std::move(RootTotals)};
592 }
593 
594 /// Filter out files in LCPStack.back(), group others by subdirectory name
595 /// and recurse on them. After returning from all subdirectories, call
596 /// generateSubDirectoryReport(). \p Files must be non-empty. The
597 /// FileCoverageSummary of this directory will be added to \p Totals.
598 Error DirectoryCoverageReport::prepareSubDirectoryReports(
599     const ArrayRef<StringRef> &Files, FileCoverageSummary *Totals) {
600   assert(!Files.empty() && "Files must have at least one element");
601 
602   auto LCP = LCPStack.back();
603   auto LCPath = Files.front().substr(0, LCP).str();
604 
605   // Use ordered map to keep entries in order.
606   SubFileReports SubFiles;
607   SubDirReports SubDirs;
608   for (auto &&File : Files) {
609     auto SubPath = File.substr(LCPath.size());
610     SmallVector<char, 128> NativeSubPath;
611     sys::path::native(SubPath, NativeSubPath);
612     StringRef NativeSubPathRef(NativeSubPath.data(), NativeSubPath.size());
613 
614     auto I = sys::path::begin(NativeSubPathRef);
615     auto E = sys::path::end(NativeSubPathRef);
616     assert(I != E && "Such case should have been filtered out in the caller");
617 
618     auto Name = SubPath.substr(0, I->size());
619     if (++I == E) {
620       auto Iter = SubFiles.insert_or_assign(Name, SubPath).first;
621       // Makes files reporting overlap with subdir reporting.
622       TPool->async(&CoverageReport::prepareSingleFileReport, File, &Coverage,
623                    Options, LCP, &Iter->second, &Filters);
624     } else {
625       SubDirs[Name].second.push_back(File);
626     }
627   }
628 
629   // Call recursively on subdirectories.
630   for (auto &&KV : SubDirs) {
631     auto &V = KV.second;
632     if (V.second.size() == 1) {
633       // If there's only one file in that subdirectory, we don't bother to
634       // recurse on it further.
635       V.first.Name = V.second.front().substr(LCP);
636       TPool->async(&CoverageReport::prepareSingleFileReport, V.second.front(),
637                    &Coverage, Options, LCP, &V.first, &Filters);
638     } else {
639       auto SubDirLCP = getRedundantPrefixLen(V.second, LCP);
640       V.first.Name = V.second.front().substr(LCP, SubDirLCP);
641       LCPStack.push_back(LCP + SubDirLCP);
642       if (auto E = prepareSubDirectoryReports(V.second, &V.first))
643         return E;
644     }
645   }
646 
647   TPool->wait();
648 
649   FileCoverageSummary CurrentTotals(LCPath);
650   for (auto &&KV : SubFiles)
651     CurrentTotals += KV.second;
652   for (auto &&KV : SubDirs)
653     CurrentTotals += KV.second.first;
654   *Totals += CurrentTotals;
655 
656   if (auto E = generateSubDirectoryReport(
657           std::move(SubFiles), std::move(SubDirs), std::move(CurrentTotals)))
658     return E;
659 
660   LCPStack.pop_back();
661   return Error::success();
662 }
663 
664 } // end namespace llvm
665