//===- 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 "RenderingSupport.h" #include "CoverageViewOptions.h" #include "CoverageFilters.h" #include "SourceCoverageDataManager.h" #include "SourceCoverageView.h" #include "CoverageSummary.h" #include "CoverageReport.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ProfileData/InstrProfReader.h" #include "llvm/ProfileData/CoverageMapping.h" #include "llvm/ProfileData/CoverageMappingReader.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/MemoryObject.h" #include "llvm/Support/Format.h" #include "llvm/Support/Path.h" #include "llvm/Support/Signals.h" #include "llvm/Support/PrettyStackTrace.h" #include #include using namespace llvm; using namespace coverage; namespace { /// \brief Distribute the functions into instantiation sets. /// /// An instantiation set is a collection of functions that have the same source /// code, ie, template functions specializations. class FunctionInstantiationSetCollector { typedef DenseMap, std::vector> MapT; MapT InstantiatedFunctions; public: void insert(const FunctionCoverageMapping &Function, unsigned FileID) { auto I = Function.CountedRegions.begin(), E = Function.CountedRegions.end(); while (I != E && I->FileID != FileID) ++I; assert(I != E && "function does not cover the given file"); auto &Functions = InstantiatedFunctions[I->startLoc()]; Functions.push_back(&Function); } MapT::iterator begin() { return InstantiatedFunctions.begin(); } MapT::iterator end() { return InstantiatedFunctions.end(); } }; /// \brief The implementation of the coverage tool. class CodeCoverageTool { public: enum Command { /// \brief The show command. Show, /// \brief The report command. Report }; /// \brief Print the error message to the error output stream. void error(const Twine &Message, StringRef Whence = ""); /// \brief Return a memory buffer for the given source file. ErrorOr getSourceFile(StringRef SourceFile); /// \brief Collect a set of function's file ids which correspond to the /// given source file. Return false if the set is empty. bool gatherInterestingFileIDs(StringRef SourceFile, const FunctionCoverageMapping &Function, SmallSet &InterestingFileIDs); /// \brief Find the file id which is not an expanded file id. bool findMainViewFileID(StringRef SourceFile, const FunctionCoverageMapping &Function, unsigned &MainViewFileID); bool findMainViewFileID(const FunctionCoverageMapping &Function, unsigned &MainViewFileID); /// \brief Create a source view which shows coverage for an expansion /// of a file. void createExpansionSubView(const CountedRegion &ExpandedRegion, const FunctionCoverageMapping &Function, SourceCoverageView &Parent); void createExpansionSubViews(SourceCoverageView &View, unsigned ViewFileID, const FunctionCoverageMapping &Function); /// \brief Create a source view which shows coverage for an instantiation /// of a funciton. void createInstantiationSubView(StringRef SourceFile, const FunctionCoverageMapping &Function, SourceCoverageView &View); /// \brief Create the main source view of a particular source file. /// Return true if this particular source file is not covered. bool createSourceFileView(StringRef SourceFile, SourceCoverageView &View, ArrayRef FunctionMappingRecords, bool UseOnlyRegionsInMainFile = false); /// \brief Load the coverage mapping data. Return true if an error occured. bool load(); int run(Command Cmd, int argc, const char **argv); typedef std::function CommandLineParserType; int show(int argc, const char **argv, CommandLineParserType commandLineParser); int report(int argc, const char **argv, CommandLineParserType commandLineParser); StringRef ObjectFilename; CoverageViewOptions ViewOpts; std::unique_ptr PGOReader; CoverageFiltersMatchAll Filters; std::vector SourceFiles; std::vector>> LoadedSourceFiles; std::vector FunctionMappingRecords; bool CompareFilenamesOnly; StringMap RemappedFilenames; }; } static std::vector getUniqueFilenames(ArrayRef FunctionMappingRecords) { std::vector Filenames; for (const auto &Function : FunctionMappingRecords) for (const auto &Filename : Function.Filenames) Filenames.push_back(Filename); std::sort(Filenames.begin(), Filenames.end()); auto Last = std::unique(Filenames.begin(), Filenames.end()); Filenames.erase(Last, Filenames.end()); return Filenames; } void CodeCoverageTool::error(const Twine &Message, StringRef Whence) { errs() << "error: "; if (!Whence.empty()) errs() << Whence << ": "; errs() << Message << "\n"; } ErrorOr CodeCoverageTool::getSourceFile(StringRef SourceFile) { // If we've remapped filenames, look up the real location for this file. 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.push_back( std::make_pair(SourceFile, std::move(Buffer.get()))); return *LoadedSourceFiles.back().second; } bool CodeCoverageTool::gatherInterestingFileIDs( StringRef SourceFile, const FunctionCoverageMapping &Function, SmallSet &InterestingFileIDs) { bool Interesting = false; for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) { if (SourceFile == Function.Filenames[I]) { InterestingFileIDs.insert(I); Interesting = true; } } return Interesting; } bool CodeCoverageTool::findMainViewFileID(StringRef SourceFile, const FunctionCoverageMapping &Function, unsigned &MainViewFileID) { llvm::SmallVector IsExpandedFile(Function.Filenames.size(), false); llvm::SmallVector FilenameEquivalence(Function.Filenames.size(), false); for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) { if (SourceFile == Function.Filenames[I]) FilenameEquivalence[I] = true; } for (const auto &CR : Function.CountedRegions) { if (CR.Kind == CounterMappingRegion::ExpansionRegion && FilenameEquivalence[CR.FileID]) IsExpandedFile[CR.ExpandedFileID] = true; } for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) { if (!FilenameEquivalence[I] || IsExpandedFile[I]) continue; MainViewFileID = I; return false; } return true; } bool CodeCoverageTool::findMainViewFileID(const FunctionCoverageMapping &Function, unsigned &MainViewFileID) { llvm::SmallVector IsExpandedFile(Function.Filenames.size(), false); for (const auto &CR : Function.CountedRegions) { if (CR.Kind == CounterMappingRegion::ExpansionRegion) IsExpandedFile[CR.ExpandedFileID] = true; } for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) { if (IsExpandedFile[I]) continue; MainViewFileID = I; return false; } return true; } void CodeCoverageTool::createExpansionSubView( const CountedRegion &ExpandedRegion, const FunctionCoverageMapping &Function, SourceCoverageView &Parent) { auto SourceBuffer = getSourceFile(Function.Filenames[ExpandedRegion.ExpandedFileID]); if (!SourceBuffer) return; auto SubView = llvm::make_unique(SourceBuffer.get(), Parent.getOptions()); auto RegionManager = llvm::make_unique(); for (const auto &CR : Function.CountedRegions) { if (CR.FileID == ExpandedRegion.ExpandedFileID) RegionManager->insert(CR); } SubView->load(std::move(RegionManager)); createExpansionSubViews(*SubView, ExpandedRegion.ExpandedFileID, Function); Parent.addExpansion(ExpandedRegion, std::move(SubView)); } void CodeCoverageTool::createExpansionSubViews( SourceCoverageView &View, unsigned ViewFileID, const FunctionCoverageMapping &Function) { if (!ViewOpts.ShowExpandedRegions) return; for (const auto &CR : Function.CountedRegions) { if (CR.Kind != CounterMappingRegion::ExpansionRegion) continue; if (CR.FileID != ViewFileID) continue; createExpansionSubView(CR, Function, View); } } void CodeCoverageTool::createInstantiationSubView( StringRef SourceFile, const FunctionCoverageMapping &Function, SourceCoverageView &View) { auto RegionManager = llvm::make_unique(); SmallSet InterestingFileIDs; if (!gatherInterestingFileIDs(SourceFile, Function, InterestingFileIDs)) return; // Get the interesting regions for (const auto &CR : Function.CountedRegions) { if (InterestingFileIDs.count(CR.FileID)) RegionManager->insert(CR); } View.load(std::move(RegionManager)); unsigned MainFileID; if (findMainViewFileID(SourceFile, Function, MainFileID)) return; createExpansionSubViews(View, MainFileID, Function); } bool CodeCoverageTool::createSourceFileView( StringRef SourceFile, SourceCoverageView &View, ArrayRef FunctionMappingRecords, bool UseOnlyRegionsInMainFile) { auto RegionManager = llvm::make_unique(); FunctionInstantiationSetCollector InstantiationSetCollector; for (const auto &Function : FunctionMappingRecords) { unsigned MainFileID; if (findMainViewFileID(SourceFile, Function, MainFileID)) continue; SmallSet InterestingFileIDs; if (UseOnlyRegionsInMainFile) { InterestingFileIDs.insert(MainFileID); } else if (!gatherInterestingFileIDs(SourceFile, Function, InterestingFileIDs)) continue; // Get the interesting regions for (const auto &CR : Function.CountedRegions) { if (InterestingFileIDs.count(CR.FileID)) RegionManager->insert(CR); } InstantiationSetCollector.insert(Function, MainFileID); createExpansionSubViews(View, MainFileID, Function); } if (RegionManager->getCoverageSegments().empty()) return true; View.load(std::move(RegionManager)); // Show instantiations if (!ViewOpts.ShowFunctionInstantiations) return false; for (const auto &InstantiationSet : InstantiationSetCollector) { if (InstantiationSet.second.size() < 2) continue; for (auto Function : InstantiationSet.second) { 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); auto SourceBuffer = getSourceFile(Function->Filenames[FileID]); if (!SourceBuffer) continue; auto SubView = llvm::make_unique(SourceBuffer.get(), View.getOptions()); createInstantiationSubView(SourceFile, *Function, *SubView); View.addInstantiation(Function->Name, Line, std::move(SubView)); } } return false; } bool CodeCoverageTool::load() { auto CounterMappingBuff = MemoryBuffer::getFileOrSTDIN(ObjectFilename); if (auto EC = CounterMappingBuff.getError()) { error(EC.message(), ObjectFilename); return true; } ObjectFileCoverageMappingReader MappingReader(CounterMappingBuff.get()); if (auto EC = MappingReader.readHeader()) { error(EC.message(), ObjectFilename); return true; } std::vector Counts; for (const auto &I : MappingReader) { FunctionCoverageMapping Function(I.FunctionName, I.Filenames); // Create the mapping regions with evaluated execution counts Counts.clear(); PGOReader->getFunctionCounts(Function.Name, I.FunctionHash, Counts); // Get the biggest referenced counters bool RegionError = false; CounterMappingContext Ctx(I.Expressions, Counts); for (const auto &R : I.MappingRegions) { // Compute the values of mapped regions if (ViewOpts.Debug) { errs() << "File " << R.FileID << "| " << R.LineStart << ":" << R.ColumnStart << " -> " << R.LineEnd << ":" << R.ColumnEnd << " = "; Ctx.dump(R.Count); if (R.Kind == CounterMappingRegion::ExpansionRegion) { errs() << " (Expanded file id = " << R.ExpandedFileID << ") "; } errs() << "\n"; } ErrorOr ExecutionCount = Ctx.evaluate(R.Count); if (ExecutionCount) { Function.CountedRegions.push_back(CountedRegion(R, *ExecutionCount)); } else if (!RegionError) { colored_ostream(errs(), raw_ostream::RED) << "error: Regions and counters don't match in a function '" << Function.Name << "' (re-run the instrumented binary)."; errs() << "\n"; RegionError = true; } } if (RegionError || !Filters.matches(Function)) continue; FunctionMappingRecords.push_back(Function); } if (CompareFilenamesOnly) { auto CoveredFiles = getUniqueFilenames(FunctionMappingRecords); for (auto &SF : SourceFiles) { StringRef SFBase = sys::path::filename(SF); for (const auto &CF : CoveredFiles) if (SFBase == sys::path::filename(CF)) { RemappedFilenames[CF] = SF; SF = CF; break; } } } return false; } int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) { // Print a stack trace if we signal out. sys::PrintStackTraceOnErrorSignal(); PrettyStackTraceProgram X(argc, argv); llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. cl::list InputSourceFiles( cl::Positional, cl::desc(""), cl::ZeroOrMore); cl::opt PGOFilename( "instr-profile", cl::Required, cl::desc( "File with the profile data obtained after an instrumented run")); cl::opt DebugDump("dump", cl::Optional, cl::desc("Show internal debug dump")); cl::opt FilenameEquivalence( "filename-equivalence", cl::Optional, cl::desc("Treat source files as equivalent to paths in the coverage data " "when the file names match, even if the full paths do not")); cl::OptionCategory FilteringCategory("Function filtering options"); cl::list NameFilters( "name", cl::Optional, cl::desc("Show code coverage only for functions with the given name"), cl::ZeroOrMore, cl::cat(FilteringCategory)); cl::list 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::opt 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 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 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 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)); auto commandLineParser = [&, this](int argc, const char **argv) -> int { cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); ViewOpts.Debug = DebugDump; CompareFilenamesOnly = FilenameEquivalence; if (auto EC = IndexedInstrProfReader::create(PGOFilename, PGOReader)) { error(EC.message(), PGOFilename); return 1; } // Create the function filters if (!NameFilters.empty() || !NameRegexFilters.empty()) { auto NameFilterer = new CoverageFilters; for (const auto &Name : NameFilters) NameFilterer->push_back(llvm::make_unique(Name)); for (const auto &Regex : NameRegexFilters) NameFilterer->push_back( llvm::make_unique(Regex)); Filters.push_back(std::unique_ptr(NameFilterer)); } if (RegionCoverageLtFilter.getNumOccurrences() || RegionCoverageGtFilter.getNumOccurrences() || LineCoverageLtFilter.getNumOccurrences() || LineCoverageGtFilter.getNumOccurrences()) { auto StatFilterer = new CoverageFilters; if (RegionCoverageLtFilter.getNumOccurrences()) StatFilterer->push_back(llvm::make_unique( RegionCoverageFilter::LessThan, RegionCoverageLtFilter)); if (RegionCoverageGtFilter.getNumOccurrences()) StatFilterer->push_back(llvm::make_unique( RegionCoverageFilter::GreaterThan, RegionCoverageGtFilter)); if (LineCoverageLtFilter.getNumOccurrences()) StatFilterer->push_back(llvm::make_unique( LineCoverageFilter::LessThan, LineCoverageLtFilter)); if (LineCoverageGtFilter.getNumOccurrences()) StatFilterer->push_back(llvm::make_unique( RegionCoverageFilter::GreaterThan, LineCoverageGtFilter)); Filters.push_back(std::unique_ptr(StatFilterer)); } for (const auto &File : InputSourceFiles) { SmallString<128> Path(File); if (std::error_code EC = sys::fs::make_absolute(Path)) { errs() << "error: " << File << ": " << EC.message(); return 1; } SourceFiles.push_back(Path.str()); } return 0; }; // Parse the object filename if (argc > 1) { StringRef Arg(argv[1]); if (Arg.equals_lower("-help") || Arg.equals_lower("-version")) { cl::ParseCommandLineOptions(2, argv, "LLVM code coverage tool\n"); return 0; } ObjectFilename = Arg; argv[1] = argv[0]; --argc; ++argv; } else { errs() << sys::path::filename(argv[0]) << ": No executable file given!\n"; return 1; } switch (Cmd) { case Show: return show(argc, argv, commandLineParser); case Report: return report(argc, argv, commandLineParser); } return 0; } int CodeCoverageTool::show(int argc, const char **argv, CommandLineParserType commandLineParser) { cl::OptionCategory ViewCategory("Viewing options"); cl::opt ShowLineExecutionCounts( "show-line-counts", cl::Optional, cl::desc("Show the execution counts for each line"), cl::init(true), cl::cat(ViewCategory)); cl::opt ShowRegions( "show-regions", cl::Optional, cl::desc("Show the execution counts for each region"), cl::cat(ViewCategory)); cl::opt 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 ShowExpansions("show-expansions", cl::Optional, cl::desc("Show expanded source regions"), cl::cat(ViewCategory)); cl::opt ShowInstantiations("show-instantiations", cl::Optional, cl::desc("Show function instantiations"), cl::cat(ViewCategory)); cl::opt NoColors("no-colors", cl::Optional, cl::desc("Don't show text colors"), cl::init(false), cl::cat(ViewCategory)); auto Err = commandLineParser(argc, argv); if (Err) return Err; ViewOpts.Colors = !NoColors; ViewOpts.ShowLineNumbers = true; ViewOpts.ShowLineStats = ShowLineExecutionCounts.getNumOccurrences() != 0 || !ShowRegions || ShowBestLineRegionsCounts; ViewOpts.ShowRegionMarkers = ShowRegions || ShowBestLineRegionsCounts; ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts; ViewOpts.ShowExpandedRegions = ShowExpansions; ViewOpts.ShowFunctionInstantiations = ShowInstantiations; if (load()) return 1; if (!Filters.empty()) { // Show functions for (const auto &Function : FunctionMappingRecords) { unsigned MainFileID; if (findMainViewFileID(Function, MainFileID)) continue; StringRef SourceFile = Function.Filenames[MainFileID]; auto SourceBuffer = getSourceFile(SourceFile); if (!SourceBuffer) return 1; SourceCoverageView mainView(SourceBuffer.get(), ViewOpts); createSourceFileView(SourceFile, mainView, Function, true); ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << Function.Name << " from " << SourceFile << ":"; outs() << "\n"; mainView.render(outs(), /*WholeFile=*/false); if (FunctionMappingRecords.size() > 1) outs() << "\n"; } return 0; } // Show files bool ShowFilenames = SourceFiles.size() != 1; if (SourceFiles.empty()) // Get the source files from the function coverage mapping for (StringRef Filename : getUniqueFilenames(FunctionMappingRecords)) SourceFiles.push_back(Filename); for (const auto &SourceFile : SourceFiles) { auto SourceBuffer = getSourceFile(SourceFile); if (!SourceBuffer) return 1; SourceCoverageView mainView(SourceBuffer.get(), ViewOpts); if (createSourceFileView(SourceFile, mainView, FunctionMappingRecords)) { ViewOpts.colored_ostream(outs(), raw_ostream::RED) << "warning: The file '" << SourceFile << "' isn't covered."; outs() << "\n"; continue; } if (ShowFilenames) { ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":"; outs() << "\n"; } mainView.render(outs(), /*Wholefile=*/true); if (SourceFiles.size() > 1) outs() << "\n"; } return 0; } int CodeCoverageTool::report(int argc, const char **argv, CommandLineParserType commandLineParser) { cl::opt NoColors("no-colors", cl::Optional, cl::desc("Don't show text colors"), cl::init(false)); auto Err = commandLineParser(argc, argv); if (Err) return Err; ViewOpts.Colors = !NoColors; if (load()) return 1; CoverageSummary Summarizer; Summarizer.createSummaries(FunctionMappingRecords); CoverageReport Report(ViewOpts, Summarizer); if (SourceFiles.empty() && Filters.empty()) { Report.renderFileReports(llvm::outs()); return 0; } Report.renderFunctionReports(llvm::outs()); return 0; } int show_main(int argc, const char **argv) { CodeCoverageTool Tool; return Tool.run(CodeCoverageTool::Show, argc, argv); } int report_main(int argc, const char **argv) { CodeCoverageTool Tool; return Tool.run(CodeCoverageTool::Report, argc, argv); }