[PATCH] Code coverage tool that uses the new coverage mapping format and clang's instrumentation based profiling data
Justin Bogner
mail at justinbogner.com
Wed Aug 6 15:55:51 PDT 2014
Hey Alex,
Overall this looks pretty good. I have quite a few nitpicky style
comments and one or two bigger things below.
-- Justin
Alex Lorenz <arphaman at gmail.com> writes:
> Index: tools/llvm-cov/CMakeLists.txt
> ===================================================================
> --- tools/llvm-cov/CMakeLists.txt
> +++ tools/llvm-cov/CMakeLists.txt
> @@ -1,6 +1,14 @@
> -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
> + SourceLanguages.cpp
> )
> Index: tools/llvm-cov/CodeCoverage.cpp
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/CodeCoverage.cpp
> @@ -0,0 +1,707 @@
> +//===- 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.
> +//
>
+//===----------------------------------------------------------------------===//
> +//
> +// This class implements a command line tool to analyze and report
coverage
> +// information using the profiling instrumentation and code coverage
mapping.
What is "This class"? This is a file. Maybe mention CodeCoverageTool
here.
> +//
>
+//===----------------------------------------------------------------------===//
> +
> +#include "FunctionCoverageMapping.h"
> +#include "CoverageViewOptions.h"
> +#include "CoverageFilters.h"
> +#include "SourceLanguages.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 <system_error>
> +#include <functional>
> +
> +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<FunctionCoverageMapping> FunctionMappings;
> + typedef uint64_t KeyType;
> + typedef std::vector<const FunctionCoverageMapping *> SetType;
> + std::unordered_map<uint64_t, SetType> 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<KeyType, SetType>::iterator begin() {
> + return InstantiatedFunctions.begin();
> + }
> +
> + std::unordered_map<KeyType, SetType>::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<const MemoryBuffer &> 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<unsigned, 8>
&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<FunctionCoverageMapping>
FunctionMappingRecords,
> + bool UseOnlyRegionsInMainFile = false);
> +
> + /// \brief Return true if an error occured during loading
> + /// of the coverage mapping data.
> + bool load();
This wording doesn't make it clear that this, you know, loads the
coverage mapping data. :)
> +
> + int run(Command Cmd, int argc, const char **argv);
> +
> + typedef std::function<int(int, const char **)> 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<IndexedInstrProfReader> PGOReader;
> + CoverageFiltersMatchAll Filters;
> + SourceLanguages Languages;
> + std::vector<std::string> SourceFiles;
> + std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>>
> + LoadedSourceFiles;
> + std::vector<FunctionCoverageMapping> FunctionMappingRecords;
> +};
> +}
> +
> +void CodeCoverageTool::error(const Twine &Message, StringRef Whence) {
> + errs() << "error: ";
> + if (!Whence.empty())
> + errs() << Whence << ": ";
> + errs() << Message << "\n";
> +}
> +
> +ErrorOr<const MemoryBuffer &>
> +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<unsigned, unsigned>
> +findExpandedFileInterestingLineRange(unsigned FileID,
> + const FunctionCoverageMapping
&Function) {
> + unsigned LineStart = std::numeric_limits<unsigned>::max();
> + unsigned LineEnd = 0;
> + for (const auto &I : Function.MappingRegions) {
I isn't a great name. Better to use something descriptive like Region.
> + if (I.FileID != FileID)
> + continue;
> + LineStart = std::min(I.LineStart, LineStart);
> + LineEnd = std::max(I.LineEnd, LineEnd);
> + }
> + return std::make_pair(LineStart, LineEnd);
> +}
> +
> +bool CodeCoverageTool::gatherInterestingFileIDs(
> + StringRef SourceFile, const FunctionCoverageMapping &Function,
> + SmallSet<unsigned, 8> &InterestingFileIDs) {
> + bool Interesting = false;
> + for (unsigned I = 0, S = Function.Filenames.size(); I < S; ++I) {
We usually use E (for end) in these loops. You use S (for size?) a few
times in this patch. Please change them to E to match the usual LLVM
convention.
> + 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<bool, 8> IsExpandedFile(Function.Filenames.size(),
false);
> + llvm::SmallVector<bool, 8>
FilenameEquivalence(Function.Filenames.size(),
> + false);
> + for (unsigned I = 0, S = Function.Filenames.size(); I < S; ++I) {
> + if (llvm::sys::fs::equivalent(SourceFile, Function.Filenames[I]))
> + FilenameEquivalence[I] = true;
> + }
> + for (const auto &R : Function.MappingRegions) {
> + if (R.Kind == MappingRegion::ExpansionRegion &&
> + FilenameEquivalence[R.FileID])
> + IsExpandedFile[R.ExpandedFileID] = true;
> + }
> + for (unsigned I = 0; I < Function.Filenames.size(); ++I) {
Please change this to "I = 0, E = size(); I < E; ..." like the above.
> + if (!FilenameEquivalence[I] || IsExpandedFile[I])
> + continue;
> + MainViewFileID = I;
> + return false;
> + }
> + return true;
> +}
> +
> +bool
> +CodeCoverageTool::findMainViewFileID(const FunctionCoverageMapping
&Function,
> + unsigned &MainViewFileID) {
> + llvm::SmallVector<bool, 8> IsExpandedFile(Function.Filenames.size(),
false);
> + for (const auto &R : Function.MappingRegions) {
> + if (R.Kind == MappingRegion::ExpansionRegion)
> + IsExpandedFile[R.ExpandedFileID] = true;
> + }
> + for (unsigned I = 0; I < Function.Filenames.size(); ++I) {
Ditto.
> + 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 = new SourceCoverageView(SourceBuffer.get(),
Parent.getOptions(),
> + ExpandedLines.first,
> + ExpandedLines.second,
ExpandedRegion);
I see later Parent.addChild wraps this in a unique pointer - better to
pass a unique pointer in so that it's obvious who owns this.
> + Parent.addChild(SubView);
> + SourceCoverageDataManager RegionManager;
> + for (const auto &I : Function.MappingRegions) {
> + if (I.FileID == ExpandedRegion.ExpandedFileID)
> + RegionManager.insert(I);
> + }
> + SubView->load(RegionManager);
> + createExpansionSubViews(*SubView, ExpandedRegion.ExpandedFileID,
Function);
> +}
> +
> +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<unsigned, 8> InterestingFileIDs;
> + if (!gatherInterestingFileIDs(SourceFile, Function,
InterestingFileIDs))
> + return;
> + // Get the interesting regions
> + for (const auto &I : Function.MappingRegions) {
> + if (InterestingFileIDs.count(I.FileID))
> + RegionManager.insert(I);
> + }
> + View.load(RegionManager);
> + unsigned MainFileID;
> + if (findMainViewFileID(SourceFile, Function, MainFileID))
> + return;
> + createExpansionSubViews(View, MainFileID, Function);
> +}
> +
> +bool CodeCoverageTool::createSourceFileView(
> + StringRef SourceFile, SourceCoverageView &View,
> + ArrayRef<FunctionCoverageMapping> FunctionMappingRecords,
> + bool UseOnlyRegionsInMainFile) {
> + SourceCoverageDataManager RegionManager;
> + FunctionInstantiationSetCollector InstantiationSetCollector;
> +
> + for (const auto &Function : FunctionMappingRecords) {
> + unsigned MainFileID;
> + if (findMainViewFileID(SourceFile, Function, MainFileID))
> + continue;
> + SmallSet<unsigned, 8> InterestingFileIDs;
> + if (UseOnlyRegionsInMainFile) {
> + InterestingFileIDs.insert(MainFileID);
> + } else {
> + if (!gatherInterestingFileIDs(SourceFile, Function,
InterestingFileIDs))
> + continue;
> + }
Isn't this just "else if"?
> + // 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 =
> + new SourceCoverageView(View, InterestingRange.first,
> + InterestingRange.second,
Function->PrettyName);
> + View.addChild(SubView);
> + createInstantiationSubView(SourceFile, *Function, *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<uint64_t> Counts;
> + for (const auto &I : MappingReader) {
> + FunctionCoverageMapping Function(I.FunctionName, I.Filenames);
> +
> + // Demangle
> + Languages.get(Function)->demangle(Function.PrettyName);
> +
> + // Create the mapping regions with evaluated execution counts
> + Counts.clear();
> + uint64_t FuncHash;
> + PGOReader->getFunctionCounts(Function.Name, FuncHash, 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().changeColor(raw_ostream::RED);
> + errs() << "error: Regions and counters don't match in a function
'"
> + << Function.PrettyName << "' (re-run the instrumented
binary).";
> + errs().resetColor();
> + 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<std::string> InputSourceFiles(
> + cl::Positional, cl::desc("<Source files>"), cl::ZeroOrMore);
> +
> + cl::opt<std::string> PGOFilename(
> + "instr-profile", cl::Required,
> + cl::desc(
> + "File with the profile data obtained after an instrumented
run"));
> +
> + cl::opt<bool> DebugDump("dump", cl::Optional,
> + cl::desc("Show internal debug dump"));
> +
> + cl::OptionCategory FilteringCategory("Function filtering options");
> +
> + cl::list<std::string> NameFilters(
> + "name", cl::Optional,
> + cl::desc("Show code coverage only for functions with the given
name"),
> + cl::ZeroOrMore, cl::cat(FilteringCategory));
> +
> + cl::list<std::string> 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<double> RegionCoverageLtFilter(
> + "region-coverage-lt", cl::Optional,
> + cl::desc("Show code coverage only for functions with region
coverage "
> + "less than the given threshold"),
> + cl::cat(FilteringCategory));
> +
> + cl::opt<double> RegionCoverageGtFilter(
> + "region-coverage-gt", cl::Optional,
> + cl::desc("Show code coverage only for functions with region
coverage "
> + "greater than the given threshold"),
> + cl::cat(FilteringCategory));
> +
> + cl::opt<double> LineCoverageLtFilter(
> + "line-coverage-lt", cl::Optional,
> + cl::desc("Show code coverage only for functions with line coverage
less "
> + "than the given threshold"),
> + cl::cat(FilteringCategory));
> +
> + cl::opt<double> LineCoverageGtFilter(
> + "line-coverage-gt", cl::Optional,
> + cl::desc("Show code coverage only for functions with line coverage
"
> + "greater than the given threshold"),
> + cl::cat(FilteringCategory));
> +
> + 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(new NameCoverageFilter(Name));
> + for (const auto &Regex : NameRegexFilters)
> + NameFilterer->push_back(new NameRegexCoverageFilter(Regex));
> + Filters.push_back(NameFilterer);
> + }
> + if (RegionCoverageLtFilter.getNumOccurrences() ||
> + RegionCoverageGtFilter.getNumOccurrences() ||
> + LineCoverageLtFilter.getNumOccurrences() ||
> + LineCoverageGtFilter.getNumOccurrences()) {
> + auto StatFilterer = new CoverageFilters;
> + if (RegionCoverageLtFilter.getNumOccurrences())
> + StatFilterer->push_back(new RegionCoverageFilter(
> + RegionCoverageFilter::LessThan, RegionCoverageLtFilter));
> + if (RegionCoverageGtFilter.getNumOccurrences())
> + StatFilterer->push_back(new RegionCoverageFilter(
> + RegionCoverageFilter::GreaterThan, RegionCoverageGtFilter));
> + if (LineCoverageLtFilter.getNumOccurrences())
> + StatFilterer->push_back(new LineCoverageFilter(
> + LineCoverageFilter::LessThan, LineCoverageLtFilter));
> + if (LineCoverageGtFilter.getNumOccurrences())
> + StatFilterer->push_back(new LineCoverageFilter(
> + RegionCoverageFilter::GreaterThan, LineCoverageGtFilter));
> + Filters.push_back(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<bool> ShowLineExecutionCounts(
> + "show-line-counts", cl::Optional,
> + cl::desc("Show the execution counts for each line"),
cl::init(true),
> + cl::cat(ViewCategory));
> +
> + cl::opt<bool> ShowRegions(
> + "show-regions", cl::Optional,
> + cl::desc("Show the execution counts for each region"),
> + cl::cat(ViewCategory));
> +
> + cl::opt<bool> ShowBestLineRegionsCounts(
> + "show-line-counts-or-regions", cl::Optional,
> + cl::desc("Show the execution counts for each line, or the
execution "
> + "counts for each region on lines that have multiple
regions"),
> + cl::cat(ViewCategory));
> +
> + cl::opt<bool> ShowExpansions("show-expansions", cl::Optional,
> + cl::desc("Show expanded source regions"),
> + cl::cat(ViewCategory));
> +
> + cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional,
> + cl::desc("Show function
instantiations"),
> + cl::cat(ViewCategory));
> +
> + cl::opt<bool> 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<SourceCoverageView> 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);
> + if (ViewOpts.Colors)
> + outs().changeColor(raw_ostream::CYAN);
> + outs() << Function.PrettyName << " from " << SourceFile << ":";
> + outs().resetColor();
Does resetColor do the right thing if we didn't change the color?
> + 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<StringRef> 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<SourceCoverageView> mainView;
> + auto SourceBuffer = getSourceFile(SourceFile);
> + if (!SourceBuffer)
> + return 1;
> + mainView.reset(new SourceCoverageView(SourceBuffer.get(), ViewOpts));
> + if (createSourceFileView(SourceFile, *mainView,
FunctionMappingRecords)) {
> + if (ViewOpts.Colors)
> + outs().changeColor(raw_ostream::RED);
> + outs() << "warning: The file '" << SourceFile << "' isn't
covered.";
> + outs().resetColor();
> + outs() << "\n";
> + continue;
> + }
> +
> + if (ShowFilenames) {
> + if (ViewOpts.Colors)
> + outs().changeColor(raw_ostream::CYAN);
> + outs() << SourceFile << ":";
> + outs().resetColor();
> + 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<bool> 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.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(CoverageFilter *filter) {
> + Filters.push_back(std::unique_ptr<CoverageFilter>(filter));
> +}
Making the argument a unique_ptr is clearer about ownership.
> +
> +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/CoverageFilters.h
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/CoverageFilters.h
> @@ -0,0 +1,123 @@
> +//===- 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 COVERAGEFILTERS_H
> +#define COVERAGEFILTERS_H
Header guards should be more specific as to avoid collisions in case
things move around. How about LLVM_COV_COVERAGEFILTERS_H? This applies
to all of the other headers as well.
> +
> +#include "FunctionCoverageMapping.h"
> +#include <vector>
> +#include <memory>
> +
> +namespace llvm {
> +
> +/// \brief Matches specific functions that pass the requirement of this
filter.
> +class CoverageFilter {
> +public:
> + /// \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 <typename T> 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<double> {
> +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<double> {
> +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<std::unique_ptr<CoverageFilter>> Filters;
> +
> +public:
> + /// \brief Append a filter to this collection.
> + void push_back(CoverageFilter *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 // COVERAGEFILTERS_H
> Index: tools/llvm-cov/CoverageReport.cpp
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/CoverageReport.cpp
> @@ -0,0 +1,204 @@
> +//===- 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 "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 <typename T>
> +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 <typename T, size_t N>
> +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 <typename T>
> +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);
> + if (Options.Colors)
> + OS.changeColor(File.RegionCoverage.isFullyCovered() ?
raw_ostream::GREEN
> + :
raw_ostream::RED);
> + OS << format("%*zd", FileReportColumns[2],
File.RegionCoverage.NotCovered);
> + if (Options.Colors)
> +
OS.changeColor(determineCoveragePercentageColor(File.RegionCoverage));
> + OS << format("%*.2f", FileReportColumns[3] - 1,
> + File.RegionCoverage.getPercentCovered()) << '%';
> + OS.resetColor();
> + OS << format("%*zd", FileReportColumns[4],
> + File.FunctionCoverage.NumFunctions);
> + if (Options.Colors)
> +
OS.changeColor(determineCoveragePercentageColor(File.FunctionCoverage));
> + OS << format("%*.2f", FileReportColumns[5] - 1,
> + File.FunctionCoverage.getPercentCovered()) << '%';
> + OS.resetColor();
> + OS << "\n";
> +}
Might be nicer to have an "unsigned Col" and increment it instead of
writing out the column index by hand. I don't feel strongly though.
> +
> +void CoverageReport::render(const FunctionCoverageSummary &Function,
> + raw_ostream &OS) {
> + OS << column(Function.Name, FunctionReportColumns[0],
Column::RightTrim)
> + << format("%*zd", FunctionReportColumns[1],
> + Function.RegionCoverage.NumRegions);
> + if (Options.Colors)
> + OS.changeColor(Function.RegionCoverage.isFullyCovered() ?
raw_ostream::GREEN
> + :
raw_ostream::RED);
> + OS << format("%*zd", FunctionReportColumns[2],
> + Function.RegionCoverage.NotCovered);
> + if (Options.Colors)
> +
OS.changeColor(determineCoveragePercentageColor(Function.RegionCoverage));
> + OS << format("%*.2f", FunctionReportColumns[3] - 1,
> + Function.RegionCoverage.getPercentCovered()) << '%';
> + OS.resetColor();
> + OS << format("%*zd", FunctionReportColumns[4],
> + Function.LineCoverage.NumLines);
> + if (Options.Colors)
> + OS.changeColor(Function.LineCoverage.isFullyCovered() ?
raw_ostream::GREEN
> + :
raw_ostream::RED);
> + OS << format("%*zd", FunctionReportColumns[5],
> + Function.LineCoverage.NotCovered);
> + if (Options.Colors)
> +
OS.changeColor(determineCoveragePercentageColor(Function.LineCoverage));
> + OS << format("%*.2f", FunctionReportColumns[6] - 1,
> + Function.LineCoverage.getPercentCovered()) << '%';
> + OS.resetColor();
> + 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/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 COVERAGEREPORT_H
> +#define 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 // COVERAGEREPORT_H
> Index: tools/llvm-cov/CoverageSummary.cpp
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/CoverageSummary.cpp
> @@ -0,0 +1,93 @@
> +//===- 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, S = Filenames.size(); I < S; ++I) {
> + if (sys::fs::equivalent(Filenames[I], Filename))
> + return I;
> + }
> + Filenames.push_back(Filename);
> + return Filenames.size() - 1;
> +}
> +
> +void
> +CoverageSummary::createSummaries(ArrayRef<FunctionCoverageMapping>
Functions) {
> + std::vector<std::pair<unsigned, size_t>> FunctionFileIDs;
> +
> + FunctionFileIDs.resize(Functions.size());
> + for (size_t I = 0, S = Functions.size(); I < S; ++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<unsigned, size_t> &lhs,
> + const std::pair<unsigned, size_t> &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, S = Functions.size(); I < S; ++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;
> + for (; CurrentSummary < FunctionSummaries.size() &&
> + FunctionFileIDs[CurrentSummary].first == FileID;
> + ++CurrentSummary)
> + ;
It's clearer (and shorter!) to use a while loop here:
while (CurrentSummary < FunctionSummaries.size() &&
FunctionFileIDs[CurrentSummary].first == FileID)
++CurrentSummary;
> + ArrayRef<FunctionCoverageSummary> 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, NumNonCodeLines = 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;
> + NumNonCodeLines += File.LineCoverage.NumNonCodeLines;
> + CoveredLines += File.LineCoverage.Covered;
> +
> + NumFunctionsCovered += File.FunctionCoverage.FullyCovered;
> + NumFunctions += File.FunctionCoverage.NumFunctions;
> + }
> + return FileCoverageSummary(
> + "TOTAL", RegionCoverageInfo(CoveredRegions, NumRegions),
> + LineCoverageInfo(CoveredLines, NumNonCodeLines, NumLines),
> + FunctionCoverageInfo(NumFunctionsCovered, NumFunctions),
> + ArrayRef<FunctionCoverageSummary>());
> +}
> 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 COVERAGESUMMARY_H
> +#define COVERAGESUMMARY_H
> +
> +#include "CoverageSummaryInfo.h"
> +#include <vector>
> +
> +namespace llvm {
> +
> +/// \brief Manager for the function and file code coverage summaries.
> +class CoverageSummary {
> + std::vector<StringRef> Filenames;
> + std::vector<FunctionCoverageSummary> FunctionSummaries;
> + std::vector<std::pair<unsigned, unsigned>> FunctionSummariesFileIDs;
> + std::vector<FileCoverageSummary> FileSummaries;
> +
> + unsigned getFileID(StringRef Filename);
> +
> +public:
> + void createSummaries(ArrayRef<FunctionCoverageMapping> Functions);
> +
> + ArrayRef<FileCoverageSummary> getFileSummaries() { return
FileSummaries; }
> +
> + FileCoverageSummary getCombinedFileSummaries();
> +
> + void render(const FunctionCoverageSummary &Summary, raw_ostream &OS);
> +
> + void render(raw_ostream &OS);
> +};
> +}
> +
> +#endif // COVERAGESUMMARY_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, S = Function.Filenames.size(); FileID < S;
> + ++FileID) {
> + // Find the line start and end of the function's source code
> + // in that particular file
> + unsigned LineStart = std::numeric_limits<unsigned>::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<uint64_t, 16> 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<FunctionCoverageSummary>
FunctionSummaries) {
> + size_t NumRegions = 0, CoveredRegions = 0;
> + size_t NumLines = 0, NumNonCodeLines = 0, CoveredLines = 0;
> + size_t NumFunctionsCovered = 0;
> + for (const auto &Func : FunctionSummaries) {
> + CoveredRegions += Func.RegionCoverage.Covered;
> + NumRegions += Func.RegionCoverage.NumRegions;
> +
> + CoveredLines += Func.LineCoverage.Covered;
> + NumNonCodeLines += Func.LineCoverage.NumNonCodeLines;
> + NumLines += Func.LineCoverage.NumLines;
> +
> + if (Func.RegionCoverage.isFullyCovered())
> + ++NumFunctionsCovered;
> + }
> +
> + return FileCoverageSummary(
> + Name, RegionCoverageInfo(CoveredRegions, NumRegions),
> + LineCoverageInfo(CoveredLines, NumNonCodeLines, NumLines),
> + FunctionCoverageInfo(NumFunctionsCovered,
FunctionSummaries.size()),
> + FunctionSummaries);
> +}
> Index: tools/llvm-cov/CoverageSummaryInfo.h
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/CoverageSummaryInfo.h
> @@ -0,0 +1,133 @@
> +//===- 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 COVERAGESUMMARYINFO_H
> +#define COVERAGESUMMARYINFO_H
> +
> +#include "FunctionCoverageMapping.h"
> +#include "llvm/Support/raw_ostream.h"
> +
> +namespace llvm {
> +
> +/// \brief Provides information about region coverage for a function/file
Comments should end in a period. You seem to have been consistent about
full sentences everywhere except for this 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 NumNonCodeLines;
NumNon is a bit awkward to say. It's fairly obvious it's a number, so
how about just 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),
> + NumNonCodeLines(NumNonCodeLines), NumLines(NumLines) {}
> +
> + bool isFullyCovered() const {
> + return Covered == (NumLines - NumNonCodeLines);
> + }
> +
> + double getPercentCovered() const {
> + return double(Covered) / double(NumLines - NumNonCodeLines) * 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<FunctionCoverageSummary> FunctionSummaries;
> +
> + FileCoverageSummary(StringRef Name, const RegionCoverageInfo
&RegionCoverage,
> + const LineCoverageInfo &LineCoverage,
> + const FunctionCoverageInfo &FunctionCoverage,
> + ArrayRef<FunctionCoverageSummary>
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<FunctionCoverageSummary>
FunctionSummaries);
> +};
> +
> +} // namespace llvm
> +
> +#endif // COVERAGESUMMARYINFO_H
> Index: tools/llvm-cov/CoverageViewOptions.h
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/CoverageViewOptions.h
> @@ -0,0 +1,28 @@
> +//===- 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 COVERAGEVIEWOPTIONS_H
> +#define COVERAGEVIEWOPTIONS_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;
> +};
> +}
> +
> +#endif // 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 FUNCTIONCOVERAGEMAPPING_H
> +#define FUNCTIONCOVERAGEMAPPING_H
> +
> +#include <string>
> +#include <vector>
> +#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<std::string> Filenames;
> + std::vector<MappingRegion> MappingRegions;
> +
> + FunctionCoverageMapping(StringRef Name, ArrayRef<StringRef> Filenames)
> + : Name(Name), PrettyName(Name),
> + Filenames(Filenames.begin(), Filenames.end()) {}
> +};
> +
> +} // namespace llvm
> +
> +#endif // 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/SourceCoverageDataManager.cpp
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/SourceCoverageDataManager.cpp
> @@ -0,0 +1,55 @@
> +//===- 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 It = Regions.find(Range);
> + if (It == Regions.end()) {
> + Regions.insert(std::make_pair(Range, Region.ExecutionCount));
> + return;
> + }
> + It->second += Region.ExecutionCount;
> +}
> +
> +ArrayRef<std::pair<SourceCoverageDataManager::SourceRange, uint64_t>>
> +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<SourceRange, uint64_t> &LHS,
> + const std::pair<SourceRange, uint64_t> &RHS) {
> + return LHS.first < RHS.first;
> + });
> +
> + return SortedRegions;
> +}
> Index: tools/llvm-cov/SourceCoverageDataManager.h
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/SourceCoverageDataManager.h
> @@ -0,0 +1,83 @@
> +//===- 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 SOURCECOVERAGEDATAMANAGER_H
> +#define SOURCECOVERAGEDATAMANAGER_H
> +
> +#include "FunctionCoverageMapping.h"
> +#include "llvm/ProfileData/CoverageMapping.h"
> +#include <vector>
> +#include <unordered_map>
> +
> +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 Range.LineStart + (Range.ColumnStart << 9) +
> + (Range.LineEnd << 16) + (Range.ColumnEnd << 25);
Where do these numbers come from? This needs some comments at the very
least.
> + }
> + };
> + };
> +
> +protected:
> + std::unordered_map<SourceRange, uint64_t, SourceRange::Hash> Regions;
> + std::vector<std::pair<SourceRange, uint64_t>> SortedRegions;
> + std::vector<SourceRange> SkippedRegions;
> +
> +public:
> + void insert(const MappingRegion &Region);
> +
> + /// \brief Return the source ranges and execution counts
> + /// obtained from the non-skipped mapping regions.
> + ArrayRef<std::pair<SourceRange, uint64_t>> getSourceRegions();
> +
> + /// \brief Return the source ranges obtained from the skipped mapping
regions.
> + ArrayRef<SourceRange> getSkippedRegions() const { return
SkippedRegions; }
> +};
> +
> +} // namespace llvm
> +
> +#endif // SOURCECOVERAGEDATAMANAGER_H
> Index: tools/llvm-cov/SourceCoverageView.cpp
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/SourceCoverageView.cpp
> @@ -0,0 +1,390 @@
> +//===- 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"
> +
> +using namespace llvm;
> +
> +void SourceCoverageView::skipToLine(StringRef &Source, unsigned
LineToFind) {
> + if (LineToFind < 2)
> + return;
> +
> + unsigned Line = 1;
> + for (size_t I = 0; I < Source.size();) {
> + if (Source[I] != '\n') {
> + ++I;
> + continue;
> + }
> + ++Line, ++I;
> + if (Line == LineToFind) {
> + Source = Source.substr(I);
> + return;
> + }
> + }
> + Source = StringRef();
> +}
> +
> +StringRef SourceCoverageView::getNextLine(StringRef &Source) {
> + for (size_t I = 0; I < Source.size(); ++I) {
> + if (Source[I] == '\n') {
> + auto Line = Source.substr(0, I);
> + Source = Source.substr(I + 1);
> + return Line;
> + }
> + }
> + // This must be the last line
> + return Source;
> +}
These functions look pretty similar to llvm/Support/LineIterator's
behaviour. Can we avoid some code duplication by using that interface
instead?
> +
> +void SourceCoverageView::renderLine(raw_ostream &OS, StringRef Line,
> + ArrayRef<HighlightRange> 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();
> + if (Line.hasMultipleRegions() && Options.Colors)
> + OS.changeColor(raw_ostream::MAGENTA, false, false);
> + // Trim
> + Str = Str.substr(0, std::min(Str.size(),
(size_t)LineCoverageColumnWidth));
> + // Align to the right
> + OS.indent(LineCoverageColumnWidth - Str.size()) << Str;
> + OS.resetColor();
> + 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<RegionMarker>
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(
Functions start with lower case letters, ie, insertHighlightRange.
> + ArrayRef<SourceCoverageView::HighlightRange> Ranges,
> + SourceCoverageView::HighlightRange RangeToInsert,
> + SmallVectorImpl<SourceCoverageView::HighlightRange> &Result) {
> + Result.clear();
> + for (const auto &Range : Ranges) {
> + if (!Range.overlaps(RangeToInsert)) {
> + Result.push_back(Range);
> + continue;
> + }
> +
> + if (RangeToInsert.contains(Range))
> + continue;
> + if (Range.contains(RangeToInsert)) {
> + // Split into 0 - 2 ranges
> + if (RangeToInsert.ColumnStart != Range.ColumnStart)
> + Result.push_back(SourceCoverageView::HighlightRange(
> + Range.Line, Range.ColumnStart, RangeToInsert.ColumnStart,
> + Range.Kind));
> + if (RangeToInsert.ColumnEnd != Range.ColumnEnd)
> + Result.push_back(SourceCoverageView::HighlightRange(
> + Range.Line, RangeToInsert.ColumnEnd, Range.ColumnEnd,
Range.Kind));
> + } else if (Range.columnStartOverlaps(RangeToInsert)) {
> + if (RangeToInsert.ColumnStart != Range.ColumnStart)
> + Result.push_back(SourceCoverageView::HighlightRange(
> + Range.Line, Range.ColumnStart, RangeToInsert.ColumnStart,
> + Range.Kind));
> + } else {
> + if (RangeToInsert.ColumnEnd != Range.ColumnEnd)
> + Result.push_back(SourceCoverageView::HighlightRange(
> + Range.Line, RangeToInsert.ColumnEnd, Range.ColumnEnd,
Range.Kind));
> + }
> + }
> + Result.push_back(RangeToInsert);
> + std::sort(Result.begin(), Result.end());
Do we really need to sort here? AFAICT createHighlightRanges sorted
these already, so we should just be able to insert our range at the
right point above, no?
Similarly, I think this logic can be simplified if the input is
guaranteed to already be sorted.
> +}
> +
> +void SourceCoverageView::sortChildren() {
> + for (auto &I : Children)
> + I->sortChildren();
> + std::sort(Children.begin(), Children.end(),
> + [](const std::unique_ptr<SourceCoverageView> &LHS,
> + const std::unique_ptr<SourceCoverageView> &RHS) {
> + return LHS->ExpansionRegion < RHS->ExpansionRegion;
> + });
> +}
> +
> +SourceCoverageView::HighlightRange
> +SourceCoverageView::getExpansionHighlightRange() const {
> + return HighlightRange(ExpansionRegion.LineStart,
ExpansionRegion.ColumnStart,
> + ExpansionRegion.ColumnEnd,
HighlightRange::Expanded);
> +}
> +
> +template <typename T>
> +ArrayRef<T> gatherLineItems(size_t &CurrentIdx, const std::vector<T>
&Items,
> + unsigned LineNo) {
> + auto PrevIdx = CurrentIdx;
> + for (auto S = Items.size();
> + CurrentIdx < S && Items[CurrentIdx].Line == LineNo; ++CurrentIdx)
> + ;
This is simpler using "while".
> + return ArrayRef<T>(Items.data() + PrevIdx, CurrentIdx - PrevIdx);
> +}
> +
> +void SourceCoverageView::render(raw_ostream &OS, unsigned Offset) {
This whole function is confusing. Notably, what is going on with
ChildIterator? You iterator over all of the children at the end of the
first iteration of the outer (LineCount) loop, and you check if that's
already happened for some of the intermediate steps.
At the very least, this needs some comments explaining what and why
different parts of the function are doing what they're doing. I suspect
it might be possible to reorganize it so it's clearer in the first place
though.
> + sortChildren();
> +
> + SmallVector<HighlightRange, 8> AdjustedLineHighlightRanges;
> + auto ChildIterator = Children.begin();
> + size_t CurrentHighlightRange = 0;
> + size_t CurrentRegionMarker = 0;
> +
> + StringRef Source = File.getBuffer();
> + skipToLine(Source, LineStart);
> + unsigned CombinedColumnWidth =
> + (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
> + (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
> + unsigned DividerWidth = CombinedColumnWidth + 4;
> +
> + for (size_t I = 0; I < LineCount; ++I) {
> + unsigned LineNo = I + LineStart;
> +
> + 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;
> + if (ChildIterator != Children.end() &&
> + (*ChildIterator)->ExpansionRegion.LineStart == LineNo &&
> + Options.Colors) {
> + InsertHighlightRange(LineHighlightRanges,
> +
(*ChildIterator)->getExpansionHighlightRange(),
> + AdjustedLineHighlightRanges);
> + LineRanges = AdjustedLineHighlightRanges;
> + }
> +
> + // Display the source code for the current line
> + StringRef Line = getNextLine(Source);
> + 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 embedded expanded child views
> + for (bool FirstChildExpansion = true;
> + ChildIterator != Children.end() &&
> + (*ChildIterator)->ExpansionRegion.LineStart == LineNo;
> + ++ChildIterator) {
> + const auto &Child = *ChildIterator;
> + unsigned NewOffset = Offset + 1;
> +
> + if (Child->isInstantiationSubView()) {
> + if (FirstChildExpansion) {
> + renderViewDivider(NewOffset, CombinedColumnWidth + 4, OS);
> + OS << "\n";
> + }
> + renderOffset(OS, NewOffset);
> + OS << ' ';
> + if (Options.Colors)
> + OS.changeColor(raw_ostream::CYAN, false, false);
> + OS << Child->FunctionName << ":";
> + OS.resetColor();
> + OS << "\n";
> + }
> +
> + if (FirstChildExpansion)
> + FirstChildExpansion = false;
> + else if (Child->isExpansionSubView()) {
> + // Re-render the current line
> + InsertHighlightRange(LineHighlightRanges,
> + Child->getExpansionHighlightRange(),
> + AdjustedLineHighlightRanges);
> + renderOffset(OS, Offset);
> + OS.indent(CombinedColumnWidth + (Offset == 0 ? 0 : 1));
> + renderLine(OS, Line, AdjustedLineHighlightRanges);
> + }
> +
> + if (Child->isExpansionSubView()) {
> + renderViewDivider(NewOffset, DividerWidth, OS);
> + OS << "\n";
> + }
> + 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<bool> 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<unsigned>::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<unsigned>::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/SourceCoverageView.h
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/SourceCoverageView.h
> @@ -0,0 +1,215 @@
> +//===- 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 SOURCECOVERAGEVIEW_H
> +#define SOURCECOVERAGEVIEW_H
> +
> +#include "CoverageViewOptions.h"
> +#include "SourceCoverageDataManager.h"
> +#include "llvm/ProfileData/CoverageMapping.h"
> +#include "llvm/Support/MemoryBuffer.h"
> +#include <vector>
> +
> +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;
Why not capitalize 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<std::unique_ptr<SourceCoverageView>> Children;
> + std::vector<LineCoverageInfo> LineStats;
> + std::vector<HighlightRange> HighlightRanges;
> + std::vector<RegionMarker> 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 Update the given source string to start at the given line
> + void skipToLine(StringRef &Source, unsigned LineToFind);
> +
> + /// \brief Return the current source line and update the source
> + /// string to start from the next line
> + StringRef getNextLine(StringRef &Source);
> +
> + /// \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<HighlightRange> 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<RegionMarker>
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; }
> +
> + void addChild(SourceCoverageView *view) {
> + Children.push_back(std::unique_ptr<SourceCoverageView>(view));
> + }
As I mentioned above, better to take a unique_ptr argument so that it's
obvious that ownership is passed.
> +
> + /// \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 // SOURCECOVERAGEVIEW_H
> Index: tools/llvm-cov/SourceLanguages.cpp
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/SourceLanguages.cpp
> @@ -0,0 +1,93 @@
> +//===- SourceLanguages.cpp - Language specific operations
---------------===//
> +//
> +// The LLVM Compiler Infrastructure
> +//
> +// This file is distributed under the University of Illinois Open Source
> +// License. See LICENSE.TXT for details.
> +//
>
+//===----------------------------------------------------------------------===//
> +//
> +// These classes provide various language specific operations.
> +//
>
+//===----------------------------------------------------------------------===//
As far as I can tell, this really only provides "demangle", and I'm not
entirely convinced this is even a good idea. This is *very* brittle and
pretty awkward to extend, and really difficult to make general enough to
be valuable. How much is it really gaining us?
> +
> +#include "SourceLanguages.h"
> +#include "llvm/ADT/StringRef.h"
> +#include "llvm/Support/Path.h"
> +
> +using namespace llvm;
> +
> +SourceLanguages::SourceLanguages() {
> + Languages[SourceLanguage::Unknown].reset(new SourceLanguage);
> + Languages[SourceLanguage::CXX].reset(new CXXSourceLanguage);
> + Languages[SourceLanguage::ObjectiveC].reset(new
ObjectiveCSourceLanguage);
> +}
> +
> +SourceLanguage *SourceLanguages::get(const FunctionCoverageMapping
&Function) {
> + unsigned Scores[SourceLanguage::NumLanguages];
> + for (unsigned I = 0; I < SourceLanguage::NumLanguages; ++I)
> + Scores[I] = 0;
> + for (unsigned I = 0; I < SourceLanguage::NumLanguages; ++I) {
> + if (Languages[I]->matchMangledName(Function.Name))
> + ++Scores[I];
> + for (const auto &File : Function.Filenames) {
> + if (Languages[I]->matchFileExtension(sys::path::extension(File)))
> + ++Scores[I];
> + }
> + }
> + unsigned BestScore = 0;
> + SourceLanguage *Match = Languages[SourceLanguage::Unknown].get();
> + for (unsigned I = 0; I < SourceLanguage::NumLanguages; ++I) {
> + if (Scores[I] > BestScore) {
> + BestScore = Scores[I];
> + Match = Languages[I].get();
> + }
> + }
> + return Match;
> +}
> +
> +#if !defined(_MSC_VER)
> +// Assume that __cxa_demangle is provided by libcxxabi (except for
Windows).
> +extern "C" char *__cxa_demangle(const char *mangled_name, char
*output_buffer,
> + size_t *length, int *status);
> +#endif
> +
> +void CXXSourceLanguage::demangle(std::string &Name) {
> + StringRef NameRef(Name);
> + std::string FileName;
> + size_t ColonPos = NameRef.find_first_of(':');
> + if (ColonPos != StringRef::npos) {
> + FileName = std::string(Name.begin(), Name.begin() + ColonPos);
> + Name = std::string(Name.begin() + ColonPos + 1, Name.end());
> + }
> +#if !defined(_MSC_VER)
> + int status = 0;
> + char *DemangledName = __cxa_demangle(Name.c_str(), 0, 0, &status);
> + if (status != 0)
> + return;
> + Name = DemangledName;
> + free(DemangledName);
> +#endif
> + if (FileName.empty())
> + return;
> + Name = FileName + ": " + Name;
> +}
> +
> +bool CXXSourceLanguage::matchMangledName(StringRef Name) {
> + return Name.startswith("_Z");
> +}
> +
> +bool CXXSourceLanguage::matchFileExtension(StringRef Ext) {
> + return Ext.equals_lower(".h") || Ext.equals_lower(".cpp") ||
> + Ext.equals_lower(".cxx") || Ext.equals_lower(".hpp") ||
> + Ext.equals_lower(".cc") || Ext.equals_lower("hxx");
> +}
> +
> +bool ObjectiveCSourceLanguage::matchMangledName(StringRef Name) {
> + return Name.startswith("_c") || Name.startswith("_i");
> +}
> +
> +bool ObjectiveCSourceLanguage::matchFileExtension(StringRef Ext) {
> + return Ext.equals_lower(".m") || Ext.equals_lower(".h") ||
> + Ext.equals_lower(".mm");
> +}
> Index: tools/llvm-cov/SourceLanguages.h
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/SourceLanguages.h
> @@ -0,0 +1,69 @@
> +//===- SourceLanguages.h - Language specific operations
-----------------===//
> +//
> +// The LLVM Compiler Infrastructure
> +//
> +// This file is distributed under the University of Illinois Open Source
> +// License. See LICENSE.TXT for details.
> +//
>
+//===----------------------------------------------------------------------===//
> +//
> +// These classes provide various language specific operations.
> +//
>
+//===----------------------------------------------------------------------===//
> +
> +#ifndef SOURCELANGUAGES_H
> +#define SOURCELANGUAGES_H
> +
> +#include "FunctionCoverageMapping.h"
> +#include "llvm/ADT/StringRef.h"
> +#include <string>
> +
> +namespace llvm {
> +
> +/// \brief Interface for language specific operations.
> +class SourceLanguage {
> +public:
> + enum LanguageKind { Unknown, CXX, ObjectiveC, NumLanguages };
> +
> + virtual void demangle(std::string &Name) {}
> +
> + /// \brief Return true if the given name has this language's mangling.
> + virtual bool matchMangledName(StringRef Name) { return false; }
> +
> + /// \brief Return true if the given file extension
> + /// is the file extension used by the source files of this language.
> + virtual bool matchFileExtension(StringRef Ext) { return false; }
> +};
> +
> +/// \brief Manages all of the known source languages.
> +class SourceLanguages {
> + std::unique_ptr<SourceLanguage>
Languages[SourceLanguage::NumLanguages];
> +
> +public:
> + SourceLanguages();
> +
> + /// \brief Return the source language object for the given function.
> + SourceLanguage *get(const FunctionCoverageMapping &Function);
> +};
> +
> +/// \brief Implementation of C++ language specific operations.
> +class CXXSourceLanguage : public SourceLanguage {
> +public:
> + void demangle(std::string &Name) override;
> +
> + bool matchMangledName(StringRef Name) override;
> +
> + bool matchFileExtension(StringRef Ext) override;
> +};
> +
> +/// \brief Implementation of Objective C language specific operations.
> +class ObjectiveCSourceLanguage : public SourceLanguage {
> +public:
> + bool matchMangledName(StringRef Name) override;
> +
> + bool matchFileExtension(StringRef Ext) override;
> +};
> +
> +} // namespace llvm
> +
> +#endif // SOURCELANGUAGES_H
> Index: tools/llvm-cov/llvm-cov.cpp
> ===================================================================
> --- tools/llvm-cov/llvm-cov.cpp
> +++ tools/llvm-cov/llvm-cov.cpp
> @@ -11,9 +11,59 @@
> //
>
//===----------------------------------------------------------------------===//
>
> +#include "llvm/ADT/StringRef.h"
> +#include "llvm/Support/raw_ostream.h"
> +#include "llvm/Support/Path.h"
> +#include <string>
> +
> +using namespace llvm;
> +
> +/// \brief The main function for the invocation of the tool with a 'show'
> +/// command
> +int show_main(int argc, const char **argv);
> +
> +/// \brief The main function for the invocation of the tool with a
'report'
> +/// command
> +int report_main(int argc, const char **argv);
These comments are a bit confusing. I'd prefer "The main entry point for
the 'show' subcommand" and similarly for 'report'.
> +
> /// \brief The main function 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: ";
> + if (argc > 1)
> + errs() << "Unrecognized command '" << argv[1] << "'.";
> + else
> + errs() << "No command given.";
If argc is 0, calling gcov_main isn't going to accomplish much (it needs
a filename at the very least) - maybe just spit out usage for that.
It would also be nice to avoid the "unrecognized command" prompt if the
argument starts with - or if it's a filename. There's diminishing
returns on how much effort to put in, but maybe only say unrecognized
command if the arg doesn't start with - and doesn't have a . in it?
> + errs() << " Using the gcov compatible mode "
> + "(this behaviour may be dropped in the future).";
> + errs().resetColor();
> + errs() << "\n";
> +
> return gcov_main(argc, argv);
> }
http://reviews.llvm.org/D4445
More information about the llvm-commits
mailing list