1 //===- CodeCoverage.cpp - Coverage tool based on profiling instrumentation-===//
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 // The 'CodeCoverageTool' class implements a command line tool to analyze and
10 // report coverage information using the profiling instrumentation and code
13 //===----------------------------------------------------------------------===//
15 #include "CoverageExporterJson.h"
16 #include "CoverageExporterLcov.h"
17 #include "CoverageFilters.h"
18 #include "CoverageReport.h"
19 #include "CoverageSummaryInfo.h"
20 #include "CoverageViewOptions.h"
21 #include "RenderingSupport.h"
22 #include "SourceCoverageView.h"
23 #include "llvm/ADT/SmallString.h"
24 #include "llvm/ADT/StringRef.h"
25 #include "llvm/Debuginfod/BuildIDFetcher.h"
26 #include "llvm/Debuginfod/Debuginfod.h"
27 #include "llvm/Debuginfod/HTTPClient.h"
28 #include "llvm/Object/BuildID.h"
29 #include "llvm/ProfileData/Coverage/CoverageMapping.h"
30 #include "llvm/ProfileData/InstrProfReader.h"
31 #include "llvm/Support/CommandLine.h"
32 #include "llvm/Support/FileSystem.h"
33 #include "llvm/Support/Format.h"
34 #include "llvm/Support/MemoryBuffer.h"
35 #include "llvm/Support/Path.h"
36 #include "llvm/Support/Process.h"
37 #include "llvm/Support/Program.h"
38 #include "llvm/Support/ScopedPrinter.h"
39 #include "llvm/Support/SpecialCaseList.h"
40 #include "llvm/Support/ThreadPool.h"
41 #include "llvm/Support/Threading.h"
42 #include "llvm/Support/ToolOutputFile.h"
43 #include "llvm/Support/VirtualFileSystem.h"
44 #include "llvm/TargetParser/Triple.h"
49 #include <system_error>
52 using namespace coverage
;
54 void exportCoverageDataToJson(const coverage::CoverageMapping
&CoverageMapping
,
55 const CoverageViewOptions
&Options
,
59 /// The implementation of the coverage tool.
60 class CodeCoverageTool
{
65 /// The report command.
67 /// The export command.
71 int run(Command Cmd
, int argc
, const char **argv
);
74 /// Print the error message to the error output stream.
75 void error(const Twine
&Message
, StringRef Whence
= "");
77 /// Print the warning message to the error output stream.
78 void warning(const Twine
&Message
, StringRef Whence
= "");
80 /// Convert \p Path into an absolute path and append it to the list
81 /// of collected paths.
82 void addCollectedPath(const std::string
&Path
);
84 /// If \p Path is a regular file, collect the path. If it's a
85 /// directory, recursively collect all of the paths within the directory.
86 void collectPaths(const std::string
&Path
);
88 /// Check if the two given files are the same file.
89 bool isEquivalentFile(StringRef FilePath1
, StringRef FilePath2
);
91 /// Retrieve a file status with a cache.
92 std::optional
<sys::fs::file_status
> getFileStatus(StringRef FilePath
);
94 /// Return a memory buffer for the given source file.
95 ErrorOr
<const MemoryBuffer
&> getSourceFile(StringRef SourceFile
);
97 /// Create source views for the expansions of the view.
98 void attachExpansionSubViews(SourceCoverageView
&View
,
99 ArrayRef
<ExpansionRecord
> Expansions
,
100 const CoverageMapping
&Coverage
);
102 /// Create source views for the branches of the view.
103 void attachBranchSubViews(SourceCoverageView
&View
, StringRef SourceName
,
104 ArrayRef
<CountedRegion
> Branches
,
105 const MemoryBuffer
&File
,
106 CoverageData
&CoverageInfo
);
108 /// Create the source view of a particular function.
109 std::unique_ptr
<SourceCoverageView
>
110 createFunctionView(const FunctionRecord
&Function
,
111 const CoverageMapping
&Coverage
);
113 /// Create the main source view of a particular source file.
114 std::unique_ptr
<SourceCoverageView
>
115 createSourceFileView(StringRef SourceFile
, const CoverageMapping
&Coverage
);
117 /// Load the coverage mapping data. Return nullptr if an error occurred.
118 std::unique_ptr
<CoverageMapping
> load();
120 /// Create a mapping from files in the Coverage data to local copies
121 /// (path-equivalence).
122 void remapPathNames(const CoverageMapping
&Coverage
);
124 /// Remove input source files which aren't mapped by \p Coverage.
125 void removeUnmappedInputs(const CoverageMapping
&Coverage
);
127 /// If a demangler is available, demangle all symbol names.
128 void demangleSymbols(const CoverageMapping
&Coverage
);
130 /// Write out a source file view to the filesystem.
131 void writeSourceFileView(StringRef SourceFile
, CoverageMapping
*Coverage
,
132 CoveragePrinter
*Printer
, bool ShowFilenames
);
134 typedef llvm::function_ref
<int(int, const char **)> CommandLineParserType
;
136 int doShow(int argc
, const char **argv
,
137 CommandLineParserType commandLineParser
);
139 int doReport(int argc
, const char **argv
,
140 CommandLineParserType commandLineParser
);
142 int doExport(int argc
, const char **argv
,
143 CommandLineParserType commandLineParser
);
145 std::vector
<StringRef
> ObjectFilenames
;
146 CoverageViewOptions ViewOpts
;
147 CoverageFiltersMatchAll Filters
;
148 CoverageFilters IgnoreFilenameFilters
;
150 /// True if InputSourceFiles are provided.
151 bool HadSourceFiles
= false;
153 /// The path to the indexed profile.
154 std::string PGOFilename
;
156 /// A list of input source files.
157 std::vector
<std::string
> SourceFiles
;
159 /// In -path-equivalence mode, this maps the absolute paths from the coverage
160 /// mapping data to the input source files.
161 StringMap
<std::string
> RemappedFilenames
;
163 /// The coverage data path to be remapped from, and the source path to be
164 /// remapped to, when using -path-equivalence.
165 std::optional
<std::pair
<std::string
, std::string
>> PathRemapping
;
167 /// File status cache used when finding the same file.
168 StringMap
<std::optional
<sys::fs::file_status
>> FileStatusCache
;
170 /// The architecture the coverage mapping data targets.
171 std::vector
<StringRef
> CoverageArches
;
173 /// A cache for demangled symbols.
176 /// A lock which guards printing to stderr.
179 /// A container for input source file buffers.
180 std::mutex LoadedSourceFilesLock
;
181 std::vector
<std::pair
<std::string
, std::unique_ptr
<MemoryBuffer
>>>
184 /// Allowlist from -name-allowlist to be used for filtering.
185 std::unique_ptr
<SpecialCaseList
> NameAllowlist
;
187 std::unique_ptr
<object::BuildIDFetcher
> BIDFetcher
;
193 static std::string
getErrorString(const Twine
&Message
, StringRef Whence
,
195 std::string Str
= (Warning
? "warning" : "error");
198 Str
+= Whence
.str() + ": ";
199 Str
+= Message
.str() + "\n";
203 void CodeCoverageTool::error(const Twine
&Message
, StringRef Whence
) {
204 std::unique_lock
<std::mutex
> Guard
{ErrsLock
};
205 ViewOpts
.colored_ostream(errs(), raw_ostream::RED
)
206 << getErrorString(Message
, Whence
, false);
209 void CodeCoverageTool::warning(const Twine
&Message
, StringRef Whence
) {
210 std::unique_lock
<std::mutex
> Guard
{ErrsLock
};
211 ViewOpts
.colored_ostream(errs(), raw_ostream::RED
)
212 << getErrorString(Message
, Whence
, true);
215 void CodeCoverageTool::addCollectedPath(const std::string
&Path
) {
216 SmallString
<128> EffectivePath(Path
);
217 if (std::error_code EC
= sys::fs::make_absolute(EffectivePath
)) {
218 error(EC
.message(), Path
);
221 sys::path::remove_dots(EffectivePath
, /*remove_dot_dot=*/true);
222 if (!IgnoreFilenameFilters
.matchesFilename(EffectivePath
))
223 SourceFiles
.emplace_back(EffectivePath
.str());
224 HadSourceFiles
= !SourceFiles
.empty();
227 void CodeCoverageTool::collectPaths(const std::string
&Path
) {
228 llvm::sys::fs::file_status Status
;
229 llvm::sys::fs::status(Path
, Status
);
230 if (!llvm::sys::fs::exists(Status
)) {
232 addCollectedPath(Path
);
234 warning("Source file doesn't exist, proceeded by ignoring it.", Path
);
238 if (llvm::sys::fs::is_regular_file(Status
)) {
239 addCollectedPath(Path
);
243 if (llvm::sys::fs::is_directory(Status
)) {
245 for (llvm::sys::fs::recursive_directory_iterator
F(Path
, EC
), E
;
246 F
!= E
; F
.increment(EC
)) {
248 auto Status
= F
->status();
250 warning(Status
.getError().message(), F
->path());
254 if (Status
->type() == llvm::sys::fs::file_type::regular_file
)
255 addCollectedPath(F
->path());
260 std::optional
<sys::fs::file_status
>
261 CodeCoverageTool::getFileStatus(StringRef FilePath
) {
262 auto It
= FileStatusCache
.try_emplace(FilePath
);
263 auto &CachedStatus
= It
.first
->getValue();
267 sys::fs::file_status Status
;
268 if (!sys::fs::status(FilePath
, Status
))
269 CachedStatus
= Status
;
273 bool CodeCoverageTool::isEquivalentFile(StringRef FilePath1
,
274 StringRef FilePath2
) {
275 auto Status1
= getFileStatus(FilePath1
);
276 auto Status2
= getFileStatus(FilePath2
);
277 return Status1
&& Status2
&& sys::fs::equivalent(*Status1
, *Status2
);
280 ErrorOr
<const MemoryBuffer
&>
281 CodeCoverageTool::getSourceFile(StringRef SourceFile
) {
282 // If we've remapped filenames, look up the real location for this file.
283 std::unique_lock
<std::mutex
> Guard
{LoadedSourceFilesLock
};
284 if (!RemappedFilenames
.empty()) {
285 auto Loc
= RemappedFilenames
.find(SourceFile
);
286 if (Loc
!= RemappedFilenames
.end())
287 SourceFile
= Loc
->second
;
289 for (const auto &Files
: LoadedSourceFiles
)
290 if (isEquivalentFile(SourceFile
, Files
.first
))
291 return *Files
.second
;
292 auto Buffer
= MemoryBuffer::getFile(SourceFile
);
293 if (auto EC
= Buffer
.getError()) {
294 error(EC
.message(), SourceFile
);
297 LoadedSourceFiles
.emplace_back(std::string(SourceFile
),
298 std::move(Buffer
.get()));
299 return *LoadedSourceFiles
.back().second
;
302 void CodeCoverageTool::attachExpansionSubViews(
303 SourceCoverageView
&View
, ArrayRef
<ExpansionRecord
> Expansions
,
304 const CoverageMapping
&Coverage
) {
305 if (!ViewOpts
.ShowExpandedRegions
)
307 for (const auto &Expansion
: Expansions
) {
308 auto ExpansionCoverage
= Coverage
.getCoverageForExpansion(Expansion
);
309 if (ExpansionCoverage
.empty())
311 auto SourceBuffer
= getSourceFile(ExpansionCoverage
.getFilename());
315 auto SubViewBranches
= ExpansionCoverage
.getBranches();
316 auto SubViewExpansions
= ExpansionCoverage
.getExpansions();
318 SourceCoverageView::create(Expansion
.Function
.Name
, SourceBuffer
.get(),
319 ViewOpts
, std::move(ExpansionCoverage
));
320 attachExpansionSubViews(*SubView
, SubViewExpansions
, Coverage
);
321 attachBranchSubViews(*SubView
, Expansion
.Function
.Name
, SubViewBranches
,
322 SourceBuffer
.get(), ExpansionCoverage
);
323 View
.addExpansion(Expansion
.Region
, std::move(SubView
));
327 void CodeCoverageTool::attachBranchSubViews(SourceCoverageView
&View
,
328 StringRef SourceName
,
329 ArrayRef
<CountedRegion
> Branches
,
330 const MemoryBuffer
&File
,
331 CoverageData
&CoverageInfo
) {
332 if (!ViewOpts
.ShowBranchCounts
&& !ViewOpts
.ShowBranchPercents
)
335 const auto *NextBranch
= Branches
.begin();
336 const auto *EndBranch
= Branches
.end();
338 // Group branches that have the same line number into the same subview.
339 while (NextBranch
!= EndBranch
) {
340 std::vector
<CountedRegion
> ViewBranches
;
341 unsigned CurrentLine
= NextBranch
->LineStart
;
343 while (NextBranch
!= EndBranch
&& CurrentLine
== NextBranch
->LineStart
)
344 ViewBranches
.push_back(*NextBranch
++);
346 if (!ViewBranches
.empty()) {
347 auto SubView
= SourceCoverageView::create(SourceName
, File
, ViewOpts
,
348 std::move(CoverageInfo
));
349 View
.addBranch(CurrentLine
, ViewBranches
, std::move(SubView
));
354 std::unique_ptr
<SourceCoverageView
>
355 CodeCoverageTool::createFunctionView(const FunctionRecord
&Function
,
356 const CoverageMapping
&Coverage
) {
357 auto FunctionCoverage
= Coverage
.getCoverageForFunction(Function
);
358 if (FunctionCoverage
.empty())
360 auto SourceBuffer
= getSourceFile(FunctionCoverage
.getFilename());
364 auto Branches
= FunctionCoverage
.getBranches();
365 auto Expansions
= FunctionCoverage
.getExpansions();
366 auto View
= SourceCoverageView::create(DC
.demangle(Function
.Name
),
367 SourceBuffer
.get(), ViewOpts
,
368 std::move(FunctionCoverage
));
369 attachExpansionSubViews(*View
, Expansions
, Coverage
);
370 attachBranchSubViews(*View
, DC
.demangle(Function
.Name
), Branches
,
371 SourceBuffer
.get(), FunctionCoverage
);
376 std::unique_ptr
<SourceCoverageView
>
377 CodeCoverageTool::createSourceFileView(StringRef SourceFile
,
378 const CoverageMapping
&Coverage
) {
379 auto SourceBuffer
= getSourceFile(SourceFile
);
382 auto FileCoverage
= Coverage
.getCoverageForFile(SourceFile
);
383 if (FileCoverage
.empty())
386 auto Branches
= FileCoverage
.getBranches();
387 auto Expansions
= FileCoverage
.getExpansions();
388 auto View
= SourceCoverageView::create(SourceFile
, SourceBuffer
.get(),
389 ViewOpts
, std::move(FileCoverage
));
390 attachExpansionSubViews(*View
, Expansions
, Coverage
);
391 attachBranchSubViews(*View
, SourceFile
, Branches
, SourceBuffer
.get(),
393 if (!ViewOpts
.ShowFunctionInstantiations
)
396 for (const auto &Group
: Coverage
.getInstantiationGroups(SourceFile
)) {
397 // Skip functions which have a single instantiation.
398 if (Group
.size() < 2)
401 for (const FunctionRecord
*Function
: Group
.getInstantiations()) {
402 std::unique_ptr
<SourceCoverageView
> SubView
{nullptr};
404 StringRef Funcname
= DC
.demangle(Function
->Name
);
406 if (Function
->ExecutionCount
> 0) {
407 auto SubViewCoverage
= Coverage
.getCoverageForFunction(*Function
);
408 auto SubViewExpansions
= SubViewCoverage
.getExpansions();
409 auto SubViewBranches
= SubViewCoverage
.getBranches();
410 SubView
= SourceCoverageView::create(
411 Funcname
, SourceBuffer
.get(), ViewOpts
, std::move(SubViewCoverage
));
412 attachExpansionSubViews(*SubView
, SubViewExpansions
, Coverage
);
413 attachBranchSubViews(*SubView
, SourceFile
, SubViewBranches
,
414 SourceBuffer
.get(), SubViewCoverage
);
417 unsigned FileID
= Function
->CountedRegions
.front().FileID
;
419 for (const auto &CR
: Function
->CountedRegions
)
420 if (CR
.FileID
== FileID
)
421 Line
= std::max(CR
.LineEnd
, Line
);
422 View
->addInstantiation(Funcname
, Line
, std::move(SubView
));
428 static bool modifiedTimeGT(StringRef LHS
, StringRef RHS
) {
429 sys::fs::file_status Status
;
430 if (sys::fs::status(LHS
, Status
))
432 auto LHSTime
= Status
.getLastModificationTime();
433 if (sys::fs::status(RHS
, Status
))
435 auto RHSTime
= Status
.getLastModificationTime();
436 return LHSTime
> RHSTime
;
439 std::unique_ptr
<CoverageMapping
> CodeCoverageTool::load() {
440 for (StringRef ObjectFilename
: ObjectFilenames
)
441 if (modifiedTimeGT(ObjectFilename
, PGOFilename
))
442 warning("profile data may be out of date - object is newer",
444 auto FS
= vfs::getRealFileSystem();
445 auto CoverageOrErr
= CoverageMapping::load(
446 ObjectFilenames
, PGOFilename
, *FS
, CoverageArches
,
447 ViewOpts
.CompilationDirectory
, BIDFetcher
.get(), CheckBinaryIDs
);
448 if (Error E
= CoverageOrErr
.takeError()) {
449 error("Failed to load coverage: " + toString(std::move(E
)));
452 auto Coverage
= std::move(CoverageOrErr
.get());
453 unsigned Mismatched
= Coverage
->getMismatchedCount();
455 warning(Twine(Mismatched
) + " functions have mismatched data");
457 if (ViewOpts
.Debug
) {
458 for (const auto &HashMismatch
: Coverage
->getHashMismatches())
459 errs() << "hash-mismatch: "
460 << "No profile record found for '" << HashMismatch
.first
<< "'"
461 << " with hash = 0x" << Twine::utohexstr(HashMismatch
.second
)
466 remapPathNames(*Coverage
);
468 if (!SourceFiles
.empty())
469 removeUnmappedInputs(*Coverage
);
471 demangleSymbols(*Coverage
);
476 void CodeCoverageTool::remapPathNames(const CoverageMapping
&Coverage
) {
480 // Convert remapping paths to native paths with trailing seperators.
481 auto nativeWithTrailing
= [](StringRef Path
) -> std::string
{
484 SmallString
<128> NativePath
;
485 sys::path::native(Path
, NativePath
);
486 sys::path::remove_dots(NativePath
, true);
487 if (!NativePath
.empty() && !sys::path::is_separator(NativePath
.back()))
488 NativePath
+= sys::path::get_separator();
489 return NativePath
.c_str();
491 std::string RemapFrom
= nativeWithTrailing(PathRemapping
->first
);
492 std::string RemapTo
= nativeWithTrailing(PathRemapping
->second
);
494 // Create a mapping from coverage data file paths to local paths.
495 for (StringRef Filename
: Coverage
.getUniqueSourceFiles()) {
496 SmallString
<128> NativeFilename
;
497 sys::path::native(Filename
, NativeFilename
);
498 sys::path::remove_dots(NativeFilename
, true);
499 if (NativeFilename
.startswith(RemapFrom
)) {
500 RemappedFilenames
[Filename
] =
501 RemapTo
+ NativeFilename
.substr(RemapFrom
.size()).str();
505 // Convert input files from local paths to coverage data file paths.
506 StringMap
<std::string
> InvRemappedFilenames
;
507 for (const auto &RemappedFilename
: RemappedFilenames
)
508 InvRemappedFilenames
[RemappedFilename
.getValue()] =
509 std::string(RemappedFilename
.getKey());
511 for (std::string
&Filename
: SourceFiles
) {
512 SmallString
<128> NativeFilename
;
513 sys::path::native(Filename
, NativeFilename
);
514 auto CovFileName
= InvRemappedFilenames
.find(NativeFilename
);
515 if (CovFileName
!= InvRemappedFilenames
.end())
516 Filename
= CovFileName
->second
;
520 void CodeCoverageTool::removeUnmappedInputs(const CoverageMapping
&Coverage
) {
521 std::vector
<StringRef
> CoveredFiles
= Coverage
.getUniqueSourceFiles();
523 // The user may have specified source files which aren't in the coverage
524 // mapping. Filter these files away.
525 llvm::erase_if(SourceFiles
, [&](const std::string
&SF
) {
526 return !std::binary_search(CoveredFiles
.begin(), CoveredFiles
.end(), SF
);
530 void CodeCoverageTool::demangleSymbols(const CoverageMapping
&Coverage
) {
531 if (!ViewOpts
.hasDemangler())
534 // Pass function names to the demangler in a temporary file.
536 SmallString
<256> InputPath
;
538 sys::fs::createTemporaryFile("demangle-in", "list", InputFD
, InputPath
);
540 error(InputPath
, EC
.message());
543 ToolOutputFile InputTOF
{InputPath
, InputFD
};
545 unsigned NumSymbols
= 0;
546 for (const auto &Function
: Coverage
.getCoveredFunctions()) {
547 InputTOF
.os() << Function
.Name
<< '\n';
550 InputTOF
.os().close();
552 // Use another temporary file to store the demangler's output.
554 SmallString
<256> OutputPath
;
555 EC
= sys::fs::createTemporaryFile("demangle-out", "list", OutputFD
,
558 error(OutputPath
, EC
.message());
561 ToolOutputFile OutputTOF
{OutputPath
, OutputFD
};
562 OutputTOF
.os().close();
564 // Invoke the demangler.
565 std::vector
<StringRef
> ArgsV
;
566 ArgsV
.reserve(ViewOpts
.DemanglerOpts
.size());
567 for (StringRef Arg
: ViewOpts
.DemanglerOpts
)
568 ArgsV
.push_back(Arg
);
569 std::optional
<StringRef
> Redirects
[] = {
570 InputPath
.str(), OutputPath
.str(), {""}};
573 sys::ExecuteAndWait(ViewOpts
.DemanglerOpts
[0], ArgsV
,
574 /*env=*/std::nullopt
, Redirects
, /*secondsToWait=*/0,
575 /*memoryLimit=*/0, &ErrMsg
);
577 error(ErrMsg
, ViewOpts
.DemanglerOpts
[0]);
581 // Parse the demangler's output.
582 auto BufOrError
= MemoryBuffer::getFile(OutputPath
);
584 error(OutputPath
, BufOrError
.getError().message());
588 std::unique_ptr
<MemoryBuffer
> DemanglerBuf
= std::move(*BufOrError
);
590 SmallVector
<StringRef
, 8> Symbols
;
591 StringRef DemanglerData
= DemanglerBuf
->getBuffer();
592 DemanglerData
.split(Symbols
, '\n', /*MaxSplit=*/NumSymbols
,
593 /*KeepEmpty=*/false);
594 if (Symbols
.size() != NumSymbols
) {
595 error("Demangler did not provide expected number of symbols");
599 // Cache the demangled names.
601 for (const auto &Function
: Coverage
.getCoveredFunctions())
602 // On Windows, lines in the demangler's output file end with "\r\n".
603 // Splitting by '\n' keeps '\r's, so cut them now.
604 DC
.DemangledNames
[Function
.Name
] = std::string(Symbols
[I
++].rtrim());
607 void CodeCoverageTool::writeSourceFileView(StringRef SourceFile
,
608 CoverageMapping
*Coverage
,
609 CoveragePrinter
*Printer
,
610 bool ShowFilenames
) {
611 auto View
= createSourceFileView(SourceFile
, *Coverage
);
613 warning("The file '" + SourceFile
+ "' isn't covered.");
617 auto OSOrErr
= Printer
->createViewFile(SourceFile
, /*InToplevel=*/false);
618 if (Error E
= OSOrErr
.takeError()) {
619 error("Could not create view file!", toString(std::move(E
)));
622 auto OS
= std::move(OSOrErr
.get());
624 View
->print(*OS
.get(), /*Wholefile=*/true,
625 /*ShowSourceName=*/ShowFilenames
,
626 /*ShowTitle=*/ViewOpts
.hasOutputDirectory());
627 Printer
->closeViewFile(std::move(OS
));
630 int CodeCoverageTool::run(Command Cmd
, int argc
, const char **argv
) {
631 cl::opt
<std::string
> CovFilename(
632 cl::Positional
, cl::desc("Covered executable or object file."));
634 cl::list
<std::string
> CovFilenames(
635 "object", cl::desc("Coverage executable or object file"));
637 cl::opt
<bool> DebugDumpCollectedObjects(
638 "dump-collected-objects", cl::Optional
, cl::Hidden
,
639 cl::desc("Show the collected coverage object files"));
641 cl::list
<std::string
> InputSourceFiles("sources", cl::Positional
,
642 cl::desc("<Source files>"));
644 cl::opt
<bool> DebugDumpCollectedPaths(
645 "dump-collected-paths", cl::Optional
, cl::Hidden
,
646 cl::desc("Show the collected paths to source files"));
648 cl::opt
<std::string
, true> PGOFilename(
649 "instr-profile", cl::Required
, cl::location(this->PGOFilename
),
651 "File with the profile data obtained after an instrumented run"));
653 cl::list
<std::string
> Arches(
654 "arch", cl::desc("architectures of the coverage mapping binaries"));
656 cl::opt
<bool> DebugDump("dump", cl::Optional
,
657 cl::desc("Show internal debug dump"));
659 cl::list
<std::string
> DebugFileDirectory(
660 "debug-file-directory",
661 cl::desc("Directories to search for object files by build ID"));
662 cl::opt
<bool> Debuginfod(
663 "debuginfod", cl::ZeroOrMore
,
664 cl::desc("Use debuginfod to look up object files from profile"),
665 cl::init(canUseDebuginfod()));
667 cl::opt
<CoverageViewOptions::OutputFormat
> Format(
668 "format", cl::desc("Output format for line-based coverage reports"),
669 cl::values(clEnumValN(CoverageViewOptions::OutputFormat::Text
, "text",
671 clEnumValN(CoverageViewOptions::OutputFormat::HTML
, "html",
673 clEnumValN(CoverageViewOptions::OutputFormat::Lcov
, "lcov",
674 "lcov tracefile output")),
675 cl::init(CoverageViewOptions::OutputFormat::Text
));
677 cl::opt
<std::string
> PathRemap(
678 "path-equivalence", cl::Optional
,
679 cl::desc("<from>,<to> Map coverage data paths to local source file "
682 cl::OptionCategory
FilteringCategory("Function filtering options");
684 cl::list
<std::string
> NameFilters(
685 "name", cl::Optional
,
686 cl::desc("Show code coverage only for functions with the given name"),
687 cl::cat(FilteringCategory
));
689 cl::list
<std::string
> NameFilterFiles(
690 "name-allowlist", cl::Optional
,
691 cl::desc("Show code coverage only for functions listed in the given "
693 cl::cat(FilteringCategory
));
695 cl::list
<std::string
> NameRegexFilters(
696 "name-regex", cl::Optional
,
697 cl::desc("Show code coverage only for functions that match the given "
698 "regular expression"),
699 cl::cat(FilteringCategory
));
701 cl::list
<std::string
> IgnoreFilenameRegexFilters(
702 "ignore-filename-regex", cl::Optional
,
703 cl::desc("Skip source code files with file paths that match the given "
704 "regular expression"),
705 cl::cat(FilteringCategory
));
707 cl::opt
<double> RegionCoverageLtFilter(
708 "region-coverage-lt", cl::Optional
,
709 cl::desc("Show code coverage only for functions with region coverage "
710 "less than the given threshold"),
711 cl::cat(FilteringCategory
));
713 cl::opt
<double> RegionCoverageGtFilter(
714 "region-coverage-gt", cl::Optional
,
715 cl::desc("Show code coverage only for functions with region coverage "
716 "greater than the given threshold"),
717 cl::cat(FilteringCategory
));
719 cl::opt
<double> LineCoverageLtFilter(
720 "line-coverage-lt", cl::Optional
,
721 cl::desc("Show code coverage only for functions with line coverage less "
722 "than the given threshold"),
723 cl::cat(FilteringCategory
));
725 cl::opt
<double> LineCoverageGtFilter(
726 "line-coverage-gt", cl::Optional
,
727 cl::desc("Show code coverage only for functions with line coverage "
728 "greater than the given threshold"),
729 cl::cat(FilteringCategory
));
731 cl::opt
<cl::boolOrDefault
> UseColor(
732 "use-color", cl::desc("Emit colored output (default=autodetect)"),
733 cl::init(cl::BOU_UNSET
));
735 cl::list
<std::string
> DemanglerOpts(
736 "Xdemangler", cl::desc("<demangler-path>|<demangler-option>"));
738 cl::opt
<bool> RegionSummary(
739 "show-region-summary", cl::Optional
,
740 cl::desc("Show region statistics in summary table"),
743 cl::opt
<bool> BranchSummary(
744 "show-branch-summary", cl::Optional
,
745 cl::desc("Show branch condition statistics in summary table"),
748 cl::opt
<bool> InstantiationSummary(
749 "show-instantiation-summary", cl::Optional
,
750 cl::desc("Show instantiation statistics in summary table"));
752 cl::opt
<bool> SummaryOnly(
753 "summary-only", cl::Optional
,
754 cl::desc("Export only summary information for each source file"));
756 cl::opt
<unsigned> NumThreads(
757 "num-threads", cl::init(0),
758 cl::desc("Number of merge threads to use (default: autodetect)"));
759 cl::alias
NumThreadsA("j", cl::desc("Alias for --num-threads"),
760 cl::aliasopt(NumThreads
));
762 cl::opt
<std::string
> CompilationDirectory(
763 "compilation-dir", cl::init(""),
764 cl::desc("Directory used as a base for relative coverage mapping paths"));
766 cl::opt
<bool> CheckBinaryIDs(
767 "check-binary-ids", cl::desc("Fail if an object couldn't be found for a "
768 "binary ID in the profile"));
770 auto commandLineParser
= [&, this](int argc
, const char **argv
) -> int {
771 cl::ParseCommandLineOptions(argc
, argv
, "LLVM code coverage tool\n");
772 ViewOpts
.Debug
= DebugDump
;
774 HTTPClient::initialize();
775 BIDFetcher
= std::make_unique
<DebuginfodFetcher
>(DebugFileDirectory
);
777 BIDFetcher
= std::make_unique
<object::BuildIDFetcher
>(DebugFileDirectory
);
779 this->CheckBinaryIDs
= CheckBinaryIDs
;
781 if (!CovFilename
.empty())
782 ObjectFilenames
.emplace_back(CovFilename
);
783 for (const std::string
&Filename
: CovFilenames
)
784 ObjectFilenames
.emplace_back(Filename
);
785 if (ObjectFilenames
.empty() && !Debuginfod
&& DebugFileDirectory
.empty()) {
786 errs() << "No filenames specified!\n";
790 if (DebugDumpCollectedObjects
) {
791 for (StringRef OF
: ObjectFilenames
)
792 outs() << OF
<< '\n';
796 ViewOpts
.Format
= Format
;
797 switch (ViewOpts
.Format
) {
798 case CoverageViewOptions::OutputFormat::Text
:
799 ViewOpts
.Colors
= UseColor
== cl::BOU_UNSET
800 ? sys::Process::StandardOutHasColors()
801 : UseColor
== cl::BOU_TRUE
;
803 case CoverageViewOptions::OutputFormat::HTML
:
804 if (UseColor
== cl::BOU_FALSE
)
805 errs() << "Color output cannot be disabled when generating html.\n";
806 ViewOpts
.Colors
= true;
808 case CoverageViewOptions::OutputFormat::Lcov
:
809 if (UseColor
== cl::BOU_TRUE
)
810 errs() << "Color output cannot be enabled when generating lcov.\n";
811 ViewOpts
.Colors
= false;
815 // If path-equivalence was given and is a comma seperated pair then set
817 if (!PathRemap
.empty()) {
818 auto EquivPair
= StringRef(PathRemap
).split(',');
819 if (EquivPair
.first
.empty() || EquivPair
.second
.empty()) {
820 error("invalid argument '" + PathRemap
+
821 "', must be in format 'from,to'",
822 "-path-equivalence");
826 PathRemapping
= {std::string(EquivPair
.first
),
827 std::string(EquivPair
.second
)};
830 // If a demangler is supplied, check if it exists and register it.
831 if (!DemanglerOpts
.empty()) {
832 auto DemanglerPathOrErr
= sys::findProgramByName(DemanglerOpts
[0]);
833 if (!DemanglerPathOrErr
) {
834 error("Could not find the demangler!",
835 DemanglerPathOrErr
.getError().message());
838 DemanglerOpts
[0] = *DemanglerPathOrErr
;
839 ViewOpts
.DemanglerOpts
.swap(DemanglerOpts
);
842 // Read in -name-allowlist files.
843 if (!NameFilterFiles
.empty()) {
844 std::string SpecialCaseListErr
;
845 NameAllowlist
= SpecialCaseList::create(
846 NameFilterFiles
, *vfs::getRealFileSystem(), SpecialCaseListErr
);
848 error(SpecialCaseListErr
);
851 // Create the function filters
852 if (!NameFilters
.empty() || NameAllowlist
|| !NameRegexFilters
.empty()) {
853 auto NameFilterer
= std::make_unique
<CoverageFilters
>();
854 for (const auto &Name
: NameFilters
)
855 NameFilterer
->push_back(std::make_unique
<NameCoverageFilter
>(Name
));
856 if (NameAllowlist
&& !NameFilterFiles
.empty())
857 NameFilterer
->push_back(
858 std::make_unique
<NameAllowlistCoverageFilter
>(*NameAllowlist
));
859 for (const auto &Regex
: NameRegexFilters
)
860 NameFilterer
->push_back(
861 std::make_unique
<NameRegexCoverageFilter
>(Regex
));
862 Filters
.push_back(std::move(NameFilterer
));
865 if (RegionCoverageLtFilter
.getNumOccurrences() ||
866 RegionCoverageGtFilter
.getNumOccurrences() ||
867 LineCoverageLtFilter
.getNumOccurrences() ||
868 LineCoverageGtFilter
.getNumOccurrences()) {
869 auto StatFilterer
= std::make_unique
<CoverageFilters
>();
870 if (RegionCoverageLtFilter
.getNumOccurrences())
871 StatFilterer
->push_back(std::make_unique
<RegionCoverageFilter
>(
872 RegionCoverageFilter::LessThan
, RegionCoverageLtFilter
));
873 if (RegionCoverageGtFilter
.getNumOccurrences())
874 StatFilterer
->push_back(std::make_unique
<RegionCoverageFilter
>(
875 RegionCoverageFilter::GreaterThan
, RegionCoverageGtFilter
));
876 if (LineCoverageLtFilter
.getNumOccurrences())
877 StatFilterer
->push_back(std::make_unique
<LineCoverageFilter
>(
878 LineCoverageFilter::LessThan
, LineCoverageLtFilter
));
879 if (LineCoverageGtFilter
.getNumOccurrences())
880 StatFilterer
->push_back(std::make_unique
<LineCoverageFilter
>(
881 RegionCoverageFilter::GreaterThan
, LineCoverageGtFilter
));
882 Filters
.push_back(std::move(StatFilterer
));
885 // Create the ignore filename filters.
886 for (const auto &RE
: IgnoreFilenameRegexFilters
)
887 IgnoreFilenameFilters
.push_back(
888 std::make_unique
<NameRegexCoverageFilter
>(RE
));
890 if (!Arches
.empty()) {
891 for (const std::string
&Arch
: Arches
) {
892 if (Triple(Arch
).getArch() == llvm::Triple::ArchType::UnknownArch
) {
893 error("Unknown architecture: " + Arch
);
896 CoverageArches
.emplace_back(Arch
);
898 if (CoverageArches
.size() != 1 &&
899 CoverageArches
.size() != ObjectFilenames
.size()) {
900 error("Number of architectures doesn't match the number of objects");
905 // IgnoreFilenameFilters are applied even when InputSourceFiles specified.
906 for (const std::string
&File
: InputSourceFiles
)
909 if (DebugDumpCollectedPaths
) {
910 for (const std::string
&SF
: SourceFiles
)
911 outs() << SF
<< '\n';
915 ViewOpts
.ShowBranchSummary
= BranchSummary
;
916 ViewOpts
.ShowRegionSummary
= RegionSummary
;
917 ViewOpts
.ShowInstantiationSummary
= InstantiationSummary
;
918 ViewOpts
.ExportSummaryOnly
= SummaryOnly
;
919 ViewOpts
.NumThreads
= NumThreads
;
920 ViewOpts
.CompilationDirectory
= CompilationDirectory
;
927 return doShow(argc
, argv
, commandLineParser
);
929 return doReport(argc
, argv
, commandLineParser
);
931 return doExport(argc
, argv
, commandLineParser
);
936 int CodeCoverageTool::doShow(int argc
, const char **argv
,
937 CommandLineParserType commandLineParser
) {
939 cl::OptionCategory
ViewCategory("Viewing options");
941 cl::opt
<bool> ShowLineExecutionCounts(
942 "show-line-counts", cl::Optional
,
943 cl::desc("Show the execution counts for each line"), cl::init(true),
944 cl::cat(ViewCategory
));
946 cl::opt
<bool> ShowRegions(
947 "show-regions", cl::Optional
,
948 cl::desc("Show the execution counts for each region"),
949 cl::cat(ViewCategory
));
951 cl::opt
<CoverageViewOptions::BranchOutputType
> ShowBranches(
952 "show-branches", cl::Optional
,
953 cl::desc("Show coverage for branch conditions"), cl::cat(ViewCategory
),
954 cl::values(clEnumValN(CoverageViewOptions::BranchOutputType::Count
,
955 "count", "Show True/False counts"),
956 clEnumValN(CoverageViewOptions::BranchOutputType::Percent
,
957 "percent", "Show True/False percent")),
958 cl::init(CoverageViewOptions::BranchOutputType::Off
));
960 cl::opt
<bool> ShowBestLineRegionsCounts(
961 "show-line-counts-or-regions", cl::Optional
,
962 cl::desc("Show the execution counts for each line, or the execution "
963 "counts for each region on lines that have multiple regions"),
964 cl::cat(ViewCategory
));
966 cl::opt
<bool> ShowExpansions("show-expansions", cl::Optional
,
967 cl::desc("Show expanded source regions"),
968 cl::cat(ViewCategory
));
970 cl::opt
<bool> ShowInstantiations("show-instantiations", cl::Optional
,
971 cl::desc("Show function instantiations"),
972 cl::init(true), cl::cat(ViewCategory
));
974 cl::opt
<std::string
> ShowOutputDirectory(
975 "output-dir", cl::init(""),
976 cl::desc("Directory in which coverage information is written out"));
977 cl::alias
ShowOutputDirectoryA("o", cl::desc("Alias for --output-dir"),
978 cl::aliasopt(ShowOutputDirectory
));
980 cl::opt
<uint32_t> TabSize(
981 "tab-size", cl::init(2),
983 "Set tab expansion size for html coverage reports (default = 2)"));
985 cl::opt
<std::string
> ProjectTitle(
986 "project-title", cl::Optional
,
987 cl::desc("Set project title for the coverage report"));
989 cl::opt
<std::string
> CovWatermark(
990 "coverage-watermark", cl::Optional
,
991 cl::desc("<high>,<low> value indicate thresholds for high and low"
992 "coverage watermark"));
994 auto Err
= commandLineParser(argc
, argv
);
998 if (ViewOpts
.Format
== CoverageViewOptions::OutputFormat::Lcov
) {
999 error("Lcov format should be used with 'llvm-cov export'.");
1003 ViewOpts
.HighCovWatermark
= 100.0;
1004 ViewOpts
.LowCovWatermark
= 80.0;
1005 if (!CovWatermark
.empty()) {
1006 auto WaterMarkPair
= StringRef(CovWatermark
).split(',');
1007 if (WaterMarkPair
.first
.empty() || WaterMarkPair
.second
.empty()) {
1008 error("invalid argument '" + CovWatermark
+
1009 "', must be in format 'high,low'",
1010 "-coverage-watermark");
1014 char *EndPointer
= nullptr;
1015 ViewOpts
.HighCovWatermark
=
1016 strtod(WaterMarkPair
.first
.begin(), &EndPointer
);
1017 if (EndPointer
!= WaterMarkPair
.first
.end()) {
1018 error("invalid number '" + WaterMarkPair
.first
+
1019 "', invalid value for 'high'",
1020 "-coverage-watermark");
1024 ViewOpts
.LowCovWatermark
=
1025 strtod(WaterMarkPair
.second
.begin(), &EndPointer
);
1026 if (EndPointer
!= WaterMarkPair
.second
.end()) {
1027 error("invalid number '" + WaterMarkPair
.second
+
1028 "', invalid value for 'low'",
1029 "-coverage-watermark");
1033 if (ViewOpts
.HighCovWatermark
> 100 || ViewOpts
.LowCovWatermark
< 0 ||
1034 ViewOpts
.HighCovWatermark
<= ViewOpts
.LowCovWatermark
) {
1036 "invalid number range '" + CovWatermark
+
1037 "', must be both high and low should be between 0-100, and high "
1039 "-coverage-watermark");
1044 ViewOpts
.ShowLineNumbers
= true;
1045 ViewOpts
.ShowLineStats
= ShowLineExecutionCounts
.getNumOccurrences() != 0 ||
1046 !ShowRegions
|| ShowBestLineRegionsCounts
;
1047 ViewOpts
.ShowRegionMarkers
= ShowRegions
|| ShowBestLineRegionsCounts
;
1048 ViewOpts
.ShowExpandedRegions
= ShowExpansions
;
1049 ViewOpts
.ShowBranchCounts
=
1050 ShowBranches
== CoverageViewOptions::BranchOutputType::Count
;
1051 ViewOpts
.ShowBranchPercents
=
1052 ShowBranches
== CoverageViewOptions::BranchOutputType::Percent
;
1053 ViewOpts
.ShowFunctionInstantiations
= ShowInstantiations
;
1054 ViewOpts
.ShowOutputDirectory
= ShowOutputDirectory
;
1055 ViewOpts
.TabSize
= TabSize
;
1056 ViewOpts
.ProjectTitle
= ProjectTitle
;
1058 if (ViewOpts
.hasOutputDirectory()) {
1059 if (auto E
= sys::fs::create_directories(ViewOpts
.ShowOutputDirectory
)) {
1060 error("Could not create output directory!", E
.message());
1065 sys::fs::file_status Status
;
1066 if (std::error_code EC
= sys::fs::status(PGOFilename
, Status
)) {
1067 error("Could not read profile data!" + EC
.message(), PGOFilename
);
1071 auto ModifiedTime
= Status
.getLastModificationTime();
1072 std::string ModifiedTimeStr
= to_string(ModifiedTime
);
1073 size_t found
= ModifiedTimeStr
.rfind(':');
1074 ViewOpts
.CreatedTimeStr
= (found
!= std::string::npos
)
1075 ? "Created: " + ModifiedTimeStr
.substr(0, found
)
1076 : "Created: " + ModifiedTimeStr
;
1078 auto Coverage
= load();
1082 auto Printer
= CoveragePrinter::create(ViewOpts
);
1084 if (SourceFiles
.empty() && !HadSourceFiles
)
1085 // Get the source files from the function coverage mapping.
1086 for (StringRef Filename
: Coverage
->getUniqueSourceFiles()) {
1087 if (!IgnoreFilenameFilters
.matchesFilename(Filename
))
1088 SourceFiles
.push_back(std::string(Filename
));
1091 // Create an index out of the source files.
1092 if (ViewOpts
.hasOutputDirectory()) {
1093 if (Error E
= Printer
->createIndexFile(SourceFiles
, *Coverage
, Filters
)) {
1094 error("Could not create index file!", toString(std::move(E
)));
1099 if (!Filters
.empty()) {
1100 // Build the map of filenames to functions.
1101 std::map
<llvm::StringRef
, std::vector
<const FunctionRecord
*>>
1102 FilenameFunctionMap
;
1103 for (const auto &SourceFile
: SourceFiles
)
1104 for (const auto &Function
: Coverage
->getCoveredFunctions(SourceFile
))
1105 if (Filters
.matches(*Coverage
, Function
))
1106 FilenameFunctionMap
[SourceFile
].push_back(&Function
);
1108 // Only print filter matching functions for each file.
1109 for (const auto &FileFunc
: FilenameFunctionMap
) {
1110 StringRef File
= FileFunc
.first
;
1111 const auto &Functions
= FileFunc
.second
;
1113 auto OSOrErr
= Printer
->createViewFile(File
, /*InToplevel=*/false);
1114 if (Error E
= OSOrErr
.takeError()) {
1115 error("Could not create view file!", toString(std::move(E
)));
1118 auto OS
= std::move(OSOrErr
.get());
1120 bool ShowTitle
= ViewOpts
.hasOutputDirectory();
1121 for (const auto *Function
: Functions
) {
1122 auto FunctionView
= createFunctionView(*Function
, *Coverage
);
1123 if (!FunctionView
) {
1124 warning("Could not read coverage for '" + Function
->Name
+ "'.");
1127 FunctionView
->print(*OS
.get(), /*WholeFile=*/false,
1128 /*ShowSourceName=*/true, ShowTitle
);
1132 Printer
->closeViewFile(std::move(OS
));
1138 bool ShowFilenames
=
1139 (SourceFiles
.size() != 1) || ViewOpts
.hasOutputDirectory() ||
1140 (ViewOpts
.Format
== CoverageViewOptions::OutputFormat::HTML
);
1142 ThreadPoolStrategy S
= hardware_concurrency(ViewOpts
.NumThreads
);
1143 if (ViewOpts
.NumThreads
== 0) {
1144 // If NumThreads is not specified, create one thread for each input, up to
1145 // the number of hardware cores.
1146 S
= heavyweight_hardware_concurrency(SourceFiles
.size());
1150 if (!ViewOpts
.hasOutputDirectory() || S
.ThreadsRequested
== 1) {
1151 for (const std::string
&SourceFile
: SourceFiles
)
1152 writeSourceFileView(SourceFile
, Coverage
.get(), Printer
.get(),
1155 // In -output-dir mode, it's safe to use multiple threads to print files.
1157 for (const std::string
&SourceFile
: SourceFiles
)
1158 Pool
.async(&CodeCoverageTool::writeSourceFileView
, this, SourceFile
,
1159 Coverage
.get(), Printer
.get(), ShowFilenames
);
1166 int CodeCoverageTool::doReport(int argc
, const char **argv
,
1167 CommandLineParserType commandLineParser
) {
1168 cl::opt
<bool> ShowFunctionSummaries(
1169 "show-functions", cl::Optional
, cl::init(false),
1170 cl::desc("Show coverage summaries for each function"));
1172 auto Err
= commandLineParser(argc
, argv
);
1176 if (ViewOpts
.Format
== CoverageViewOptions::OutputFormat::HTML
) {
1177 error("HTML output for summary reports is not yet supported.");
1179 } else if (ViewOpts
.Format
== CoverageViewOptions::OutputFormat::Lcov
) {
1180 error("Lcov format should be used with 'llvm-cov export'.");
1184 sys::fs::file_status Status
;
1185 if (std::error_code EC
= sys::fs::status(PGOFilename
, Status
)) {
1186 error("Could not read profile data!" + EC
.message(), PGOFilename
);
1190 auto Coverage
= load();
1194 CoverageReport
Report(ViewOpts
, *Coverage
);
1195 if (!ShowFunctionSummaries
) {
1196 if (SourceFiles
.empty())
1197 Report
.renderFileReports(llvm::outs(), IgnoreFilenameFilters
);
1199 Report
.renderFileReports(llvm::outs(), SourceFiles
);
1201 if (SourceFiles
.empty()) {
1202 error("Source files must be specified when -show-functions=true is "
1207 Report
.renderFunctionReports(SourceFiles
, DC
, llvm::outs());
1212 int CodeCoverageTool::doExport(int argc
, const char **argv
,
1213 CommandLineParserType commandLineParser
) {
1215 cl::OptionCategory
ExportCategory("Exporting options");
1217 cl::opt
<bool> SkipExpansions("skip-expansions", cl::Optional
,
1218 cl::desc("Don't export expanded source regions"),
1219 cl::cat(ExportCategory
));
1221 cl::opt
<bool> SkipFunctions("skip-functions", cl::Optional
,
1222 cl::desc("Don't export per-function data"),
1223 cl::cat(ExportCategory
));
1225 cl::opt
<bool> SkipBranches("skip-branches", cl::Optional
,
1226 cl::desc("Don't export branch data (LCOV)"),
1227 cl::cat(ExportCategory
));
1229 auto Err
= commandLineParser(argc
, argv
);
1233 ViewOpts
.SkipExpansions
= SkipExpansions
;
1234 ViewOpts
.SkipFunctions
= SkipFunctions
;
1235 ViewOpts
.SkipBranches
= SkipBranches
;
1237 if (ViewOpts
.Format
!= CoverageViewOptions::OutputFormat::Text
&&
1238 ViewOpts
.Format
!= CoverageViewOptions::OutputFormat::Lcov
) {
1239 error("Coverage data can only be exported as textual JSON or an "
1244 sys::fs::file_status Status
;
1245 if (std::error_code EC
= sys::fs::status(PGOFilename
, Status
)) {
1246 error("Could not read profile data!" + EC
.message(), PGOFilename
);
1250 auto Coverage
= load();
1252 error("Could not load coverage information");
1256 std::unique_ptr
<CoverageExporter
> Exporter
;
1258 switch (ViewOpts
.Format
) {
1259 case CoverageViewOptions::OutputFormat::Text
:
1261 std::make_unique
<CoverageExporterJson
>(*Coverage
, ViewOpts
, outs());
1263 case CoverageViewOptions::OutputFormat::HTML
:
1264 // Unreachable because we should have gracefully terminated with an error
1266 llvm_unreachable("Export in HTML is not supported!");
1267 case CoverageViewOptions::OutputFormat::Lcov
:
1269 std::make_unique
<CoverageExporterLcov
>(*Coverage
, ViewOpts
, outs());
1273 if (SourceFiles
.empty())
1274 Exporter
->renderRoot(IgnoreFilenameFilters
);
1276 Exporter
->renderRoot(SourceFiles
);
1281 int showMain(int argc
, const char *argv
[]) {
1282 CodeCoverageTool Tool
;
1283 return Tool
.run(CodeCoverageTool::Show
, argc
, argv
);
1286 int reportMain(int argc
, const char *argv
[]) {
1287 CodeCoverageTool Tool
;
1288 return Tool
.run(CodeCoverageTool::Report
, argc
, argv
);
1291 int exportMain(int argc
, const char *argv
[]) {
1292 CodeCoverageTool Tool
;
1293 return Tool
.run(CodeCoverageTool::Export
, argc
, argv
);