| //===- CodeCoverage.cpp - Coverage tool based on profiling instrumentation-===// | |
| // | |
| // The LLVM Compiler Infrastructure | |
| // | |
| // This file is distributed under the University of Illinois Open Source | |
| // License. See LICENSE.TXT for details. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // 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 "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)) { | |
| if (EC) { | |
| warning(EC.message(), F->path()); | |
| continue; | |
| } | |
| if (llvm::sys::fs::is_regular_file(F->path())) | |
| 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'; | |
| for (const auto &CounterMismatch : Coverage->getCounterMismatches()) | |
| errs() << "counter-mismatch: " | |
| << "Coverage mapping for " << CounterMismatch.first | |
| << " only has " << CounterMismatch.second | |
| << " valid counter expressions\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")), | |
| 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; | |
| } | |
| // 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.size()) { | |
| 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; | |
| 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; | |
| } | |
| 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) { | |
| error("Coverage data can only be exported as textual JSON."); | |
| return 1; | |
| } | |
| auto Coverage = load(); | |
| if (!Coverage) { | |
| error("Could not load coverage information"); | |
| return 1; | |
| } | |
| auto Exporter = CoverageExporterJson(*Coverage.get(), ViewOpts, outs()); | |
| 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); | |
| } |