xref: /llvm-project/llvm/tools/llvm-cov/CoverageReport.cpp (revision cc4ecfd68b79a44f101fe9924d088a83477797c0)
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,
90                               16, 10, 12, 18, 10, 12, 18, 10};
91 size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 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   OS << "\n";
295 }
296 
297 void CoverageReport::render(const FunctionCoverageSummary &Function,
298                             const DemangleCache &DC,
299                             raw_ostream &OS) const {
300   auto FuncCoverageColor =
301       determineCoveragePercentageColor(Function.RegionCoverage);
302   auto LineCoverageColor =
303       determineCoveragePercentageColor(Function.LineCoverage);
304   OS << column(DC.demangle(Function.Name), FunctionReportColumns[0],
305                Column::RightTrim)
306      << format("%*u", FunctionReportColumns[1],
307                (unsigned)Function.RegionCoverage.getNumRegions());
308   Options.colored_ostream(OS, FuncCoverageColor)
309       << format("%*u", FunctionReportColumns[2],
310                 (unsigned)(Function.RegionCoverage.getNumRegions() -
311                            Function.RegionCoverage.getCovered()));
312   Options.colored_ostream(
313       OS, determineCoveragePercentageColor(Function.RegionCoverage))
314       << format("%*.2f", FunctionReportColumns[3] - 1,
315                 Function.RegionCoverage.getPercentCovered())
316       << '%';
317   OS << format("%*u", FunctionReportColumns[4],
318                (unsigned)Function.LineCoverage.getNumLines());
319   Options.colored_ostream(OS, LineCoverageColor)
320       << format("%*u", FunctionReportColumns[5],
321                 (unsigned)(Function.LineCoverage.getNumLines() -
322                            Function.LineCoverage.getCovered()));
323   Options.colored_ostream(
324       OS, determineCoveragePercentageColor(Function.LineCoverage))
325       << format("%*.2f", FunctionReportColumns[6] - 1,
326                 Function.LineCoverage.getPercentCovered())
327       << '%';
328   if (Options.ShowBranchSummary) {
329     OS << format("%*u", FunctionReportColumns[7],
330                  (unsigned)Function.BranchCoverage.getNumBranches());
331     Options.colored_ostream(OS, LineCoverageColor)
332         << format("%*u", FunctionReportColumns[8],
333                   (unsigned)(Function.BranchCoverage.getNumBranches() -
334                              Function.BranchCoverage.getCovered()));
335     Options.colored_ostream(
336         OS, determineCoveragePercentageColor(Function.BranchCoverage))
337         << format("%*.2f", FunctionReportColumns[9] - 1,
338                   Function.BranchCoverage.getPercentCovered())
339         << '%';
340   }
341   OS << "\n";
342 }
343 
344 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
345                                            const DemangleCache &DC,
346                                            raw_ostream &OS) {
347   bool isFirst = true;
348   for (StringRef Filename : Files) {
349     auto Functions = Coverage.getCoveredFunctions(Filename);
350 
351     if (isFirst)
352       isFirst = false;
353     else
354       OS << "\n";
355 
356     std::vector<StringRef> Funcnames;
357     for (const auto &F : Functions)
358       Funcnames.emplace_back(DC.demangle(F.Name));
359     adjustColumnWidths({}, Funcnames);
360 
361     OS << "File '" << Filename << "':\n";
362     OS << column("Name", FunctionReportColumns[0])
363        << column("Regions", FunctionReportColumns[1], Column::RightAlignment)
364        << column("Miss", FunctionReportColumns[2], Column::RightAlignment)
365        << column("Cover", FunctionReportColumns[3], Column::RightAlignment)
366        << column("Lines", FunctionReportColumns[4], Column::RightAlignment)
367        << column("Miss", FunctionReportColumns[5], Column::RightAlignment)
368        << column("Cover", FunctionReportColumns[6], Column::RightAlignment);
369     if (Options.ShowBranchSummary)
370       OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment)
371          << column("Miss", FunctionReportColumns[8], Column::RightAlignment)
372          << column("Cover", FunctionReportColumns[9], Column::RightAlignment);
373     OS << "\n";
374     renderDivider(FunctionReportColumns, OS);
375     OS << "\n";
376     FunctionCoverageSummary Totals("TOTAL");
377     for (const auto &F : Functions) {
378       auto Function = FunctionCoverageSummary::get(Coverage, F);
379       ++Totals.ExecutionCount;
380       Totals.RegionCoverage += Function.RegionCoverage;
381       Totals.LineCoverage += Function.LineCoverage;
382       Totals.BranchCoverage += Function.BranchCoverage;
383       render(Function, DC, OS);
384     }
385     if (Totals.ExecutionCount) {
386       renderDivider(FunctionReportColumns, OS);
387       OS << "\n";
388       render(Totals, DC, OS);
389     }
390   }
391 }
392 
393 void CoverageReport::prepareSingleFileReport(const StringRef Filename,
394     const coverage::CoverageMapping *Coverage,
395     const CoverageViewOptions &Options, const unsigned LCP,
396     FileCoverageSummary *FileReport, const CoverageFilter *Filters) {
397   for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {
398     std::vector<FunctionCoverageSummary> InstantiationSummaries;
399     for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
400       if (!Filters->matches(*Coverage, *F))
401         continue;
402       auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);
403       FileReport->addInstantiation(InstantiationSummary);
404       InstantiationSummaries.push_back(InstantiationSummary);
405     }
406     if (InstantiationSummaries.empty())
407       continue;
408 
409     auto GroupSummary =
410         FunctionCoverageSummary::get(Group, InstantiationSummaries);
411 
412     if (Options.Debug)
413       outs() << "InstantiationGroup: " << GroupSummary.Name << " with "
414              << "size = " << Group.size() << "\n";
415 
416     FileReport->addFunction(GroupSummary);
417   }
418 }
419 
420 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(
421     const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals,
422     ArrayRef<std::string> Files, const CoverageViewOptions &Options,
423     const CoverageFilter &Filters) {
424   unsigned LCP = getRedundantPrefixLen(Files);
425 
426   ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);
427   if (Options.NumThreads == 0) {
428     // If NumThreads is not specified, create one thread for each input, up to
429     // the number of hardware cores.
430     S = heavyweight_hardware_concurrency(Files.size());
431     S.Limit = true;
432   }
433   ThreadPool Pool(S);
434 
435   std::vector<FileCoverageSummary> FileReports;
436   FileReports.reserve(Files.size());
437 
438   for (StringRef Filename : Files) {
439     FileReports.emplace_back(Filename.drop_front(LCP));
440     Pool.async(&CoverageReport::prepareSingleFileReport, Filename,
441                &Coverage, Options, LCP, &FileReports.back(), &Filters);
442   }
443   Pool.wait();
444 
445   for (const auto &FileReport : FileReports)
446     Totals += FileReport;
447 
448   return FileReports;
449 }
450 
451 void CoverageReport::renderFileReports(
452     raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const {
453   std::vector<std::string> UniqueSourceFiles;
454   for (StringRef SF : Coverage.getUniqueSourceFiles()) {
455     // Apply ignore source files filters.
456     if (!IgnoreFilenameFilters.matchesFilename(SF))
457       UniqueSourceFiles.emplace_back(SF.str());
458   }
459   renderFileReports(OS, UniqueSourceFiles);
460 }
461 
462 void CoverageReport::renderFileReports(
463     raw_ostream &OS, ArrayRef<std::string> Files) const {
464   renderFileReports(OS, Files, CoverageFiltersMatchAll());
465 }
466 
467 void CoverageReport::renderFileReports(
468     raw_ostream &OS, ArrayRef<std::string> Files,
469     const CoverageFiltersMatchAll &Filters) const {
470   FileCoverageSummary Totals("TOTAL");
471   auto FileReports =
472       prepareFileReports(Coverage, Totals, Files, Options, Filters);
473   renderFileReports(OS, FileReports, Totals, Filters.empty());
474 }
475 
476 void CoverageReport::renderFileReports(
477     raw_ostream &OS, const std::vector<FileCoverageSummary> &FileReports,
478     const FileCoverageSummary &Totals, bool ShowEmptyFiles) const {
479   std::vector<StringRef> Filenames;
480   Filenames.reserve(FileReports.size());
481   for (const FileCoverageSummary &FCS : FileReports)
482     Filenames.emplace_back(FCS.Name);
483   adjustColumnWidths(Filenames, {});
484 
485   OS << column("Filename", FileReportColumns[0]);
486   if (Options.ShowRegionSummary)
487     OS << column("Regions", FileReportColumns[1], Column::RightAlignment)
488        << column("Missed Regions", FileReportColumns[2], Column::RightAlignment)
489        << column("Cover", FileReportColumns[3], Column::RightAlignment);
490   OS << column("Functions", FileReportColumns[4], Column::RightAlignment)
491      << column("Missed Functions", FileReportColumns[5], Column::RightAlignment)
492      << column("Executed", FileReportColumns[6], Column::RightAlignment);
493   if (Options.ShowInstantiationSummary)
494     OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment)
495        << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment)
496        << column("Executed", FileReportColumns[9], Column::RightAlignment);
497   OS << column("Lines", FileReportColumns[10], Column::RightAlignment)
498      << column("Missed Lines", FileReportColumns[11], Column::RightAlignment)
499      << column("Cover", FileReportColumns[12], Column::RightAlignment);
500   if (Options.ShowBranchSummary)
501     OS << column("Branches", FileReportColumns[13], Column::RightAlignment)
502        << column("Missed Branches", FileReportColumns[14],
503                  Column::RightAlignment)
504        << column("Cover", FileReportColumns[15], Column::RightAlignment);
505   OS << "\n";
506   renderDivider(FileReportColumns, OS);
507   OS << "\n";
508 
509   std::vector<const FileCoverageSummary *> EmptyFiles;
510   for (const FileCoverageSummary &FCS : FileReports) {
511     if (FCS.FunctionCoverage.getNumFunctions())
512       render(FCS, OS);
513     else
514       EmptyFiles.push_back(&FCS);
515   }
516 
517   if (!EmptyFiles.empty() && ShowEmptyFiles) {
518     OS << "\n"
519        << "Files which contain no functions:\n";
520 
521     for (auto FCS : EmptyFiles)
522       render(*FCS, OS);
523   }
524 
525   renderDivider(FileReportColumns, OS);
526   OS << "\n";
527   render(Totals, OS);
528 }
529 
530 Expected<FileCoverageSummary> DirectoryCoverageReport::prepareDirectoryReports(
531     ArrayRef<std::string> SourceFiles) {
532   std::vector<StringRef> Files(SourceFiles.begin(), SourceFiles.end());
533 
534   unsigned RootLCP = getRedundantPrefixLen(Files, 0);
535   auto LCPath = Files.front().substr(0, RootLCP);
536 
537   ThreadPoolStrategy PoolS = hardware_concurrency(Options.NumThreads);
538   if (Options.NumThreads == 0) {
539     PoolS = heavyweight_hardware_concurrency(Files.size());
540     PoolS.Limit = true;
541   }
542   ThreadPool Pool(PoolS);
543 
544   TPool = &Pool;
545   LCPStack = {RootLCP};
546   FileCoverageSummary RootTotals(LCPath);
547   if (auto E = prepareSubDirectoryReports(Files, &RootTotals))
548     return {std::move(E)};
549   return {std::move(RootTotals)};
550 }
551 
552 /// Filter out files in LCPStack.back(), group others by subdirectory name
553 /// and recurse on them. After returning from all subdirectories, call
554 /// generateSubDirectoryReport(). \p Files must be non-empty. The
555 /// FileCoverageSummary of this directory will be added to \p Totals.
556 Error DirectoryCoverageReport::prepareSubDirectoryReports(
557     const ArrayRef<StringRef> &Files, FileCoverageSummary *Totals) {
558   assert(!Files.empty() && "Files must have at least one element");
559 
560   auto LCP = LCPStack.back();
561   auto LCPath = Files.front().substr(0, LCP).str();
562 
563   // Use ordered map to keep entries in order.
564   SubFileReports SubFiles;
565   SubDirReports SubDirs;
566   for (auto &&File : Files) {
567     auto SubPath = File.substr(LCPath.size());
568     SmallVector<char, 128> NativeSubPath;
569     sys::path::native(SubPath, NativeSubPath);
570     StringRef NativeSubPathRef(NativeSubPath.data(), NativeSubPath.size());
571 
572     auto I = sys::path::begin(NativeSubPathRef);
573     auto E = sys::path::end(NativeSubPathRef);
574     assert(I != E && "Such case should have been filtered out in the caller");
575 
576     auto Name = SubPath.substr(0, I->size());
577     if (++I == E) {
578       auto Iter = SubFiles.insert_or_assign(Name, SubPath).first;
579       // Makes files reporting overlap with subdir reporting.
580       TPool->async(&CoverageReport::prepareSingleFileReport, File, &Coverage,
581                    Options, LCP, &Iter->second, &Filters);
582     } else {
583       SubDirs[Name].second.push_back(File);
584     }
585   }
586 
587   // Call recursively on subdirectories.
588   for (auto &&KV : SubDirs) {
589     auto &V = KV.second;
590     if (V.second.size() == 1) {
591       // If there's only one file in that subdirectory, we don't bother to
592       // recurse on it further.
593       V.first.Name = V.second.front().substr(LCP);
594       TPool->async(&CoverageReport::prepareSingleFileReport, V.second.front(),
595                    &Coverage, Options, LCP, &V.first, &Filters);
596     } else {
597       auto SubDirLCP = getRedundantPrefixLen(V.second, LCP);
598       V.first.Name = V.second.front().substr(LCP, SubDirLCP);
599       LCPStack.push_back(LCP + SubDirLCP);
600       if (auto E = prepareSubDirectoryReports(V.second, &V.first))
601         return E;
602     }
603   }
604 
605   TPool->wait();
606 
607   FileCoverageSummary CurrentTotals(LCPath);
608   for (auto &&KV : SubFiles)
609     CurrentTotals += KV.second;
610   for (auto &&KV : SubDirs)
611     CurrentTotals += KV.second.first;
612   *Totals += CurrentTotals;
613 
614   if (auto E = generateSubDirectoryReport(
615           std::move(SubFiles), std::move(SubDirs), std::move(CurrentTotals)))
616     return E;
617 
618   LCPStack.pop_back();
619   return Error::success();
620 }
621 
622 } // end namespace llvm
623