Path: blob/main/contrib/llvm-project/llvm/tools/llvm-cov/CoverageReport.cpp
35231 views
//===- CoverageReport.cpp - Code coverage report -------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This class implements rendering of a code coverage report.9//10//===----------------------------------------------------------------------===//1112#include "CoverageReport.h"13#include "RenderingSupport.h"14#include "llvm/ADT/SmallString.h"15#include "llvm/Support/Format.h"16#include "llvm/Support/Path.h"17#include "llvm/Support/ThreadPool.h"18#include "llvm/Support/Threading.h"19#include <numeric>2021using namespace llvm;2223namespace {2425/// Helper struct which prints trimmed and aligned columns.26struct Column {27enum TrimKind { NoTrim, WidthTrim, RightTrim };2829enum AlignmentKind { LeftAlignment, RightAlignment };3031StringRef Str;32unsigned Width;33TrimKind Trim;34AlignmentKind Alignment;3536Column(StringRef Str, unsigned Width)37: Str(Str), Width(Width), Trim(WidthTrim), Alignment(LeftAlignment) {}3839Column &set(TrimKind Value) {40Trim = Value;41return *this;42}4344Column &set(AlignmentKind Value) {45Alignment = Value;46return *this;47}4849void render(raw_ostream &OS) const {50if (Str.size() <= Width) {51if (Alignment == RightAlignment) {52OS.indent(Width - Str.size());53OS << Str;54return;55}56OS << Str;57OS.indent(Width - Str.size());58return;59}6061switch (Trim) {62case NoTrim:63OS << Str;64break;65case WidthTrim:66OS << Str.substr(0, Width);67break;68case RightTrim:69OS << Str.substr(0, Width - 3) << "...";70break;71}72}73};7475raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {76Value.render(OS);77return OS;78}7980Column column(StringRef Str, unsigned Width) { return Column(Str, Width); }8182template <typename T>83Column column(StringRef Str, unsigned Width, const T &Value) {84return Column(Str, Width).set(Value);85}8687// Specify the default column widths.88size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 16, 16, 10,8912, 18, 10, 12, 18, 10, 20, 21, 10};90size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8, 20, 8, 8};9192/// Adjust column widths to fit long file paths and function names.93void adjustColumnWidths(ArrayRef<StringRef> Files,94ArrayRef<StringRef> Functions) {95for (StringRef Filename : Files)96FileReportColumns[0] = std::max(FileReportColumns[0], Filename.size());97for (StringRef Funcname : Functions)98FunctionReportColumns[0] =99std::max(FunctionReportColumns[0], Funcname.size());100}101102/// Prints a horizontal divider long enough to cover the given column103/// widths.104void renderDivider(raw_ostream &OS, const CoverageViewOptions &Options, bool isFileReport) {105size_t Length;106if (isFileReport) {107Length = std::accumulate(std::begin(FileReportColumns), std::end(FileReportColumns), 0);108if (!Options.ShowRegionSummary)109Length -= (FileReportColumns[1] + FileReportColumns[2] + FileReportColumns[3]);110if (!Options.ShowInstantiationSummary)111Length -= (FileReportColumns[7] + FileReportColumns[8] + FileReportColumns[9]);112if (!Options.ShowBranchSummary)113Length -= (FileReportColumns[13] + FileReportColumns[14] + FileReportColumns[15]);114if (!Options.ShowMCDCSummary)115Length -= (FileReportColumns[16] + FileReportColumns[17] + FileReportColumns[18]);116} else {117Length = std::accumulate(std::begin(FunctionReportColumns), std::end(FunctionReportColumns), 0);118if (!Options.ShowBranchSummary)119Length -= (FunctionReportColumns[7] + FunctionReportColumns[8] + FunctionReportColumns[9]);120if (!Options.ShowMCDCSummary)121Length -= (FunctionReportColumns[10] + FunctionReportColumns[11] + FunctionReportColumns[12]);122}123for (size_t I = 0; I < Length; ++I)124OS << '-';125}126127/// Return the color which correponds to the coverage percentage of a128/// certain metric.129template <typename T>130raw_ostream::Colors determineCoveragePercentageColor(const T &Info) {131if (Info.isFullyCovered())132return raw_ostream::GREEN;133return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW134: raw_ostream::RED;135}136137/// Get the number of redundant path components in each path in \p Paths.138unsigned getNumRedundantPathComponents(ArrayRef<std::string> Paths) {139// To start, set the number of redundant path components to the maximum140// possible value.141SmallVector<StringRef, 8> FirstPathComponents{sys::path::begin(Paths[0]),142sys::path::end(Paths[0])};143unsigned NumRedundant = FirstPathComponents.size();144145for (unsigned I = 1, E = Paths.size(); NumRedundant > 0 && I < E; ++I) {146StringRef Path = Paths[I];147for (const auto &Component :148enumerate(make_range(sys::path::begin(Path), sys::path::end(Path)))) {149// Do not increase the number of redundant components: that would remove150// useful parts of already-visited paths.151if (Component.index() >= NumRedundant)152break;153154// Lower the number of redundant components when there's a mismatch155// between the first path, and the path under consideration.156if (FirstPathComponents[Component.index()] != Component.value()) {157NumRedundant = Component.index();158break;159}160}161}162163return NumRedundant;164}165166/// Determine the length of the longest redundant prefix of the paths in167/// \p Paths.168unsigned getRedundantPrefixLen(ArrayRef<std::string> Paths) {169// If there's at most one path, no path components are redundant.170if (Paths.size() <= 1)171return 0;172173unsigned PrefixLen = 0;174unsigned NumRedundant = getNumRedundantPathComponents(Paths);175auto Component = sys::path::begin(Paths[0]);176for (unsigned I = 0; I < NumRedundant; ++I) {177auto LastComponent = Component;178++Component;179PrefixLen += Component - LastComponent;180}181return PrefixLen;182}183184/// Determine the length of the longest redundant prefix of the substrs starts185/// from \p LCP in \p Paths. \p Paths can't be empty. If there's only one186/// element in \p Paths, the length of the substr is returned. Note this is187/// differnet from the behavior of the function above.188unsigned getRedundantPrefixLen(ArrayRef<StringRef> Paths, unsigned LCP) {189assert(!Paths.empty() && "Paths must have at least one element");190191auto Iter = Paths.begin();192auto IterE = Paths.end();193auto Prefix = Iter->substr(LCP);194while (++Iter != IterE) {195auto Other = Iter->substr(LCP);196auto Len = std::min(Prefix.size(), Other.size());197for (std::size_t I = 0; I < Len; ++I) {198if (Prefix[I] != Other[I]) {199Prefix = Prefix.substr(0, I);200break;201}202}203}204205for (auto I = Prefix.size(); --I != SIZE_MAX;) {206if (Prefix[I] == '/' || Prefix[I] == '\\')207return I + 1;208}209210return Prefix.size();211}212213} // end anonymous namespace214215namespace llvm {216217void CoverageReport::render(const FileCoverageSummary &File,218raw_ostream &OS) const {219auto FileCoverageColor =220determineCoveragePercentageColor(File.RegionCoverage);221auto FuncCoverageColor =222determineCoveragePercentageColor(File.FunctionCoverage);223auto InstantiationCoverageColor =224determineCoveragePercentageColor(File.InstantiationCoverage);225auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage);226SmallString<256> FileName = File.Name;227sys::path::native(FileName);228229// remove_dots will remove trailing slash, so we need to check before it.230auto IsDir = FileName.ends_with(sys::path::get_separator());231sys::path::remove_dots(FileName, /*remove_dot_dot=*/true);232if (IsDir)233FileName += sys::path::get_separator();234235OS << column(FileName, FileReportColumns[0], Column::NoTrim);236237if (Options.ShowRegionSummary) {238OS << format("%*u", FileReportColumns[1],239(unsigned)File.RegionCoverage.getNumRegions());240Options.colored_ostream(OS, FileCoverageColor)241<< format("%*u", FileReportColumns[2],242(unsigned)(File.RegionCoverage.getNumRegions() -243File.RegionCoverage.getCovered()));244if (File.RegionCoverage.getNumRegions())245Options.colored_ostream(OS, FileCoverageColor)246<< format("%*.2f", FileReportColumns[3] - 1,247File.RegionCoverage.getPercentCovered())248<< '%';249else250OS << column("-", FileReportColumns[3], Column::RightAlignment);251}252253OS << format("%*u", FileReportColumns[4],254(unsigned)File.FunctionCoverage.getNumFunctions());255OS << format("%*u", FileReportColumns[5],256(unsigned)(File.FunctionCoverage.getNumFunctions() -257File.FunctionCoverage.getExecuted()));258if (File.FunctionCoverage.getNumFunctions())259Options.colored_ostream(OS, FuncCoverageColor)260<< format("%*.2f", FileReportColumns[6] - 1,261File.FunctionCoverage.getPercentCovered())262<< '%';263else264OS << column("-", FileReportColumns[6], Column::RightAlignment);265266if (Options.ShowInstantiationSummary) {267OS << format("%*u", FileReportColumns[7],268(unsigned)File.InstantiationCoverage.getNumFunctions());269OS << format("%*u", FileReportColumns[8],270(unsigned)(File.InstantiationCoverage.getNumFunctions() -271File.InstantiationCoverage.getExecuted()));272if (File.InstantiationCoverage.getNumFunctions())273Options.colored_ostream(OS, InstantiationCoverageColor)274<< format("%*.2f", FileReportColumns[9] - 1,275File.InstantiationCoverage.getPercentCovered())276<< '%';277else278OS << column("-", FileReportColumns[9], Column::RightAlignment);279}280281OS << format("%*u", FileReportColumns[10],282(unsigned)File.LineCoverage.getNumLines());283Options.colored_ostream(OS, LineCoverageColor) << format(284"%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() -285File.LineCoverage.getCovered()));286if (File.LineCoverage.getNumLines())287Options.colored_ostream(OS, LineCoverageColor)288<< format("%*.2f", FileReportColumns[12] - 1,289File.LineCoverage.getPercentCovered())290<< '%';291else292OS << column("-", FileReportColumns[12], Column::RightAlignment);293294if (Options.ShowBranchSummary) {295OS << format("%*u", FileReportColumns[13],296(unsigned)File.BranchCoverage.getNumBranches());297Options.colored_ostream(OS, LineCoverageColor)298<< format("%*u", FileReportColumns[14],299(unsigned)(File.BranchCoverage.getNumBranches() -300File.BranchCoverage.getCovered()));301if (File.BranchCoverage.getNumBranches())302Options.colored_ostream(OS, LineCoverageColor)303<< format("%*.2f", FileReportColumns[15] - 1,304File.BranchCoverage.getPercentCovered())305<< '%';306else307OS << column("-", FileReportColumns[15], Column::RightAlignment);308}309310if (Options.ShowMCDCSummary) {311OS << format("%*u", FileReportColumns[16],312(unsigned)File.MCDCCoverage.getNumPairs());313Options.colored_ostream(OS, LineCoverageColor)314<< format("%*u", FileReportColumns[17],315(unsigned)(File.MCDCCoverage.getNumPairs() -316File.MCDCCoverage.getCoveredPairs()));317if (File.MCDCCoverage.getNumPairs())318Options.colored_ostream(OS, LineCoverageColor)319<< format("%*.2f", FileReportColumns[18] - 1,320File.MCDCCoverage.getPercentCovered())321<< '%';322else323OS << column("-", FileReportColumns[18], Column::RightAlignment);324}325326OS << "\n";327}328329void CoverageReport::render(const FunctionCoverageSummary &Function,330const DemangleCache &DC,331raw_ostream &OS) const {332auto FuncCoverageColor =333determineCoveragePercentageColor(Function.RegionCoverage);334auto LineCoverageColor =335determineCoveragePercentageColor(Function.LineCoverage);336OS << column(DC.demangle(Function.Name), FunctionReportColumns[0],337Column::RightTrim)338<< format("%*u", FunctionReportColumns[1],339(unsigned)Function.RegionCoverage.getNumRegions());340Options.colored_ostream(OS, FuncCoverageColor)341<< format("%*u", FunctionReportColumns[2],342(unsigned)(Function.RegionCoverage.getNumRegions() -343Function.RegionCoverage.getCovered()));344Options.colored_ostream(345OS, determineCoveragePercentageColor(Function.RegionCoverage))346<< format("%*.2f", FunctionReportColumns[3] - 1,347Function.RegionCoverage.getPercentCovered())348<< '%';349OS << format("%*u", FunctionReportColumns[4],350(unsigned)Function.LineCoverage.getNumLines());351Options.colored_ostream(OS, LineCoverageColor)352<< format("%*u", FunctionReportColumns[5],353(unsigned)(Function.LineCoverage.getNumLines() -354Function.LineCoverage.getCovered()));355Options.colored_ostream(356OS, determineCoveragePercentageColor(Function.LineCoverage))357<< format("%*.2f", FunctionReportColumns[6] - 1,358Function.LineCoverage.getPercentCovered())359<< '%';360if (Options.ShowBranchSummary) {361OS << format("%*u", FunctionReportColumns[7],362(unsigned)Function.BranchCoverage.getNumBranches());363Options.colored_ostream(OS, LineCoverageColor)364<< format("%*u", FunctionReportColumns[8],365(unsigned)(Function.BranchCoverage.getNumBranches() -366Function.BranchCoverage.getCovered()));367Options.colored_ostream(368OS, determineCoveragePercentageColor(Function.BranchCoverage))369<< format("%*.2f", FunctionReportColumns[9] - 1,370Function.BranchCoverage.getPercentCovered())371<< '%';372}373if (Options.ShowMCDCSummary) {374OS << format("%*u", FunctionReportColumns[10],375(unsigned)Function.MCDCCoverage.getNumPairs());376Options.colored_ostream(OS, LineCoverageColor)377<< format("%*u", FunctionReportColumns[11],378(unsigned)(Function.MCDCCoverage.getNumPairs() -379Function.MCDCCoverage.getCoveredPairs()));380Options.colored_ostream(381OS, determineCoveragePercentageColor(Function.MCDCCoverage))382<< format("%*.2f", FunctionReportColumns[12] - 1,383Function.MCDCCoverage.getPercentCovered())384<< '%';385}386OS << "\n";387}388389void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,390const DemangleCache &DC,391raw_ostream &OS) {392bool isFirst = true;393for (StringRef Filename : Files) {394auto Functions = Coverage.getCoveredFunctions(Filename);395396if (isFirst)397isFirst = false;398else399OS << "\n";400401std::vector<StringRef> Funcnames;402for (const auto &F : Functions)403Funcnames.emplace_back(DC.demangle(F.Name));404adjustColumnWidths({}, Funcnames);405406OS << "File '" << Filename << "':\n";407OS << column("Name", FunctionReportColumns[0])408<< column("Regions", FunctionReportColumns[1], Column::RightAlignment)409<< column("Miss", FunctionReportColumns[2], Column::RightAlignment)410<< column("Cover", FunctionReportColumns[3], Column::RightAlignment)411<< column("Lines", FunctionReportColumns[4], Column::RightAlignment)412<< column("Miss", FunctionReportColumns[5], Column::RightAlignment)413<< column("Cover", FunctionReportColumns[6], Column::RightAlignment);414if (Options.ShowBranchSummary)415OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment)416<< column("Miss", FunctionReportColumns[8], Column::RightAlignment)417<< column("Cover", FunctionReportColumns[9], Column::RightAlignment);418if (Options.ShowMCDCSummary)419OS << column("MC/DC Conditions", FunctionReportColumns[10],420Column::RightAlignment)421<< column("Miss", FunctionReportColumns[11], Column::RightAlignment)422<< column("Cover", FunctionReportColumns[12], Column::RightAlignment);423OS << "\n";424renderDivider(OS, Options, false);425OS << "\n";426FunctionCoverageSummary Totals("TOTAL");427for (const auto &F : Functions) {428auto Function = FunctionCoverageSummary::get(Coverage, F);429++Totals.ExecutionCount;430Totals.RegionCoverage += Function.RegionCoverage;431Totals.LineCoverage += Function.LineCoverage;432Totals.BranchCoverage += Function.BranchCoverage;433Totals.MCDCCoverage += Function.MCDCCoverage;434render(Function, DC, OS);435}436if (Totals.ExecutionCount) {437renderDivider(OS, Options, false);438OS << "\n";439render(Totals, DC, OS);440}441}442}443444void CoverageReport::prepareSingleFileReport(const StringRef Filename,445const coverage::CoverageMapping *Coverage,446const CoverageViewOptions &Options, const unsigned LCP,447FileCoverageSummary *FileReport, const CoverageFilter *Filters) {448for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {449std::vector<FunctionCoverageSummary> InstantiationSummaries;450for (const coverage::FunctionRecord *F : Group.getInstantiations()) {451if (!Filters->matches(*Coverage, *F))452continue;453auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);454FileReport->addInstantiation(InstantiationSummary);455InstantiationSummaries.push_back(InstantiationSummary);456}457if (InstantiationSummaries.empty())458continue;459460auto GroupSummary =461FunctionCoverageSummary::get(Group, InstantiationSummaries);462463if (Options.Debug)464outs() << "InstantiationGroup: " << GroupSummary.Name << " with "465<< "size = " << Group.size() << "\n";466467FileReport->addFunction(GroupSummary);468}469}470471std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(472const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals,473ArrayRef<std::string> Files, const CoverageViewOptions &Options,474const CoverageFilter &Filters) {475unsigned LCP = getRedundantPrefixLen(Files);476477ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);478if (Options.NumThreads == 0) {479// If NumThreads is not specified, create one thread for each input, up to480// the number of hardware cores.481S = heavyweight_hardware_concurrency(Files.size());482S.Limit = true;483}484DefaultThreadPool Pool(S);485486std::vector<FileCoverageSummary> FileReports;487FileReports.reserve(Files.size());488489for (StringRef Filename : Files) {490FileReports.emplace_back(Filename.drop_front(LCP));491Pool.async(&CoverageReport::prepareSingleFileReport, Filename,492&Coverage, Options, LCP, &FileReports.back(), &Filters);493}494Pool.wait();495496for (const auto &FileReport : FileReports)497Totals += FileReport;498499return FileReports;500}501502void CoverageReport::renderFileReports(503raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const {504std::vector<std::string> UniqueSourceFiles;505for (StringRef SF : Coverage.getUniqueSourceFiles()) {506// Apply ignore source files filters.507if (!IgnoreFilenameFilters.matchesFilename(SF))508UniqueSourceFiles.emplace_back(SF.str());509}510renderFileReports(OS, UniqueSourceFiles);511}512513void CoverageReport::renderFileReports(514raw_ostream &OS, ArrayRef<std::string> Files) const {515renderFileReports(OS, Files, CoverageFiltersMatchAll());516}517518void CoverageReport::renderFileReports(519raw_ostream &OS, ArrayRef<std::string> Files,520const CoverageFiltersMatchAll &Filters) const {521FileCoverageSummary Totals("TOTAL");522auto FileReports =523prepareFileReports(Coverage, Totals, Files, Options, Filters);524renderFileReports(OS, FileReports, Totals, Filters.empty());525}526527void CoverageReport::renderFileReports(528raw_ostream &OS, const std::vector<FileCoverageSummary> &FileReports,529const FileCoverageSummary &Totals, bool ShowEmptyFiles) const {530std::vector<StringRef> Filenames;531Filenames.reserve(FileReports.size());532for (const FileCoverageSummary &FCS : FileReports)533Filenames.emplace_back(FCS.Name);534adjustColumnWidths(Filenames, {});535536OS << column("Filename", FileReportColumns[0]);537if (Options.ShowRegionSummary)538OS << column("Regions", FileReportColumns[1], Column::RightAlignment)539<< column("Missed Regions", FileReportColumns[2], Column::RightAlignment)540<< column("Cover", FileReportColumns[3], Column::RightAlignment);541OS << column("Functions", FileReportColumns[4], Column::RightAlignment)542<< column("Missed Functions", FileReportColumns[5], Column::RightAlignment)543<< column("Executed", FileReportColumns[6], Column::RightAlignment);544if (Options.ShowInstantiationSummary)545OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment)546<< column("Missed Insts.", FileReportColumns[8], Column::RightAlignment)547<< column("Executed", FileReportColumns[9], Column::RightAlignment);548OS << column("Lines", FileReportColumns[10], Column::RightAlignment)549<< column("Missed Lines", FileReportColumns[11], Column::RightAlignment)550<< column("Cover", FileReportColumns[12], Column::RightAlignment);551if (Options.ShowBranchSummary)552OS << column("Branches", FileReportColumns[13], Column::RightAlignment)553<< column("Missed Branches", FileReportColumns[14],554Column::RightAlignment)555<< column("Cover", FileReportColumns[15], Column::RightAlignment);556if (Options.ShowMCDCSummary)557OS << column("MC/DC Conditions", FileReportColumns[16],558Column::RightAlignment)559<< column("Missed Conditions", FileReportColumns[17],560Column::RightAlignment)561<< column("Cover", FileReportColumns[18], Column::RightAlignment);562OS << "\n";563renderDivider(OS, Options, true);564OS << "\n";565566std::vector<const FileCoverageSummary *> EmptyFiles;567for (const FileCoverageSummary &FCS : FileReports) {568if (FCS.FunctionCoverage.getNumFunctions())569render(FCS, OS);570else571EmptyFiles.push_back(&FCS);572}573574if (!EmptyFiles.empty() && ShowEmptyFiles) {575OS << "\n"576<< "Files which contain no functions:\n";577578for (auto FCS : EmptyFiles)579render(*FCS, OS);580}581582renderDivider(OS, Options, true);583OS << "\n";584render(Totals, OS);585}586587Expected<FileCoverageSummary> DirectoryCoverageReport::prepareDirectoryReports(588ArrayRef<std::string> SourceFiles) {589std::vector<StringRef> Files(SourceFiles.begin(), SourceFiles.end());590591unsigned RootLCP = getRedundantPrefixLen(Files, 0);592auto LCPath = Files.front().substr(0, RootLCP);593594ThreadPoolStrategy PoolS = hardware_concurrency(Options.NumThreads);595if (Options.NumThreads == 0) {596PoolS = heavyweight_hardware_concurrency(Files.size());597PoolS.Limit = true;598}599DefaultThreadPool Pool(PoolS);600601TPool = &Pool;602LCPStack = {RootLCP};603FileCoverageSummary RootTotals(LCPath);604if (auto E = prepareSubDirectoryReports(Files, &RootTotals))605return {std::move(E)};606return {std::move(RootTotals)};607}608609/// Filter out files in LCPStack.back(), group others by subdirectory name610/// and recurse on them. After returning from all subdirectories, call611/// generateSubDirectoryReport(). \p Files must be non-empty. The612/// FileCoverageSummary of this directory will be added to \p Totals.613Error DirectoryCoverageReport::prepareSubDirectoryReports(614const ArrayRef<StringRef> &Files, FileCoverageSummary *Totals) {615assert(!Files.empty() && "Files must have at least one element");616617auto LCP = LCPStack.back();618auto LCPath = Files.front().substr(0, LCP).str();619620// Use ordered map to keep entries in order.621SubFileReports SubFiles;622SubDirReports SubDirs;623for (auto &&File : Files) {624auto SubPath = File.substr(LCPath.size());625SmallVector<char, 128> NativeSubPath;626sys::path::native(SubPath, NativeSubPath);627StringRef NativeSubPathRef(NativeSubPath.data(), NativeSubPath.size());628629auto I = sys::path::begin(NativeSubPathRef);630auto E = sys::path::end(NativeSubPathRef);631assert(I != E && "Such case should have been filtered out in the caller");632633auto Name = SubPath.substr(0, I->size());634if (++I == E) {635auto Iter = SubFiles.insert_or_assign(Name, SubPath).first;636// Makes files reporting overlap with subdir reporting.637TPool->async(&CoverageReport::prepareSingleFileReport, File, &Coverage,638Options, LCP, &Iter->second, &Filters);639} else {640SubDirs[Name].second.push_back(File);641}642}643644// Call recursively on subdirectories.645for (auto &&KV : SubDirs) {646auto &V = KV.second;647if (V.second.size() == 1) {648// If there's only one file in that subdirectory, we don't bother to649// recurse on it further.650V.first.Name = V.second.front().substr(LCP);651TPool->async(&CoverageReport::prepareSingleFileReport, V.second.front(),652&Coverage, Options, LCP, &V.first, &Filters);653} else {654auto SubDirLCP = getRedundantPrefixLen(V.second, LCP);655V.first.Name = V.second.front().substr(LCP, SubDirLCP);656LCPStack.push_back(LCP + SubDirLCP);657if (auto E = prepareSubDirectoryReports(V.second, &V.first))658return E;659}660}661662TPool->wait();663664FileCoverageSummary CurrentTotals(LCPath);665for (auto &&KV : SubFiles)666CurrentTotals += KV.second;667for (auto &&KV : SubDirs)668CurrentTotals += KV.second.first;669*Totals += CurrentTotals;670671if (auto E = generateSubDirectoryReport(672std::move(SubFiles), std::move(SubDirs), std::move(CurrentTotals)))673return E;674675LCPStack.pop_back();676return Error::success();677}678679} // end namespace llvm680681682