Path: blob/main/contrib/llvm-project/llvm/tools/llvm-cov/CodeCoverage.cpp
35231 views
//===- CodeCoverage.cpp - Coverage tool based on profiling instrumentation-===//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// The 'CodeCoverageTool' class implements a command line tool to analyze and9// report coverage information using the profiling instrumentation and code10// coverage mapping.11//12//===----------------------------------------------------------------------===//1314#include "CoverageExporterJson.h"15#include "CoverageExporterLcov.h"16#include "CoverageFilters.h"17#include "CoverageReport.h"18#include "CoverageSummaryInfo.h"19#include "CoverageViewOptions.h"20#include "RenderingSupport.h"21#include "SourceCoverageView.h"22#include "llvm/ADT/SmallString.h"23#include "llvm/ADT/StringRef.h"24#include "llvm/Debuginfod/BuildIDFetcher.h"25#include "llvm/Debuginfod/Debuginfod.h"26#include "llvm/Debuginfod/HTTPClient.h"27#include "llvm/Object/BuildID.h"28#include "llvm/ProfileData/Coverage/CoverageMapping.h"29#include "llvm/ProfileData/InstrProfReader.h"30#include "llvm/Support/CommandLine.h"31#include "llvm/Support/FileSystem.h"32#include "llvm/Support/Format.h"33#include "llvm/Support/MemoryBuffer.h"34#include "llvm/Support/Path.h"35#include "llvm/Support/Process.h"36#include "llvm/Support/Program.h"37#include "llvm/Support/ScopedPrinter.h"38#include "llvm/Support/SpecialCaseList.h"39#include "llvm/Support/ThreadPool.h"40#include "llvm/Support/Threading.h"41#include "llvm/Support/ToolOutputFile.h"42#include "llvm/Support/VirtualFileSystem.h"43#include "llvm/TargetParser/Triple.h"4445#include <functional>46#include <map>47#include <optional>48#include <system_error>4950using namespace llvm;51using namespace coverage;5253void exportCoverageDataToJson(const coverage::CoverageMapping &CoverageMapping,54const CoverageViewOptions &Options,55raw_ostream &OS);5657namespace {58/// The implementation of the coverage tool.59class CodeCoverageTool {60public:61enum Command {62/// The show command.63Show,64/// The report command.65Report,66/// The export command.67Export68};6970int run(Command Cmd, int argc, const char **argv);7172private:73/// Print the error message to the error output stream.74void error(const Twine &Message, StringRef Whence = "");7576/// Print the warning message to the error output stream.77void warning(const Twine &Message, StringRef Whence = "");7879/// Convert \p Path into an absolute path and append it to the list80/// of collected paths.81void addCollectedPath(const std::string &Path);8283/// If \p Path is a regular file, collect the path. If it's a84/// directory, recursively collect all of the paths within the directory.85void collectPaths(const std::string &Path);8687/// Check if the two given files are the same file.88bool isEquivalentFile(StringRef FilePath1, StringRef FilePath2);8990/// Retrieve a file status with a cache.91std::optional<sys::fs::file_status> getFileStatus(StringRef FilePath);9293/// Return a memory buffer for the given source file.94ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile);9596/// Create source views for the expansions of the view.97void attachExpansionSubViews(SourceCoverageView &View,98ArrayRef<ExpansionRecord> Expansions,99const CoverageMapping &Coverage);100101/// Create source views for the branches of the view.102void attachBranchSubViews(SourceCoverageView &View,103ArrayRef<CountedRegion> Branches);104105/// Create source views for the MCDC records.106void attachMCDCSubViews(SourceCoverageView &View,107ArrayRef<MCDCRecord> MCDCRecords);108109/// Create the source view of a particular function.110std::unique_ptr<SourceCoverageView>111createFunctionView(const FunctionRecord &Function,112const CoverageMapping &Coverage);113114/// Create the main source view of a particular source file.115std::unique_ptr<SourceCoverageView>116createSourceFileView(StringRef SourceFile, const CoverageMapping &Coverage);117118/// Load the coverage mapping data. Return nullptr if an error occurred.119std::unique_ptr<CoverageMapping> load();120121/// Create a mapping from files in the Coverage data to local copies122/// (path-equivalence).123void remapPathNames(const CoverageMapping &Coverage);124125/// Remove input source files which aren't mapped by \p Coverage.126void removeUnmappedInputs(const CoverageMapping &Coverage);127128/// If a demangler is available, demangle all symbol names.129void demangleSymbols(const CoverageMapping &Coverage);130131/// Write out a source file view to the filesystem.132void writeSourceFileView(StringRef SourceFile, CoverageMapping *Coverage,133CoveragePrinter *Printer, bool ShowFilenames);134135typedef llvm::function_ref<int(int, const char **)> CommandLineParserType;136137int doShow(int argc, const char **argv,138CommandLineParserType commandLineParser);139140int doReport(int argc, const char **argv,141CommandLineParserType commandLineParser);142143int doExport(int argc, const char **argv,144CommandLineParserType commandLineParser);145146std::vector<StringRef> ObjectFilenames;147CoverageViewOptions ViewOpts;148CoverageFiltersMatchAll Filters;149CoverageFilters IgnoreFilenameFilters;150151/// True if InputSourceFiles are provided.152bool HadSourceFiles = false;153154/// The path to the indexed profile.155std::string PGOFilename;156157/// A list of input source files.158std::vector<std::string> SourceFiles;159160/// In -path-equivalence mode, this maps the absolute paths from the coverage161/// mapping data to the input source files.162StringMap<std::string> RemappedFilenames;163164/// The coverage data path to be remapped from, and the source path to be165/// remapped to, when using -path-equivalence.166std::optional<std::vector<std::pair<std::string, std::string>>>167PathRemappings;168169/// File status cache used when finding the same file.170StringMap<std::optional<sys::fs::file_status>> FileStatusCache;171172/// The architecture the coverage mapping data targets.173std::vector<StringRef> CoverageArches;174175/// A cache for demangled symbols.176DemangleCache DC;177178/// A lock which guards printing to stderr.179std::mutex ErrsLock;180181/// A container for input source file buffers.182std::mutex LoadedSourceFilesLock;183std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>>184LoadedSourceFiles;185186/// Allowlist from -name-allowlist to be used for filtering.187std::unique_ptr<SpecialCaseList> NameAllowlist;188189std::unique_ptr<object::BuildIDFetcher> BIDFetcher;190191bool CheckBinaryIDs;192};193}194195static std::string getErrorString(const Twine &Message, StringRef Whence,196bool Warning) {197std::string Str = (Warning ? "warning" : "error");198Str += ": ";199if (!Whence.empty())200Str += Whence.str() + ": ";201Str += Message.str() + "\n";202return Str;203}204205void CodeCoverageTool::error(const Twine &Message, StringRef Whence) {206std::unique_lock<std::mutex> Guard{ErrsLock};207ViewOpts.colored_ostream(errs(), raw_ostream::RED)208<< getErrorString(Message, Whence, false);209}210211void CodeCoverageTool::warning(const Twine &Message, StringRef Whence) {212std::unique_lock<std::mutex> Guard{ErrsLock};213ViewOpts.colored_ostream(errs(), raw_ostream::RED)214<< getErrorString(Message, Whence, true);215}216217void CodeCoverageTool::addCollectedPath(const std::string &Path) {218SmallString<128> EffectivePath(Path);219if (std::error_code EC = sys::fs::make_absolute(EffectivePath)) {220error(EC.message(), Path);221return;222}223sys::path::remove_dots(EffectivePath, /*remove_dot_dot=*/true);224if (!IgnoreFilenameFilters.matchesFilename(EffectivePath))225SourceFiles.emplace_back(EffectivePath.str());226HadSourceFiles = !SourceFiles.empty();227}228229void CodeCoverageTool::collectPaths(const std::string &Path) {230llvm::sys::fs::file_status Status;231llvm::sys::fs::status(Path, Status);232if (!llvm::sys::fs::exists(Status)) {233if (PathRemappings)234addCollectedPath(Path);235else236warning("Source file doesn't exist, proceeded by ignoring it.", Path);237return;238}239240if (llvm::sys::fs::is_regular_file(Status)) {241addCollectedPath(Path);242return;243}244245if (llvm::sys::fs::is_directory(Status)) {246std::error_code EC;247for (llvm::sys::fs::recursive_directory_iterator F(Path, EC), E;248F != E; F.increment(EC)) {249250auto Status = F->status();251if (!Status) {252warning(Status.getError().message(), F->path());253continue;254}255256if (Status->type() == llvm::sys::fs::file_type::regular_file)257addCollectedPath(F->path());258}259}260}261262std::optional<sys::fs::file_status>263CodeCoverageTool::getFileStatus(StringRef FilePath) {264auto It = FileStatusCache.try_emplace(FilePath);265auto &CachedStatus = It.first->getValue();266if (!It.second)267return CachedStatus;268269sys::fs::file_status Status;270if (!sys::fs::status(FilePath, Status))271CachedStatus = Status;272return CachedStatus;273}274275bool CodeCoverageTool::isEquivalentFile(StringRef FilePath1,276StringRef FilePath2) {277auto Status1 = getFileStatus(FilePath1);278auto Status2 = getFileStatus(FilePath2);279return Status1 && Status2 && sys::fs::equivalent(*Status1, *Status2);280}281282ErrorOr<const MemoryBuffer &>283CodeCoverageTool::getSourceFile(StringRef SourceFile) {284// If we've remapped filenames, look up the real location for this file.285std::unique_lock<std::mutex> Guard{LoadedSourceFilesLock};286if (!RemappedFilenames.empty()) {287auto Loc = RemappedFilenames.find(SourceFile);288if (Loc != RemappedFilenames.end())289SourceFile = Loc->second;290}291for (const auto &Files : LoadedSourceFiles)292if (isEquivalentFile(SourceFile, Files.first))293return *Files.second;294auto Buffer = MemoryBuffer::getFile(SourceFile);295if (auto EC = Buffer.getError()) {296error(EC.message(), SourceFile);297return EC;298}299LoadedSourceFiles.emplace_back(std::string(SourceFile),300std::move(Buffer.get()));301return *LoadedSourceFiles.back().second;302}303304void CodeCoverageTool::attachExpansionSubViews(305SourceCoverageView &View, ArrayRef<ExpansionRecord> Expansions,306const CoverageMapping &Coverage) {307if (!ViewOpts.ShowExpandedRegions)308return;309for (const auto &Expansion : Expansions) {310auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion);311if (ExpansionCoverage.empty())312continue;313auto SourceBuffer = getSourceFile(ExpansionCoverage.getFilename());314if (!SourceBuffer)315continue;316317auto SubViewBranches = ExpansionCoverage.getBranches();318auto SubViewExpansions = ExpansionCoverage.getExpansions();319auto SubView =320SourceCoverageView::create(Expansion.Function.Name, SourceBuffer.get(),321ViewOpts, std::move(ExpansionCoverage));322attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);323attachBranchSubViews(*SubView, SubViewBranches);324View.addExpansion(Expansion.Region, std::move(SubView));325}326}327328void CodeCoverageTool::attachBranchSubViews(SourceCoverageView &View,329ArrayRef<CountedRegion> Branches) {330if (!ViewOpts.ShowBranchCounts && !ViewOpts.ShowBranchPercents)331return;332333const auto *NextBranch = Branches.begin();334const auto *EndBranch = Branches.end();335336// Group branches that have the same line number into the same subview.337while (NextBranch != EndBranch) {338SmallVector<CountedRegion, 0> ViewBranches;339unsigned CurrentLine = NextBranch->LineStart;340while (NextBranch != EndBranch && CurrentLine == NextBranch->LineStart)341ViewBranches.push_back(*NextBranch++);342343View.addBranch(CurrentLine, std::move(ViewBranches));344}345}346347void CodeCoverageTool::attachMCDCSubViews(SourceCoverageView &View,348ArrayRef<MCDCRecord> MCDCRecords) {349if (!ViewOpts.ShowMCDC)350return;351352const auto *NextRecord = MCDCRecords.begin();353const auto *EndRecord = MCDCRecords.end();354355// Group and process MCDC records that have the same line number into the356// same subview.357while (NextRecord != EndRecord) {358SmallVector<MCDCRecord, 0> ViewMCDCRecords;359unsigned CurrentLine = NextRecord->getDecisionRegion().LineEnd;360while (NextRecord != EndRecord &&361CurrentLine == NextRecord->getDecisionRegion().LineEnd)362ViewMCDCRecords.push_back(*NextRecord++);363364View.addMCDCRecord(CurrentLine, std::move(ViewMCDCRecords));365}366}367368std::unique_ptr<SourceCoverageView>369CodeCoverageTool::createFunctionView(const FunctionRecord &Function,370const CoverageMapping &Coverage) {371auto FunctionCoverage = Coverage.getCoverageForFunction(Function);372if (FunctionCoverage.empty())373return nullptr;374auto SourceBuffer = getSourceFile(FunctionCoverage.getFilename());375if (!SourceBuffer)376return nullptr;377378auto Branches = FunctionCoverage.getBranches();379auto Expansions = FunctionCoverage.getExpansions();380auto MCDCRecords = FunctionCoverage.getMCDCRecords();381auto View = SourceCoverageView::create(DC.demangle(Function.Name),382SourceBuffer.get(), ViewOpts,383std::move(FunctionCoverage));384attachExpansionSubViews(*View, Expansions, Coverage);385attachBranchSubViews(*View, Branches);386attachMCDCSubViews(*View, MCDCRecords);387388return View;389}390391std::unique_ptr<SourceCoverageView>392CodeCoverageTool::createSourceFileView(StringRef SourceFile,393const CoverageMapping &Coverage) {394auto SourceBuffer = getSourceFile(SourceFile);395if (!SourceBuffer)396return nullptr;397auto FileCoverage = Coverage.getCoverageForFile(SourceFile);398if (FileCoverage.empty())399return nullptr;400401auto Branches = FileCoverage.getBranches();402auto Expansions = FileCoverage.getExpansions();403auto MCDCRecords = FileCoverage.getMCDCRecords();404auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(),405ViewOpts, std::move(FileCoverage));406attachExpansionSubViews(*View, Expansions, Coverage);407attachBranchSubViews(*View, Branches);408attachMCDCSubViews(*View, MCDCRecords);409if (!ViewOpts.ShowFunctionInstantiations)410return View;411412for (const auto &Group : Coverage.getInstantiationGroups(SourceFile)) {413// Skip functions which have a single instantiation.414if (Group.size() < 2)415continue;416417for (const FunctionRecord *Function : Group.getInstantiations()) {418std::unique_ptr<SourceCoverageView> SubView{nullptr};419420StringRef Funcname = DC.demangle(Function->Name);421422if (Function->ExecutionCount > 0) {423auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);424auto SubViewExpansions = SubViewCoverage.getExpansions();425auto SubViewBranches = SubViewCoverage.getBranches();426auto SubViewMCDCRecords = SubViewCoverage.getMCDCRecords();427SubView = SourceCoverageView::create(428Funcname, SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));429attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);430attachBranchSubViews(*SubView, SubViewBranches);431attachMCDCSubViews(*SubView, SubViewMCDCRecords);432}433434unsigned FileID = Function->CountedRegions.front().FileID;435unsigned Line = 0;436for (const auto &CR : Function->CountedRegions)437if (CR.FileID == FileID)438Line = std::max(CR.LineEnd, Line);439View->addInstantiation(Funcname, Line, std::move(SubView));440}441}442return View;443}444445static bool modifiedTimeGT(StringRef LHS, StringRef RHS) {446sys::fs::file_status Status;447if (sys::fs::status(LHS, Status))448return false;449auto LHSTime = Status.getLastModificationTime();450if (sys::fs::status(RHS, Status))451return false;452auto RHSTime = Status.getLastModificationTime();453return LHSTime > RHSTime;454}455456std::unique_ptr<CoverageMapping> CodeCoverageTool::load() {457for (StringRef ObjectFilename : ObjectFilenames)458if (modifiedTimeGT(ObjectFilename, PGOFilename))459warning("profile data may be out of date - object is newer",460ObjectFilename);461auto FS = vfs::getRealFileSystem();462auto CoverageOrErr = CoverageMapping::load(463ObjectFilenames, PGOFilename, *FS, CoverageArches,464ViewOpts.CompilationDirectory, BIDFetcher.get(), CheckBinaryIDs);465if (Error E = CoverageOrErr.takeError()) {466error("failed to load coverage: " + toString(std::move(E)));467return nullptr;468}469auto Coverage = std::move(CoverageOrErr.get());470unsigned Mismatched = Coverage->getMismatchedCount();471if (Mismatched) {472warning(Twine(Mismatched) + " functions have mismatched data");473474if (ViewOpts.Debug) {475for (const auto &HashMismatch : Coverage->getHashMismatches())476errs() << "hash-mismatch: "477<< "No profile record found for '" << HashMismatch.first << "'"478<< " with hash = 0x" << Twine::utohexstr(HashMismatch.second)479<< '\n';480}481}482483remapPathNames(*Coverage);484485if (!SourceFiles.empty())486removeUnmappedInputs(*Coverage);487488demangleSymbols(*Coverage);489490return Coverage;491}492493void CodeCoverageTool::remapPathNames(const CoverageMapping &Coverage) {494if (!PathRemappings)495return;496497// Convert remapping paths to native paths with trailing separators.498auto nativeWithTrailing = [](StringRef Path) -> std::string {499if (Path.empty())500return "";501SmallString<128> NativePath;502sys::path::native(Path, NativePath);503sys::path::remove_dots(NativePath, true);504if (!NativePath.empty() && !sys::path::is_separator(NativePath.back()))505NativePath += sys::path::get_separator();506return NativePath.c_str();507};508509for (std::pair<std::string, std::string> &PathRemapping : *PathRemappings) {510std::string RemapFrom = nativeWithTrailing(PathRemapping.first);511std::string RemapTo = nativeWithTrailing(PathRemapping.second);512513// Create a mapping from coverage data file paths to local paths.514for (StringRef Filename : Coverage.getUniqueSourceFiles()) {515if (RemappedFilenames.count(Filename) == 1)516continue;517518SmallString<128> NativeFilename;519sys::path::native(Filename, NativeFilename);520sys::path::remove_dots(NativeFilename, true);521if (NativeFilename.starts_with(RemapFrom)) {522RemappedFilenames[Filename] =523RemapTo + NativeFilename.substr(RemapFrom.size()).str();524}525}526}527528// Convert input files from local paths to coverage data file paths.529StringMap<std::string> InvRemappedFilenames;530for (const auto &RemappedFilename : RemappedFilenames)531InvRemappedFilenames[RemappedFilename.getValue()] =532std::string(RemappedFilename.getKey());533534for (std::string &Filename : SourceFiles) {535SmallString<128> NativeFilename;536sys::path::native(Filename, NativeFilename);537auto CovFileName = InvRemappedFilenames.find(NativeFilename);538if (CovFileName != InvRemappedFilenames.end())539Filename = CovFileName->second;540}541}542543void CodeCoverageTool::removeUnmappedInputs(const CoverageMapping &Coverage) {544std::vector<StringRef> CoveredFiles = Coverage.getUniqueSourceFiles();545546// The user may have specified source files which aren't in the coverage547// mapping. Filter these files away.548llvm::erase_if(SourceFiles, [&](const std::string &SF) {549return !std::binary_search(CoveredFiles.begin(), CoveredFiles.end(), SF);550});551}552553void CodeCoverageTool::demangleSymbols(const CoverageMapping &Coverage) {554if (!ViewOpts.hasDemangler())555return;556557// Pass function names to the demangler in a temporary file.558int InputFD;559SmallString<256> InputPath;560std::error_code EC =561sys::fs::createTemporaryFile("demangle-in", "list", InputFD, InputPath);562if (EC) {563error(InputPath, EC.message());564return;565}566ToolOutputFile InputTOF{InputPath, InputFD};567568unsigned NumSymbols = 0;569for (const auto &Function : Coverage.getCoveredFunctions()) {570InputTOF.os() << Function.Name << '\n';571++NumSymbols;572}573InputTOF.os().close();574575// Use another temporary file to store the demangler's output.576int OutputFD;577SmallString<256> OutputPath;578EC = sys::fs::createTemporaryFile("demangle-out", "list", OutputFD,579OutputPath);580if (EC) {581error(OutputPath, EC.message());582return;583}584ToolOutputFile OutputTOF{OutputPath, OutputFD};585OutputTOF.os().close();586587// Invoke the demangler.588std::vector<StringRef> ArgsV;589ArgsV.reserve(ViewOpts.DemanglerOpts.size());590for (StringRef Arg : ViewOpts.DemanglerOpts)591ArgsV.push_back(Arg);592std::optional<StringRef> Redirects[] = {593InputPath.str(), OutputPath.str(), {""}};594std::string ErrMsg;595int RC =596sys::ExecuteAndWait(ViewOpts.DemanglerOpts[0], ArgsV,597/*env=*/std::nullopt, Redirects, /*secondsToWait=*/0,598/*memoryLimit=*/0, &ErrMsg);599if (RC) {600error(ErrMsg, ViewOpts.DemanglerOpts[0]);601return;602}603604// Parse the demangler's output.605auto BufOrError = MemoryBuffer::getFile(OutputPath);606if (!BufOrError) {607error(OutputPath, BufOrError.getError().message());608return;609}610611std::unique_ptr<MemoryBuffer> DemanglerBuf = std::move(*BufOrError);612613SmallVector<StringRef, 8> Symbols;614StringRef DemanglerData = DemanglerBuf->getBuffer();615DemanglerData.split(Symbols, '\n', /*MaxSplit=*/NumSymbols,616/*KeepEmpty=*/false);617if (Symbols.size() != NumSymbols) {618error("demangler did not provide expected number of symbols");619return;620}621622// Cache the demangled names.623unsigned I = 0;624for (const auto &Function : Coverage.getCoveredFunctions())625// On Windows, lines in the demangler's output file end with "\r\n".626// Splitting by '\n' keeps '\r's, so cut them now.627DC.DemangledNames[Function.Name] = std::string(Symbols[I++].rtrim());628}629630void CodeCoverageTool::writeSourceFileView(StringRef SourceFile,631CoverageMapping *Coverage,632CoveragePrinter *Printer,633bool ShowFilenames) {634auto View = createSourceFileView(SourceFile, *Coverage);635if (!View) {636warning("The file '" + SourceFile + "' isn't covered.");637return;638}639640auto OSOrErr = Printer->createViewFile(SourceFile, /*InToplevel=*/false);641if (Error E = OSOrErr.takeError()) {642error("could not create view file!", toString(std::move(E)));643return;644}645auto OS = std::move(OSOrErr.get());646647View->print(*OS.get(), /*Wholefile=*/true,648/*ShowSourceName=*/ShowFilenames,649/*ShowTitle=*/ViewOpts.hasOutputDirectory());650Printer->closeViewFile(std::move(OS));651}652653int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {654cl::opt<std::string> CovFilename(655cl::Positional, cl::desc("Covered executable or object file."));656657cl::list<std::string> CovFilenames(658"object", cl::desc("Coverage executable or object file"));659660cl::opt<bool> DebugDumpCollectedObjects(661"dump-collected-objects", cl::Optional, cl::Hidden,662cl::desc("Show the collected coverage object files"));663664cl::list<std::string> InputSourceFiles("sources", cl::Positional,665cl::desc("<Source files>"));666667cl::opt<bool> DebugDumpCollectedPaths(668"dump-collected-paths", cl::Optional, cl::Hidden,669cl::desc("Show the collected paths to source files"));670671cl::opt<std::string, true> PGOFilename(672"instr-profile", cl::Required, cl::location(this->PGOFilename),673cl::desc(674"File with the profile data obtained after an instrumented run"));675676cl::list<std::string> Arches(677"arch", cl::desc("architectures of the coverage mapping binaries"));678679cl::opt<bool> DebugDump("dump", cl::Optional,680cl::desc("Show internal debug dump"));681682cl::list<std::string> DebugFileDirectory(683"debug-file-directory",684cl::desc("Directories to search for object files by build ID"));685cl::opt<bool> Debuginfod(686"debuginfod", cl::ZeroOrMore,687cl::desc("Use debuginfod to look up object files from profile"),688cl::init(canUseDebuginfod()));689690cl::opt<CoverageViewOptions::OutputFormat> Format(691"format", cl::desc("Output format for line-based coverage reports"),692cl::values(clEnumValN(CoverageViewOptions::OutputFormat::Text, "text",693"Text output"),694clEnumValN(CoverageViewOptions::OutputFormat::HTML, "html",695"HTML output"),696clEnumValN(CoverageViewOptions::OutputFormat::Lcov, "lcov",697"lcov tracefile output")),698cl::init(CoverageViewOptions::OutputFormat::Text));699700cl::list<std::string> PathRemaps(701"path-equivalence", cl::Optional,702cl::desc("<from>,<to> Map coverage data paths to local source file "703"paths"));704705cl::OptionCategory FilteringCategory("Function filtering options");706707cl::list<std::string> NameFilters(708"name", cl::Optional,709cl::desc("Show code coverage only for functions with the given name"),710cl::cat(FilteringCategory));711712cl::list<std::string> NameFilterFiles(713"name-allowlist", cl::Optional,714cl::desc("Show code coverage only for functions listed in the given "715"file"),716cl::cat(FilteringCategory));717718cl::list<std::string> NameRegexFilters(719"name-regex", cl::Optional,720cl::desc("Show code coverage only for functions that match the given "721"regular expression"),722cl::cat(FilteringCategory));723724cl::list<std::string> IgnoreFilenameRegexFilters(725"ignore-filename-regex", cl::Optional,726cl::desc("Skip source code files with file paths that match the given "727"regular expression"),728cl::cat(FilteringCategory));729730cl::opt<double> RegionCoverageLtFilter(731"region-coverage-lt", cl::Optional,732cl::desc("Show code coverage only for functions with region coverage "733"less than the given threshold"),734cl::cat(FilteringCategory));735736cl::opt<double> RegionCoverageGtFilter(737"region-coverage-gt", cl::Optional,738cl::desc("Show code coverage only for functions with region coverage "739"greater than the given threshold"),740cl::cat(FilteringCategory));741742cl::opt<double> LineCoverageLtFilter(743"line-coverage-lt", cl::Optional,744cl::desc("Show code coverage only for functions with line coverage less "745"than the given threshold"),746cl::cat(FilteringCategory));747748cl::opt<double> LineCoverageGtFilter(749"line-coverage-gt", cl::Optional,750cl::desc("Show code coverage only for functions with line coverage "751"greater than the given threshold"),752cl::cat(FilteringCategory));753754cl::opt<cl::boolOrDefault> UseColor(755"use-color", cl::desc("Emit colored output (default=autodetect)"),756cl::init(cl::BOU_UNSET));757758cl::list<std::string> DemanglerOpts(759"Xdemangler", cl::desc("<demangler-path>|<demangler-option>"));760761cl::opt<bool> RegionSummary(762"show-region-summary", cl::Optional,763cl::desc("Show region statistics in summary table"),764cl::init(true));765766cl::opt<bool> BranchSummary(767"show-branch-summary", cl::Optional,768cl::desc("Show branch condition statistics in summary table"),769cl::init(true));770771cl::opt<bool> MCDCSummary("show-mcdc-summary", cl::Optional,772cl::desc("Show MCDC statistics in summary table"),773cl::init(false));774775cl::opt<bool> InstantiationSummary(776"show-instantiation-summary", cl::Optional,777cl::desc("Show instantiation statistics in summary table"));778779cl::opt<bool> SummaryOnly(780"summary-only", cl::Optional,781cl::desc("Export only summary information for each source file"));782783cl::opt<unsigned> NumThreads(784"num-threads", cl::init(0),785cl::desc("Number of merge threads to use (default: autodetect)"));786cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"),787cl::aliasopt(NumThreads));788789cl::opt<std::string> CompilationDirectory(790"compilation-dir", cl::init(""),791cl::desc("Directory used as a base for relative coverage mapping paths"));792793cl::opt<bool> CheckBinaryIDs(794"check-binary-ids", cl::desc("Fail if an object couldn't be found for a "795"binary ID in the profile"));796797auto commandLineParser = [&, this](int argc, const char **argv) -> int {798cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");799ViewOpts.Debug = DebugDump;800if (Debuginfod) {801HTTPClient::initialize();802BIDFetcher = std::make_unique<DebuginfodFetcher>(DebugFileDirectory);803} else {804BIDFetcher = std::make_unique<object::BuildIDFetcher>(DebugFileDirectory);805}806this->CheckBinaryIDs = CheckBinaryIDs;807808if (!CovFilename.empty())809ObjectFilenames.emplace_back(CovFilename);810for (const std::string &Filename : CovFilenames)811ObjectFilenames.emplace_back(Filename);812if (ObjectFilenames.empty() && !Debuginfod && DebugFileDirectory.empty()) {813errs() << "No filenames specified!\n";814::exit(1);815}816817if (DebugDumpCollectedObjects) {818for (StringRef OF : ObjectFilenames)819outs() << OF << '\n';820::exit(0);821}822823ViewOpts.Format = Format;824switch (ViewOpts.Format) {825case CoverageViewOptions::OutputFormat::Text:826ViewOpts.Colors = UseColor == cl::BOU_UNSET827? sys::Process::StandardOutHasColors()828: UseColor == cl::BOU_TRUE;829break;830case CoverageViewOptions::OutputFormat::HTML:831if (UseColor == cl::BOU_FALSE)832errs() << "Color output cannot be disabled when generating html.\n";833ViewOpts.Colors = true;834break;835case CoverageViewOptions::OutputFormat::Lcov:836if (UseColor == cl::BOU_TRUE)837errs() << "Color output cannot be enabled when generating lcov.\n";838ViewOpts.Colors = false;839break;840}841842if (!PathRemaps.empty()) {843std::vector<std::pair<std::string, std::string>> Remappings;844845for (const std::string &PathRemap : PathRemaps) {846auto EquivPair = StringRef(PathRemap).split(',');847if (EquivPair.first.empty() || EquivPair.second.empty()) {848error("invalid argument '" + PathRemap +849"', must be in format 'from,to'",850"-path-equivalence");851return 1;852}853854Remappings.push_back(855{std::string(EquivPair.first), std::string(EquivPair.second)});856}857858PathRemappings = Remappings;859}860861// If a demangler is supplied, check if it exists and register it.862if (!DemanglerOpts.empty()) {863auto DemanglerPathOrErr = sys::findProgramByName(DemanglerOpts[0]);864if (!DemanglerPathOrErr) {865error("could not find the demangler!",866DemanglerPathOrErr.getError().message());867return 1;868}869DemanglerOpts[0] = *DemanglerPathOrErr;870ViewOpts.DemanglerOpts.swap(DemanglerOpts);871}872873// Read in -name-allowlist files.874if (!NameFilterFiles.empty()) {875std::string SpecialCaseListErr;876NameAllowlist = SpecialCaseList::create(877NameFilterFiles, *vfs::getRealFileSystem(), SpecialCaseListErr);878if (!NameAllowlist)879error(SpecialCaseListErr);880}881882// Create the function filters883if (!NameFilters.empty() || NameAllowlist || !NameRegexFilters.empty()) {884auto NameFilterer = std::make_unique<CoverageFilters>();885for (const auto &Name : NameFilters)886NameFilterer->push_back(std::make_unique<NameCoverageFilter>(Name));887if (NameAllowlist && !NameFilterFiles.empty())888NameFilterer->push_back(889std::make_unique<NameAllowlistCoverageFilter>(*NameAllowlist));890for (const auto &Regex : NameRegexFilters)891NameFilterer->push_back(892std::make_unique<NameRegexCoverageFilter>(Regex));893Filters.push_back(std::move(NameFilterer));894}895896if (RegionCoverageLtFilter.getNumOccurrences() ||897RegionCoverageGtFilter.getNumOccurrences() ||898LineCoverageLtFilter.getNumOccurrences() ||899LineCoverageGtFilter.getNumOccurrences()) {900auto StatFilterer = std::make_unique<CoverageFilters>();901if (RegionCoverageLtFilter.getNumOccurrences())902StatFilterer->push_back(std::make_unique<RegionCoverageFilter>(903RegionCoverageFilter::LessThan, RegionCoverageLtFilter));904if (RegionCoverageGtFilter.getNumOccurrences())905StatFilterer->push_back(std::make_unique<RegionCoverageFilter>(906RegionCoverageFilter::GreaterThan, RegionCoverageGtFilter));907if (LineCoverageLtFilter.getNumOccurrences())908StatFilterer->push_back(std::make_unique<LineCoverageFilter>(909LineCoverageFilter::LessThan, LineCoverageLtFilter));910if (LineCoverageGtFilter.getNumOccurrences())911StatFilterer->push_back(std::make_unique<LineCoverageFilter>(912RegionCoverageFilter::GreaterThan, LineCoverageGtFilter));913Filters.push_back(std::move(StatFilterer));914}915916// Create the ignore filename filters.917for (const auto &RE : IgnoreFilenameRegexFilters)918IgnoreFilenameFilters.push_back(919std::make_unique<NameRegexCoverageFilter>(RE));920921if (!Arches.empty()) {922for (const std::string &Arch : Arches) {923if (Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) {924error("unknown architecture: " + Arch);925return 1;926}927CoverageArches.emplace_back(Arch);928}929if (CoverageArches.size() != 1 &&930CoverageArches.size() != ObjectFilenames.size()) {931error("number of architectures doesn't match the number of objects");932return 1;933}934}935936// IgnoreFilenameFilters are applied even when InputSourceFiles specified.937for (const std::string &File : InputSourceFiles)938collectPaths(File);939940if (DebugDumpCollectedPaths) {941for (const std::string &SF : SourceFiles)942outs() << SF << '\n';943::exit(0);944}945946ViewOpts.ShowMCDCSummary = MCDCSummary;947ViewOpts.ShowBranchSummary = BranchSummary;948ViewOpts.ShowRegionSummary = RegionSummary;949ViewOpts.ShowInstantiationSummary = InstantiationSummary;950ViewOpts.ExportSummaryOnly = SummaryOnly;951ViewOpts.NumThreads = NumThreads;952ViewOpts.CompilationDirectory = CompilationDirectory;953954return 0;955};956957switch (Cmd) {958case Show:959return doShow(argc, argv, commandLineParser);960case Report:961return doReport(argc, argv, commandLineParser);962case Export:963return doExport(argc, argv, commandLineParser);964}965return 0;966}967968int CodeCoverageTool::doShow(int argc, const char **argv,969CommandLineParserType commandLineParser) {970971cl::OptionCategory ViewCategory("Viewing options");972973cl::opt<bool> ShowLineExecutionCounts(974"show-line-counts", cl::Optional,975cl::desc("Show the execution counts for each line"), cl::init(true),976cl::cat(ViewCategory));977978cl::opt<bool> ShowRegions(979"show-regions", cl::Optional,980cl::desc("Show the execution counts for each region"),981cl::cat(ViewCategory));982983cl::opt<CoverageViewOptions::BranchOutputType> ShowBranches(984"show-branches", cl::Optional,985cl::desc("Show coverage for branch conditions"), cl::cat(ViewCategory),986cl::values(clEnumValN(CoverageViewOptions::BranchOutputType::Count,987"count", "Show True/False counts"),988clEnumValN(CoverageViewOptions::BranchOutputType::Percent,989"percent", "Show True/False percent")),990cl::init(CoverageViewOptions::BranchOutputType::Off));991992cl::opt<bool> ShowMCDC(993"show-mcdc", cl::Optional,994cl::desc("Show the MCDC Coverage for each applicable boolean expression"),995cl::cat(ViewCategory));996997cl::opt<bool> ShowBestLineRegionsCounts(998"show-line-counts-or-regions", cl::Optional,999cl::desc("Show the execution counts for each line, or the execution "1000"counts for each region on lines that have multiple regions"),1001cl::cat(ViewCategory));10021003cl::opt<bool> ShowExpansions("show-expansions", cl::Optional,1004cl::desc("Show expanded source regions"),1005cl::cat(ViewCategory));10061007cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional,1008cl::desc("Show function instantiations"),1009cl::init(true), cl::cat(ViewCategory));10101011cl::opt<bool> ShowDirectoryCoverage("show-directory-coverage", cl::Optional,1012cl::desc("Show directory coverage"),1013cl::cat(ViewCategory));10141015cl::opt<std::string> ShowOutputDirectory(1016"output-dir", cl::init(""),1017cl::desc("Directory in which coverage information is written out"));1018cl::alias ShowOutputDirectoryA("o", cl::desc("Alias for --output-dir"),1019cl::aliasopt(ShowOutputDirectory));10201021cl::opt<uint32_t> TabSize(1022"tab-size", cl::init(2),1023cl::desc(1024"Set tab expansion size for html coverage reports (default = 2)"));10251026cl::opt<std::string> ProjectTitle(1027"project-title", cl::Optional,1028cl::desc("Set project title for the coverage report"));10291030cl::opt<std::string> CovWatermark(1031"coverage-watermark", cl::Optional,1032cl::desc("<high>,<low> value indicate thresholds for high and low"1033"coverage watermark"));10341035auto Err = commandLineParser(argc, argv);1036if (Err)1037return Err;10381039if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Lcov) {1040error("lcov format should be used with 'llvm-cov export'.");1041return 1;1042}10431044ViewOpts.HighCovWatermark = 100.0;1045ViewOpts.LowCovWatermark = 80.0;1046if (!CovWatermark.empty()) {1047auto WaterMarkPair = StringRef(CovWatermark).split(',');1048if (WaterMarkPair.first.empty() || WaterMarkPair.second.empty()) {1049error("invalid argument '" + CovWatermark +1050"', must be in format 'high,low'",1051"-coverage-watermark");1052return 1;1053}10541055char *EndPointer = nullptr;1056ViewOpts.HighCovWatermark =1057strtod(WaterMarkPair.first.begin(), &EndPointer);1058if (EndPointer != WaterMarkPair.first.end()) {1059error("invalid number '" + WaterMarkPair.first +1060"', invalid value for 'high'",1061"-coverage-watermark");1062return 1;1063}10641065ViewOpts.LowCovWatermark =1066strtod(WaterMarkPair.second.begin(), &EndPointer);1067if (EndPointer != WaterMarkPair.second.end()) {1068error("invalid number '" + WaterMarkPair.second +1069"', invalid value for 'low'",1070"-coverage-watermark");1071return 1;1072}10731074if (ViewOpts.HighCovWatermark > 100 || ViewOpts.LowCovWatermark < 0 ||1075ViewOpts.HighCovWatermark <= ViewOpts.LowCovWatermark) {1076error(1077"invalid number range '" + CovWatermark +1078"', must be both high and low should be between 0-100, and high "1079"> low",1080"-coverage-watermark");1081return 1;1082}1083}10841085ViewOpts.ShowLineNumbers = true;1086ViewOpts.ShowLineStats = ShowLineExecutionCounts.getNumOccurrences() != 0 ||1087!ShowRegions || ShowBestLineRegionsCounts;1088ViewOpts.ShowRegionMarkers = ShowRegions || ShowBestLineRegionsCounts;1089ViewOpts.ShowExpandedRegions = ShowExpansions;1090ViewOpts.ShowBranchCounts =1091ShowBranches == CoverageViewOptions::BranchOutputType::Count;1092ViewOpts.ShowMCDC = ShowMCDC;1093ViewOpts.ShowBranchPercents =1094ShowBranches == CoverageViewOptions::BranchOutputType::Percent;1095ViewOpts.ShowFunctionInstantiations = ShowInstantiations;1096ViewOpts.ShowDirectoryCoverage = ShowDirectoryCoverage;1097ViewOpts.ShowOutputDirectory = ShowOutputDirectory;1098ViewOpts.TabSize = TabSize;1099ViewOpts.ProjectTitle = ProjectTitle;11001101if (ViewOpts.hasOutputDirectory()) {1102if (auto E = sys::fs::create_directories(ViewOpts.ShowOutputDirectory)) {1103error("could not create output directory!", E.message());1104return 1;1105}1106}11071108sys::fs::file_status Status;1109if (std::error_code EC = sys::fs::status(PGOFilename, Status)) {1110error("could not read profile data!" + EC.message(), PGOFilename);1111return 1;1112}11131114auto ModifiedTime = Status.getLastModificationTime();1115std::string ModifiedTimeStr = to_string(ModifiedTime);1116size_t found = ModifiedTimeStr.rfind(':');1117ViewOpts.CreatedTimeStr = (found != std::string::npos)1118? "Created: " + ModifiedTimeStr.substr(0, found)1119: "Created: " + ModifiedTimeStr;11201121auto Coverage = load();1122if (!Coverage)1123return 1;11241125auto Printer = CoveragePrinter::create(ViewOpts);11261127if (SourceFiles.empty() && !HadSourceFiles)1128// Get the source files from the function coverage mapping.1129for (StringRef Filename : Coverage->getUniqueSourceFiles()) {1130if (!IgnoreFilenameFilters.matchesFilename(Filename))1131SourceFiles.push_back(std::string(Filename));1132}11331134// Create an index out of the source files.1135if (ViewOpts.hasOutputDirectory()) {1136if (Error E = Printer->createIndexFile(SourceFiles, *Coverage, Filters)) {1137error("could not create index file!", toString(std::move(E)));1138return 1;1139}1140}11411142if (!Filters.empty()) {1143// Build the map of filenames to functions.1144std::map<llvm::StringRef, std::vector<const FunctionRecord *>>1145FilenameFunctionMap;1146for (const auto &SourceFile : SourceFiles)1147for (const auto &Function : Coverage->getCoveredFunctions(SourceFile))1148if (Filters.matches(*Coverage, Function))1149FilenameFunctionMap[SourceFile].push_back(&Function);11501151// Only print filter matching functions for each file.1152for (const auto &FileFunc : FilenameFunctionMap) {1153StringRef File = FileFunc.first;1154const auto &Functions = FileFunc.second;11551156auto OSOrErr = Printer->createViewFile(File, /*InToplevel=*/false);1157if (Error E = OSOrErr.takeError()) {1158error("could not create view file!", toString(std::move(E)));1159return 1;1160}1161auto OS = std::move(OSOrErr.get());11621163bool ShowTitle = ViewOpts.hasOutputDirectory();1164for (const auto *Function : Functions) {1165auto FunctionView = createFunctionView(*Function, *Coverage);1166if (!FunctionView) {1167warning("Could not read coverage for '" + Function->Name + "'.");1168continue;1169}1170FunctionView->print(*OS.get(), /*WholeFile=*/false,1171/*ShowSourceName=*/true, ShowTitle);1172ShowTitle = false;1173}11741175Printer->closeViewFile(std::move(OS));1176}1177return 0;1178}11791180// Show files1181bool ShowFilenames =1182(SourceFiles.size() != 1) || ViewOpts.hasOutputDirectory() ||1183(ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML);11841185ThreadPoolStrategy S = hardware_concurrency(ViewOpts.NumThreads);1186if (ViewOpts.NumThreads == 0) {1187// If NumThreads is not specified, create one thread for each input, up to1188// the number of hardware cores.1189S = heavyweight_hardware_concurrency(SourceFiles.size());1190S.Limit = true;1191}11921193if (!ViewOpts.hasOutputDirectory() || S.ThreadsRequested == 1) {1194for (const std::string &SourceFile : SourceFiles)1195writeSourceFileView(SourceFile, Coverage.get(), Printer.get(),1196ShowFilenames);1197} else {1198// In -output-dir mode, it's safe to use multiple threads to print files.1199DefaultThreadPool Pool(S);1200for (const std::string &SourceFile : SourceFiles)1201Pool.async(&CodeCoverageTool::writeSourceFileView, this, SourceFile,1202Coverage.get(), Printer.get(), ShowFilenames);1203Pool.wait();1204}12051206return 0;1207}12081209int CodeCoverageTool::doReport(int argc, const char **argv,1210CommandLineParserType commandLineParser) {1211cl::opt<bool> ShowFunctionSummaries(1212"show-functions", cl::Optional, cl::init(false),1213cl::desc("Show coverage summaries for each function"));12141215auto Err = commandLineParser(argc, argv);1216if (Err)1217return Err;12181219if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML) {1220error("HTML output for summary reports is not yet supported.");1221return 1;1222} else if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Lcov) {1223error("lcov format should be used with 'llvm-cov export'.");1224return 1;1225}12261227sys::fs::file_status Status;1228if (std::error_code EC = sys::fs::status(PGOFilename, Status)) {1229error("could not read profile data!" + EC.message(), PGOFilename);1230return 1;1231}12321233auto Coverage = load();1234if (!Coverage)1235return 1;12361237CoverageReport Report(ViewOpts, *Coverage);1238if (!ShowFunctionSummaries) {1239if (SourceFiles.empty())1240Report.renderFileReports(llvm::outs(), IgnoreFilenameFilters);1241else1242Report.renderFileReports(llvm::outs(), SourceFiles);1243} else {1244if (SourceFiles.empty()) {1245error("source files must be specified when -show-functions=true is "1246"specified");1247return 1;1248}12491250Report.renderFunctionReports(SourceFiles, DC, llvm::outs());1251}1252return 0;1253}12541255int CodeCoverageTool::doExport(int argc, const char **argv,1256CommandLineParserType commandLineParser) {12571258cl::OptionCategory ExportCategory("Exporting options");12591260cl::opt<bool> SkipExpansions("skip-expansions", cl::Optional,1261cl::desc("Don't export expanded source regions"),1262cl::cat(ExportCategory));12631264cl::opt<bool> SkipFunctions("skip-functions", cl::Optional,1265cl::desc("Don't export per-function data"),1266cl::cat(ExportCategory));12671268cl::opt<bool> SkipBranches("skip-branches", cl::Optional,1269cl::desc("Don't export branch data (LCOV)"),1270cl::cat(ExportCategory));12711272auto Err = commandLineParser(argc, argv);1273if (Err)1274return Err;12751276ViewOpts.SkipExpansions = SkipExpansions;1277ViewOpts.SkipFunctions = SkipFunctions;1278ViewOpts.SkipBranches = SkipBranches;12791280if (ViewOpts.Format != CoverageViewOptions::OutputFormat::Text &&1281ViewOpts.Format != CoverageViewOptions::OutputFormat::Lcov) {1282error("coverage data can only be exported as textual JSON or an "1283"lcov tracefile.");1284return 1;1285}12861287sys::fs::file_status Status;1288if (std::error_code EC = sys::fs::status(PGOFilename, Status)) {1289error("could not read profile data!" + EC.message(), PGOFilename);1290return 1;1291}12921293auto Coverage = load();1294if (!Coverage) {1295error("could not load coverage information");1296return 1;1297}12981299std::unique_ptr<CoverageExporter> Exporter;13001301switch (ViewOpts.Format) {1302case CoverageViewOptions::OutputFormat::Text:1303Exporter =1304std::make_unique<CoverageExporterJson>(*Coverage, ViewOpts, outs());1305break;1306case CoverageViewOptions::OutputFormat::HTML:1307// Unreachable because we should have gracefully terminated with an error1308// above.1309llvm_unreachable("Export in HTML is not supported!");1310case CoverageViewOptions::OutputFormat::Lcov:1311Exporter =1312std::make_unique<CoverageExporterLcov>(*Coverage, ViewOpts, outs());1313break;1314}13151316if (SourceFiles.empty())1317Exporter->renderRoot(IgnoreFilenameFilters);1318else1319Exporter->renderRoot(SourceFiles);13201321return 0;1322}13231324int showMain(int argc, const char *argv[]) {1325CodeCoverageTool Tool;1326return Tool.run(CodeCoverageTool::Show, argc, argv);1327}13281329int reportMain(int argc, const char *argv[]) {1330CodeCoverageTool Tool;1331return Tool.run(CodeCoverageTool::Report, argc, argv);1332}13331334int exportMain(int argc, const char *argv[]) {1335CodeCoverageTool Tool;1336return Tool.run(CodeCoverageTool::Export, argc, argv);1337}133813391340