1 //===- CoverageReport.cpp - Code coverage report -------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 // This class implements rendering of a code coverage report.
11 //===----------------------------------------------------------------------===//
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"
27 /// Helper struct which prints trimmed and aligned columns.
29 enum TrimKind
{ NoTrim
, WidthTrim
, RightTrim
};
31 enum AlignmentKind
{ LeftAlignment
, RightAlignment
};
36 AlignmentKind Alignment
;
38 Column(StringRef Str
, unsigned Width
)
39 : Str(Str
), Width(Width
), Trim(WidthTrim
), Alignment(LeftAlignment
) {}
41 Column
&set(TrimKind Value
) {
46 Column
&set(AlignmentKind Value
) {
51 void render(raw_ostream
&OS
) const {
52 if (Str
.size() <= Width
) {
53 if (Alignment
== RightAlignment
) {
54 OS
.indent(Width
- Str
.size());
59 OS
.indent(Width
- Str
.size());
68 OS
<< Str
.substr(0, Width
);
71 OS
<< Str
.substr(0, Width
- 3) << "...";
77 raw_ostream
&operator<<(raw_ostream
&OS
, const Column
&Value
) {
82 Column
column(StringRef Str
, unsigned Width
) { return Column(Str
, Width
); }
85 Column
column(StringRef Str
, unsigned Width
, const T
&Value
) {
86 return Column(Str
, Width
).set(Value
);
89 // Specify the default column widths.
90 size_t FileReportColumns
[] = {25, 12, 18, 10, 12, 18, 10, 16,
91 16, 10, 12, 18, 10, 12, 18, 10};
92 size_t FunctionReportColumns
[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8};
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());
104 /// Prints a horizontal divider long enough to cover the given column
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
)
112 /// Return the color which correponds to the coverage percentage of a
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
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
126 SmallVector
<StringRef
, 8> FirstPathComponents
{sys::path::begin(Paths
[0]),
127 sys::path::end(Paths
[0])};
128 unsigned NumRedundant
= FirstPathComponents
.size();
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
)
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();
151 /// Determine the length of the longest redundant prefix of the paths in
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)
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
;
164 PrefixLen
+= Component
- LastComponent
;
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");
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
);
190 for (auto I
= Prefix
.size(); --I
!= SIZE_MAX
;) {
191 if (Prefix
[I
] == '/' || Prefix
[I
] == '\\')
195 return Prefix
.size();
198 } // end anonymous namespace
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
);
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);
218 FileName
+= sys::path::get_separator();
220 OS
<< column(FileName
, FileReportColumns
[0], Column::NoTrim
);
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())
235 OS
<< column("-", FileReportColumns
[3], Column::RightAlignment
);
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())
249 OS
<< column("-", FileReportColumns
[6], Column::RightAlignment
);
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())
263 OS
<< column("-", FileReportColumns
[9], Column::RightAlignment
);
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())
277 OS
<< column("-", FileReportColumns
[12], Column::RightAlignment
);
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())
292 OS
<< column("-", FileReportColumns
[15], Column::RightAlignment
);
298 void CoverageReport::render(const FunctionCoverageSummary
&Function
,
299 const DemangleCache
&DC
,
300 raw_ostream
&OS
) const {
301 auto FuncCoverageColor
=
302 determineCoveragePercentageColor(Function
.RegionCoverage
);
303 auto LineCoverageColor
=
304 determineCoveragePercentageColor(Function
.LineCoverage
);
305 OS
<< column(DC
.demangle(Function
.Name
), FunctionReportColumns
[0],
307 << format("%*u", FunctionReportColumns
[1],
308 (unsigned)Function
.RegionCoverage
.getNumRegions());
309 Options
.colored_ostream(OS
, FuncCoverageColor
)
310 << format("%*u", FunctionReportColumns
[2],
311 (unsigned)(Function
.RegionCoverage
.getNumRegions() -
312 Function
.RegionCoverage
.getCovered()));
313 Options
.colored_ostream(
314 OS
, determineCoveragePercentageColor(Function
.RegionCoverage
))
315 << format("%*.2f", FunctionReportColumns
[3] - 1,
316 Function
.RegionCoverage
.getPercentCovered())
318 OS
<< format("%*u", FunctionReportColumns
[4],
319 (unsigned)Function
.LineCoverage
.getNumLines());
320 Options
.colored_ostream(OS
, LineCoverageColor
)
321 << format("%*u", FunctionReportColumns
[5],
322 (unsigned)(Function
.LineCoverage
.getNumLines() -
323 Function
.LineCoverage
.getCovered()));
324 Options
.colored_ostream(
325 OS
, determineCoveragePercentageColor(Function
.LineCoverage
))
326 << format("%*.2f", FunctionReportColumns
[6] - 1,
327 Function
.LineCoverage
.getPercentCovered())
329 if (Options
.ShowBranchSummary
) {
330 OS
<< format("%*u", FunctionReportColumns
[7],
331 (unsigned)Function
.BranchCoverage
.getNumBranches());
332 Options
.colored_ostream(OS
, LineCoverageColor
)
333 << format("%*u", FunctionReportColumns
[8],
334 (unsigned)(Function
.BranchCoverage
.getNumBranches() -
335 Function
.BranchCoverage
.getCovered()));
336 Options
.colored_ostream(
337 OS
, determineCoveragePercentageColor(Function
.BranchCoverage
))
338 << format("%*.2f", FunctionReportColumns
[9] - 1,
339 Function
.BranchCoverage
.getPercentCovered())
345 void CoverageReport::renderFunctionReports(ArrayRef
<std::string
> Files
,
346 const DemangleCache
&DC
,
349 for (StringRef Filename
: Files
) {
350 auto Functions
= Coverage
.getCoveredFunctions(Filename
);
357 std::vector
<StringRef
> Funcnames
;
358 for (const auto &F
: Functions
)
359 Funcnames
.emplace_back(DC
.demangle(F
.Name
));
360 adjustColumnWidths({}, Funcnames
);
362 OS
<< "File '" << Filename
<< "':\n";
363 OS
<< column("Name", FunctionReportColumns
[0])
364 << column("Regions", FunctionReportColumns
[1], Column::RightAlignment
)
365 << column("Miss", FunctionReportColumns
[2], Column::RightAlignment
)
366 << column("Cover", FunctionReportColumns
[3], Column::RightAlignment
)
367 << column("Lines", FunctionReportColumns
[4], Column::RightAlignment
)
368 << column("Miss", FunctionReportColumns
[5], Column::RightAlignment
)
369 << column("Cover", FunctionReportColumns
[6], Column::RightAlignment
);
370 if (Options
.ShowBranchSummary
)
371 OS
<< column("Branches", FunctionReportColumns
[7], Column::RightAlignment
)
372 << column("Miss", FunctionReportColumns
[8], Column::RightAlignment
)
373 << column("Cover", FunctionReportColumns
[9], Column::RightAlignment
);
375 renderDivider(FunctionReportColumns
, OS
);
377 FunctionCoverageSummary
Totals("TOTAL");
378 for (const auto &F
: Functions
) {
379 auto Function
= FunctionCoverageSummary::get(Coverage
, F
);
380 ++Totals
.ExecutionCount
;
381 Totals
.RegionCoverage
+= Function
.RegionCoverage
;
382 Totals
.LineCoverage
+= Function
.LineCoverage
;
383 Totals
.BranchCoverage
+= Function
.BranchCoverage
;
384 render(Function
, DC
, OS
);
386 if (Totals
.ExecutionCount
) {
387 renderDivider(FunctionReportColumns
, OS
);
389 render(Totals
, DC
, OS
);
394 void CoverageReport::prepareSingleFileReport(const StringRef Filename
,
395 const coverage::CoverageMapping
*Coverage
,
396 const CoverageViewOptions
&Options
, const unsigned LCP
,
397 FileCoverageSummary
*FileReport
, const CoverageFilter
*Filters
) {
398 for (const auto &Group
: Coverage
->getInstantiationGroups(Filename
)) {
399 std::vector
<FunctionCoverageSummary
> InstantiationSummaries
;
400 for (const coverage::FunctionRecord
*F
: Group
.getInstantiations()) {
401 if (!Filters
->matches(*Coverage
, *F
))
403 auto InstantiationSummary
= FunctionCoverageSummary::get(*Coverage
, *F
);
404 FileReport
->addInstantiation(InstantiationSummary
);
405 InstantiationSummaries
.push_back(InstantiationSummary
);
407 if (InstantiationSummaries
.empty())
411 FunctionCoverageSummary::get(Group
, InstantiationSummaries
);
414 outs() << "InstantiationGroup: " << GroupSummary
.Name
<< " with "
415 << "size = " << Group
.size() << "\n";
417 FileReport
->addFunction(GroupSummary
);
421 std::vector
<FileCoverageSummary
> CoverageReport::prepareFileReports(
422 const coverage::CoverageMapping
&Coverage
, FileCoverageSummary
&Totals
,
423 ArrayRef
<std::string
> Files
, const CoverageViewOptions
&Options
,
424 const CoverageFilter
&Filters
) {
425 unsigned LCP
= getRedundantPrefixLen(Files
);
427 ThreadPoolStrategy S
= hardware_concurrency(Options
.NumThreads
);
428 if (Options
.NumThreads
== 0) {
429 // If NumThreads is not specified, create one thread for each input, up to
430 // the number of hardware cores.
431 S
= heavyweight_hardware_concurrency(Files
.size());
436 std::vector
<FileCoverageSummary
> FileReports
;
437 FileReports
.reserve(Files
.size());
439 for (StringRef Filename
: Files
) {
440 FileReports
.emplace_back(Filename
.drop_front(LCP
));
441 Pool
.async(&CoverageReport::prepareSingleFileReport
, Filename
,
442 &Coverage
, Options
, LCP
, &FileReports
.back(), &Filters
);
446 for (const auto &FileReport
: FileReports
)
447 Totals
+= FileReport
;
452 void CoverageReport::renderFileReports(
453 raw_ostream
&OS
, const CoverageFilters
&IgnoreFilenameFilters
) const {
454 std::vector
<std::string
> UniqueSourceFiles
;
455 for (StringRef SF
: Coverage
.getUniqueSourceFiles()) {
456 // Apply ignore source files filters.
457 if (!IgnoreFilenameFilters
.matchesFilename(SF
))
458 UniqueSourceFiles
.emplace_back(SF
.str());
460 renderFileReports(OS
, UniqueSourceFiles
);
463 void CoverageReport::renderFileReports(
464 raw_ostream
&OS
, ArrayRef
<std::string
> Files
) const {
465 renderFileReports(OS
, Files
, CoverageFiltersMatchAll());
468 void CoverageReport::renderFileReports(
469 raw_ostream
&OS
, ArrayRef
<std::string
> Files
,
470 const CoverageFiltersMatchAll
&Filters
) const {
471 FileCoverageSummary
Totals("TOTAL");
473 prepareFileReports(Coverage
, Totals
, Files
, Options
, Filters
);
474 renderFileReports(OS
, FileReports
, Totals
, Filters
.empty());
477 void CoverageReport::renderFileReports(
478 raw_ostream
&OS
, const std::vector
<FileCoverageSummary
> &FileReports
,
479 const FileCoverageSummary
&Totals
, bool ShowEmptyFiles
) const {
480 std::vector
<StringRef
> Filenames
;
481 Filenames
.reserve(FileReports
.size());
482 for (const FileCoverageSummary
&FCS
: FileReports
)
483 Filenames
.emplace_back(FCS
.Name
);
484 adjustColumnWidths(Filenames
, {});
486 OS
<< column("Filename", FileReportColumns
[0]);
487 if (Options
.ShowRegionSummary
)
488 OS
<< column("Regions", FileReportColumns
[1], Column::RightAlignment
)
489 << column("Missed Regions", FileReportColumns
[2], Column::RightAlignment
)
490 << column("Cover", FileReportColumns
[3], Column::RightAlignment
);
491 OS
<< column("Functions", FileReportColumns
[4], Column::RightAlignment
)
492 << column("Missed Functions", FileReportColumns
[5], Column::RightAlignment
)
493 << column("Executed", FileReportColumns
[6], Column::RightAlignment
);
494 if (Options
.ShowInstantiationSummary
)
495 OS
<< column("Instantiations", FileReportColumns
[7], Column::RightAlignment
)
496 << column("Missed Insts.", FileReportColumns
[8], Column::RightAlignment
)
497 << column("Executed", FileReportColumns
[9], Column::RightAlignment
);
498 OS
<< column("Lines", FileReportColumns
[10], Column::RightAlignment
)
499 << column("Missed Lines", FileReportColumns
[11], Column::RightAlignment
)
500 << column("Cover", FileReportColumns
[12], Column::RightAlignment
);
501 if (Options
.ShowBranchSummary
)
502 OS
<< column("Branches", FileReportColumns
[13], Column::RightAlignment
)
503 << column("Missed Branches", FileReportColumns
[14],
504 Column::RightAlignment
)
505 << column("Cover", FileReportColumns
[15], Column::RightAlignment
);
507 renderDivider(FileReportColumns
, OS
);
510 std::vector
<const FileCoverageSummary
*> EmptyFiles
;
511 for (const FileCoverageSummary
&FCS
: FileReports
) {
512 if (FCS
.FunctionCoverage
.getNumFunctions())
515 EmptyFiles
.push_back(&FCS
);
518 if (!EmptyFiles
.empty() && ShowEmptyFiles
) {
520 << "Files which contain no functions:\n";
522 for (auto FCS
: EmptyFiles
)
526 renderDivider(FileReportColumns
, OS
);
531 Expected
<FileCoverageSummary
> DirectoryCoverageReport::prepareDirectoryReports(
532 ArrayRef
<std::string
> SourceFiles
) {
533 std::vector
<StringRef
> Files(SourceFiles
.begin(), SourceFiles
.end());
535 unsigned RootLCP
= getRedundantPrefixLen(Files
, 0);
536 auto LCPath
= Files
.front().substr(0, RootLCP
);
538 ThreadPoolStrategy PoolS
= hardware_concurrency(Options
.NumThreads
);
539 if (Options
.NumThreads
== 0) {
540 PoolS
= heavyweight_hardware_concurrency(Files
.size());
543 ThreadPool
Pool(PoolS
);
546 LCPStack
= {RootLCP
};
547 FileCoverageSummary
RootTotals(LCPath
);
548 if (auto E
= prepareSubDirectoryReports(Files
, &RootTotals
))
549 return {std::move(E
)};
550 return {std::move(RootTotals
)};
553 /// Filter out files in LCPStack.back(), group others by subdirectory name
554 /// and recurse on them. After returning from all subdirectories, call
555 /// generateSubDirectoryReport(). \p Files must be non-empty. The
556 /// FileCoverageSummary of this directory will be added to \p Totals.
557 Error
DirectoryCoverageReport::prepareSubDirectoryReports(
558 const ArrayRef
<StringRef
> &Files
, FileCoverageSummary
*Totals
) {
559 assert(!Files
.empty() && "Files must have at least one element");
561 auto LCP
= LCPStack
.back();
562 auto LCPath
= Files
.front().substr(0, LCP
).str();
564 // Use ordered map to keep entries in order.
565 SubFileReports SubFiles
;
566 SubDirReports SubDirs
;
567 for (auto &&File
: Files
) {
568 auto SubPath
= File
.substr(LCPath
.size());
569 SmallVector
<char, 128> NativeSubPath
;
570 sys::path::native(SubPath
, NativeSubPath
);
571 StringRef
NativeSubPathRef(NativeSubPath
.data(), NativeSubPath
.size());
573 auto I
= sys::path::begin(NativeSubPathRef
);
574 auto E
= sys::path::end(NativeSubPathRef
);
575 assert(I
!= E
&& "Such case should have been filtered out in the caller");
577 auto Name
= SubPath
.substr(0, I
->size());
579 auto Iter
= SubFiles
.insert_or_assign(Name
, SubPath
).first
;
580 // Makes files reporting overlap with subdir reporting.
581 TPool
->async(&CoverageReport::prepareSingleFileReport
, File
, &Coverage
,
582 Options
, LCP
, &Iter
->second
, &Filters
);
584 SubDirs
[Name
].second
.push_back(File
);
588 // Call recursively on subdirectories.
589 for (auto &&KV
: SubDirs
) {
591 if (V
.second
.size() == 1) {
592 // If there's only one file in that subdirectory, we don't bother to
593 // recurse on it further.
594 V
.first
.Name
= V
.second
.front().substr(LCP
);
595 TPool
->async(&CoverageReport::prepareSingleFileReport
, V
.second
.front(),
596 &Coverage
, Options
, LCP
, &V
.first
, &Filters
);
598 auto SubDirLCP
= getRedundantPrefixLen(V
.second
, LCP
);
599 V
.first
.Name
= V
.second
.front().substr(LCP
, SubDirLCP
);
600 LCPStack
.push_back(LCP
+ SubDirLCP
);
601 if (auto E
= prepareSubDirectoryReports(V
.second
, &V
.first
))
608 FileCoverageSummary
CurrentTotals(LCPath
);
609 for (auto &&KV
: SubFiles
)
610 CurrentTotals
+= KV
.second
;
611 for (auto &&KV
: SubDirs
)
612 CurrentTotals
+= KV
.second
.first
;
613 *Totals
+= CurrentTotals
;
615 if (auto E
= generateSubDirectoryReport(
616 std::move(SubFiles
), std::move(SubDirs
), std::move(CurrentTotals
)))
620 return Error::success();
623 } // end namespace llvm