llvm-cov: add code coverage tool that's based on coverage mapping format and clang's pgo.

This commit expands llvm-cov's functionality by adding support for a new code coverage
tool that uses LLVM's coverage mapping format and clang's instrumentation based profiling.
The gcov compatible tool can be invoked by supplying the 'gcov' command as the first argument,
or by modifying the tool's name to end with 'gcov'.

Differential Revision: http://reviews.llvm.org/D4445

llvm-svn: 216300
This commit is contained in:
Alex Lorenz 2014-08-22 22:56:03 +00:00
parent ec33fa9aca
commit e82d89cc37
32 changed files with 2737 additions and 34 deletions

View File

@ -289,18 +289,6 @@ ObjectFileCoverageMappingReader::ObjectFileCoverageMappingReader(
Object = std::move(File.get());
}
ObjectFileCoverageMappingReader::ObjectFileCoverageMappingReader(
std::unique_ptr<MemoryBuffer> &ObjectBuffer, sys::fs::file_magic Type)
: CurrentRecord(0) {
auto File = object::ObjectFile::createObjectFile(
ObjectBuffer->getMemBufferRef(), Type);
if (!File)
error(File.getError());
else
Object = OwningBinary<ObjectFile>(std::move(File.get()),
std::move(ObjectBuffer));
}
namespace {
/// \brief The coverage mapping data for a single function.
/// It points to the function's name.
@ -347,19 +335,11 @@ struct SectionData {
template <typename T>
std::error_code readCoverageMappingData(
SectionRef &ProfileNames, SectionRef &CoverageMapping,
SectionData &ProfileNames, StringRef Data,
std::vector<ObjectFileCoverageMappingReader::ProfileMappingRecord> &Records,
std::vector<StringRef> &Filenames) {
llvm::DenseSet<T> UniqueFunctionMappingData;
// Get the contents of the given sections.
StringRef Data;
if (auto Err = CoverageMapping.getContents(Data))
return Err;
SectionData ProfileNamesData;
if (auto Err = ProfileNamesData.load(ProfileNames))
return Err;
// Read the records in the coverage data section.
while (!Data.empty()) {
if (Data.size() < sizeof(CoverageMappingTURecord<T>))
@ -418,9 +398,9 @@ std::error_code readCoverageMappingData(
continue;
UniqueFunctionMappingData.insert(MappingRecord.FunctionNamePtr);
StringRef FunctionName;
if (auto Err = ProfileNamesData.get(MappingRecord.FunctionNamePtr,
MappingRecord.FunctionNameSize,
FunctionName))
if (auto Err =
ProfileNames.get(MappingRecord.FunctionNamePtr,
MappingRecord.FunctionNameSize, FunctionName))
return Err;
Records.push_back(ObjectFileCoverageMappingReader::ProfileMappingRecord(
Version, FunctionName, MappingRecord.FunctionHash, Mapping,
@ -431,6 +411,63 @@ std::error_code readCoverageMappingData(
return instrprof_error::success;
}
static const char *TestingFormatMagic = "llvmcovmtestdata";
static std::error_code decodeTestingFormat(StringRef Data,
SectionData &ProfileNames,
StringRef &CoverageMapping) {
Data = Data.substr(StringRef(TestingFormatMagic).size());
if (Data.size() < 1)
return instrprof_error::truncated;
unsigned N = 0;
auto ProfileNamesSize =
decodeULEB128(reinterpret_cast<const uint8_t *>(Data.data()), &N);
if (N > Data.size())
return instrprof_error::malformed;
Data = Data.substr(N);
if (Data.size() < 1)
return instrprof_error::truncated;
N = 0;
ProfileNames.Address =
decodeULEB128(reinterpret_cast<const uint8_t *>(Data.data()), &N);
if (N > Data.size())
return instrprof_error::malformed;
Data = Data.substr(N);
if (Data.size() < ProfileNamesSize)
return instrprof_error::malformed;
ProfileNames.Data = Data.substr(0, ProfileNamesSize);
CoverageMapping = Data.substr(ProfileNamesSize);
return instrprof_error::success;
}
ObjectFileCoverageMappingReader::ObjectFileCoverageMappingReader(
std::unique_ptr<MemoryBuffer> &ObjectBuffer, sys::fs::file_magic Type)
: CurrentRecord(0) {
if (ObjectBuffer->getBuffer().startswith(TestingFormatMagic)) {
// This is a special format used for testing.
SectionData ProfileNames;
StringRef CoverageMapping;
if (auto Err = decodeTestingFormat(ObjectBuffer->getBuffer(), ProfileNames,
CoverageMapping)) {
error(Err);
return;
}
error(readCoverageMappingData<uint64_t>(ProfileNames, CoverageMapping,
MappingRecords, Filenames));
Object = OwningBinary<ObjectFile>(std::unique_ptr<ObjectFile>(),
std::move(ObjectBuffer));
return;
}
auto File = object::ObjectFile::createObjectFile(
ObjectBuffer->getMemBufferRef(), Type);
if (!File)
error(File.getError());
else
Object = OwningBinary<ObjectFile>(std::move(File.get()),
std::move(ObjectBuffer));
}
std::error_code ObjectFileCoverageMappingReader::readHeader() {
ObjectFile *OF = Object.getBinary().get();
if (!OF)
@ -457,13 +494,21 @@ std::error_code ObjectFileCoverageMappingReader::readHeader() {
if (FoundSectionCount != 2)
return error(instrprof_error::bad_header);
// Get the contents of the given sections.
StringRef Data;
if (auto Err = CoverageMapping.getContents(Data))
return Err;
SectionData ProfileNamesData;
if (auto Err = ProfileNamesData.load(ProfileNames))
return Err;
// Load the data from the found sections.
std::error_code Err;
if (BytesInAddress == 4)
Err = readCoverageMappingData<uint32_t>(ProfileNames, CoverageMapping,
Err = readCoverageMappingData<uint32_t>(ProfileNamesData, Data,
MappingRecords, Filenames);
else
Err = readCoverageMappingData<uint64_t>(ProfileNames, CoverageMapping,
Err = readCoverageMappingData<uint64_t>(ProfileNamesData, Data,
MappingRecords, Filenames);
if (Err)
return error(Err);

View File

@ -1,7 +1,21 @@
These inputs were pre-generated to allow for easier testing of llvm-cov.
test.gcno and test.gcda were create by running clang:
clang++ -g -ftest-coverage -fprofile-arcs test.cpp
The files used to test the gcov compatible code coverage tool were generated
using the following method:
test.cpp.gcov was created by running gcov 4.2.1:
gcov test.cpp
test.gcno and test.gcda were create by running clang:
clang++ -g -ftest-coverage -fprofile-arcs test.cpp
test.cpp.gcov was created by running gcov 4.2.1:
gcov test.cpp
The 'covmapping' files that are used to test llvm-cov contain raw sections
with the coverage mapping data generated by the compiler and linker. They are
created by running clang and llvm-cov:
clang++ -fprofile-instr-generate -fcoverage-mapping -o test test.cpp
llvm-cov convert-for-testing -o test.covmapping test
The 'profdata' files were generated by running an instrumented version of the
program and merging the raw profile data using llvm-profdata.
./test
llvm-profdata merge -o test.profdata default.profraw

Binary file not shown.

View File

@ -0,0 +1,45 @@
// RUN: llvm-cov show %S/Inputs/highlightedRanges.covmapping -instr-profile %S/Inputs/highlightedRanges.profdata -dump -filename-equivalence %s | FileCheck %s
void func() {
return;
int i = 0; // CHECK: Highlighted line [[@LINE]], 3 -> 12
}
void func2(int x) {
if(x > 5) {
while(x >= 9) {
return;
--x; // CHECK: Highlighted line [[@LINE]], 7 -> 10
}
int i = 0; // CHECK: Highlighted line [[@LINE]], 5 -> 14
}
}
void test() {
int x = 0;
if (x) { // CHECK: Highlighted line [[@LINE]], 10 -> ?
x = 0; // CHECK: Highlighted line [[@LINE]], 1 -> ?
} else { // CHECK: Highlighted line [[@LINE]], 1 -> 4
x = 1;
}
// CHECK: Highlighted line [[@LINE+1]], 26 -> 29
for (int i = 0; i < 0; ++i) { // CHECK: Highlighted line [[@LINE]], 31 -> ?
x = 1; // CHECK: Highlighted line [[@LINE]], 1 -> ?
} // CHECK: Highlighted line [[@LINE]], 1 -> 4
x = x < 10 ? x +
1
: x - 1; // CHECK: Highlighted line [[@LINE]], 16 -> 21
x = x > 10 ? x + // CHECK: Highlighted line [[@LINE]], 16 -> ?
1 // CHECK: Highlighted line [[@LINE]], 1 -> 17
: x - 1;
}
int main() {
test();
func();
func2(9);
return 0;
}

View File

@ -0,0 +1,22 @@
// RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %S/Inputs/lineExecutionCounts.profdata -no-colors -filename-equivalence %s | FileCheck %s
int main() { // CHECK: 1| [[@LINE]]|int main(
int x = 0; // CHECK: 1| [[@LINE]]| int x
// CHECK: 1| [[@LINE]]|
if (x) { // CHECK: 0| [[@LINE]]| if (x)
x = 0; // CHECK: 0| [[@LINE]]| x = 0
} else { // CHECK: 1| [[@LINE]]| } else
x = 1; // CHECK: 1| [[@LINE]]| x = 1
} // CHECK: 1| [[@LINE]]| }
// CHECK: 1| [[@LINE]]|
for (int i = 0; i < 100; ++i) { // CHECK: 100| [[@LINE]]| for (
x = 1; // CHECK: 100| [[@LINE]]| x = 1
} // CHECK: 100| [[@LINE]]| }
// CHECK: 1| [[@LINE]]|
x = x < 10 ? x + 1 : x - 1; // CHECK: 0| [[@LINE]]| x =
x = x > 10 ? // CHECK: 1| [[@LINE]]| x =
x - 1: // CHECK: 0| [[@LINE]]| x
x + 1; // CHECK: 1| [[@LINE]]| x
// CHECK: 1| [[@LINE]]|
return 0; // CHECK: 1| [[@LINE]]| return
} // CHECK: 1| [[@LINE]]|}

View File

@ -0,0 +1,23 @@
// RUN: llvm-cov show %S/Inputs/regionMarkers.covmapping -instr-profile %S/Inputs/regionMarkers.profdata -show-regions -dump -filename-equivalence %s | FileCheck %s
int main() { // CHECK: Marker at [[@LINE]]:12 = 1
int x = 0;
if (x) { // CHECK: Marker at [[@LINE]]:10 = 0
x = 0;
} else { // CHECK: Marker at [[@LINE]]:10 = 1
x = 1;
}
// CHECK: Marker at [[@LINE+2]]:19 = 101
// CHECK: Marker at [[@LINE+1]]:28 = 100
for (int i = 0; i < 100; ++i) { // CHECK: Marker at [[@LINE]]:33 = 100
x = 1;
}
// CHECK: Marker at [[@LINE+1]]:16 = 1
x = x < 10 ? x + 1 : x - 1; // CHECK: Marker at [[@LINE]]:24 = 0
x = x > 10 ?
x - 1: // CHECK: Marker at [[@LINE]]:9 = 0
x + 1; // CHECK: Marker at [[@LINE]]:9 = 1
return 0;
}

View File

@ -1,6 +1,14 @@
set(LLVM_LINK_COMPONENTS core support )
set(LLVM_LINK_COMPONENTS core support object profiledata)
add_llvm_tool(llvm-cov
llvm-cov.cpp
gcov.cpp
CodeCoverage.cpp
CoverageFilters.cpp
CoverageReport.cpp
CoverageSummary.cpp
CoverageSummaryInfo.cpp
SourceCoverageDataManager.cpp
SourceCoverageView.cpp
TestingSupport.cpp
)

View File

@ -0,0 +1,709 @@
//===- CodeCoverage.cpp - Coverage tool based on profiling instrumentation-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// The 'CodeCoverageTool' class implements a command line tool to analyze and
// report coverage information using the profiling instrumentation and code
// coverage mapping.
//
//===----------------------------------------------------------------------===//
#include "FunctionCoverageMapping.h"
#include "RenderingSupport.h"
#include "CoverageViewOptions.h"
#include "CoverageFilters.h"
#include "SourceCoverageDataManager.h"
#include "SourceCoverageView.h"
#include "CoverageSummary.h"
#include "CoverageReport.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ProfileData/InstrProfReader.h"
#include "llvm/ProfileData/CoverageMapping.h"
#include "llvm/ProfileData/CoverageMappingReader.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/MemoryObject.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/PrettyStackTrace.h"
#include <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 Return true if two filepaths refer to the same file.
bool equivalentFiles(StringRef A, StringRef B);
/// \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 Load the coverage mapping data. Return true if an error occured.
bool load();
int run(Command Cmd, int argc, const char **argv);
typedef std::function<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;
std::vector<std::string> SourceFiles;
std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>>
LoadedSourceFiles;
std::vector<FunctionCoverageMapping> FunctionMappingRecords;
bool CompareFilenamesOnly;
};
}
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 &Region : Function.MappingRegions) {
if (Region.FileID != FileID)
continue;
LineStart = std::min(Region.LineStart, LineStart);
LineEnd = std::max(Region.LineEnd, LineEnd);
}
return std::make_pair(LineStart, LineEnd);
}
bool CodeCoverageTool::equivalentFiles(StringRef A, StringRef B) {
if (CompareFilenamesOnly)
return sys::path::filename(A).equals_lower(sys::path::filename(B));
return sys::fs::equivalent(A, B);
}
bool CodeCoverageTool::gatherInterestingFileIDs(
StringRef SourceFile, const FunctionCoverageMapping &Function,
SmallSet<unsigned, 8> &InterestingFileIDs) {
bool Interesting = false;
for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) {
if (equivalentFiles(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, E = Function.Filenames.size(); I < E; ++I) {
if (equivalentFiles(SourceFile, Function.Filenames[I]))
FilenameEquivalence[I] = true;
}
for (const auto &Region : Function.MappingRegions) {
if (Region.Kind == MappingRegion::ExpansionRegion &&
FilenameEquivalence[Region.FileID])
IsExpandedFile[Region.ExpandedFileID] = true;
}
for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) {
if (!FilenameEquivalence[I] || IsExpandedFile[I])
continue;
MainViewFileID = I;
return false;
}
return true;
}
bool
CodeCoverageTool::findMainViewFileID(const FunctionCoverageMapping &Function,
unsigned &MainViewFileID) {
llvm::SmallVector<bool, 8> IsExpandedFile(Function.Filenames.size(), false);
for (const auto &Region : Function.MappingRegions) {
if (Region.Kind == MappingRegion::ExpansionRegion)
IsExpandedFile[Region.ExpandedFileID] = true;
}
for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) {
if (IsExpandedFile[I])
continue;
MainViewFileID = I;
return false;
}
return true;
}
void CodeCoverageTool::createExpansionSubView(
const MappingRegion &ExpandedRegion,
const FunctionCoverageMapping &Function, SourceCoverageView &Parent) {
auto ExpandedLines = findExpandedFileInterestingLineRange(
ExpandedRegion.ExpandedFileID, Function);
if (ViewOpts.Debug)
llvm::outs() << "Expansion of " << ExpandedRegion.ExpandedFileID << ":"
<< ExpandedLines.first << " -> " << ExpandedLines.second
<< " @ " << ExpandedRegion.FileID << ", "
<< ExpandedRegion.LineStart << ":"
<< ExpandedRegion.ColumnStart << "\n";
auto SourceBuffer =
getSourceFile(Function.Filenames[ExpandedRegion.ExpandedFileID]);
if (!SourceBuffer)
return;
auto SubView = llvm::make_unique<SourceCoverageView>(
SourceBuffer.get(), Parent.getOptions(), ExpandedLines.first,
ExpandedLines.second, ExpandedRegion);
SourceCoverageDataManager RegionManager;
for (const auto &Region : Function.MappingRegions) {
if (Region.FileID == ExpandedRegion.ExpandedFileID)
RegionManager.insert(Region);
}
SubView->load(RegionManager);
createExpansionSubViews(*SubView, ExpandedRegion.ExpandedFileID, Function);
Parent.addChild(std::move(SubView));
}
void CodeCoverageTool::createExpansionSubViews(
SourceCoverageView &View, unsigned ViewFileID,
const FunctionCoverageMapping &Function) {
if (!ViewOpts.ShowExpandedRegions)
return;
for (const auto &Region : Function.MappingRegions) {
if (Region.Kind != CounterMappingRegion::ExpansionRegion)
continue;
if (Region.FileID != ViewFileID)
continue;
createExpansionSubView(Region, Function, View);
}
}
void CodeCoverageTool::createInstantiationSubView(
StringRef SourceFile, const FunctionCoverageMapping &Function,
SourceCoverageView &View) {
SourceCoverageDataManager RegionManager;
SmallSet<unsigned, 8> InterestingFileIDs;
if (!gatherInterestingFileIDs(SourceFile, Function, InterestingFileIDs))
return;
// Get the interesting regions
for (const auto &Region : Function.MappingRegions) {
if (InterestingFileIDs.count(Region.FileID))
RegionManager.insert(Region);
}
View.load(RegionManager);
unsigned MainFileID;
if (findMainViewFileID(SourceFile, Function, MainFileID))
return;
createExpansionSubViews(View, MainFileID, Function);
}
bool CodeCoverageTool::createSourceFileView(
StringRef SourceFile, SourceCoverageView &View,
ArrayRef<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;
// Get the interesting regions
for (const auto &Region : Function.MappingRegions) {
if (InterestingFileIDs.count(Region.FileID))
RegionManager.insert(Region);
}
InstantiationSetCollector.insert(Function, MainFileID);
createExpansionSubViews(View, MainFileID, Function);
}
if (RegionManager.getSourceRegions().empty())
return true;
View.load(RegionManager);
// Show instantiations
if (!ViewOpts.ShowFunctionInstantiations)
return false;
for (const auto &InstantiationSet : InstantiationSetCollector) {
if (InstantiationSet.second.size() < 2)
continue;
auto InterestingRange = findExpandedFileInterestingLineRange(
InstantiationSet.second.front()->MappingRegions.front().FileID,
*InstantiationSet.second.front());
for (auto Function : InstantiationSet.second) {
auto SubView = llvm::make_unique<SourceCoverageView>(
View, InterestingRange.first, InterestingRange.second,
Function->PrettyName);
createInstantiationSubView(SourceFile, *Function, *SubView);
View.addChild(std::move(SubView));
}
}
return false;
}
bool CodeCoverageTool::load() {
auto CounterMappingBuff = MemoryBuffer::getFileOrSTDIN(ObjectFilename);
if (auto EC = CounterMappingBuff.getError()) {
error(EC.message(), ObjectFilename);
return true;
}
ObjectFileCoverageMappingReader MappingReader(CounterMappingBuff.get());
if (auto EC = MappingReader.readHeader()) {
error(EC.message(), ObjectFilename);
return true;
}
std::vector<uint64_t> Counts;
for (const auto &I : MappingReader) {
FunctionCoverageMapping Function(I.FunctionName, I.Filenames);
// Create the mapping regions with evaluated execution counts
Counts.clear();
PGOReader->getFunctionCounts(Function.Name, I.FunctionHash, Counts);
// Get the biggest referenced counters
bool RegionError = false;
CounterMappingContext Ctx(I.Expressions, Counts);
for (const auto &R : I.MappingRegions) {
// Compute the values of mapped regions
if (ViewOpts.Debug) {
outs() << "File " << R.FileID << "| " << R.LineStart << ":"
<< R.ColumnStart << " -> " << R.LineEnd << ":" << R.ColumnEnd
<< " = ";
Ctx.dump(R.Count);
if (R.Kind == CounterMappingRegion::ExpansionRegion) {
outs() << " (Expanded file id = " << R.ExpandedFileID << ") ";
}
outs() << "\n";
}
std::error_code Error;
Function.MappingRegions.push_back(
MappingRegion(R, Ctx.evaluate(R.Count, Error)));
if (Error && !RegionError) {
colored_ostream(errs(), raw_ostream::RED)
<< "error: Regions and counters don't match in a function '"
<< Function.PrettyName << "' (re-run the instrumented binary).";
errs() << "\n";
RegionError = true;
}
}
if (RegionError || !Filters.matches(Function))
continue;
FunctionMappingRecords.push_back(Function);
}
return false;
}
int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
// Print a stack trace if we signal out.
sys::PrintStackTraceOnErrorSignal();
PrettyStackTraceProgram X(argc, argv);
llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
cl::list<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::opt<bool> FilenameEquivalence(
"filename-equivalence", cl::Optional,
cl::desc("Compare the filenames instead of full filepaths"));
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;
CompareFilenamesOnly = FilenameEquivalence;
if (auto EC = IndexedInstrProfReader::create(PGOFilename, PGOReader)) {
error(EC.message(), PGOFilename);
return 1;
}
// Create the function filters
if (!NameFilters.empty() || !NameRegexFilters.empty()) {
auto NameFilterer = new CoverageFilters;
for (const auto &Name : NameFilters)
NameFilterer->push_back(llvm::make_unique<NameCoverageFilter>(Name));
for (const auto &Regex : NameRegexFilters)
NameFilterer->push_back(
llvm::make_unique<NameRegexCoverageFilter>(Regex));
Filters.push_back(std::unique_ptr<CoverageFilter>(NameFilterer));
}
if (RegionCoverageLtFilter.getNumOccurrences() ||
RegionCoverageGtFilter.getNumOccurrences() ||
LineCoverageLtFilter.getNumOccurrences() ||
LineCoverageGtFilter.getNumOccurrences()) {
auto StatFilterer = new CoverageFilters;
if (RegionCoverageLtFilter.getNumOccurrences())
StatFilterer->push_back(llvm::make_unique<RegionCoverageFilter>(
RegionCoverageFilter::LessThan, RegionCoverageLtFilter));
if (RegionCoverageGtFilter.getNumOccurrences())
StatFilterer->push_back(llvm::make_unique<RegionCoverageFilter>(
RegionCoverageFilter::GreaterThan, RegionCoverageGtFilter));
if (LineCoverageLtFilter.getNumOccurrences())
StatFilterer->push_back(llvm::make_unique<LineCoverageFilter>(
LineCoverageFilter::LessThan, LineCoverageLtFilter));
if (LineCoverageGtFilter.getNumOccurrences())
StatFilterer->push_back(llvm::make_unique<LineCoverageFilter>(
RegionCoverageFilter::GreaterThan, LineCoverageGtFilter));
Filters.push_back(std::unique_ptr<CoverageFilter>(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);
ViewOpts.colored_ostream(outs(), raw_ostream::CYAN)
<< Function.PrettyName << " from " << SourceFile << ":";
outs() << "\n";
mainView->render(outs());
if (FunctionMappingRecords.size() > 1)
outs() << "\n";
}
return 0;
}
// Show files
bool ShowFilenames = SourceFiles.size() != 1;
if (SourceFiles.empty()) {
// Get the source files from the function coverage mapping
std::set<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)) {
ViewOpts.colored_ostream(outs(), raw_ostream::RED)
<< "warning: The file '" << SourceFile << "' isn't covered.";
outs() << "\n";
continue;
}
if (ShowFilenames) {
ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":";
outs() << "\n";
}
mainView->render(outs());
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);
}

View File

@ -0,0 +1,57 @@
//===- CoverageFilters.cpp - Function coverage mapping filters ------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// These classes provide filtering for function coverage mapping records.
//
//===----------------------------------------------------------------------===//
#include "CoverageFilters.h"
#include "CoverageSummaryInfo.h"
#include "llvm/Support/Regex.h"
using namespace llvm;
bool NameCoverageFilter::matches(const FunctionCoverageMapping &Function) {
StringRef FuncName = Function.PrettyName;
return FuncName.find(Name) != StringRef::npos;
}
bool NameRegexCoverageFilter::matches(const FunctionCoverageMapping &Function) {
return llvm::Regex(Regex).match(Function.PrettyName);
}
bool RegionCoverageFilter::matches(const FunctionCoverageMapping &Function) {
return PassesThreshold(FunctionCoverageSummary::get(Function)
.RegionCoverage.getPercentCovered());
}
bool LineCoverageFilter::matches(const FunctionCoverageMapping &Function) {
return PassesThreshold(
FunctionCoverageSummary::get(Function).LineCoverage.getPercentCovered());
}
void CoverageFilters::push_back(std::unique_ptr<CoverageFilter> Filter) {
Filters.push_back(std::move(Filter));
}
bool CoverageFilters::matches(const FunctionCoverageMapping &Function) {
for (const auto &Filter : Filters) {
if (Filter->matches(Function))
return true;
}
return false;
}
bool CoverageFiltersMatchAll::matches(const FunctionCoverageMapping &Function) {
for (const auto &Filter : Filters) {
if (!Filter->matches(Function))
return false;
}
return true;
}

View File

@ -0,0 +1,125 @@
//===- CoverageFilters.h - Function coverage mapping filters --------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// These classes provide filtering for function coverage mapping records.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_COV_COVERAGEFILTERS_H
#define LLVM_COV_COVERAGEFILTERS_H
#include "FunctionCoverageMapping.h"
#include <vector>
#include <memory>
namespace llvm {
/// \brief Matches specific functions that pass the requirement of this filter.
class CoverageFilter {
public:
virtual ~CoverageFilter() {}
/// \brief Return true if the function passes the requirements of this filter.
virtual bool matches(const FunctionCoverageMapping &Function) { return true; }
};
/// \brief Matches functions that contain a specific string in their name.
class NameCoverageFilter : public CoverageFilter {
StringRef Name;
public:
NameCoverageFilter(StringRef Name) : Name(Name) {}
bool matches(const FunctionCoverageMapping &Function) override;
};
/// \brief Matches functions whose name matches a certain regular expression.
class NameRegexCoverageFilter : public CoverageFilter {
StringRef Regex;
public:
NameRegexCoverageFilter(StringRef Regex) : Regex(Regex) {}
bool matches(const FunctionCoverageMapping &Function) override;
};
/// \brief Matches numbers that pass a certain threshold.
template <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(std::unique_ptr<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 // LLVM_COV_COVERAGEFILTERS_H

View File

@ -0,0 +1,201 @@
//===- CoverageReport.cpp - Code coverage report -------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This class implements rendering of a code coverage report.
//
//===----------------------------------------------------------------------===//
#include "CoverageReport.h"
#include "CoverageSummary.h"
#include "RenderingSupport.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/FileSystem.h"
using namespace llvm;
namespace {
/// \brief Helper struct which prints trimmed and aligned columns.
struct Column {
enum TrimKind { NoTrim, LeftTrim, RightTrim };
enum AlignmentKind { LeftAlignment, RightAlignment };
StringRef Str;
unsigned Width;
TrimKind Trim;
AlignmentKind Alignment;
Column(StringRef Str, unsigned Width)
: Str(Str), Width(Width), Trim(NoTrim), Alignment(LeftAlignment) {}
Column &set(TrimKind Value) {
Trim = Value;
return *this;
}
Column &set(AlignmentKind Value) {
Alignment = Value;
return *this;
}
void render(raw_ostream &OS) const;
};
raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {
Value.render(OS);
return OS;
}
}
void Column::render(raw_ostream &OS) const {
if (Str.size() <= Width) {
if (Alignment == RightAlignment) {
OS.indent(Width - Str.size());
OS << Str;
return;
}
OS << Str;
OS.indent(Width - Str.size());
return;
}
switch (Trim) {
case NoTrim:
OS << Str.substr(0, Width);
break;
case LeftTrim:
OS << "..." << Str.substr(Str.size() - Width + 3);
break;
case RightTrim:
OS << Str.substr(0, Width - 3) << "...";
break;
}
}
static Column column(StringRef Str, unsigned Width) {
return Column(Str, Width);
}
template <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);
Options.colored_ostream(OS, File.RegionCoverage.isFullyCovered()
? raw_ostream::GREEN
: raw_ostream::RED)
<< format("%*zd", FileReportColumns[2], File.RegionCoverage.NotCovered);
Options.colored_ostream(OS,
determineCoveragePercentageColor(File.RegionCoverage))
<< format("%*.2f", FileReportColumns[3] - 1,
File.RegionCoverage.getPercentCovered()) << '%';
OS << format("%*zd", FileReportColumns[4],
File.FunctionCoverage.NumFunctions);
Options.colored_ostream(
OS, determineCoveragePercentageColor(File.FunctionCoverage))
<< format("%*.2f", FileReportColumns[5] - 1,
File.FunctionCoverage.getPercentCovered()) << '%';
OS << "\n";
}
void CoverageReport::render(const FunctionCoverageSummary &Function,
raw_ostream &OS) {
OS << column(Function.Name, FunctionReportColumns[0], Column::RightTrim)
<< format("%*zd", FunctionReportColumns[1],
Function.RegionCoverage.NumRegions);
Options.colored_ostream(OS, Function.RegionCoverage.isFullyCovered()
? raw_ostream::GREEN
: raw_ostream::RED)
<< format("%*zd", FunctionReportColumns[2],
Function.RegionCoverage.NotCovered);
Options.colored_ostream(
OS, determineCoveragePercentageColor(Function.RegionCoverage))
<< format("%*.2f", FunctionReportColumns[3] - 1,
Function.RegionCoverage.getPercentCovered()) << '%';
OS << format("%*zd", FunctionReportColumns[4],
Function.LineCoverage.NumLines);
Options.colored_ostream(OS, Function.LineCoverage.isFullyCovered()
? raw_ostream::GREEN
: raw_ostream::RED)
<< format("%*zd", FunctionReportColumns[5],
Function.LineCoverage.NotCovered);
Options.colored_ostream(
OS, determineCoveragePercentageColor(Function.LineCoverage))
<< format("%*.2f", FunctionReportColumns[6] - 1,
Function.LineCoverage.getPercentCovered()) << '%';
OS << "\n";
}
void CoverageReport::renderFunctionReports(raw_ostream &OS) {
bool isFirst = true;
for (const auto &File : Summary.getFileSummaries()) {
if (isFirst)
isFirst = false;
else
OS << "\n";
OS << "File '" << File.Name << "':\n";
OS << column("Name", FunctionReportColumns[0])
<< column("Regions", FunctionReportColumns[1], Column::RightAlignment)
<< column("Miss", FunctionReportColumns[2], Column::RightAlignment)
<< column("Cover", FunctionReportColumns[3], Column::RightAlignment)
<< column("Lines", FunctionReportColumns[4], Column::RightAlignment)
<< column("Miss", FunctionReportColumns[5], Column::RightAlignment)
<< column("Cover", FunctionReportColumns[6], Column::RightAlignment);
OS << "\n";
renderDivider(FunctionReportColumns, OS);
OS << "\n";
for (const auto &Function : File.FunctionSummaries)
render(Function, OS);
renderDivider(FunctionReportColumns, OS);
OS << "\n";
render(FunctionCoverageSummary("TOTAL", File.RegionCoverage,
File.LineCoverage),
OS);
}
}
void CoverageReport::renderFileReports(raw_ostream &OS) {
OS << column("Filename", FileReportColumns[0])
<< column("Regions", FileReportColumns[1], Column::RightAlignment)
<< column("Miss", FileReportColumns[2], Column::RightAlignment)
<< column("Cover", FileReportColumns[3], Column::RightAlignment)
<< column("Functions", FileReportColumns[4], Column::RightAlignment)
<< column("Cover", FileReportColumns[5], Column::RightAlignment) << "\n";
renderDivider(FileReportColumns, OS);
OS << "\n";
for (const auto &File : Summary.getFileSummaries())
render(File, OS);
renderDivider(FileReportColumns, OS);
OS << "\n";
render(Summary.getCombinedFileSummaries(), OS);
}

View File

@ -0,0 +1,40 @@
//===- CoverageReport.h - Code coverage report ---------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This class implements rendering of a code coverage report.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_COV_COVERAGEREPORT_H
#define LLVM_COV_COVERAGEREPORT_H
#include "CoverageViewOptions.h"
#include "CoverageSummary.h"
namespace llvm {
/// \brief Displays the code coverage report.
class CoverageReport {
const CoverageViewOptions &Options;
CoverageSummary &Summary;
void render(const FileCoverageSummary &File, raw_ostream &OS);
void render(const FunctionCoverageSummary &Function, raw_ostream &OS);
public:
CoverageReport(const CoverageViewOptions &Options, CoverageSummary &Summary)
: Options(Options), Summary(Summary) {}
void renderFunctionReports(raw_ostream &OS);
void renderFileReports(raw_ostream &OS);
};
}
#endif // LLVM_COV_COVERAGEREPORT_H

View File

@ -0,0 +1,92 @@
//===- CoverageSummary.cpp - Code coverage summary ------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This class implements data management and rendering for the code coverage
// summaries of all files and functions.
//
//===----------------------------------------------------------------------===//
#include "CoverageSummary.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Format.h"
using namespace llvm;
unsigned CoverageSummary::getFileID(StringRef Filename) {
for (unsigned I = 0, E = Filenames.size(); I < E; ++I) {
if (sys::fs::equivalent(Filenames[I], Filename))
return I;
}
Filenames.push_back(Filename);
return Filenames.size() - 1;
}
void
CoverageSummary::createSummaries(ArrayRef<FunctionCoverageMapping> Functions) {
std::vector<std::pair<unsigned, size_t>> FunctionFileIDs;
FunctionFileIDs.resize(Functions.size());
for (size_t I = 0, E = Functions.size(); I < E; ++I) {
StringRef Filename = Functions[I].Filenames[0];
FunctionFileIDs[I] = std::make_pair(getFileID(Filename), I);
}
// Sort the function records by file ids
std::sort(FunctionFileIDs.begin(), FunctionFileIDs.end(),
[](const std::pair<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, E = Functions.size(); I < E; ++I)
FunctionSummaries.push_back(
FunctionCoverageSummary::get(Functions[FunctionFileIDs[I].second]));
// Create file summaries
size_t CurrentSummary = 0;
for (unsigned FileID = 0; FileID < Filenames.size(); ++FileID) {
// Gather the relevant functions summaries
auto PrevSummary = CurrentSummary;
while (CurrentSummary < FunctionSummaries.size() &&
FunctionFileIDs[CurrentSummary].first == FileID)
++CurrentSummary;
ArrayRef<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, NonCodeLines = 0, CoveredLines = 0;
size_t NumFunctionsCovered = 0, NumFunctions = 0;
for (const auto &File : FileSummaries) {
NumRegions += File.RegionCoverage.NumRegions;
CoveredRegions += File.RegionCoverage.Covered;
NumLines += File.LineCoverage.NumLines;
NonCodeLines += File.LineCoverage.NonCodeLines;
CoveredLines += File.LineCoverage.Covered;
NumFunctionsCovered += File.FunctionCoverage.FullyCovered;
NumFunctions += File.FunctionCoverage.NumFunctions;
}
return FileCoverageSummary(
"TOTAL", RegionCoverageInfo(CoveredRegions, NumRegions),
LineCoverageInfo(CoveredLines, NonCodeLines, NumLines),
FunctionCoverageInfo(NumFunctionsCovered, NumFunctions),
ArrayRef<FunctionCoverageSummary>());
}

View File

@ -0,0 +1,45 @@
//===- CoverageSummary.h - Code coverage summary --------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This class implements data management and rendering for the code coverage
// summaries of all files and functions.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_COV_COVERAGESUMMARY_H
#define LLVM_COV_COVERAGESUMMARY_H
#include "CoverageSummaryInfo.h"
#include <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 // LLVM_COV_COVERAGESUMMARY_H

View File

@ -0,0 +1,95 @@
//===- CoverageSummaryInfo.cpp - Coverage summary for function/file -------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// These structures are used to represent code coverage metrics
// for functions/files.
//
//===----------------------------------------------------------------------===//
#include "CoverageSummaryInfo.h"
using namespace llvm;
using namespace coverage;
FunctionCoverageSummary
FunctionCoverageSummary::get(const FunctionCoverageMapping &Function) {
// Compute the region coverage
size_t NumCodeRegions = 0, CoveredRegions = 0;
for (auto &Region : Function.MappingRegions) {
if (Region.Kind != CounterMappingRegion::CodeRegion)
continue;
++NumCodeRegions;
if (Region.ExecutionCount != 0)
++CoveredRegions;
}
// Compute the line coverage
size_t NumLines = 0, CoveredLines = 0;
for (unsigned FileID = 0, E = Function.Filenames.size(); FileID < E;
++FileID) {
// Find the line start and end of the function's source code
// in that particular file
unsigned LineStart = std::numeric_limits<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, NonCodeLines = 0, CoveredLines = 0;
size_t NumFunctionsCovered = 0;
for (const auto &Func : FunctionSummaries) {
CoveredRegions += Func.RegionCoverage.Covered;
NumRegions += Func.RegionCoverage.NumRegions;
CoveredLines += Func.LineCoverage.Covered;
NonCodeLines += Func.LineCoverage.NonCodeLines;
NumLines += Func.LineCoverage.NumLines;
if (Func.RegionCoverage.isFullyCovered())
++NumFunctionsCovered;
}
return FileCoverageSummary(
Name, RegionCoverageInfo(CoveredRegions, NumRegions),
LineCoverageInfo(CoveredLines, NonCodeLines, NumLines),
FunctionCoverageInfo(NumFunctionsCovered, FunctionSummaries.size()),
FunctionSummaries);
}

View File

@ -0,0 +1,131 @@
//===- CoverageSummaryInfo.h - Coverage summary for function/file ---------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// These structures are used to represent code coverage metrics
// for functions/files.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_COV_COVERAGESUMMARYINFO_H
#define LLVM_COV_COVERAGESUMMARYINFO_H
#include "FunctionCoverageMapping.h"
#include "llvm/Support/raw_ostream.h"
namespace llvm {
/// \brief Provides information about region coverage for a function/file.
struct RegionCoverageInfo {
/// \brief The number of regions that were executed at least once.
size_t Covered;
/// \brief The number of regions that weren't executed.
size_t NotCovered;
/// \brief The total number of regions in a function/file.
size_t NumRegions;
RegionCoverageInfo(size_t Covered, size_t NumRegions)
: Covered(Covered), NotCovered(NumRegions - Covered),
NumRegions(NumRegions) {}
bool isFullyCovered() const { return Covered == NumRegions; }
double getPercentCovered() const {
return double(Covered) / double(NumRegions) * 100.0;
}
};
/// \brief Provides information about line coverage for a function/file.
struct LineCoverageInfo {
/// \brief The number of lines that were executed at least once.
size_t Covered;
/// \brief The number of lines that weren't executed.
size_t NotCovered;
/// \brief The number of lines that aren't code.
size_t NonCodeLines;
/// \brief The total number of lines in a function/file.
size_t NumLines;
LineCoverageInfo(size_t Covered, size_t NumNonCodeLines, size_t NumLines)
: Covered(Covered), NotCovered(NumLines - NumNonCodeLines - Covered),
NonCodeLines(NumNonCodeLines), NumLines(NumLines) {}
bool isFullyCovered() const { return Covered == (NumLines - NonCodeLines); }
double getPercentCovered() const {
return double(Covered) / double(NumLines - NonCodeLines) * 100.0;
}
};
/// \brief Provides information about function coverage for a file.
struct FunctionCoverageInfo {
/// \brief The number of functions that have full
/// region coverage.
size_t FullyCovered;
/// \brief The total number of functions in this file.
size_t NumFunctions;
FunctionCoverageInfo(size_t FullyCovered, size_t NumFunctions)
: FullyCovered(FullyCovered), NumFunctions(NumFunctions) {}
bool isFullyCovered() const { return FullyCovered == NumFunctions; }
double getPercentCovered() const {
return double(FullyCovered) / double(NumFunctions) * 100.0;
}
};
/// \brief A summary of function's code coverage.
struct FunctionCoverageSummary {
StringRef Name;
RegionCoverageInfo RegionCoverage;
LineCoverageInfo LineCoverage;
FunctionCoverageSummary(StringRef Name,
const RegionCoverageInfo &RegionCoverage,
const LineCoverageInfo &LineCoverage)
: Name(Name), RegionCoverage(RegionCoverage), LineCoverage(LineCoverage) {
}
/// \brief Compute the code coverage summary for the given function coverage
/// mapping record.
static FunctionCoverageSummary get(const FunctionCoverageMapping &Function);
};
/// \brief A summary of file's code coverage.
struct FileCoverageSummary {
StringRef Name;
RegionCoverageInfo RegionCoverage;
LineCoverageInfo LineCoverage;
FunctionCoverageInfo FunctionCoverage;
/// \brief The summary of every function
/// in this file.
ArrayRef<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 // LLVM_COV_COVERAGESUMMARYINFO_H

View File

@ -0,0 +1,36 @@
//===- CoverageViewOptions.h - Code coverage display options -------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_COV_COVERAGEVIEWOPTIONS_H
#define LLVM_COV_COVERAGEVIEWOPTIONS_H
#include "RenderingSupport.h"
namespace llvm {
/// \brief The options for displaying the code coverage information.
struct CoverageViewOptions {
bool Debug;
bool Colors;
bool ShowLineNumbers;
bool ShowLineStats;
bool ShowRegionMarkers;
bool ShowLineStatsOrRegionMarkers;
bool ShowExpandedRegions;
bool ShowFunctionInstantiations;
/// \brief Change the output's stream color if the colors are enabled.
ColoredRawOstream colored_ostream(raw_ostream &OS,
raw_ostream::Colors Color) const {
return llvm::colored_ostream(OS, Color, Colors);
}
};
}
#endif // LLVM_COV_COVERAGEVIEWOPTIONS_H

View File

@ -0,0 +1,50 @@
//===- FunctionCoverageMapping.h - Function coverage mapping record -------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// A structure that stores the coverage mapping record for a single function.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_COV_FUNCTIONCOVERAGEMAPPING_H
#define LLVM_COV_FUNCTIONCOVERAGEMAPPING_H
#include <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 // LLVM_COV_FUNCTIONCOVERAGEMAPPING_H

View File

@ -19,4 +19,4 @@
type = Tool
name = llvm-cov
parent = Tools
required_libraries = Instrumentation
required_libraries = ProfileData Support Instrumentation

View File

@ -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

View File

@ -0,0 +1,59 @@
//===- RenderingSupport.h - output stream rendering support functions ----===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_COV_RENDERINGSUPPORT_H
#define LLVM_COV_RENDERINGSUPPORT_H
#include "llvm/Support/raw_ostream.h"
#include <utility>
namespace llvm {
/// \brief A helper class that resets the output stream's color if needed
/// when destroyed.
class ColoredRawOstream {
ColoredRawOstream(const ColoredRawOstream &OS) LLVM_DELETED_FUNCTION;
public:
raw_ostream &OS;
bool IsColorUsed;
ColoredRawOstream(raw_ostream &OS, bool IsColorUsed)
: OS(OS), IsColorUsed(IsColorUsed) {}
ColoredRawOstream(ColoredRawOstream &&Other)
: OS(Other.OS), IsColorUsed(Other.IsColorUsed) {
// Reset the other IsColorUsed so that the other object won't reset the
// color when destroyed.
Other.IsColorUsed = false;
}
~ColoredRawOstream() {
if (IsColorUsed)
OS.resetColor();
}
};
template <typename T>
inline raw_ostream &operator<<(const ColoredRawOstream &OS, T &&Value) {
return OS.OS << std::forward<T>(Value);
}
/// \brief Change the color of the output stream if the `IsColorUsed` flag
/// is true. Returns an object that resets the color when destroyed.
inline ColoredRawOstream colored_ostream(raw_ostream &OS,
raw_ostream::Colors Color,
bool IsColorUsed = false) {
if (IsColorUsed)
OS.changeColor(Color);
return ColoredRawOstream(OS, IsColorUsed);
}
}
#endif // LLVM_COV_RENDERINGSUPPORT_H

View File

@ -0,0 +1,57 @@
//===- 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;
}
Regions.push_back(std::make_pair(Range, Region.ExecutionCount));
}
ArrayRef<std::pair<SourceCoverageDataManager::SourceRange, uint64_t>>
SourceCoverageDataManager::getSourceRegions() {
if (Uniqued || Regions.size() <= 1)
return Regions;
// Sort.
std::sort(Regions.begin(), Regions.end(),
[](const std::pair<SourceRange, uint64_t> &LHS,
const std::pair<SourceRange, uint64_t> &RHS) {
return LHS.first < RHS.first;
});
// Merge duplicate source ranges and sum their execution counts.
auto Prev = Regions.begin();
for (auto I = Prev + 1, E = Regions.end(); I != E; ++I) {
if (I->first == Prev->first) {
Prev->second += I->second;
continue;
}
++Prev;
*Prev = *I;
}
++Prev;
Regions.erase(Prev, Regions.end());
Uniqued = true;
return Regions;
}

View File

@ -0,0 +1,79 @@
//===- SourceCoverageDataManager.h - Manager for source file coverage data-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This class separates and merges mapping regions for a specific source file.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_COV_SOURCECOVERAGEDATAMANAGER_H
#define LLVM_COV_SOURCECOVERAGEDATAMANAGER_H
#include "FunctionCoverageMapping.h"
#include "llvm/ProfileData/CoverageMapping.h"
#include "llvm/ADT/Hashing.h"
#include <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;
}
};
protected:
std::vector<std::pair<SourceRange, uint64_t>> Regions;
std::vector<SourceRange> SkippedRegions;
bool Uniqued;
public:
SourceCoverageDataManager() : Uniqued(false) {}
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 // LLVM_COV_SOURCECOVERAGEDATAMANAGER_H

View File

@ -0,0 +1,411 @@
//===- SourceCoverageView.cpp - Code coverage view for source code --------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This class implements rendering for code coverage of source code.
//
//===----------------------------------------------------------------------===//
#include "SourceCoverageView.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/LineIterator.h"
using namespace llvm;
void SourceCoverageView::renderLine(raw_ostream &OS, StringRef Line,
ArrayRef<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();
// Trim
Str = Str.substr(0, std::min(Str.size(), (size_t)LineCoverageColumnWidth));
// Align to the right
OS.indent(LineCoverageColumnWidth - Str.size());
colored_ostream(OS, raw_ostream::MAGENTA,
Line.hasMultipleRegions() && Options.Colors)
<< Str;
OS << '|';
}
void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS,
unsigned LineNo) {
SmallString<32> Buffer;
raw_svector_ostream BufferOS(Buffer);
BufferOS << LineNo;
auto Str = BufferOS.str();
// Trim and align to the right
Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
}
void SourceCoverageView::renderRegionMarkers(raw_ostream &OS,
ArrayRef<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(
ArrayRef<SourceCoverageView::HighlightRange> Ranges,
SourceCoverageView::HighlightRange RangeToInsert,
SmallVectorImpl<SourceCoverageView::HighlightRange> &Result) {
Result.clear();
size_t I = 0;
auto E = Ranges.size();
for (; I < E; ++I) {
if (RangeToInsert.ColumnStart < Ranges[I].ColumnEnd) {
const auto &Range = Ranges[I];
bool NextRangeContainsInserted = false;
// If the next range starts before the inserted range, move the end of the
// next range to the start of the inserted range.
if (Range.ColumnStart < RangeToInsert.ColumnStart) {
if (RangeToInsert.ColumnStart != Range.ColumnStart)
Result.push_back(SourceCoverageView::HighlightRange(
Range.Line, Range.ColumnStart, RangeToInsert.ColumnStart,
Range.Kind));
// If the next range also ends after the inserted range, keep this range
// and create a new range that starts at the inserted range and ends
// at the next range later.
if (Range.ColumnEnd > RangeToInsert.ColumnEnd)
NextRangeContainsInserted = true;
}
if (!NextRangeContainsInserted) {
++I;
// Ignore ranges that are contained in inserted range
while (I < E && RangeToInsert.contains(Ranges[I]))
++I;
}
break;
}
Result.push_back(Ranges[I]);
}
Result.push_back(RangeToInsert);
// If the next range starts before the inserted range end, move the start
// of the next range to the end of the inserted range.
if (I < E && Ranges[I].ColumnStart < RangeToInsert.ColumnEnd) {
const auto &Range = Ranges[I];
if (RangeToInsert.ColumnEnd != Range.ColumnEnd)
Result.push_back(SourceCoverageView::HighlightRange(
Range.Line, RangeToInsert.ColumnEnd, Range.ColumnEnd, Range.Kind));
++I;
}
// Add the remaining ranges that are located after the inserted range
for (; I < E; ++I)
Result.push_back(Ranges[I]);
}
void SourceCoverageView::sortChildren() {
for (auto &I : Children)
I->sortChildren();
std::sort(Children.begin(), Children.end(),
[](const std::unique_ptr<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;
auto E = Items.size();
while (CurrentIdx < E && Items[CurrentIdx].Line == LineNo)
++CurrentIdx;
return ArrayRef<T>(Items.data() + PrevIdx, CurrentIdx - PrevIdx);
}
ArrayRef<std::unique_ptr<SourceCoverageView>>
gatherLineSubViews(size_t &CurrentIdx,
ArrayRef<std::unique_ptr<SourceCoverageView>> Items,
unsigned LineNo) {
auto PrevIdx = CurrentIdx;
auto E = Items.size();
while (CurrentIdx < E &&
Items[CurrentIdx]->getSubViewsExpansionLine() == LineNo)
++CurrentIdx;
return ArrayRef<std::unique_ptr<SourceCoverageView>>(Items.data() + PrevIdx,
CurrentIdx - PrevIdx);
}
void SourceCoverageView::render(raw_ostream &OS, unsigned Offset) {
// Make sure that the children are in sorted order.
sortChildren();
SmallVector<HighlightRange, 8> AdjustedLineHighlightRanges;
size_t CurrentChild = 0;
size_t CurrentHighlightRange = 0;
size_t CurrentRegionMarker = 0;
line_iterator Lines(File);
// Advance the line iterator to the first line.
while (Lines.line_number() < LineStart)
++Lines;
// The width of the leading columns
unsigned CombinedColumnWidth =
(Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
(Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
// The width of the line that is used to divide between the view and the
// subviews.
unsigned DividerWidth = CombinedColumnWidth + 4;
for (size_t I = 0; I < LineCount; ++I) {
unsigned LineNo = I + LineStart;
// Gather the child subviews that are visible on this line.
auto LineSubViews = gatherLineSubViews(CurrentChild, Children, LineNo);
renderOffset(OS, Offset);
if (Options.ShowLineStats)
renderLineCoverageColumn(OS, LineStats[I]);
if (Options.ShowLineNumbers)
renderLineNumberColumn(OS, LineNo);
// Gather highlighting ranges.
auto LineHighlightRanges =
gatherLineItems(CurrentHighlightRange, HighlightRanges, LineNo);
auto LineRanges = LineHighlightRanges;
// Highlight the expansion range if there is an expansion subview on this
// line.
if (!LineSubViews.empty() && LineSubViews.front()->isExpansionSubView() &&
Options.Colors) {
insertHighlightRange(LineHighlightRanges,
LineSubViews.front()->getExpansionHighlightRange(),
AdjustedLineHighlightRanges);
LineRanges = AdjustedLineHighlightRanges;
}
// Display the source code for the current line.
StringRef Line = *Lines;
// Check if the line is empty, as line_iterator skips blank lines.
if (LineNo < Lines.line_number())
Line = "";
else if (!Lines.is_at_eof())
++Lines;
renderLine(OS, Line, LineRanges);
// Show the region markers.
bool ShowMarkers = !Options.ShowLineStatsOrRegionMarkers ||
LineStats[I].hasMultipleRegions();
auto LineMarkers = gatherLineItems(CurrentRegionMarker, Markers, LineNo);
if (ShowMarkers && !LineMarkers.empty()) {
renderOffset(OS, Offset);
OS.indent(CombinedColumnWidth);
renderRegionMarkers(OS, LineMarkers);
}
// Show the line's expanded child subviews.
bool FirstChildExpansion = true;
if (LineSubViews.empty())
continue;
unsigned NewOffset = Offset + 1;
renderViewDivider(NewOffset, DividerWidth, OS);
OS << "\n";
for (const auto &Child : LineSubViews) {
// If this subview shows a function instantiation, render the function's
// name.
if (Child->isInstantiationSubView()) {
renderOffset(OS, NewOffset);
OS << ' ';
Options.colored_ostream(OS, raw_ostream::CYAN) << Child->FunctionName
<< ":";
OS << "\n";
} else {
if (!FirstChildExpansion) {
// Re-render the current line and highlight the expansion range for
// this
// subview.
insertHighlightRange(LineHighlightRanges,
Child->getExpansionHighlightRange(),
AdjustedLineHighlightRanges);
renderOffset(OS, Offset);
OS.indent(CombinedColumnWidth + (Offset == 0 ? 0 : 1));
renderLine(OS, Line, AdjustedLineHighlightRanges);
renderViewDivider(NewOffset, DividerWidth, OS);
OS << "\n";
} else
FirstChildExpansion = false;
}
// Render the child subview
Child->render(OS, NewOffset);
renderViewDivider(NewOffset, DividerWidth, OS);
OS << "\n";
}
}
}
void
SourceCoverageView::createLineCoverageInfo(SourceCoverageDataManager &Data) {
LineStats.resize(LineCount);
for (const auto &Region : Data.getSourceRegions()) {
auto Value = Region.second;
LineStats[Region.first.LineStart - LineStart].addRegionStartCount(Value);
for (unsigned Line = Region.first.LineStart + 1;
Line <= Region.first.LineEnd; ++Line)
LineStats[Line - LineStart].addRegionCount(Value);
}
// Reset the line stats for skipped regions.
for (const auto &Region : Data.getSkippedRegions()) {
for (unsigned Line = Region.LineStart; Line <= Region.LineEnd; ++Line)
LineStats[Line - LineStart] = LineCoverageInfo();
}
}
void
SourceCoverageView::createHighlightRanges(SourceCoverageDataManager &Data) {
auto Regions = Data.getSourceRegions();
std::vector<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 (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());
if (Options.Debug) {
for (const auto &Range : HighlightRanges) {
outs() << "Highlighted line " << Range.Line << ", " << Range.ColumnStart
<< " -> ";
if (Range.ColumnEnd == std::numeric_limits<unsigned>::max()) {
outs() << "?\n";
} else {
outs() << Range.ColumnEnd << "\n";
}
}
}
}
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));
}
if (Options.Debug) {
for (const auto &Marker : Markers) {
outs() << "Marker at " << Marker.Line << ":" << Marker.Column << " = "
<< Marker.ExecutionCount << "\n";
}
}
}
void SourceCoverageView::load(SourceCoverageDataManager &Data) {
if (Options.ShowLineStats)
createLineCoverageInfo(Data);
if (Options.Colors)
createHighlightRanges(Data);
if (Options.ShowRegionMarkers)
createRegionMarkers(Data);
}

View File

@ -0,0 +1,213 @@
//===- SourceCoverageView.h - Code coverage view for source code ----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This class implements rendering for code coverage of source code.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_COV_SOURCECOVERAGEVIEW_H
#define LLVM_COV_SOURCECOVERAGEVIEW_H
#include "CoverageViewOptions.h"
#include "SourceCoverageDataManager.h"
#include "llvm/ProfileData/CoverageMapping.h"
#include "llvm/Support/MemoryBuffer.h"
#include <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;
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 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; }
/// \brief Return the line number after which the subview expansion is shown.
unsigned getSubViewsExpansionLine() const {
return ExpansionRegion.LineStart;
}
void addChild(std::unique_ptr<SourceCoverageView> View) {
Children.push_back(std::move(View));
}
/// \brief Print the code coverage information for a specific
/// portion of a source file to the output stream.
void render(raw_ostream &OS, unsigned Offset = 0);
/// \brief Load the coverage information required for rendering
/// from the mapping regions in the data manager.
void load(SourceCoverageDataManager &Data);
};
} // namespace llvm
#endif // LLVM_COV_SOURCECOVERAGEVIEW_H

View File

@ -0,0 +1,92 @@
//===- TestingSupport.cpp - Convert objects files into test files --------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/MemoryObject.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/PrettyStackTrace.h"
#include <system_error>
#include <functional>
using namespace llvm;
using namespace object;
int convert_for_testing_main(int argc, const char **argv) {
sys::PrintStackTraceOnErrorSignal();
PrettyStackTraceProgram X(argc, argv);
llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
cl::opt<std::string> InputSourceFile(cl::Positional, cl::Required,
cl::desc("<Source file>"));
cl::opt<std::string> OutputFilename(
"o", cl::Required,
cl::desc(
"File with the profile data obtained after an instrumented run"));
cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
auto ObjErr = llvm::object::ObjectFile::createObjectFile(InputSourceFile);
if (auto Err = ObjErr.getError()) {
errs() << "error: " << Err.message() << "\n";
return 1;
}
ObjectFile *OF = ObjErr.get().getBinary().get();
auto BytesInAddress = OF->getBytesInAddress();
if (BytesInAddress != 8) {
errs() << "error: 64 bit binary expected\n";
return 1;
}
// Look for the sections that we are interested in.
int FoundSectionCount = 0;
SectionRef ProfileNames, CoverageMapping;
for (const auto &Section : OF->sections()) {
StringRef Name;
if (Section.getName(Name))
return 1;
if (Name == "__llvm_prf_names") {
ProfileNames = Section;
} else if (Name == "__llvm_covmap") {
CoverageMapping = Section;
} else
continue;
++FoundSectionCount;
}
if (FoundSectionCount != 2)
return 1;
// Get the contents of the given sections.
StringRef CoverageMappingData;
uint64_t ProfileNamesAddress;
StringRef ProfileNamesData;
if (CoverageMapping.getContents(CoverageMappingData) ||
ProfileNames.getAddress(ProfileNamesAddress) ||
ProfileNames.getContents(ProfileNamesData))
return 1;
int FD;
if (auto Err =
sys::fs::openFileForWrite(OutputFilename, FD, sys::fs::F_None)) {
errs() << "error: " << Err.message() << "\n";
return 1;
}
raw_fd_ostream OS(FD, true);
OS << "llvmcovmtestdata";
encodeULEB128(ProfileNamesData.size(), OS);
encodeULEB128(ProfileNamesAddress, OS);
OS << ProfileNamesData << CoverageMappingData;
return 0;
}

View File

@ -11,9 +11,63 @@
//
//===----------------------------------------------------------------------===//
/// \brief The main function for the gcov compatible coverage tool
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Path.h"
#include <string>
using namespace llvm;
/// \brief The main entry point for the 'show' subcommand.
int show_main(int argc, const char **argv);
/// \brief The main entry point for the 'report' subcommand.
int report_main(int argc, const char **argv);
/// \brief The main entry point for the 'convert-for-testing' subcommand.
int convert_for_testing_main(int argc, const char **argv);
/// \brief The main entry point for the gcov compatible coverage tool.
int gcov_main(int argc, const char **argv);
int main(int argc, const char **argv) {
// If argv[0] is or ends with 'gcov', always be gcov compatible
if (sys::path::stem(argv[0]).endswith_lower("gcov"))
return gcov_main(argc, argv);
// Check if we are invoking a specific tool command.
if (argc > 1) {
int (*func)(int, const char **) = nullptr;
StringRef command = argv[1];
if (command.equals_lower("show"))
func = show_main;
else if (command.equals_lower("report"))
func = report_main;
else if (command.equals_lower("convert-for-testing"))
func = convert_for_testing_main;
else if (command.equals_lower("gcov"))
func = gcov_main;
if (func) {
std::string Invocation(std::string(argv[0]) + " " + argv[1]);
argv[1] = Invocation.c_str();
return func(argc - 1, argv + 1);
}
}
// Give a warning and fall back to gcov
errs().changeColor(raw_ostream::RED);
errs() << "warning:";
// Assume that argv[1] wasn't a command when it stats with a '-' or is a
// filename (i.e. contains a '.')
if (argc > 1 && !StringRef(argv[1]).startswith("-") &&
StringRef(argv[1]).find(".") == StringRef::npos)
errs() << " Unrecognized command '" << argv[1] << "'.";
errs() << " Using the gcov compatible mode "
"(this behaviour may be dropped in the future).";
errs().resetColor();
errs() << "\n";
return gcov_main(argc, argv);
}