| //===- CodeCoverage.cpp - Coverage tool based on profiling instrumentation-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // The 'CodeCoverageTool' class implements a command line tool to analyze and |
| // report coverage information using the profiling instrumentation and code |
| // coverage mapping. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "CoverageExporterJson.h" |
| #include "CoverageExporterLcov.h" |
| #include "CoverageFilters.h" |
| #include "CoverageReport.h" |
| #include "CoverageSummaryInfo.h" |
| #include "CoverageViewOptions.h" |
| #include "RenderingSupport.h" |
| #include "SourceCoverageView.h" |
| #include "llvm/ADT/SmallString.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/ADT/Triple.h" |
| #include "llvm/ProfileData/Coverage/CoverageMapping.h" |
| #include "llvm/ProfileData/InstrProfReader.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/Format.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/Process.h" |
| #include "llvm/Support/Program.h" |
| #include "llvm/Support/ScopedPrinter.h" |
| #include "llvm/Support/ThreadPool.h" |
| #include "llvm/Support/Threading.h" |
| #include "llvm/Support/ToolOutputFile.h" |
| |
| #include <functional> |
| #include <map> |
| #include <system_error> |
| |
| using namespace llvm; |
| using namespace coverage; |
| |
| void exportCoverageDataToJson(const coverage::CoverageMapping &CoverageMapping, |
| const CoverageViewOptions &Options, |
| raw_ostream &OS); |
| |
| namespace { |
| /// The implementation of the coverage tool. |
| class CodeCoverageTool { |
| public: |
| enum Command { |
| /// The show command. |
| Show, |
| /// The report command. |
| Report, |
| /// The export command. |
| Export |
| }; |
| |
| int run(Command Cmd, int argc, const char **argv); |
| |
| private: |
| /// Print the error message to the error output stream. |
| void error(const Twine &Message, StringRef Whence = ""); |
| |
| /// Print the warning message to the error output stream. |
| void warning(const Twine &Message, StringRef Whence = ""); |
| |
| /// Convert \p Path into an absolute path and append it to the list |
| /// of collected paths. |
| void addCollectedPath(const std::string &Path); |
| |
| /// If \p Path is a regular file, collect the path. If it's a |
| /// directory, recursively collect all of the paths within the directory. |
| void collectPaths(const std::string &Path); |
| |
| /// Return a memory buffer for the given source file. |
| ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile); |
| |
| /// Create source views for the expansions of the view. |
| void attachExpansionSubViews(SourceCoverageView &View, |
| ArrayRef<ExpansionRecord> Expansions, |
| const CoverageMapping &Coverage); |
| |
| /// Create the source view of a particular function. |
| std::unique_ptr<SourceCoverageView> |
| createFunctionView(const FunctionRecord &Function, |
| const CoverageMapping &Coverage); |
| |
| /// Create the main source view of a particular source file. |
| std::unique_ptr<SourceCoverageView> |
| createSourceFileView(StringRef SourceFile, const CoverageMapping &Coverage); |
| |
| /// Load the coverage mapping data. Return nullptr if an error occurred. |
| std::unique_ptr<CoverageMapping> load(); |
| |
| /// Create a mapping from files in the Coverage data to local copies |
| /// (path-equivalence). |
| void remapPathNames(const CoverageMapping &Coverage); |
| |
| /// Remove input source files which aren't mapped by \p Coverage. |
| void removeUnmappedInputs(const CoverageMapping &Coverage); |
| |
| /// If a demangler is available, demangle all symbol names. |
| void demangleSymbols(const CoverageMapping &Coverage); |
| |
| /// Write out a source file view to the filesystem. |
| void writeSourceFileView(StringRef SourceFile, CoverageMapping *Coverage, |
| CoveragePrinter *Printer, bool ShowFilenames); |
| |
| typedef llvm::function_ref<int(int, const char **)> CommandLineParserType; |
| |
| int doShow(int argc, const char **argv, |
| CommandLineParserType commandLineParser); |
| |
| int doReport(int argc, const char **argv, |
| CommandLineParserType commandLineParser); |
| |
| int doExport(int argc, const char **argv, |
| CommandLineParserType commandLineParser); |
| |
| std::vector<StringRef> ObjectFilenames; |
| CoverageViewOptions ViewOpts; |
| CoverageFiltersMatchAll Filters; |
| CoverageFilters IgnoreFilenameFilters; |
| |
| /// The path to the indexed profile. |
| std::string PGOFilename; |
| |
| /// A list of input source files. |
| std::vector<std::string> SourceFiles; |
| |
| /// In -path-equivalence mode, this maps the absolute paths from the coverage |
| /// mapping data to the input source files. |
| StringMap<std::string> RemappedFilenames; |
| |
| /// The coverage data path to be remapped from, and the source path to be |
| /// remapped to, when using -path-equivalence. |
| Optional<std::pair<std::string, std::string>> PathRemapping; |
| |
| /// The architecture the coverage mapping data targets. |
| std::vector<StringRef> CoverageArches; |
| |
| /// A cache for demangled symbols. |
| DemangleCache DC; |
| |
| /// A lock which guards printing to stderr. |
| std::mutex ErrsLock; |
| |
| /// A container for input source file buffers. |
| std::mutex LoadedSourceFilesLock; |
| std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>> |
| LoadedSourceFiles; |
| |
| /// Whitelist from -name-whitelist to be used for filtering. |
| std::unique_ptr<SpecialCaseList> NameWhitelist; |
| }; |
| } |
| |
| static std::string getErrorString(const Twine &Message, StringRef Whence, |
| bool Warning) { |
| std::string Str = (Warning ? "warning" : "error"); |
| Str += ": "; |
| if (!Whence.empty()) |
| Str += Whence.str() + ": "; |
| Str += Message.str() + "\n"; |
| return Str; |
| } |
| |
| void CodeCoverageTool::error(const Twine &Message, StringRef Whence) { |
| std::unique_lock<std::mutex> Guard{ErrsLock}; |
| ViewOpts.colored_ostream(errs(), raw_ostream::RED) |
| << getErrorString(Message, Whence, false); |
| } |
| |
| void CodeCoverageTool::warning(const Twine &Message, StringRef Whence) { |
| std::unique_lock<std::mutex> Guard{ErrsLock}; |
| ViewOpts.colored_ostream(errs(), raw_ostream::RED) |
| << getErrorString(Message, Whence, true); |
| } |
| |
| void CodeCoverageTool::addCollectedPath(const std::string &Path) { |
| SmallString<128> EffectivePath(Path); |
| if (std::error_code EC = sys::fs::make_absolute(EffectivePath)) { |
| error(EC.message(), Path); |
| return; |
| } |
| sys::path::remove_dots(EffectivePath, /*remove_dot_dots=*/true); |
| if (!IgnoreFilenameFilters.matchesFilename(EffectivePath)) |
| SourceFiles.emplace_back(EffectivePath.str()); |
| } |
| |
| void CodeCoverageTool::collectPaths(const std::string &Path) { |
| llvm::sys::fs::file_status Status; |
| llvm::sys::fs::status(Path, Status); |
| if (!llvm::sys::fs::exists(Status)) { |
| if (PathRemapping) |
| addCollectedPath(Path); |
| else |
| warning("Source file doesn't exist, proceeded by ignoring it.", Path); |
| return; |
| } |
| |
| if (llvm::sys::fs::is_regular_file(Status)) { |
| addCollectedPath(Path); |
| return; |
| } |
| |
| if (llvm::sys::fs::is_directory(Status)) { |
| std::error_code EC; |
| for (llvm::sys::fs::recursive_directory_iterator F(Path, EC), E; |
| F != E; F.increment(EC)) { |
| |
| auto Status = F->status(); |
| if (!Status) { |
| warning(Status.getError().message(), F->path()); |
| continue; |
| } |
| |
| if (Status->type() == llvm::sys::fs::file_type::regular_file) |
| addCollectedPath(F->path()); |
| } |
| } |
| } |
| |
| ErrorOr<const MemoryBuffer &> |
| CodeCoverageTool::getSourceFile(StringRef SourceFile) { |
| // If we've remapped filenames, look up the real location for this file. |
| std::unique_lock<std::mutex> Guard{LoadedSourceFilesLock}; |
| if (!RemappedFilenames.empty()) { |
| auto Loc = RemappedFilenames.find(SourceFile); |
| if (Loc != RemappedFilenames.end()) |
| SourceFile = Loc->second; |
| } |
| for (const auto &Files : LoadedSourceFiles) |
| if (sys::fs::equivalent(SourceFile, Files.first)) |
| return *Files.second; |
| auto Buffer = MemoryBuffer::getFile(SourceFile); |
| if (auto EC = Buffer.getError()) { |
| error(EC.message(), SourceFile); |
| return EC; |
| } |
| LoadedSourceFiles.emplace_back(SourceFile, std::move(Buffer.get())); |
| return *LoadedSourceFiles.back().second; |
| } |
| |
| void CodeCoverageTool::attachExpansionSubViews( |
| SourceCoverageView &View, ArrayRef<ExpansionRecord> Expansions, |
| const CoverageMapping &Coverage) { |
| if (!ViewOpts.ShowExpandedRegions) |
| return; |
| for (const auto &Expansion : Expansions) { |
| auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion); |
| if (ExpansionCoverage.empty()) |
| continue; |
| auto SourceBuffer = getSourceFile(ExpansionCoverage.getFilename()); |
| if (!SourceBuffer) |
| continue; |
| |
| auto SubViewExpansions = ExpansionCoverage.getExpansions(); |
| auto SubView = |
| SourceCoverageView::create(Expansion.Function.Name, SourceBuffer.get(), |
| ViewOpts, std::move(ExpansionCoverage)); |
| attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); |
| View.addExpansion(Expansion.Region, std::move(SubView)); |
| } |
| } |
| |
| std::unique_ptr<SourceCoverageView> |
| CodeCoverageTool::createFunctionView(const FunctionRecord &Function, |
| const CoverageMapping &Coverage) { |
| auto FunctionCoverage = Coverage.getCoverageForFunction(Function); |
| if (FunctionCoverage.empty()) |
| return nullptr; |
| auto SourceBuffer = getSourceFile(FunctionCoverage.getFilename()); |
| if (!SourceBuffer) |
| return nullptr; |
| |
| auto Expansions = FunctionCoverage.getExpansions(); |
| auto View = SourceCoverageView::create(DC.demangle(Function.Name), |
| SourceBuffer.get(), ViewOpts, |
| std::move(FunctionCoverage)); |
| attachExpansionSubViews(*View, Expansions, Coverage); |
| |
| return View; |
| } |
| |
| std::unique_ptr<SourceCoverageView> |
| CodeCoverageTool::createSourceFileView(StringRef SourceFile, |
| const CoverageMapping &Coverage) { |
| auto SourceBuffer = getSourceFile(SourceFile); |
| if (!SourceBuffer) |
| return nullptr; |
| auto FileCoverage = Coverage.getCoverageForFile(SourceFile); |
| if (FileCoverage.empty()) |
| return nullptr; |
| |
| auto Expansions = FileCoverage.getExpansions(); |
| auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(), |
| ViewOpts, std::move(FileCoverage)); |
| attachExpansionSubViews(*View, Expansions, Coverage); |
| if (!ViewOpts.ShowFunctionInstantiations) |
| return View; |
| |
| for (const auto &Group : Coverage.getInstantiationGroups(SourceFile)) { |
| // Skip functions which have a single instantiation. |
| if (Group.size() < 2) |
| continue; |
| |
| for (const FunctionRecord *Function : Group.getInstantiations()) { |
| std::unique_ptr<SourceCoverageView> SubView{nullptr}; |
| |
| StringRef Funcname = DC.demangle(Function->Name); |
| |
| if (Function->ExecutionCount > 0) { |
| auto SubViewCoverage = Coverage.getCoverageForFunction(*Function); |
| auto SubViewExpansions = SubViewCoverage.getExpansions(); |
| SubView = SourceCoverageView::create( |
| Funcname, SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage)); |
| attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); |
| } |
| |
| unsigned FileID = Function->CountedRegions.front().FileID; |
| unsigned Line = 0; |
| for (const auto &CR : Function->CountedRegions) |
| if (CR.FileID == FileID) |
| Line = std::max(CR.LineEnd, Line); |
| View->addInstantiation(Funcname, Line, std::move(SubView)); |
| } |
| } |
| return View; |
| } |
| |
| static bool modifiedTimeGT(StringRef LHS, StringRef RHS) { |
| sys::fs::file_status Status; |
| if (sys::fs::status(LHS, Status)) |
| return false; |
| auto LHSTime = Status.getLastModificationTime(); |
| if (sys::fs::status(RHS, Status)) |
| return false; |
| auto RHSTime = Status.getLastModificationTime(); |
| return LHSTime > RHSTime; |
| } |
| |
| std::unique_ptr<CoverageMapping> CodeCoverageTool::load() { |
| for (StringRef ObjectFilename : ObjectFilenames) |
| if (modifiedTimeGT(ObjectFilename, PGOFilename)) |
| warning("profile data may be out of date - object is newer", |
| ObjectFilename); |
| auto CoverageOrErr = |
| CoverageMapping::load(ObjectFilenames, PGOFilename, CoverageArches); |
| if (Error E = CoverageOrErr.takeError()) { |
| error("Failed to load coverage: " + toString(std::move(E)), |
| join(ObjectFilenames.begin(), ObjectFilenames.end(), ", ")); |
| return nullptr; |
| } |
| auto Coverage = std::move(CoverageOrErr.get()); |
| unsigned Mismatched = Coverage->getMismatchedCount(); |
| if (Mismatched) { |
| warning(Twine(Mismatched) + " functions have mismatched data"); |
| |
| if (ViewOpts.Debug) { |
| for (const auto &HashMismatch : Coverage->getHashMismatches()) |
| errs() << "hash-mismatch: " |
| << "No profile record found for '" << HashMismatch.first << "'" |
| << " with hash = 0x" << Twine::utohexstr(HashMismatch.second) |
| << '\n'; |
| } |
| } |
| |
| remapPathNames(*Coverage); |
| |
| if (!SourceFiles.empty()) |
| removeUnmappedInputs(*Coverage); |
| |
| demangleSymbols(*Coverage); |
| |
| return Coverage; |
| } |
| |
| void CodeCoverageTool::remapPathNames(const CoverageMapping &Coverage) { |
| if (!PathRemapping) |
| return; |
| |
| // Convert remapping paths to native paths with trailing seperators. |
| auto nativeWithTrailing = [](StringRef Path) -> std::string { |
| if (Path.empty()) |
| return ""; |
| SmallString<128> NativePath; |
| sys::path::native(Path, NativePath); |
| if (!sys::path::is_separator(NativePath.back())) |
| NativePath += sys::path::get_separator(); |
| return NativePath.c_str(); |
| }; |
| std::string RemapFrom = nativeWithTrailing(PathRemapping->first); |
| std::string RemapTo = nativeWithTrailing(PathRemapping->second); |
| |
| // Create a mapping from coverage data file paths to local paths. |
| for (StringRef Filename : Coverage.getUniqueSourceFiles()) { |
| SmallString<128> NativeFilename; |
| sys::path::native(Filename, NativeFilename); |
| if (NativeFilename.startswith(RemapFrom)) { |
| RemappedFilenames[Filename] = |
| RemapTo + NativeFilename.substr(RemapFrom.size()).str(); |
| } |
| } |
| |
| // Convert input files from local paths to coverage data file paths. |
| StringMap<std::string> InvRemappedFilenames; |
| for (const auto &RemappedFilename : RemappedFilenames) |
| InvRemappedFilenames[RemappedFilename.getValue()] = RemappedFilename.getKey(); |
| |
| for (std::string &Filename : SourceFiles) { |
| SmallString<128> NativeFilename; |
| sys::path::native(Filename, NativeFilename); |
| auto CovFileName = InvRemappedFilenames.find(NativeFilename); |
| if (CovFileName != InvRemappedFilenames.end()) |
| Filename = CovFileName->second; |
| } |
| } |
| |
| void CodeCoverageTool::removeUnmappedInputs(const CoverageMapping &Coverage) { |
| std::vector<StringRef> CoveredFiles = Coverage.getUniqueSourceFiles(); |
| |
| auto UncoveredFilesIt = SourceFiles.end(); |
| // The user may have specified source files which aren't in the coverage |
| // mapping. Filter these files away. |
| UncoveredFilesIt = std::remove_if( |
| SourceFiles.begin(), SourceFiles.end(), [&](const std::string &SF) { |
| return !std::binary_search(CoveredFiles.begin(), CoveredFiles.end(), |
| SF); |
| }); |
| |
| SourceFiles.erase(UncoveredFilesIt, SourceFiles.end()); |
| } |
| |
| void CodeCoverageTool::demangleSymbols(const CoverageMapping &Coverage) { |
| if (!ViewOpts.hasDemangler()) |
| return; |
| |
| // Pass function names to the demangler in a temporary file. |
| int InputFD; |
| SmallString<256> InputPath; |
| std::error_code EC = |
| sys::fs::createTemporaryFile("demangle-in", "list", InputFD, InputPath); |
| if (EC) { |
| error(InputPath, EC.message()); |
| return; |
| } |
| ToolOutputFile InputTOF{InputPath, InputFD}; |
| |
| unsigned NumSymbols = 0; |
| for (const auto &Function : Coverage.getCoveredFunctions()) { |
| InputTOF.os() << Function.Name << '\n'; |
| ++NumSymbols; |
| } |
| InputTOF.os().close(); |
| |
| // Use another temporary file to store the demangler's output. |
| int OutputFD; |
| SmallString<256> OutputPath; |
| EC = sys::fs::createTemporaryFile("demangle-out", "list", OutputFD, |
| OutputPath); |
| if (EC) { |
| error(OutputPath, EC.message()); |
| return; |
| } |
| ToolOutputFile OutputTOF{OutputPath, OutputFD}; |
| OutputTOF.os().close(); |
| |
| // Invoke the demangler. |
| std::vector<StringRef> ArgsV; |
| for (StringRef Arg : ViewOpts.DemanglerOpts) |
| ArgsV.push_back(Arg); |
| Optional<StringRef> Redirects[] = {InputPath.str(), OutputPath.str(), {""}}; |
| std::string ErrMsg; |
| int RC = sys::ExecuteAndWait(ViewOpts.DemanglerOpts[0], ArgsV, |
| /*env=*/None, Redirects, /*secondsToWait=*/0, |
| /*memoryLimit=*/0, &ErrMsg); |
| if (RC) { |
| error(ErrMsg, ViewOpts.DemanglerOpts[0]); |
| return; |
| } |
| |
| // Parse the demangler's output. |
| auto BufOrError = MemoryBuffer::getFile(OutputPath); |
| if (!BufOrError) { |
| error(OutputPath, BufOrError.getError().message()); |
| return; |
| } |
| |
| std::unique_ptr<MemoryBuffer> DemanglerBuf = std::move(*BufOrError); |
| |
| SmallVector<StringRef, 8> Symbols; |
| StringRef DemanglerData = DemanglerBuf->getBuffer(); |
| DemanglerData.split(Symbols, '\n', /*MaxSplit=*/NumSymbols, |
| /*KeepEmpty=*/false); |
| if (Symbols.size() != NumSymbols) { |
| error("Demangler did not provide expected number of symbols"); |
| return; |
| } |
| |
| // Cache the demangled names. |
| unsigned I = 0; |
| for (const auto &Function : Coverage.getCoveredFunctions()) |
| // On Windows, lines in the demangler's output file end with "\r\n". |
| // Splitting by '\n' keeps '\r's, so cut them now. |
| DC.DemangledNames[Function.Name] = Symbols[I++].rtrim(); |
| } |
| |
| void CodeCoverageTool::writeSourceFileView(StringRef SourceFile, |
| CoverageMapping *Coverage, |
| CoveragePrinter *Printer, |
| bool ShowFilenames) { |
| auto View = createSourceFileView(SourceFile, *Coverage); |
| if (!View) { |
| warning("The file '" + SourceFile + "' isn't covered."); |
| return; |
| } |
| |
| auto OSOrErr = Printer->createViewFile(SourceFile, /*InToplevel=*/false); |
| if (Error E = OSOrErr.takeError()) { |
| error("Could not create view file!", toString(std::move(E))); |
| return; |
| } |
| auto OS = std::move(OSOrErr.get()); |
| |
| View->print(*OS.get(), /*Wholefile=*/true, |
| /*ShowSourceName=*/ShowFilenames, |
| /*ShowTitle=*/ViewOpts.hasOutputDirectory()); |
| Printer->closeViewFile(std::move(OS)); |
| } |
| |
| int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) { |
| cl::opt<std::string> CovFilename( |
| cl::Positional, cl::desc("Covered executable or object file.")); |
| |
| cl::list<std::string> CovFilenames( |
| "object", cl::desc("Coverage executable or object file"), cl::ZeroOrMore, |
| cl::CommaSeparated); |
| |
| cl::list<std::string> InputSourceFiles( |
| cl::Positional, cl::desc("<Source files>"), cl::ZeroOrMore); |
| |
| cl::opt<bool> DebugDumpCollectedPaths( |
| "dump-collected-paths", cl::Optional, cl::Hidden, |
| cl::desc("Show the collected paths to source files")); |
| |
| cl::opt<std::string, true> PGOFilename( |
| "instr-profile", cl::Required, cl::location(this->PGOFilename), |
| cl::desc( |
| "File with the profile data obtained after an instrumented run")); |
| |
| cl::list<std::string> Arches( |
| "arch", cl::desc("architectures of the coverage mapping binaries")); |
| |
| cl::opt<bool> DebugDump("dump", cl::Optional, |
| cl::desc("Show internal debug dump")); |
| |
| cl::opt<CoverageViewOptions::OutputFormat> Format( |
| "format", cl::desc("Output format for line-based coverage reports"), |
| cl::values(clEnumValN(CoverageViewOptions::OutputFormat::Text, "text", |
| "Text output"), |
| clEnumValN(CoverageViewOptions::OutputFormat::HTML, "html", |
| "HTML output"), |
| clEnumValN(CoverageViewOptions::OutputFormat::Lcov, "lcov", |
| "lcov tracefile output")), |
| cl::init(CoverageViewOptions::OutputFormat::Text)); |
| |
| cl::opt<std::string> PathRemap( |
| "path-equivalence", cl::Optional, |
| cl::desc("<from>,<to> Map coverage data paths to local source file " |
| "paths")); |
| |
| cl::OptionCategory FilteringCategory("Function filtering options"); |
| |
| cl::list<std::string> NameFilters( |
| "name", cl::Optional, |
| cl::desc("Show code coverage only for functions with the given name"), |
| cl::ZeroOrMore, cl::cat(FilteringCategory)); |
| |
| cl::list<std::string> NameFilterFiles( |
| "name-whitelist", cl::Optional, |
| cl::desc("Show code coverage only for functions listed in the given " |
| "file"), |
| cl::ZeroOrMore, cl::cat(FilteringCategory)); |
| |
| cl::list<std::string> NameRegexFilters( |
| "name-regex", cl::Optional, |
| cl::desc("Show code coverage only for functions that match the given " |
| "regular expression"), |
| cl::ZeroOrMore, cl::cat(FilteringCategory)); |
| |
| cl::list<std::string> IgnoreFilenameRegexFilters( |
| "ignore-filename-regex", cl::Optional, |
| cl::desc("Skip source code files with file paths that match the given " |
| "regular expression"), |
| cl::ZeroOrMore, cl::cat(FilteringCategory)); |
| |
| cl::opt<double> RegionCoverageLtFilter( |
| "region-coverage-lt", cl::Optional, |
| cl::desc("Show code coverage only for functions with region coverage " |
| "less than the given threshold"), |
| cl::cat(FilteringCategory)); |
| |
| cl::opt<double> RegionCoverageGtFilter( |
| "region-coverage-gt", cl::Optional, |
| cl::desc("Show code coverage only for functions with region coverage " |
| "greater than the given threshold"), |
| cl::cat(FilteringCategory)); |
| |
| cl::opt<double> LineCoverageLtFilter( |
| "line-coverage-lt", cl::Optional, |
| cl::desc("Show code coverage only for functions with line coverage less " |
| "than the given threshold"), |
| cl::cat(FilteringCategory)); |
| |
| cl::opt<double> LineCoverageGtFilter( |
| "line-coverage-gt", cl::Optional, |
| cl::desc("Show code coverage only for functions with line coverage " |
| "greater than the given threshold"), |
| cl::cat(FilteringCategory)); |
| |
| cl::opt<cl::boolOrDefault> UseColor( |
| "use-color", cl::desc("Emit colored output (default=autodetect)"), |
| cl::init(cl::BOU_UNSET)); |
| |
| cl::list<std::string> DemanglerOpts( |
| "Xdemangler", cl::desc("<demangler-path>|<demangler-option>")); |
| |
| cl::opt<bool> RegionSummary( |
| "show-region-summary", cl::Optional, |
| cl::desc("Show region statistics in summary table"), |
| cl::init(true)); |
| |
| cl::opt<bool> InstantiationSummary( |
| "show-instantiation-summary", cl::Optional, |
| cl::desc("Show instantiation statistics in summary table")); |
| |
| cl::opt<bool> SummaryOnly( |
| "summary-only", cl::Optional, |
| cl::desc("Export only summary information for each source file")); |
| |
| cl::opt<unsigned> NumThreads( |
| "num-threads", cl::init(0), |
| cl::desc("Number of merge threads to use (default: autodetect)")); |
| cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"), |
| cl::aliasopt(NumThreads)); |
| |
| auto commandLineParser = [&, this](int argc, const char **argv) -> int { |
| cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); |
| ViewOpts.Debug = DebugDump; |
| |
| if (!CovFilename.empty()) |
| ObjectFilenames.emplace_back(CovFilename); |
| for (const std::string &Filename : CovFilenames) |
| ObjectFilenames.emplace_back(Filename); |
| if (ObjectFilenames.empty()) { |
| errs() << "No filenames specified!\n"; |
| ::exit(1); |
| } |
| |
| ViewOpts.Format = Format; |
| switch (ViewOpts.Format) { |
| case CoverageViewOptions::OutputFormat::Text: |
| ViewOpts.Colors = UseColor == cl::BOU_UNSET |
| ? sys::Process::StandardOutHasColors() |
| : UseColor == cl::BOU_TRUE; |
| break; |
| case CoverageViewOptions::OutputFormat::HTML: |
| if (UseColor == cl::BOU_FALSE) |
| errs() << "Color output cannot be disabled when generating html.\n"; |
| ViewOpts.Colors = true; |
| break; |
| case CoverageViewOptions::OutputFormat::Lcov: |
| if (UseColor == cl::BOU_TRUE) |
| errs() << "Color output cannot be enabled when generating lcov.\n"; |
| ViewOpts.Colors = false; |
| break; |
| } |
| |
| // If path-equivalence was given and is a comma seperated pair then set |
| // PathRemapping. |
| auto EquivPair = StringRef(PathRemap).split(','); |
| if (!(EquivPair.first.empty() && EquivPair.second.empty())) |
| PathRemapping = EquivPair; |
| |
| // If a demangler is supplied, check if it exists and register it. |
| if (!DemanglerOpts.empty()) { |
| auto DemanglerPathOrErr = sys::findProgramByName(DemanglerOpts[0]); |
| if (!DemanglerPathOrErr) { |
| error("Could not find the demangler!", |
| DemanglerPathOrErr.getError().message()); |
| return 1; |
| } |
| DemanglerOpts[0] = *DemanglerPathOrErr; |
| ViewOpts.DemanglerOpts.swap(DemanglerOpts); |
| } |
| |
| // Read in -name-whitelist files. |
| if (!NameFilterFiles.empty()) { |
| std::string SpecialCaseListErr; |
| NameWhitelist = |
| SpecialCaseList::create(NameFilterFiles, SpecialCaseListErr); |
| if (!NameWhitelist) |
| error(SpecialCaseListErr); |
| } |
| |
| // Create the function filters |
| if (!NameFilters.empty() || NameWhitelist || !NameRegexFilters.empty()) { |
| auto NameFilterer = llvm::make_unique<CoverageFilters>(); |
| for (const auto &Name : NameFilters) |
| NameFilterer->push_back(llvm::make_unique<NameCoverageFilter>(Name)); |
| if (NameWhitelist) |
| NameFilterer->push_back( |
| llvm::make_unique<NameWhitelistCoverageFilter>(*NameWhitelist)); |
| for (const auto &Regex : NameRegexFilters) |
| NameFilterer->push_back( |
| llvm::make_unique<NameRegexCoverageFilter>(Regex)); |
| Filters.push_back(std::move(NameFilterer)); |
| } |
| |
| if (RegionCoverageLtFilter.getNumOccurrences() || |
| RegionCoverageGtFilter.getNumOccurrences() || |
| LineCoverageLtFilter.getNumOccurrences() || |
| LineCoverageGtFilter.getNumOccurrences()) { |
| auto StatFilterer = llvm::make_unique<CoverageFilters>(); |
| if (RegionCoverageLtFilter.getNumOccurrences()) |
| StatFilterer->push_back(llvm::make_unique<RegionCoverageFilter>( |
| RegionCoverageFilter::LessThan, RegionCoverageLtFilter)); |
| if (RegionCoverageGtFilter.getNumOccurrences()) |
| StatFilterer->push_back(llvm::make_unique<RegionCoverageFilter>( |
| RegionCoverageFilter::GreaterThan, RegionCoverageGtFilter)); |
| if (LineCoverageLtFilter.getNumOccurrences()) |
| StatFilterer->push_back(llvm::make_unique<LineCoverageFilter>( |
| LineCoverageFilter::LessThan, LineCoverageLtFilter)); |
| if (LineCoverageGtFilter.getNumOccurrences()) |
| StatFilterer->push_back(llvm::make_unique<LineCoverageFilter>( |
| RegionCoverageFilter::GreaterThan, LineCoverageGtFilter)); |
| Filters.push_back(std::move(StatFilterer)); |
| } |
| |
| // Create the ignore filename filters. |
| for (const auto &RE : IgnoreFilenameRegexFilters) |
| IgnoreFilenameFilters.push_back( |
| llvm::make_unique<NameRegexCoverageFilter>(RE)); |
| |
| if (!Arches.empty()) { |
| for (const std::string &Arch : Arches) { |
| if (Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) { |
| error("Unknown architecture: " + Arch); |
| return 1; |
| } |
| CoverageArches.emplace_back(Arch); |
| } |
| if (CoverageArches.size() != ObjectFilenames.size()) { |
| error("Number of architectures doesn't match the number of objects"); |
| return 1; |
| } |
| } |
| |
| // IgnoreFilenameFilters are applied even when InputSourceFiles specified. |
| for (const std::string &File : InputSourceFiles) |
| collectPaths(File); |
| |
| if (DebugDumpCollectedPaths) { |
| for (const std::string &SF : SourceFiles) |
| outs() << SF << '\n'; |
| ::exit(0); |
| } |
| |
| ViewOpts.ShowRegionSummary = RegionSummary; |
| ViewOpts.ShowInstantiationSummary = InstantiationSummary; |
| ViewOpts.ExportSummaryOnly = SummaryOnly; |
| ViewOpts.NumThreads = NumThreads; |
| |
| return 0; |
| }; |
| |
| switch (Cmd) { |
| case Show: |
| return doShow(argc, argv, commandLineParser); |
| case Report: |
| return doReport(argc, argv, commandLineParser); |
| case Export: |
| return doExport(argc, argv, commandLineParser); |
| } |
| return 0; |
| } |
| |
| int CodeCoverageTool::doShow(int argc, const char **argv, |
| CommandLineParserType commandLineParser) { |
| |
| cl::OptionCategory ViewCategory("Viewing options"); |
| |
| cl::opt<bool> ShowLineExecutionCounts( |
| "show-line-counts", cl::Optional, |
| cl::desc("Show the execution counts for each line"), cl::init(true), |
| cl::cat(ViewCategory)); |
| |
| cl::opt<bool> ShowRegions( |
| "show-regions", cl::Optional, |
| cl::desc("Show the execution counts for each region"), |
| cl::cat(ViewCategory)); |
| |
| cl::opt<bool> ShowBestLineRegionsCounts( |
| "show-line-counts-or-regions", cl::Optional, |
| cl::desc("Show the execution counts for each line, or the execution " |
| "counts for each region on lines that have multiple regions"), |
| cl::cat(ViewCategory)); |
| |
| cl::opt<bool> ShowExpansions("show-expansions", cl::Optional, |
| cl::desc("Show expanded source regions"), |
| cl::cat(ViewCategory)); |
| |
| cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional, |
| cl::desc("Show function instantiations"), |
| cl::init(true), cl::cat(ViewCategory)); |
| |
| cl::opt<std::string> ShowOutputDirectory( |
| "output-dir", cl::init(""), |
| cl::desc("Directory in which coverage information is written out")); |
| cl::alias ShowOutputDirectoryA("o", cl::desc("Alias for --output-dir"), |
| cl::aliasopt(ShowOutputDirectory)); |
| |
| cl::opt<uint32_t> TabSize( |
| "tab-size", cl::init(2), |
| cl::desc( |
| "Set tab expansion size for html coverage reports (default = 2)")); |
| |
| cl::opt<std::string> ProjectTitle( |
| "project-title", cl::Optional, |
| cl::desc("Set project title for the coverage report")); |
| |
| auto Err = commandLineParser(argc, argv); |
| if (Err) |
| return Err; |
| |
| if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Lcov) { |
| error("Lcov format should be used with 'llvm-cov export'."); |
| return 1; |
| } |
| |
| ViewOpts.ShowLineNumbers = true; |
| ViewOpts.ShowLineStats = ShowLineExecutionCounts.getNumOccurrences() != 0 || |
| !ShowRegions || ShowBestLineRegionsCounts; |
| ViewOpts.ShowRegionMarkers = ShowRegions || ShowBestLineRegionsCounts; |
| ViewOpts.ShowExpandedRegions = ShowExpansions; |
| ViewOpts.ShowFunctionInstantiations = ShowInstantiations; |
| ViewOpts.ShowOutputDirectory = ShowOutputDirectory; |
| ViewOpts.TabSize = TabSize; |
| ViewOpts.ProjectTitle = ProjectTitle; |
| |
| if (ViewOpts.hasOutputDirectory()) { |
| if (auto E = sys::fs::create_directories(ViewOpts.ShowOutputDirectory)) { |
| error("Could not create output directory!", E.message()); |
| return 1; |
| } |
| } |
| |
| sys::fs::file_status Status; |
| if (sys::fs::status(PGOFilename, Status)) { |
| error("profdata file error: can not get the file status. \n"); |
| return 1; |
| } |
| |
| auto ModifiedTime = Status.getLastModificationTime(); |
| std::string ModifiedTimeStr = to_string(ModifiedTime); |
| size_t found = ModifiedTimeStr.rfind(':'); |
| ViewOpts.CreatedTimeStr = (found != std::string::npos) |
| ? "Created: " + ModifiedTimeStr.substr(0, found) |
| : "Created: " + ModifiedTimeStr; |
| |
| auto Coverage = load(); |
| if (!Coverage) |
| return 1; |
| |
| auto Printer = CoveragePrinter::create(ViewOpts); |
| |
| if (SourceFiles.empty()) |
| // Get the source files from the function coverage mapping. |
| for (StringRef Filename : Coverage->getUniqueSourceFiles()) { |
| if (!IgnoreFilenameFilters.matchesFilename(Filename)) |
| SourceFiles.push_back(Filename); |
| } |
| |
| // Create an index out of the source files. |
| if (ViewOpts.hasOutputDirectory()) { |
| if (Error E = Printer->createIndexFile(SourceFiles, *Coverage, Filters)) { |
| error("Could not create index file!", toString(std::move(E))); |
| return 1; |
| } |
| } |
| |
| if (!Filters.empty()) { |
| // Build the map of filenames to functions. |
| std::map<llvm::StringRef, std::vector<const FunctionRecord *>> |
| FilenameFunctionMap; |
| for (const auto &SourceFile : SourceFiles) |
| for (const auto &Function : Coverage->getCoveredFunctions(SourceFile)) |
| if (Filters.matches(*Coverage.get(), Function)) |
| FilenameFunctionMap[SourceFile].push_back(&Function); |
| |
| // Only print filter matching functions for each file. |
| for (const auto &FileFunc : FilenameFunctionMap) { |
| StringRef File = FileFunc.first; |
| const auto &Functions = FileFunc.second; |
| |
| auto OSOrErr = Printer->createViewFile(File, /*InToplevel=*/false); |
| if (Error E = OSOrErr.takeError()) { |
| error("Could not create view file!", toString(std::move(E))); |
| return 1; |
| } |
| auto OS = std::move(OSOrErr.get()); |
| |
| bool ShowTitle = ViewOpts.hasOutputDirectory(); |
| for (const auto *Function : Functions) { |
| auto FunctionView = createFunctionView(*Function, *Coverage); |
| if (!FunctionView) { |
| warning("Could not read coverage for '" + Function->Name + "'."); |
| continue; |
| } |
| FunctionView->print(*OS.get(), /*WholeFile=*/false, |
| /*ShowSourceName=*/true, ShowTitle); |
| ShowTitle = false; |
| } |
| |
| Printer->closeViewFile(std::move(OS)); |
| } |
| return 0; |
| } |
| |
| // Show files |
| bool ShowFilenames = |
| (SourceFiles.size() != 1) || ViewOpts.hasOutputDirectory() || |
| (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML); |
| |
| auto NumThreads = ViewOpts.NumThreads; |
| |
| // If NumThreads is not specified, auto-detect a good default. |
| if (NumThreads == 0) |
| NumThreads = |
| std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(), |
| unsigned(SourceFiles.size()))); |
| |
| if (!ViewOpts.hasOutputDirectory() || NumThreads == 1) { |
| for (const std::string &SourceFile : SourceFiles) |
| writeSourceFileView(SourceFile, Coverage.get(), Printer.get(), |
| ShowFilenames); |
| } else { |
| // In -output-dir mode, it's safe to use multiple threads to print files. |
| ThreadPool Pool(NumThreads); |
| for (const std::string &SourceFile : SourceFiles) |
| Pool.async(&CodeCoverageTool::writeSourceFileView, this, SourceFile, |
| Coverage.get(), Printer.get(), ShowFilenames); |
| Pool.wait(); |
| } |
| |
| return 0; |
| } |
| |
| int CodeCoverageTool::doReport(int argc, const char **argv, |
| CommandLineParserType commandLineParser) { |
| cl::opt<bool> ShowFunctionSummaries( |
| "show-functions", cl::Optional, cl::init(false), |
| cl::desc("Show coverage summaries for each function")); |
| |
| auto Err = commandLineParser(argc, argv); |
| if (Err) |
| return Err; |
| |
| if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML) { |
| error("HTML output for summary reports is not yet supported."); |
| return 1; |
| } else if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Lcov) { |
| error("Lcov format should be used with 'llvm-cov export'."); |
| return 1; |
| } |
| |
| auto Coverage = load(); |
| if (!Coverage) |
| return 1; |
| |
| CoverageReport Report(ViewOpts, *Coverage.get()); |
| if (!ShowFunctionSummaries) { |
| if (SourceFiles.empty()) |
| Report.renderFileReports(llvm::outs(), IgnoreFilenameFilters); |
| else |
| Report.renderFileReports(llvm::outs(), SourceFiles); |
| } else { |
| if (SourceFiles.empty()) { |
| error("Source files must be specified when -show-functions=true is " |
| "specified"); |
| return 1; |
| } |
| |
| Report.renderFunctionReports(SourceFiles, DC, llvm::outs()); |
| } |
| return 0; |
| } |
| |
| int CodeCoverageTool::doExport(int argc, const char **argv, |
| CommandLineParserType commandLineParser) { |
| |
| auto Err = commandLineParser(argc, argv); |
| if (Err) |
| return Err; |
| |
| if (ViewOpts.Format != CoverageViewOptions::OutputFormat::Text && |
| ViewOpts.Format != CoverageViewOptions::OutputFormat::Lcov) { |
| error("Coverage data can only be exported as textual JSON or an " |
| "lcov tracefile."); |
| return 1; |
| } |
| |
| auto Coverage = load(); |
| if (!Coverage) { |
| error("Could not load coverage information"); |
| return 1; |
| } |
| |
| std::unique_ptr<CoverageExporter> Exporter; |
| |
| switch (ViewOpts.Format) { |
| case CoverageViewOptions::OutputFormat::Text: |
| Exporter = llvm::make_unique<CoverageExporterJson>(*Coverage.get(), |
| ViewOpts, outs()); |
| break; |
| case CoverageViewOptions::OutputFormat::HTML: |
| // Unreachable because we should have gracefully terminated with an error |
| // above. |
| llvm_unreachable("Export in HTML is not supported!"); |
| case CoverageViewOptions::OutputFormat::Lcov: |
| Exporter = llvm::make_unique<CoverageExporterLcov>(*Coverage.get(), |
| ViewOpts, outs()); |
| break; |
| } |
| |
| if (SourceFiles.empty()) |
| Exporter->renderRoot(IgnoreFilenameFilters); |
| else |
| Exporter->renderRoot(SourceFiles); |
| |
| return 0; |
| } |
| |
| int showMain(int argc, const char *argv[]) { |
| CodeCoverageTool Tool; |
| return Tool.run(CodeCoverageTool::Show, argc, argv); |
| } |
| |
| int reportMain(int argc, const char *argv[]) { |
| CodeCoverageTool Tool; |
| return Tool.run(CodeCoverageTool::Report, argc, argv); |
| } |
| |
| int exportMain(int argc, const char *argv[]) { |
| CodeCoverageTool Tool; |
| return Tool.run(CodeCoverageTool::Export, argc, argv); |
| } |