Index: tools/llvm-cov/CMakeLists.txt =================================================================== --- tools/llvm-cov/CMakeLists.txt +++ tools/llvm-cov/CMakeLists.txt @@ -1,6 +1,13 @@ -set(LLVM_LINK_COMPONENTS core support ) +set(LLVM_LINK_COMPONENTS core support object) add_llvm_tool(llvm-cov llvm-cov.cpp gcov.cpp + CodeCoverage.cpp + CoverageFilters.cpp + CoverageReport.cpp + CoverageSummary.cpp + CoverageSummaryInfo.cpp + SourceCoverageDataManager.cpp + SourceCoverageView.cpp ) Index: tools/llvm-cov/CodeCoverage.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/CodeCoverage.cpp @@ -0,0 +1,694 @@ +//===- 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 "FunctionCoverageMapping.h" +#include "RenderingSupport.h" +#include "CoverageViewOptions.h" +#include "CoverageFilters.h" +#include "SourceCoverageDataManager.h" +#include "SourceCoverageView.h" +#include "CoverageSummary.h" +#include "CoverageReport.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, e.g. +/// template functions specializations. +class FunctionInstantiationSetCollector { + ArrayRef FunctionMappings; + typedef uint64_t KeyType; + typedef std::vector SetType; + std::unordered_map InstantiatedFunctions; + + static KeyType getKey(const MappingRegion &R) { + return uint64_t(R.LineStart) | uint64_t(R.ColumnStart) << 32; + } + +public: + void insert(const FunctionCoverageMapping &Function, unsigned FileID) { + KeyType Key = 0; + for (const auto &R : Function.MappingRegions) { + if (R.FileID == FileID) { + Key = getKey(R); + break; + } + } + auto I = InstantiatedFunctions.find(Key); + if (I == InstantiatedFunctions.end()) { + SetType Set; + Set.push_back(&Function); + InstantiatedFunctions.insert(std::make_pair(Key, Set)); + } else + I->second.push_back(&Function); + } + + std::unordered_map::iterator begin() { + return InstantiatedFunctions.begin(); + } + + std::unordered_map::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 MappingRegion &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; +}; +} + +void CodeCoverageTool::error(const Twine &Message, StringRef Whence) { + errs() << "error: "; + if (!Whence.empty()) + errs() << Whence << ": "; + errs() << Message << "\n"; +} + +ErrorOr +CodeCoverageTool::getSourceFile(StringRef SourceFile) { + SmallString<256> Path(SourceFile); + sys::fs::make_absolute(Path); + for (const auto &Files : LoadedSourceFiles) { + if (sys::fs::equivalent(Path.str(), 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( + std::string(Path.begin(), Path.end()), std::move(Buffer.get()))); + return *LoadedSourceFiles.back().second; +} + +/// \brief Return a line start - line end range which contains +/// all the mapping regions of a given function with a particular file id. +std::pair +findExpandedFileInterestingLineRange(unsigned FileID, + const FunctionCoverageMapping &Function) { + unsigned LineStart = std::numeric_limits::max(); + unsigned LineEnd = 0; + for (const auto &Region : Function.MappingRegions) { + if (Region.FileID != FileID) + continue; + LineStart = std::min(Region.LineStart, LineStart); + LineEnd = std::max(Region.LineEnd, LineEnd); + } + return std::make_pair(LineStart, LineEnd); +} + +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 (llvm::sys::fs::equivalent(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 (llvm::sys::fs::equivalent(SourceFile, Function.Filenames[I])) + FilenameEquivalence[I] = true; + } + for (const auto &Region : Function.MappingRegions) { + if (Region.Kind == MappingRegion::ExpansionRegion && + FilenameEquivalence[Region.FileID]) + IsExpandedFile[Region.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 &Region : Function.MappingRegions) { + if (Region.Kind == MappingRegion::ExpansionRegion) + IsExpandedFile[Region.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 MappingRegion &ExpandedRegion, + const FunctionCoverageMapping &Function, SourceCoverageView &Parent) { + auto ExpandedLines = findExpandedFileInterestingLineRange( + ExpandedRegion.ExpandedFileID, Function); + if (ViewOpts.Debug) + llvm::outs() << "Expansion of " << ExpandedRegion.ExpandedFileID << ":" + << ExpandedLines.first << " -> " << ExpandedLines.second + << " @ " << ExpandedRegion.FileID << ", " + << ExpandedRegion.LineStart << ":" + << ExpandedRegion.ColumnStart << "\n"; + auto SourceBuffer = + getSourceFile(Function.Filenames[ExpandedRegion.ExpandedFileID]); + if (!SourceBuffer) + return; + auto SubView = llvm::make_unique( + SourceBuffer.get(), Parent.getOptions(), ExpandedLines.first, + ExpandedLines.second, ExpandedRegion); + SourceCoverageDataManager RegionManager; + for (const auto &Region : Function.MappingRegions) { + if (Region.FileID == ExpandedRegion.ExpandedFileID) + RegionManager.insert(Region); + } + SubView->load(RegionManager); + createExpansionSubViews(*SubView, ExpandedRegion.ExpandedFileID, Function); + Parent.addChild(std::move(SubView)); +} + +void CodeCoverageTool::createExpansionSubViews( + SourceCoverageView &View, unsigned ViewFileID, + const FunctionCoverageMapping &Function) { + if (!ViewOpts.ShowExpandedRegions) + return; + for (const auto &Region : Function.MappingRegions) { + if (Region.Kind != CounterMappingRegion::ExpansionRegion) + continue; + if (Region.FileID != ViewFileID) + continue; + createExpansionSubView(Region, Function, View); + } +} + +void CodeCoverageTool::createInstantiationSubView( + StringRef SourceFile, const FunctionCoverageMapping &Function, + SourceCoverageView &View) { + SourceCoverageDataManager RegionManager; + SmallSet InterestingFileIDs; + if (!gatherInterestingFileIDs(SourceFile, Function, InterestingFileIDs)) + return; + // Get the interesting regions + for (const auto &Region : Function.MappingRegions) { + if (InterestingFileIDs.count(Region.FileID)) + RegionManager.insert(Region); + } + View.load(RegionManager); + unsigned MainFileID; + if (findMainViewFileID(SourceFile, Function, MainFileID)) + return; + createExpansionSubViews(View, MainFileID, Function); +} + +bool CodeCoverageTool::createSourceFileView( + StringRef SourceFile, SourceCoverageView &View, + ArrayRef FunctionMappingRecords, + bool UseOnlyRegionsInMainFile) { + SourceCoverageDataManager RegionManager; + 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 &Region : Function.MappingRegions) { + if (InterestingFileIDs.count(Region.FileID)) + RegionManager.insert(Region); + } + InstantiationSetCollector.insert(Function, MainFileID); + createExpansionSubViews(View, MainFileID, Function); + } + if (RegionManager.getSourceRegions().empty()) + return true; + View.load(RegionManager); + // Show instantiations + if (!ViewOpts.ShowFunctionInstantiations) + return false; + for (const auto &InstantiationSet : InstantiationSetCollector) { + if (InstantiationSet.second.size() < 2) + continue; + auto InterestingRange = findExpandedFileInterestingLineRange( + InstantiationSet.second.front()->MappingRegions.front().FileID, + *InstantiationSet.second.front()); + for (auto Function : InstantiationSet.second) { + auto SubView = llvm::make_unique( + View, InterestingRange.first, InterestingRange.second, + Function->PrettyName); + createInstantiationSubView(SourceFile, *Function, *SubView); + View.addChild(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) { + outs() << "File " << R.FileID << "| " << R.LineStart << ":" + << R.ColumnStart << " -> " << R.LineEnd << ":" << R.ColumnEnd + << " = "; + Ctx.dump(R.Count); + if (R.Kind == CounterMappingRegion::ExpansionRegion) { + outs() << " (Expanded file id = " << R.ExpandedFileID << ") "; + } + outs() << "\n"; + } + std::error_code Error; + Function.MappingRegions.push_back( + MappingRegion(R, Ctx.evaluate(R.Count, Error))); + if (Error && !RegionError) { + errs() << ostream_color(raw_ostream::RED) + << "error: Regions and counters don't match in a function '" + << Function.PrettyName << "' (re-run the instrumented binary)."; + errs() << "\n"; + RegionError = true; + } + } + + if (RegionError || !Filters.matches(Function)) + continue; + + FunctionMappingRecords.push_back(Function); + } + 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::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; + + 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)); + } + + SourceFiles = InputSourceFiles; + 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]; + std::unique_ptr mainView; + auto SourceBuffer = getSourceFile(SourceFile); + if (!SourceBuffer) + return 1; + auto Range = findExpandedFileInterestingLineRange(MainFileID, Function); + mainView.reset(new SourceCoverageView(SourceBuffer.get(), ViewOpts, + Range.first, Range.second)); + createSourceFileView(SourceFile, *mainView, Function, true); + outs() << ViewOpts.color(raw_ostream::CYAN) << Function.PrettyName + << " from " << SourceFile << ":"; + outs() << "\n"; + mainView->render(outs()); + 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 + std::set UniqueFilenames; + for (const auto &Function : FunctionMappingRecords) { + for (const auto &Filename : Function.Filenames) + UniqueFilenames.insert(Filename); + } + for (const auto &Filename : UniqueFilenames) + SourceFiles.push_back(Filename); + } + + for (const auto &SourceFile : SourceFiles) { + std::unique_ptr mainView; + auto SourceBuffer = getSourceFile(SourceFile); + if (!SourceBuffer) + return 1; + mainView.reset(new SourceCoverageView(SourceBuffer.get(), ViewOpts)); + if (createSourceFileView(SourceFile, *mainView, FunctionMappingRecords)) { + outs() << ViewOpts.color(raw_ostream::RED) << "warning: The file '" + << SourceFile << "' isn't covered."; + outs() << "\n"; + continue; + } + + if (ShowFilenames) { + outs() << ViewOpts.color(raw_ostream::CYAN) << SourceFile << ":"; + outs() << "\n"; + } + mainView->render(outs()); + 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); +} Index: tools/llvm-cov/CoverageFilters.h =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageFilters.h @@ -0,0 +1,125 @@ +//===- CoverageFilters.h - Function coverage mapping filters --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// These classes provide filtering for function coverage mapping records. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGEFILTERS_H +#define LLVM_COV_COVERAGEFILTERS_H + +#include "FunctionCoverageMapping.h" +#include +#include + +namespace llvm { + +/// \brief Matches specific functions that pass the requirement of this filter. +class CoverageFilter { +public: + virtual ~CoverageFilter() {} + + /// \brief Return true if the function passes the requirements of this filter. + virtual bool matches(const FunctionCoverageMapping &Function) { return true; } +}; + +/// \brief Matches functions that contain a specific string in their name. +class NameCoverageFilter : public CoverageFilter { + StringRef Name; + +public: + NameCoverageFilter(StringRef Name) : Name(Name) {} + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief Matches functions whose name matches a certain regular expression. +class NameRegexCoverageFilter : public CoverageFilter { + StringRef Regex; + +public: + NameRegexCoverageFilter(StringRef Regex) : Regex(Regex) {} + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief Matches numbers that pass a certain threshold. +template class StatisticThresholdFilter { +public: + enum Operation { LessThan, GreaterThan }; + +protected: + Operation Op; + T Threshold; + + StatisticThresholdFilter(Operation Op, T Threshold) + : Op(Op), Threshold(Threshold) {} + + /// \brief Return true if the given number is less than + /// or greater than the certain threshold. + bool PassesThreshold(T Value) const { + switch (Op) { + case LessThan: + return Value < Threshold; + case GreaterThan: + return Value > Threshold; + } + return false; + } +}; + +/// \brief Matches functions whose region coverage percentage +/// is above/below a certain percentage. +class RegionCoverageFilter : public CoverageFilter, + public StatisticThresholdFilter { +public: + RegionCoverageFilter(Operation Op, double Threshold) + : StatisticThresholdFilter(Op, Threshold) {} + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief Matches functions whose line coverage percentage +/// is above/below a certain percentage. +class LineCoverageFilter : public CoverageFilter, + public StatisticThresholdFilter { +public: + LineCoverageFilter(Operation Op, double Threshold) + : StatisticThresholdFilter(Op, Threshold) {} + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief A collection of filters. +/// Matches functions that match any filters contained +/// in an instance of this class. +class CoverageFilters : public CoverageFilter { +protected: + std::vector> Filters; + +public: + /// \brief Append a filter to this collection. + void push_back(std::unique_ptr Filter); + + bool empty() const { return Filters.empty(); } + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief A collection of filters. +/// Matches functions that match all of the filters contained +/// in an instance of this class. +class CoverageFiltersMatchAll : public CoverageFilters { +public: + bool matches(const FunctionCoverageMapping &Function) override; +}; + +} // namespace llvm + +#endif // LLVM_COV_COVERAGEFILTERS_H Index: tools/llvm-cov/CoverageFilters.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageFilters.cpp @@ -0,0 +1,57 @@ +//===- CoverageFilters.cpp - Function coverage mapping filters ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// These classes provide filtering for function coverage mapping records. +// +//===----------------------------------------------------------------------===// + +#include "CoverageFilters.h" +#include "CoverageSummaryInfo.h" +#include "llvm/Support/Regex.h" + +using namespace llvm; + +bool NameCoverageFilter::matches(const FunctionCoverageMapping &Function) { + StringRef FuncName = Function.PrettyName; + return FuncName.find(Name) != StringRef::npos; +} + +bool NameRegexCoverageFilter::matches(const FunctionCoverageMapping &Function) { + return llvm::Regex(Regex).match(Function.PrettyName); +} + +bool RegionCoverageFilter::matches(const FunctionCoverageMapping &Function) { + return PassesThreshold(FunctionCoverageSummary::get(Function) + .RegionCoverage.getPercentCovered()); +} + +bool LineCoverageFilter::matches(const FunctionCoverageMapping &Function) { + return PassesThreshold( + FunctionCoverageSummary::get(Function).LineCoverage.getPercentCovered()); +} + +void CoverageFilters::push_back(std::unique_ptr Filter) { + Filters.push_back(std::move(Filter)); +} + +bool CoverageFilters::matches(const FunctionCoverageMapping &Function) { + for (const auto &Filter : Filters) { + if (Filter->matches(Function)) + return true; + } + return false; +} + +bool CoverageFiltersMatchAll::matches(const FunctionCoverageMapping &Function) { + for (const auto &Filter : Filters) { + if (!Filter->matches(Function)) + return false; + } + return true; +} Index: tools/llvm-cov/CoverageReport.h =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageReport.h @@ -0,0 +1,40 @@ +//===- CoverageReport.h - Code coverage report ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class implements rendering of a code coverage report. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGEREPORT_H +#define LLVM_COV_COVERAGEREPORT_H + +#include "CoverageViewOptions.h" +#include "CoverageSummary.h" + +namespace llvm { + +/// \brief Displays the code coverage report. +class CoverageReport { + const CoverageViewOptions &Options; + CoverageSummary &Summary; + + void render(const FileCoverageSummary &File, raw_ostream &OS); + void render(const FunctionCoverageSummary &Function, raw_ostream &OS); + +public: + CoverageReport(const CoverageViewOptions &Options, CoverageSummary &Summary) + : Options(Options), Summary(Summary) {} + + void renderFunctionReports(raw_ostream &OS); + + void renderFileReports(raw_ostream &OS); +}; +} + +#endif // LLVM_COV_COVERAGEREPORT_H Index: tools/llvm-cov/CoverageReport.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageReport.cpp @@ -0,0 +1,201 @@ +//===- CoverageReport.cpp - Code coverage report -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class implements rendering of a code coverage report. +// +//===----------------------------------------------------------------------===// + +#include "CoverageReport.h" +#include "CoverageSummary.h" +#include "RenderingSupport.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FileSystem.h" + +using namespace llvm; +namespace { +/// \brief Helper struct which prints trimmed and aligned columns. +struct Column { + enum TrimKind { NoTrim, LeftTrim, RightTrim }; + + enum AlignmentKind { LeftAlignment, RightAlignment }; + + StringRef Str; + unsigned Width; + TrimKind Trim; + AlignmentKind Alignment; + + Column(StringRef Str, unsigned Width) + : Str(Str), Width(Width), Trim(NoTrim), Alignment(LeftAlignment) {} + + Column &set(TrimKind Value) { + Trim = Value; + return *this; + } + + Column &set(AlignmentKind Value) { + Alignment = Value; + return *this; + } + + void render(raw_ostream &OS) const; +}; +raw_ostream &operator<<(raw_ostream &OS, const Column &Value) { + Value.render(OS); + return OS; +} +} + +void Column::render(raw_ostream &OS) const { + if (Str.size() <= Width) { + if (Alignment == RightAlignment) { + OS.indent(Width - Str.size()); + OS << Str; + return; + } + OS << Str; + OS.indent(Width - Str.size()); + return; + } + + switch (Trim) { + case NoTrim: + OS << Str.substr(0, Width); + break; + case LeftTrim: + OS << "..." << Str.substr(Str.size() - Width + 3); + break; + case RightTrim: + OS << Str.substr(0, Width - 3) << "..."; + break; + } +} + +static Column column(StringRef Str, unsigned Width) { + return Column(Str, Width); +} + +template +static Column column(StringRef Str, unsigned Width, const T &Value) { + return Column(Str, Width).set(Value); +} + +static const unsigned FileReportColumns[] = {25, 10, 8, 8, 10, 8}; +static const unsigned FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8}; + +/// \brief Prints a horizontal divider which spans across the given columns. +template +static void renderDivider(T (&Columns)[N], raw_ostream &OS) { + unsigned Length = 0; + for (unsigned I = 0; I < N; ++I) + Length += Columns[I]; + for (unsigned I = 0; I < Length; ++I) + OS << '-'; +} + +/// \brief Return the color which correponds to the coverage +/// percentage of a certain metric. +template +static raw_ostream::Colors determineCoveragePercentageColor(const T &Info) { + if (Info.isFullyCovered()) + return raw_ostream::GREEN; + return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW + : raw_ostream::RED; +} + +void CoverageReport::render(const FileCoverageSummary &File, raw_ostream &OS) { + OS << column(File.Name, FileReportColumns[0], Column::LeftTrim) + << format("%*zd", FileReportColumns[1], File.RegionCoverage.NumRegions); + (OS << Options.color(File.RegionCoverage.isFullyCovered() ? raw_ostream::GREEN + : raw_ostream::RED)) + .OS + << format("%*zd", FileReportColumns[2], File.RegionCoverage.NotCovered); + (OS << Options.color(determineCoveragePercentageColor(File.RegionCoverage))) + .OS + << format("%*.2f", FileReportColumns[3] - 1, + File.RegionCoverage.getPercentCovered()) << '%'; + OS << format("%*zd", FileReportColumns[4], + File.FunctionCoverage.NumFunctions); + (OS << Options.color(determineCoveragePercentageColor(File.FunctionCoverage))) + .OS + << format("%*.2f", FileReportColumns[5] - 1, + File.FunctionCoverage.getPercentCovered()) << '%'; + OS << "\n"; +} + +void CoverageReport::render(const FunctionCoverageSummary &Function, + raw_ostream &OS) { + OS << column(Function.Name, FunctionReportColumns[0], Column::RightTrim) + << format("%*zd", FunctionReportColumns[1], + Function.RegionCoverage.NumRegions); + (OS << Options.color(Function.RegionCoverage.isFullyCovered() + ? raw_ostream::GREEN + : raw_ostream::RED)).OS + << format("%*zd", FunctionReportColumns[2], + Function.RegionCoverage.NotCovered); + (OS << Options.color( + determineCoveragePercentageColor(Function.RegionCoverage))).OS + << format("%*.2f", FunctionReportColumns[3] - 1, + Function.RegionCoverage.getPercentCovered()) << '%'; + OS << format("%*zd", FunctionReportColumns[4], + Function.LineCoverage.NumLines); + (OS << Options.color(Function.LineCoverage.isFullyCovered() + ? raw_ostream::GREEN + : raw_ostream::RED)).OS + << format("%*zd", FunctionReportColumns[5], + Function.LineCoverage.NotCovered); + (OS << Options.color(determineCoveragePercentageColor(Function.LineCoverage))) + .OS + << format("%*.2f", FunctionReportColumns[6] - 1, + Function.LineCoverage.getPercentCovered()) << '%'; + OS << "\n"; +} + +void CoverageReport::renderFunctionReports(raw_ostream &OS) { + bool isFirst = true; + for (const auto &File : Summary.getFileSummaries()) { + if (isFirst) + isFirst = false; + else + OS << "\n"; + OS << "File '" << File.Name << "':\n"; + OS << column("Name", FunctionReportColumns[0]) + << column("Regions", FunctionReportColumns[1], Column::RightAlignment) + << column("Miss", FunctionReportColumns[2], Column::RightAlignment) + << column("Cover", FunctionReportColumns[3], Column::RightAlignment) + << column("Lines", FunctionReportColumns[4], Column::RightAlignment) + << column("Miss", FunctionReportColumns[5], Column::RightAlignment) + << column("Cover", FunctionReportColumns[6], Column::RightAlignment); + OS << "\n"; + renderDivider(FunctionReportColumns, OS); + OS << "\n"; + for (const auto &Function : File.FunctionSummaries) + render(Function, OS); + renderDivider(FunctionReportColumns, OS); + OS << "\n"; + render(FunctionCoverageSummary("TOTAL", File.RegionCoverage, + File.LineCoverage), + OS); + } +} + +void CoverageReport::renderFileReports(raw_ostream &OS) { + OS << column("Filename", FileReportColumns[0]) + << column("Regions", FileReportColumns[1], Column::RightAlignment) + << column("Miss", FileReportColumns[2], Column::RightAlignment) + << column("Cover", FileReportColumns[3], Column::RightAlignment) + << column("Functions", FileReportColumns[4], Column::RightAlignment) + << column("Cover", FileReportColumns[5], Column::RightAlignment) << "\n"; + renderDivider(FileReportColumns, OS); + OS << "\n"; + for (const auto &File : Summary.getFileSummaries()) + render(File, OS); + renderDivider(FileReportColumns, OS); + OS << "\n"; + render(Summary.getCombinedFileSummaries(), OS); +} Index: tools/llvm-cov/CoverageSummary.h =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageSummary.h @@ -0,0 +1,45 @@ +//===- CoverageSummary.h - Code coverage summary --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class implements data management and rendering for the code coverage +// summaries of all files and functions. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGESUMMARY_H +#define LLVM_COV_COVERAGESUMMARY_H + +#include "CoverageSummaryInfo.h" +#include + +namespace llvm { + +/// \brief Manager for the function and file code coverage summaries. +class CoverageSummary { + std::vector Filenames; + std::vector FunctionSummaries; + std::vector> FunctionSummariesFileIDs; + std::vector FileSummaries; + + unsigned getFileID(StringRef Filename); + +public: + void createSummaries(ArrayRef Functions); + + ArrayRef getFileSummaries() { return FileSummaries; } + + FileCoverageSummary getCombinedFileSummaries(); + + void render(const FunctionCoverageSummary &Summary, raw_ostream &OS); + + void render(raw_ostream &OS); +}; +} + +#endif // LLVM_COV_COVERAGESUMMARY_H Index: tools/llvm-cov/CoverageSummary.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageSummary.cpp @@ -0,0 +1,92 @@ +//===- CoverageSummary.cpp - Code coverage summary ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class implements data management and rendering for the code coverage +// summaries of all files and functions. +// +//===----------------------------------------------------------------------===// + +#include "CoverageSummary.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Format.h" + +using namespace llvm; + +unsigned CoverageSummary::getFileID(StringRef Filename) { + for (unsigned I = 0, E = Filenames.size(); I < E; ++I) { + if (sys::fs::equivalent(Filenames[I], Filename)) + return I; + } + Filenames.push_back(Filename); + return Filenames.size() - 1; +} + +void +CoverageSummary::createSummaries(ArrayRef Functions) { + std::vector> FunctionFileIDs; + + FunctionFileIDs.resize(Functions.size()); + for (size_t I = 0, E = Functions.size(); I < E; ++I) { + StringRef Filename = Functions[I].Filenames[0]; + FunctionFileIDs[I] = std::make_pair(getFileID(Filename), I); + } + + // Sort the function records by file ids + std::sort(FunctionFileIDs.begin(), FunctionFileIDs.end(), + [](const std::pair &lhs, + const std::pair &rhs) { + return lhs.first < rhs.first; + }); + + // Create function summaries in a sorted order (by file ids) + FunctionSummaries.reserve(Functions.size()); + for (size_t I = 0, E = Functions.size(); I < E; ++I) + FunctionSummaries.push_back( + FunctionCoverageSummary::get(Functions[FunctionFileIDs[I].second])); + + // Create file summaries + size_t CurrentSummary = 0; + for (unsigned FileID = 0; FileID < Filenames.size(); ++FileID) { + // Gather the relevant functions summaries + auto PrevSummary = CurrentSummary; + while (CurrentSummary < FunctionSummaries.size() && + FunctionFileIDs[CurrentSummary].first == FileID) + ++CurrentSummary; + ArrayRef LocalSummaries( + FunctionSummaries.data() + PrevSummary, + FunctionSummaries.data() + CurrentSummary); + if (LocalSummaries.empty()) + continue; + + FileSummaries.push_back( + FileCoverageSummary::get(Filenames[FileID], LocalSummaries)); + } +} + +FileCoverageSummary CoverageSummary::getCombinedFileSummaries() { + size_t NumRegions = 0, CoveredRegions = 0; + size_t NumLines = 0, NonCodeLines = 0, CoveredLines = 0; + size_t NumFunctionsCovered = 0, NumFunctions = 0; + for (const auto &File : FileSummaries) { + NumRegions += File.RegionCoverage.NumRegions; + CoveredRegions += File.RegionCoverage.Covered; + + NumLines += File.LineCoverage.NumLines; + NonCodeLines += File.LineCoverage.NonCodeLines; + CoveredLines += File.LineCoverage.Covered; + + NumFunctionsCovered += File.FunctionCoverage.FullyCovered; + NumFunctions += File.FunctionCoverage.NumFunctions; + } + return FileCoverageSummary( + "TOTAL", RegionCoverageInfo(CoveredRegions, NumRegions), + LineCoverageInfo(CoveredLines, NonCodeLines, NumLines), + FunctionCoverageInfo(NumFunctionsCovered, NumFunctions), + ArrayRef()); +} Index: tools/llvm-cov/CoverageSummaryInfo.h =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageSummaryInfo.h @@ -0,0 +1,131 @@ +//===- CoverageSummaryInfo.h - Coverage summary for function/file ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// These structures are used to represent code coverage metrics +// for functions/files. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGESUMMARYINFO_H +#define LLVM_COV_COVERAGESUMMARYINFO_H + +#include "FunctionCoverageMapping.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { + +/// \brief Provides information about region coverage for a function/file. +struct RegionCoverageInfo { + /// \brief The number of regions that were executed at least once. + size_t Covered; + + /// \brief The number of regions that weren't executed. + size_t NotCovered; + + /// \brief The total number of regions in a function/file. + size_t NumRegions; + + RegionCoverageInfo(size_t Covered, size_t NumRegions) + : Covered(Covered), NotCovered(NumRegions - Covered), + NumRegions(NumRegions) {} + + bool isFullyCovered() const { return Covered == NumRegions; } + + double getPercentCovered() const { + return double(Covered) / double(NumRegions) * 100.0; + } +}; + +/// \brief Provides information about line coverage for a function/file. +struct LineCoverageInfo { + /// \brief The number of lines that were executed at least once. + size_t Covered; + + /// \brief The number of lines that weren't executed. + size_t NotCovered; + + /// \brief The number of lines that aren't code. + size_t NonCodeLines; + + /// \brief The total number of lines in a function/file. + size_t NumLines; + + LineCoverageInfo(size_t Covered, size_t NumNonCodeLines, size_t NumLines) + : Covered(Covered), NotCovered(NumLines - NumNonCodeLines - Covered), + NonCodeLines(NumNonCodeLines), NumLines(NumLines) {} + + bool isFullyCovered() const { return Covered == (NumLines - NonCodeLines); } + + double getPercentCovered() const { + return double(Covered) / double(NumLines - NonCodeLines) * 100.0; + } +}; + +/// \brief Provides information about function coverage for a file. +struct FunctionCoverageInfo { + /// \brief The number of functions that have full + /// region coverage. + size_t FullyCovered; + + /// \brief The total number of functions in this file. + size_t NumFunctions; + + FunctionCoverageInfo(size_t FullyCovered, size_t NumFunctions) + : FullyCovered(FullyCovered), NumFunctions(NumFunctions) {} + + bool isFullyCovered() const { return FullyCovered == NumFunctions; } + + double getPercentCovered() const { + return double(FullyCovered) / double(NumFunctions) * 100.0; + } +}; + +/// \brief A summary of function's code coverage. +struct FunctionCoverageSummary { + StringRef Name; + RegionCoverageInfo RegionCoverage; + LineCoverageInfo LineCoverage; + + FunctionCoverageSummary(StringRef Name, + const RegionCoverageInfo &RegionCoverage, + const LineCoverageInfo &LineCoverage) + : Name(Name), RegionCoverage(RegionCoverage), LineCoverage(LineCoverage) { + } + + /// \brief Compute the code coverage summary for the given function coverage + /// mapping record. + static FunctionCoverageSummary get(const FunctionCoverageMapping &Function); +}; + +/// \brief A summary of file's code coverage. +struct FileCoverageSummary { + StringRef Name; + RegionCoverageInfo RegionCoverage; + LineCoverageInfo LineCoverage; + FunctionCoverageInfo FunctionCoverage; + /// \brief The summary of every function + /// in this file. + ArrayRef FunctionSummaries; + + FileCoverageSummary(StringRef Name, const RegionCoverageInfo &RegionCoverage, + const LineCoverageInfo &LineCoverage, + const FunctionCoverageInfo &FunctionCoverage, + ArrayRef FunctionSummaries) + : Name(Name), RegionCoverage(RegionCoverage), LineCoverage(LineCoverage), + FunctionCoverage(FunctionCoverage), + FunctionSummaries(FunctionSummaries) {} + + /// \brief Compute the code coverage summary for a file. + static FileCoverageSummary + get(StringRef Name, ArrayRef FunctionSummaries); +}; + +} // namespace llvm + +#endif // LLVM_COV_COVERAGESUMMARYINFO_H Index: tools/llvm-cov/CoverageSummaryInfo.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageSummaryInfo.cpp @@ -0,0 +1,95 @@ +//===- CoverageSummaryInfo.cpp - Coverage summary for function/file -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// These structures are used to represent code coverage metrics +// for functions/files. +// +//===----------------------------------------------------------------------===// + +#include "CoverageSummaryInfo.h" + +using namespace llvm; +using namespace coverage; + +FunctionCoverageSummary +FunctionCoverageSummary::get(const FunctionCoverageMapping &Function) { + // Compute the region coverage + size_t NumCodeRegions = 0, CoveredRegions = 0; + for (auto &Region : Function.MappingRegions) { + if (Region.Kind != CounterMappingRegion::CodeRegion) + continue; + ++NumCodeRegions; + if (Region.ExecutionCount != 0) + ++CoveredRegions; + } + + // Compute the line coverage + size_t NumLines = 0, CoveredLines = 0; + for (unsigned FileID = 0, E = Function.Filenames.size(); FileID < E; + ++FileID) { + // Find the line start and end of the function's source code + // in that particular file + unsigned LineStart = std::numeric_limits::max(); + unsigned LineEnd = 0; + for (auto &Region : Function.MappingRegions) { + if (Region.FileID != FileID) + continue; + LineStart = std::min(LineStart, Region.LineStart); + LineEnd = std::max(LineEnd, Region.LineEnd); + } + unsigned LineCount = LineEnd - LineStart + 1; + + // Get counters + llvm::SmallVector ExecutionCounts; + ExecutionCounts.resize(LineCount, 0); + for (auto &Region : Function.MappingRegions) { + if (Region.FileID != FileID) + continue; + // Ignore the lines that were skipped by the preprocessor. + auto ExecutionCount = Region.ExecutionCount; + if (Region.Kind == MappingRegion::SkippedRegion) { + LineCount -= Region.LineEnd - Region.LineStart + 1; + ExecutionCount = 1; + } + for (unsigned I = Region.LineStart; I <= Region.LineEnd; ++I) + ExecutionCounts[I - LineStart] = ExecutionCount; + } + CoveredLines += LineCount - std::count(ExecutionCounts.begin(), + ExecutionCounts.end(), 0); + NumLines += LineCount; + } + return FunctionCoverageSummary( + Function.PrettyName, RegionCoverageInfo(CoveredRegions, NumCodeRegions), + LineCoverageInfo(CoveredLines, 0, NumLines)); +} + +FileCoverageSummary +FileCoverageSummary::get(StringRef Name, + ArrayRef FunctionSummaries) { + size_t NumRegions = 0, CoveredRegions = 0; + size_t NumLines = 0, NonCodeLines = 0, CoveredLines = 0; + size_t NumFunctionsCovered = 0; + for (const auto &Func : FunctionSummaries) { + CoveredRegions += Func.RegionCoverage.Covered; + NumRegions += Func.RegionCoverage.NumRegions; + + CoveredLines += Func.LineCoverage.Covered; + NonCodeLines += Func.LineCoverage.NonCodeLines; + NumLines += Func.LineCoverage.NumLines; + + if (Func.RegionCoverage.isFullyCovered()) + ++NumFunctionsCovered; + } + + return FileCoverageSummary( + Name, RegionCoverageInfo(CoveredRegions, NumRegions), + LineCoverageInfo(CoveredLines, NonCodeLines, NumLines), + FunctionCoverageInfo(NumFunctionsCovered, FunctionSummaries.size()), + FunctionSummaries); +} Index: tools/llvm-cov/CoverageViewOptions.h =================================================================== --- /dev/null +++ tools/llvm-cov/CoverageViewOptions.h @@ -0,0 +1,35 @@ +//===- CoverageViewOptions.h - Code coverage display options -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGEVIEWOPTIONS_H +#define LLVM_COV_COVERAGEVIEWOPTIONS_H + +#include "RenderingSupport.h" + +namespace llvm { + +/// \brief The options for displaying the code coverage information. +struct CoverageViewOptions { + bool Debug; + bool Colors; + bool ShowLineNumbers; + bool ShowLineStats; + bool ShowRegionMarkers; + bool ShowLineStatsOrRegionMarkers; + bool ShowExpandedRegions; + bool ShowFunctionInstantiations; + + /// \brief Change the output's stream color if the colors are enabled. + ColoredRawOstream::ColorValue color(raw_ostream::Colors Color) const { + return ostream_color(Color, Colors); + } +}; +} + +#endif // LLVM_COV_COVERAGEVIEWOPTIONS_H Index: tools/llvm-cov/FunctionCoverageMapping.h =================================================================== --- /dev/null +++ tools/llvm-cov/FunctionCoverageMapping.h @@ -0,0 +1,50 @@ +//===- FunctionCoverageMapping.h - Function coverage mapping record -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// A structure that stores the coverage mapping record for a single function. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_FUNCTIONCOVERAGEMAPPING_H +#define LLVM_COV_FUNCTIONCOVERAGEMAPPING_H + +#include +#include +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ProfileData/CoverageMapping.h" + +namespace llvm { + +/// \brief Associates a source range with an execution count. +struct MappingRegion : public coverage::CounterMappingRegion { + uint64_t ExecutionCount; + + MappingRegion(const CounterMappingRegion &R, uint64_t ExecutionCount) + : CounterMappingRegion(R), ExecutionCount(ExecutionCount) {} +}; + +/// \brief Stores all the required information +/// about code coverage for a single function. +struct FunctionCoverageMapping { + /// \brief Raw function name. + std::string Name; + /// \brief Demangled function name. + std::string PrettyName; + std::vector Filenames; + std::vector MappingRegions; + + FunctionCoverageMapping(StringRef Name, ArrayRef Filenames) + : Name(Name), PrettyName(Name), + Filenames(Filenames.begin(), Filenames.end()) {} +}; + +} // namespace llvm + +#endif // LLVM_COV_FUNCTIONCOVERAGEMAPPING_H Index: tools/llvm-cov/LLVMBuild.txt =================================================================== --- tools/llvm-cov/LLVMBuild.txt +++ tools/llvm-cov/LLVMBuild.txt @@ -19,4 +19,4 @@ type = Tool name = llvm-cov parent = Tools -required_libraries = Instrumentation +required_libraries = ProfileData Support Instrumentation Index: tools/llvm-cov/Makefile =================================================================== --- tools/llvm-cov/Makefile +++ tools/llvm-cov/Makefile @@ -9,7 +9,7 @@ LEVEL := ../.. TOOLNAME := llvm-cov -LINK_COMPONENTS := core support +LINK_COMPONENTS := core support profiledata object # This tool has no plugins, optimize startup time. TOOL_NO_EXPORTS := 1 Index: tools/llvm-cov/RenderingSupport.h =================================================================== --- /dev/null +++ tools/llvm-cov/RenderingSupport.h @@ -0,0 +1,60 @@ +//===- RenderingSupport.h - output stream rendering support functions ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_RENDERINGSUPPORT_H +#define LLVM_COV_RENDERINGSUPPORT_H + +#include "llvm/Support/raw_ostream.h" + +namespace llvm { + +struct ColoredRawOstream { + raw_ostream &OS; + bool IsColorUsed; + + struct ColorValue { + raw_ostream::Colors Color; + bool IsColorUsed; + + ColorValue(raw_ostream::Colors Color, bool IsColorUsed) + : Color(Color), IsColorUsed(IsColorUsed) {} + }; + + ColoredRawOstream(raw_ostream &OS, bool IsColorUsed) + : OS(OS), IsColorUsed(IsColorUsed) {} + + ColoredRawOstream(const ColoredRawOstream &OS) LLVM_DELETED_FUNCTION; + + ColoredRawOstream(ColoredRawOstream &&OS) + : OS(OS.OS), IsColorUsed(OS.IsColorUsed) { + OS.IsColorUsed = false; + } + + ~ColoredRawOstream() { + if (IsColorUsed) + OS.resetColor(); + } + + operator raw_ostream &() const { return OS; } +}; + +inline ColoredRawOstream::ColorValue ostream_color(raw_ostream::Colors Color, + bool IsColorUsed = false) { + return ColoredRawOstream::ColorValue(Color, IsColorUsed); +} + +inline ColoredRawOstream +operator<<(raw_ostream &OS, const ColoredRawOstream::ColorValue &Color) { + if (Color.IsColorUsed) + OS.changeColor(Color.Color); + return ColoredRawOstream(OS, Color.IsColorUsed); +} +} + +#endif // LLVM_COV_RENDERINGSUPPORT_H Index: tools/llvm-cov/SourceCoverageDataManager.h =================================================================== --- /dev/null +++ tools/llvm-cov/SourceCoverageDataManager.h @@ -0,0 +1,84 @@ +//===- SourceCoverageDataManager.h - Manager for source file coverage data-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class separates and merges mapping regions for a specific source file. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_SOURCECOVERAGEDATAMANAGER_H +#define LLVM_COV_SOURCECOVERAGEDATAMANAGER_H + +#include "FunctionCoverageMapping.h" +#include "llvm/ProfileData/CoverageMapping.h" +#include "llvm/ADT/Hashing.h" +#include +#include + +namespace llvm { + +/// \brief Partions mapping regions by their kind and sums +/// the execution counts of the regions that start at the same location. +class SourceCoverageDataManager { +public: + struct SourceRange { + unsigned LineStart, ColumnStart, LineEnd, ColumnEnd; + + SourceRange(unsigned LineStart, unsigned ColumnStart, unsigned LineEnd, + unsigned ColumnEnd) + : LineStart(LineStart), ColumnStart(ColumnStart), LineEnd(LineEnd), + ColumnEnd(ColumnEnd) {} + + bool operator==(const SourceRange &Other) const { + return LineStart == Other.LineStart && ColumnStart == Other.ColumnStart && + LineEnd == Other.LineEnd && ColumnEnd == Other.ColumnEnd; + } + + bool operator<(const SourceRange &Other) const { + if (LineStart == Other.LineStart) + return ColumnStart < Other.ColumnStart; + return LineStart < Other.LineStart; + } + + bool contains(const SourceRange &Other) { + if (LineStart > Other.LineStart || + (LineStart == Other.LineStart && ColumnStart > Other.ColumnStart)) + return false; + if (LineEnd < Other.LineEnd || + (LineEnd == Other.LineEnd && ColumnEnd < Other.ColumnEnd)) + return false; + return true; + } + + struct Hash { + size_t operator()(const SourceRange &Range) const { + return hash_combine(Range.LineStart, Range.ColumnStart, Range.LineEnd, + Range.ColumnEnd); + } + }; + }; + +protected: + std::unordered_map Regions; + std::vector> SortedRegions; + std::vector SkippedRegions; + +public: + void insert(const MappingRegion &Region); + + /// \brief Return the source ranges and execution counts + /// obtained from the non-skipped mapping regions. + ArrayRef> getSourceRegions(); + + /// \brief Return the source ranges obtained from the skipped mapping regions. + ArrayRef getSkippedRegions() const { return SkippedRegions; } +}; + +} // namespace llvm + +#endif // LLVM_COV_SOURCECOVERAGEDATAMANAGER_H Index: tools/llvm-cov/SourceCoverageDataManager.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/SourceCoverageDataManager.cpp @@ -0,0 +1,53 @@ +//===- SourceCoverageDataManager.cpp - Manager for source file coverage +// data-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class separates and merges mapping regions for a specific source file. +// +//===----------------------------------------------------------------------===// + +#include "SourceCoverageDataManager.h" + +using namespace llvm; +using namespace coverage; + +void SourceCoverageDataManager::insert(const MappingRegion &Region) { + SourceRange Range(Region.LineStart, Region.ColumnStart, Region.LineEnd, + Region.ColumnEnd); + if (Region.Kind == CounterMappingRegion::SkippedRegion) { + SkippedRegions.push_back(Range); + return; + } + auto Inserted = Regions.insert(std::make_pair(Range, Region.ExecutionCount)); + if (Inserted.second) + return; + Inserted.first->second += Region.ExecutionCount; +} + +ArrayRef> +SourceCoverageDataManager::getSourceRegions() { + if (!SortedRegions.empty()) + return SortedRegions; + + SortedRegions.reserve(Regions.size()); + for (const auto &Region : Regions) + SortedRegions.push_back(std::make_pair(Region.first, Region.second)); + + // Get rid of the map + Regions.clear(); + + // Sort the final source regions + std::sort(SortedRegions.begin(), SortedRegions.end(), + [](const std::pair &LHS, + const std::pair &RHS) { + return LHS.first < RHS.first; + }); + + return SortedRegions; +} Index: tools/llvm-cov/SourceCoverageView.h =================================================================== --- /dev/null +++ tools/llvm-cov/SourceCoverageView.h @@ -0,0 +1,213 @@ +//===- SourceCoverageView.h - Code coverage view for source code ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class implements rendering for code coverage of source code. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_SOURCECOVERAGEVIEW_H +#define LLVM_COV_SOURCECOVERAGEVIEW_H + +#include "CoverageViewOptions.h" +#include "SourceCoverageDataManager.h" +#include "llvm/ProfileData/CoverageMapping.h" +#include "llvm/Support/MemoryBuffer.h" +#include + +namespace llvm { + +/// \brief A code coverage view of a specific source file. +/// It can have embedded coverage views. +class SourceCoverageView { +public: + enum SubViewKind { View, ExpansionView, InstantiationView }; + + /// \brief Coverage information for a single line. + struct LineCoverageInfo { + uint64_t ExecutionCount; + unsigned RegionCount; + bool Mapped; + + LineCoverageInfo() : ExecutionCount(0), RegionCount(0), Mapped(false) {} + + bool isMapped() const { return Mapped; } + + bool hasMultipleRegions() const { return RegionCount > 1; } + + void addRegionStartCount(uint64_t Count) { + Mapped = true; + ExecutionCount = Count; + ++RegionCount; + } + + void addRegionCount(uint64_t Count) { + Mapped = true; + ExecutionCount = Count; + } + }; + + /// \brief A marker that points at the start + /// of a specific mapping region. + struct RegionMarker { + unsigned Line, Column; + uint64_t ExecutionCount; + + RegionMarker(unsigned Line, unsigned Column, uint64_t Value) + : Line(Line), Column(Column), ExecutionCount(Value) {} + }; + + /// \brief A single line source range used to + /// render highlighted text. + struct HighlightRange { + enum HighlightKind { + /// The code that wasn't executed. + NotCovered, + + /// The region of code that was expanded. + Expanded + }; + HighlightKind Kind; + unsigned Line; + unsigned ColumnStart; + unsigned ColumnEnd; + + HighlightRange(unsigned Line, unsigned ColumnStart, unsigned ColumnEnd, + HighlightKind Kind = NotCovered) + : Kind(Kind), Line(Line), ColumnStart(ColumnStart), + ColumnEnd(ColumnEnd) {} + + bool operator<(const HighlightRange &Other) const { + if (Line == Other.Line) + return ColumnStart < Other.ColumnStart; + return Line < Other.Line; + } + + bool columnStartOverlaps(const HighlightRange &Other) const { + return ColumnStart <= Other.ColumnStart && ColumnEnd > Other.ColumnStart; + } + bool columnEndOverlaps(const HighlightRange &Other) const { + return ColumnEnd >= Other.ColumnEnd && ColumnStart < Other.ColumnEnd; + } + bool contains(const HighlightRange &Other) const { + if (Line != Other.Line) + return false; + return ColumnStart <= Other.ColumnStart && ColumnEnd >= Other.ColumnEnd; + } + + bool overlaps(const HighlightRange &Other) const { + if (Line != Other.Line) + return false; + return columnStartOverlaps(Other) || columnEndOverlaps(Other); + } + }; + +private: + const MemoryBuffer &File; + const CoverageViewOptions &Options; + unsigned LineStart, LineCount; + SubViewKind Kind; + coverage::CounterMappingRegion ExpansionRegion; + std::vector> Children; + std::vector LineStats; + std::vector HighlightRanges; + std::vector Markers; + StringRef FunctionName; + + /// \brief Create the line coverage information using the coverage data. + void createLineCoverageInfo(SourceCoverageDataManager &Data); + + /// \brief Create the line highlighting ranges using the coverage data. + void createHighlightRanges(SourceCoverageDataManager &Data); + + /// \brief Create the region markers using the coverage data. + void createRegionMarkers(SourceCoverageDataManager &Data); + + /// \brief Sort children by the starting location. + void sortChildren(); + + /// \brief Return a highlight range for the expansion region of this view. + HighlightRange getExpansionHighlightRange() const; + + /// \brief Render a source line with highlighting. + void renderLine(raw_ostream &OS, StringRef Line, + ArrayRef Ranges); + + void renderOffset(raw_ostream &OS, unsigned I); + + void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS); + + /// \brief Render the line's execution count column. + void renderLineCoverageColumn(raw_ostream &OS, const LineCoverageInfo &Line); + + /// \brief Render the line number column. + void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo); + + /// \brief Render all the region's execution counts on a line. + void renderRegionMarkers(raw_ostream &OS, ArrayRef Regions); + + static const unsigned LineCoverageColumnWidth = 7; + static const unsigned LineNumberColumnWidth = 5; + +public: + SourceCoverageView(const MemoryBuffer &File, + const CoverageViewOptions &Options) + : File(File), Options(Options), LineStart(1), Kind(View), + ExpansionRegion(coverage::Counter(), 0, 0, 0, 0, 0) { + LineCount = File.getBuffer().count('\n') + 1; + } + + SourceCoverageView(const MemoryBuffer &File, + const CoverageViewOptions &Options, unsigned LineStart, + unsigned LineEnd) + : File(File), Options(Options), LineStart(LineStart), + LineCount(LineEnd - LineStart + 1), Kind(View), + ExpansionRegion(coverage::Counter(), 0, 0, 0, 0, 0) {} + + SourceCoverageView(SourceCoverageView &Parent, unsigned LineStart, + unsigned LineEnd, StringRef FunctionName) + : File(Parent.File), Options(Parent.Options), LineStart(LineStart), + LineCount(LineEnd - LineStart + 1), Kind(InstantiationView), + ExpansionRegion(coverage::Counter(), 0, LineEnd, 0, LineEnd, 0), + FunctionName(FunctionName) {} + + SourceCoverageView(const MemoryBuffer &File, + const CoverageViewOptions &Options, unsigned LineStart, + unsigned LineEnd, + const coverage::CounterMappingRegion &ExpansionRegion) + : File(File), Options(Options), LineStart(LineStart), + LineCount(LineEnd - LineStart + 1), Kind(ExpansionView), + ExpansionRegion(ExpansionRegion) {} + + const CoverageViewOptions &getOptions() const { return Options; } + + bool isExpansionSubView() const { return Kind == ExpansionView; } + + bool isInstantiationSubView() const { return Kind == InstantiationView; } + + /// \brief Return the line number after which the subview expansion is shown. + unsigned getSubViewsExpansionLine() const { + return ExpansionRegion.LineStart; + } + + void addChild(std::unique_ptr View) { + Children.push_back(std::move(View)); + } + + /// \brief Print the code coverage information for a specific + /// portion of a source file to the output stream. + void render(raw_ostream &OS, unsigned Offset = 0); + + /// \brief Load the coverage information required for rendering + /// from the mapping regions in the data manager. + void load(SourceCoverageDataManager &Data); +}; + +} // namespace llvm + +#endif // LLVM_COV_SOURCECOVERAGEVIEW_H Index: tools/llvm-cov/SourceCoverageView.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/SourceCoverageView.cpp @@ -0,0 +1,394 @@ +//===- SourceCoverageView.cpp - Code coverage view for source code --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class implements rendering for code coverage of source code. +// +//===----------------------------------------------------------------------===// + +#include "SourceCoverageView.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/LineIterator.h" + +using namespace llvm; + +void SourceCoverageView::renderLine(raw_ostream &OS, StringRef Line, + ArrayRef Ranges) { + if (Ranges.empty()) { + OS << Line << "\n"; + return; + } + if (Line.empty()) + Line = " "; + + unsigned PrevColumnStart = 0; + unsigned Start = 1; + for (const auto &Range : Ranges) { + if (PrevColumnStart == Range.ColumnStart) + continue; + + // Show the unhighlighted part + unsigned ColumnStart = PrevColumnStart = Range.ColumnStart; + OS << Line.substr(Start - 1, ColumnStart - Start); + + // Show the highlighted part + auto Color = Range.Kind == HighlightRange::NotCovered ? raw_ostream::RED + : raw_ostream::CYAN; + OS.changeColor(Color, false, true); + unsigned ColumnEnd = std::min(Range.ColumnEnd, (unsigned)Line.size() + 1); + OS << Line.substr(ColumnStart - 1, ColumnEnd - ColumnStart); + Start = ColumnEnd; + OS.resetColor(); + } + + // Show the rest of the line + OS << Line.substr(Start - 1, Line.size() - Start + 1); + OS << "\n"; +} + +void SourceCoverageView::renderOffset(raw_ostream &OS, unsigned I) { + for (unsigned J = 0; J < I; ++J) + OS << " |"; +} + +void SourceCoverageView::renderViewDivider(unsigned Offset, unsigned Length, + raw_ostream &OS) { + for (unsigned J = 1; J < Offset; ++J) + OS << " |"; + if (Offset != 0) + OS.indent(2); + for (unsigned I = 0; I < Length; ++I) + OS << "-"; +} + +void +SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS, + const LineCoverageInfo &Line) { + if (!Line.isMapped()) { + OS.indent(LineCoverageColumnWidth) << '|'; + return; + } + SmallString<32> Buffer; + raw_svector_ostream BufferOS(Buffer); + BufferOS << Line.ExecutionCount; + auto Str = BufferOS.str(); + bool ChangeColor = Line.hasMultipleRegions() && Options.Colors; + // Trim + Str = Str.substr(0, std::min(Str.size(), (size_t)LineCoverageColumnWidth)); + // Align to the right + OS.indent(LineCoverageColumnWidth - Str.size()) + << ostream_color(raw_ostream::MAGENTA, ChangeColor) << Str; + OS << '|'; +} + +void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS, + unsigned LineNo) { + SmallString<32> Buffer; + raw_svector_ostream BufferOS(Buffer); + BufferOS << LineNo; + auto Str = BufferOS.str(); + // Trim and align to the right + Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth)); + OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|'; +} + +void SourceCoverageView::renderRegionMarkers(raw_ostream &OS, + ArrayRef Regions) { + SmallString<32> Buffer; + raw_svector_ostream BufferOS(Buffer); + + unsigned PrevColumn = 1; + for (const auto &Region : Regions) { + // Skip to the new region + if (Region.Column > PrevColumn) + OS.indent(Region.Column - PrevColumn); + PrevColumn = Region.Column + 1; + BufferOS << Region.ExecutionCount; + StringRef Str = BufferOS.str(); + // Trim the execution count + Str = Str.substr(0, std::min(Str.size(), (size_t)7)); + PrevColumn += Str.size(); + OS << '^' << Str; + Buffer.clear(); + } + OS << "\n"; +} + +/// \brief Insert a new highlighting range into the line's highlighting ranges +/// Return line's new highlighting ranges in result. +static void insertHighlightRange( + ArrayRef Ranges, + SourceCoverageView::HighlightRange RangeToInsert, + SmallVectorImpl &Result) { + Result.clear(); + size_t I = 0; + auto E = Ranges.size(); + for (; I < E; ++I) { + if (RangeToInsert.ColumnStart < Ranges[I].ColumnEnd) { + const auto &Range = Ranges[I]; + bool NextRangeContainsInserted = false; + // If the next range starts before the inserted range, move the end of the + // next range to the start of the inserted range. + if (Range.ColumnStart < RangeToInsert.ColumnStart) { + if (RangeToInsert.ColumnStart != Range.ColumnStart) + Result.push_back(SourceCoverageView::HighlightRange( + Range.Line, Range.ColumnStart, RangeToInsert.ColumnStart, + Range.Kind)); + // If the next range also ends after the inserted range, keep this range + // and create a new range that starts at the inserted range and ends + // at the next range later. + if (Range.ColumnEnd > RangeToInsert.ColumnEnd) + NextRangeContainsInserted = true; + } + if (!NextRangeContainsInserted) { + ++I; + // Ignore ranges that are contained in inserted range + while (I < E && RangeToInsert.contains(Ranges[I])) + ++I; + } + break; + } + Result.push_back(Ranges[I]); + } + Result.push_back(RangeToInsert); + // If the next range starts before the inserted range end, move the start + // of the next range to the end of the inserted range. + if (I < E && Ranges[I].ColumnStart < RangeToInsert.ColumnEnd) { + const auto &Range = Ranges[I]; + if (RangeToInsert.ColumnEnd != Range.ColumnEnd) + Result.push_back(SourceCoverageView::HighlightRange( + Range.Line, RangeToInsert.ColumnEnd, Range.ColumnEnd, Range.Kind)); + ++I; + } + // Add the remaining ranges that are located after the inserted range + for (; I < E; ++I) + Result.push_back(Ranges[I]); +} + +void SourceCoverageView::sortChildren() { + for (auto &I : Children) + I->sortChildren(); + std::sort(Children.begin(), Children.end(), + [](const std::unique_ptr &LHS, + const std::unique_ptr &RHS) { + return LHS->ExpansionRegion < RHS->ExpansionRegion; + }); +} + +SourceCoverageView::HighlightRange +SourceCoverageView::getExpansionHighlightRange() const { + return HighlightRange(ExpansionRegion.LineStart, ExpansionRegion.ColumnStart, + ExpansionRegion.ColumnEnd, HighlightRange::Expanded); +} + +template +ArrayRef gatherLineItems(size_t &CurrentIdx, const std::vector &Items, + unsigned LineNo) { + auto PrevIdx = CurrentIdx; + auto E = Items.size(); + while (CurrentIdx < E && Items[CurrentIdx].Line == LineNo) + ++CurrentIdx; + return ArrayRef(Items.data() + PrevIdx, CurrentIdx - PrevIdx); +} + +ArrayRef> +gatherLineSubViews(size_t &CurrentIdx, + ArrayRef> Items, + unsigned LineNo) { + auto PrevIdx = CurrentIdx; + auto E = Items.size(); + while (CurrentIdx < E && + Items[CurrentIdx]->getSubViewsExpansionLine() == LineNo) + ++CurrentIdx; + return ArrayRef>(Items.data() + PrevIdx, + CurrentIdx - PrevIdx); +} + +void SourceCoverageView::render(raw_ostream &OS, unsigned Offset) { + // Make sure that the children are in sorted order. + sortChildren(); + + SmallVector AdjustedLineHighlightRanges; + size_t CurrentChild = 0; + size_t CurrentHighlightRange = 0; + size_t CurrentRegionMarker = 0; + + line_iterator Lines(File); + // Advance the line iterator to the first line. + while (Lines.line_number() < LineStart) + ++Lines; + + // The width of the leading columns + unsigned CombinedColumnWidth = + (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) + + (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0); + // The width of the line that is used to divide between the view and the + // subviews. + unsigned DividerWidth = CombinedColumnWidth + 4; + + for (size_t I = 0; I < LineCount; ++I) { + unsigned LineNo = I + LineStart; + + // Gather the child subviews that are visible on this line. + auto LineSubViews = gatherLineSubViews(CurrentChild, Children, LineNo); + + renderOffset(OS, Offset); + if (Options.ShowLineStats) + renderLineCoverageColumn(OS, LineStats[I]); + if (Options.ShowLineNumbers) + renderLineNumberColumn(OS, LineNo); + + // Gather highlighting ranges. + auto LineHighlightRanges = + gatherLineItems(CurrentHighlightRange, HighlightRanges, LineNo); + auto LineRanges = LineHighlightRanges; + // Highlight the expansion range if there is an expansion subview on this + // line. + if (!LineSubViews.empty() && LineSubViews.front()->isExpansionSubView() && + Options.Colors) { + insertHighlightRange(LineHighlightRanges, + LineSubViews.front()->getExpansionHighlightRange(), + AdjustedLineHighlightRanges); + LineRanges = AdjustedLineHighlightRanges; + } + + // Display the source code for the current line. + StringRef Line = *Lines; + // Check if the line is empty, as line_iterator skips blank lines. + if (LineNo < Lines.line_number()) + Line = ""; + else if (!Lines.is_at_eof()) + ++Lines; + renderLine(OS, Line, LineRanges); + + // Show the region markers. + bool ShowMarkers = !Options.ShowLineStatsOrRegionMarkers || + LineStats[I].hasMultipleRegions(); + auto LineMarkers = gatherLineItems(CurrentRegionMarker, Markers, LineNo); + if (ShowMarkers && !LineMarkers.empty()) { + renderOffset(OS, Offset); + OS.indent(CombinedColumnWidth); + renderRegionMarkers(OS, LineMarkers); + } + + // Show the line's expanded child subviews. + bool FirstChildExpansion = true; + if (LineSubViews.empty()) + continue; + unsigned NewOffset = Offset + 1; + renderViewDivider(NewOffset, DividerWidth, OS); + OS << "\n"; + for (const auto &Child : LineSubViews) { + // If this subview shows a function instantiation, render the function's + // name. + if (Child->isInstantiationSubView()) { + renderOffset(OS, NewOffset); + OS << ' '; + OS << Options.color(raw_ostream::CYAN) << Child->FunctionName << ":"; + OS << "\n"; + } else { + if (!FirstChildExpansion) { + // Re-render the current line and highlight the expansion range for + // this + // subview. + insertHighlightRange(LineHighlightRanges, + Child->getExpansionHighlightRange(), + AdjustedLineHighlightRanges); + renderOffset(OS, Offset); + OS.indent(CombinedColumnWidth + (Offset == 0 ? 0 : 1)); + renderLine(OS, Line, AdjustedLineHighlightRanges); + renderViewDivider(NewOffset, DividerWidth, OS); + OS << "\n"; + } else + FirstChildExpansion = false; + } + // Render the child subview + Child->render(OS, NewOffset); + renderViewDivider(NewOffset, DividerWidth, OS); + OS << "\n"; + } + } +} + +void +SourceCoverageView::createLineCoverageInfo(SourceCoverageDataManager &Data) { + LineStats.resize(LineCount); + for (const auto &Region : Data.getSourceRegions()) { + auto Value = Region.second; + LineStats[Region.first.LineStart - LineStart].addRegionStartCount(Value); + for (unsigned Line = Region.first.LineStart + 1; + Line <= Region.first.LineEnd; ++Line) + LineStats[Line - LineStart].addRegionCount(Value); + } + + // Reset the line stats for skipped regions. + for (const auto &Region : Data.getSkippedRegions()) { + for (unsigned Line = Region.LineStart; Line <= Region.LineEnd; ++Line) + LineStats[Line - LineStart] = LineCoverageInfo(); + } +} + +void +SourceCoverageView::createHighlightRanges(SourceCoverageDataManager &Data) { + auto Regions = Data.getSourceRegions(); + std::vector AlreadyHighlighted; + AlreadyHighlighted.resize(Regions.size(), false); + + for (size_t I = 0, S = Regions.size(); I < S; ++I) { + const auto &Region = Regions[I]; + auto Value = Region.second; + auto SrcRange = Region.first; + if (Value != 0) + continue; + if (AlreadyHighlighted[I]) + continue; + for (size_t J = 0; J < S; ++J) { + if (SrcRange.contains(Regions[J].first)) { + AlreadyHighlighted[J] = true; + } + } + if (Options.Debug) + outs() << "Highlighted range " << Value << ", " + << " " << SrcRange.LineStart << ":" << SrcRange.ColumnStart + << " -> " << SrcRange.LineEnd << ":" << SrcRange.ColumnEnd << "\n"; + if (SrcRange.LineStart == SrcRange.LineEnd) { + HighlightRanges.push_back(HighlightRange( + SrcRange.LineStart, SrcRange.ColumnStart, SrcRange.ColumnEnd)); + continue; + } + HighlightRanges.push_back( + HighlightRange(SrcRange.LineStart, SrcRange.ColumnStart, + std::numeric_limits::max())); + HighlightRanges.push_back( + HighlightRange(SrcRange.LineEnd, 1, SrcRange.ColumnEnd)); + for (unsigned Line = SrcRange.LineStart + 1; Line < SrcRange.LineEnd; + ++Line) { + HighlightRanges.push_back( + HighlightRange(Line, 1, std::numeric_limits::max())); + } + } + + std::sort(HighlightRanges.begin(), HighlightRanges.end()); +} + +void SourceCoverageView::createRegionMarkers(SourceCoverageDataManager &Data) { + for (const auto &Region : Data.getSourceRegions()) { + if (Region.first.LineStart >= LineStart) + Markers.push_back(RegionMarker(Region.first.LineStart, + Region.first.ColumnStart, Region.second)); + } +} + +void SourceCoverageView::load(SourceCoverageDataManager &Data) { + if (Options.ShowLineStats) + createLineCoverageInfo(Data); + if (Options.Colors) + createHighlightRanges(Data); + if (Options.ShowRegionMarkers) + createRegionMarkers(Data); +} Index: tools/llvm-cov/llvm-cov.cpp =================================================================== --- tools/llvm-cov/llvm-cov.cpp +++ tools/llvm-cov/llvm-cov.cpp @@ -11,9 +11,58 @@ // //===----------------------------------------------------------------------===// -/// \brief The main function for the gcov compatible coverage tool +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/Path.h" +#include + +using namespace llvm; + +/// \brief The main entry point for the 'show' subcommand. +int show_main(int argc, const char **argv); + +/// \brief The main entry point for the 'report' subcommand. +int report_main(int argc, const char **argv); + +/// \brief The main entry point for the gcov compatible coverage tool. int gcov_main(int argc, const char **argv); int main(int argc, const char **argv) { + // If argv[0] is or ends with 'gcov', always be gcov compatible + if (sys::path::stem(argv[0]).endswith_lower("gcov")) + return gcov_main(argc, argv); + + // Check if we are invoking a specific tool command. + if (argc > 1) { + int (*func)(int, const char **) = nullptr; + + StringRef command = argv[1]; + if (command.equals_lower("show")) + func = show_main; + else if (command.equals_lower("report")) + func = report_main; + else if (command.equals_lower("gcov")) + func = gcov_main; + + if (func) { + std::string Invocation(std::string(argv[0]) + " " + argv[1]); + argv[1] = Invocation.c_str(); + return func(argc - 1, argv + 1); + } + } + + // Give a warning and fall back to gcov + errs().changeColor(raw_ostream::RED); + errs() << "warning:"; + // Assume that argv[1] wasn't a command when it stats with a '-' or is a + // filename (i.e. contains a '.') + if (argc > 1 && !StringRef(argv[1]).startswith("-") && + StringRef(argv[1]).find(".") == StringRef::npos) + errs() << " Unrecognized command '" << argv[1] << "'."; + errs() << " Using the gcov compatible mode " + "(this behaviour may be dropped in the future)."; + errs().resetColor(); + errs() << "\n"; + return gcov_main(argc, argv); }