mirror of
https://github.com/RPCSX/llvm.git
synced 2025-02-13 09:14:38 +00:00
[sancov] Adding covered/uncovered tables to coverage report.
Summary: This change adds 3 tables to html report: - list of covered files with number of functions covered. - list of not covered files - list of not covered functions. I tried to put most coverage-calculating functionality into SourceCoverageData. Differential Revision: http://reviews.llvm.org/D17421 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@261287 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
parent
3a1cea0888
commit
c0110eaff6
@ -25,7 +25,9 @@
|
||||
#include "llvm/MC/MCSubtargetInfo.h"
|
||||
#include "llvm/Object/Archive.h"
|
||||
#include "llvm/Object/Binary.h"
|
||||
#include "llvm/Object/ELFObjectFile.h"
|
||||
#include "llvm/Object/ObjectFile.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/Errc.h"
|
||||
#include "llvm/Support/ErrorOr.h"
|
||||
@ -93,7 +95,7 @@ static cl::opt<bool> ClUseDefaultBlacklist(
|
||||
"use_default_blacklist", cl::init(true), cl::Hidden,
|
||||
cl::desc("Controls if default blacklist should be used."));
|
||||
|
||||
static const char *const DefaultBlacklist = "fun:__sanitizer_*";
|
||||
static const char *const DefaultBlacklistStr = "fun:__sanitizer_*";
|
||||
|
||||
// --------- FORMAT SPECIFICATION ---------
|
||||
|
||||
@ -106,7 +108,7 @@ static const uint32_t BinCoverageMagic = 0xC0BFFFFF;
|
||||
static const uint32_t Bitness32 = 0xFFFFFF32;
|
||||
static const uint32_t Bitness64 = 0xFFFFFF64;
|
||||
|
||||
// ---------
|
||||
// --------- ERROR HANDLING ---------
|
||||
|
||||
static void Fail(const llvm::Twine &E) {
|
||||
errs() << "Error: " << E << "\n";
|
||||
@ -138,6 +140,23 @@ static void FailIfEmpty(const std::unique_ptr<T> &Ptr,
|
||||
Fail(Message);
|
||||
}
|
||||
|
||||
// ---------
|
||||
|
||||
// Produces std::map<K, std::vector<E>> grouping input
|
||||
// elements by FuncTy result.
|
||||
template <class RangeTy, class FuncTy>
|
||||
static inline auto group_by(const RangeTy &R, FuncTy F)
|
||||
-> std::map<typename std::decay<decltype(F(*R.begin()))>::type,
|
||||
std::vector<typename std::decay<decltype(*R.begin())>::type>> {
|
||||
std::map<typename std::decay<decltype(F(*R.begin()))>::type,
|
||||
std::vector<typename std::decay<decltype(*R.begin())>::type>>
|
||||
Result;
|
||||
for (const auto &E : R) {
|
||||
Result[F(E)].push_back(E);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void readInts(const char *Start, const char *End,
|
||||
std::set<uint64_t> *Ints) {
|
||||
@ -155,8 +174,18 @@ struct FileLoc {
|
||||
uint32_t Line;
|
||||
};
|
||||
|
||||
struct FunctionLoc {
|
||||
bool operator<(const FunctionLoc &RHS) const {
|
||||
struct FileFn {
|
||||
bool operator<(const FileFn &RHS) const {
|
||||
return std::tie(FileName, FunctionName) <
|
||||
std::tie(RHS.FileName, RHS.FunctionName);
|
||||
}
|
||||
|
||||
std::string FileName;
|
||||
std::string FunctionName;
|
||||
};
|
||||
|
||||
struct FnLoc {
|
||||
bool operator<(const FnLoc &RHS) const {
|
||||
return std::tie(Loc, FunctionName) < std::tie(RHS.Loc, RHS.FunctionName);
|
||||
}
|
||||
|
||||
@ -181,51 +210,86 @@ static std::unique_ptr<symbolize::LLVMSymbolizer> createSymbolizer() {
|
||||
new symbolize::LLVMSymbolizer(SymbolizerOptions));
|
||||
}
|
||||
|
||||
// Compute [FileLoc -> FunctionName] map for given addresses.
|
||||
static std::map<FileLoc, std::string>
|
||||
computeFunctionsMap(std::string ObjectFile, const std::set<uint64_t> &Addrs) {
|
||||
std::map<FileLoc, std::string> Fns;
|
||||
// A DILineInfo with address.
|
||||
struct AddrInfo : public DILineInfo {
|
||||
uint64_t Addr;
|
||||
|
||||
auto Symbolizer(createSymbolizer());
|
||||
|
||||
// Fill in Fns map.
|
||||
for (auto Addr : Addrs) {
|
||||
auto InliningInfo = Symbolizer->symbolizeInlinedCode(ObjectFile, Addr);
|
||||
FailIfError(InliningInfo);
|
||||
for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) {
|
||||
auto FrameInfo = InliningInfo->getFrame(I);
|
||||
SmallString<256> FileName(FrameInfo.FileName);
|
||||
sys::path::remove_dots(FileName, /* remove_dot_dot */ true);
|
||||
FileLoc Loc = {FileName.str(), FrameInfo.Line};
|
||||
Fns[Loc] = FrameInfo.FunctionName;
|
||||
}
|
||||
AddrInfo(const DILineInfo &DI, uint64_t Addr) : DILineInfo(DI), Addr(Addr) {
|
||||
FileName = normalizeFilename(FileName);
|
||||
}
|
||||
|
||||
return Fns;
|
||||
}
|
||||
private:
|
||||
static std::string normalizeFilename(std::string FileName) {
|
||||
SmallString<256> S(FileName);
|
||||
sys::path::remove_dots(S, /* remove_dot_dot */ true);
|
||||
return S.str().str();
|
||||
}
|
||||
};
|
||||
|
||||
// Compute functions for given addresses. It keeps only the first
|
||||
// occurence of a function within a file.
|
||||
std::set<FunctionLoc> computeFunctionLocs(std::string ObjectFile,
|
||||
const std::set<uint64_t> &Addrs) {
|
||||
std::map<FileLoc, std::string> Fns = computeFunctionsMap(ObjectFile, Addrs);
|
||||
class Blacklists {
|
||||
public:
|
||||
Blacklists()
|
||||
: DefaultBlacklist(createDefaultBlacklist()),
|
||||
UserBlacklist(createUserBlacklist()) {}
|
||||
|
||||
std::set<FunctionLoc> Result;
|
||||
std::string LastFileName;
|
||||
std::set<std::string> ProcessedFunctions;
|
||||
bool isBlacklisted(const DILineInfo &DI) {
|
||||
if (DefaultBlacklist && DefaultBlacklist->inSection("fun", DI.FunctionName))
|
||||
return true;
|
||||
if (DefaultBlacklist && DefaultBlacklist->inSection("src", DI.FileName))
|
||||
return true;
|
||||
if (UserBlacklist && UserBlacklist->inSection("fun", DI.FunctionName))
|
||||
return true;
|
||||
if (UserBlacklist && UserBlacklist->inSection("src", DI.FileName))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &P : Fns) {
|
||||
std::string FileName = P.first.FileName;
|
||||
std::string FunctionName = P.second;
|
||||
private:
|
||||
static std::unique_ptr<SpecialCaseList> createDefaultBlacklist() {
|
||||
if (!ClUseDefaultBlacklist)
|
||||
return std::unique_ptr<SpecialCaseList>();
|
||||
std::unique_ptr<MemoryBuffer> MB =
|
||||
MemoryBuffer::getMemBuffer(DefaultBlacklistStr);
|
||||
std::string Error;
|
||||
auto Blacklist = SpecialCaseList::create(MB.get(), Error);
|
||||
FailIfNotEmpty(Error);
|
||||
return Blacklist;
|
||||
}
|
||||
|
||||
if (LastFileName != FileName)
|
||||
ProcessedFunctions.clear();
|
||||
LastFileName = FileName;
|
||||
static std::unique_ptr<SpecialCaseList> createUserBlacklist() {
|
||||
if (ClBlacklist.empty())
|
||||
return std::unique_ptr<SpecialCaseList>();
|
||||
|
||||
if (!ProcessedFunctions.insert(FunctionName).second)
|
||||
return SpecialCaseList::createOrDie({{ClBlacklist}});
|
||||
}
|
||||
std::unique_ptr<SpecialCaseList> DefaultBlacklist;
|
||||
std::unique_ptr<SpecialCaseList> UserBlacklist;
|
||||
};
|
||||
|
||||
// Collect all debug info for given addresses.
|
||||
static std::vector<AddrInfo> getAddrInfo(std::string ObjectFile,
|
||||
const std::set<uint64_t> &Addrs,
|
||||
bool InlinedCode) {
|
||||
std::vector<AddrInfo> Result;
|
||||
auto Symbolizer(createSymbolizer());
|
||||
Blacklists B;
|
||||
|
||||
for (auto Addr : Addrs) {
|
||||
auto LineInfo = Symbolizer->symbolizeCode(ObjectFile, Addr);
|
||||
FailIfError(LineInfo);
|
||||
if (B.isBlacklisted(*LineInfo))
|
||||
continue;
|
||||
|
||||
Result.insert(FunctionLoc{P.first, P.second});
|
||||
Result.push_back(AddrInfo(*LineInfo, Addr));
|
||||
if (InlinedCode) {
|
||||
auto InliningInfo = Symbolizer->symbolizeInlinedCode(ObjectFile, Addr);
|
||||
FailIfError(InliningInfo);
|
||||
for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) {
|
||||
auto FrameInfo = InliningInfo->getFrame(I);
|
||||
if (B.isBlacklisted(FrameInfo))
|
||||
continue;
|
||||
Result.push_back(AddrInfo(FrameInfo, Addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
@ -247,13 +311,11 @@ findSanitizerCovFunctions(const object::ObjectFile &O) {
|
||||
|
||||
if (Name == "__sanitizer_cov" || Name == "__sanitizer_cov_with_check" ||
|
||||
Name == "__sanitizer_cov_trace_func_enter") {
|
||||
Result.insert(AddressOrErr.get());
|
||||
if (!(Symbol.getFlags() & object::BasicSymbolRef::SF_Undefined))
|
||||
Result.insert(AddressOrErr.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (Result.empty())
|
||||
Fail("__sanitizer_cov* functions not found");
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
@ -296,6 +358,8 @@ static void getObjectCoveragePoints(const object::ObjectFile &O,
|
||||
FailIfEmpty(MIA, "no instruction analysis info for target " + TripleName);
|
||||
|
||||
auto SanCovAddrs = findSanitizerCovFunctions(O);
|
||||
if (SanCovAddrs.empty())
|
||||
Fail("__sanitizer_cov* functions not found");
|
||||
|
||||
for (const auto Section : O.sections()) {
|
||||
if (Section.isVirtual() || !Section.isText()) // llvm-objdump does the same.
|
||||
@ -305,9 +369,6 @@ static void getObjectCoveragePoints(const object::ObjectFile &O,
|
||||
if (!SectSize)
|
||||
continue;
|
||||
|
||||
StringRef SectionName;
|
||||
FailIfError(Section.getName(SectionName));
|
||||
|
||||
StringRef BytesStr;
|
||||
FailIfError(Section.getContents(BytesStr));
|
||||
ArrayRef<uint8_t> Bytes(reinterpret_cast<const uint8_t *>(BytesStr.data()),
|
||||
@ -322,94 +383,69 @@ static void getObjectCoveragePoints(const object::ObjectFile &O,
|
||||
Size = 1;
|
||||
continue;
|
||||
}
|
||||
uint64_t Addr = Index + SectionAddr;
|
||||
// Sanitizer coverage uses the address of the next instruction - 1.
|
||||
uint64_t CovPoint = Addr + Size - 1;
|
||||
uint64_t Target;
|
||||
if (MIA->isCall(Inst) &&
|
||||
MIA->evaluateBranch(Inst, SectionAddr + Index, Size, Target)) {
|
||||
if (SanCovAddrs.find(Target) != SanCovAddrs.end()) {
|
||||
// Sanitizer coverage uses the address of the next instruction - 1.
|
||||
Addrs->insert(Index + SectionAddr + Size - 1);
|
||||
}
|
||||
}
|
||||
MIA->evaluateBranch(Inst, SectionAddr + Index, Size, Target) &&
|
||||
SanCovAddrs.find(Target) != SanCovAddrs.end())
|
||||
Addrs->insert(CovPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void getArchiveCoveragePoints(const object::Archive &A,
|
||||
std::set<uint64_t> *Addrs) {
|
||||
static void
|
||||
visitObjectFiles(const object::Archive &A,
|
||||
std::function<void(const object::ObjectFile &)> Fn) {
|
||||
for (auto &ErrorOrChild : A.children()) {
|
||||
FailIfError(ErrorOrChild);
|
||||
const object::Archive::Child &C = *ErrorOrChild;
|
||||
ErrorOr<std::unique_ptr<object::Binary>> ChildOrErr = C.getAsBinary();
|
||||
FailIfError(ChildOrErr);
|
||||
if (object::ObjectFile *O =
|
||||
dyn_cast<object::ObjectFile>(&*ChildOrErr.get()))
|
||||
getObjectCoveragePoints(*O, Addrs);
|
||||
if (auto *O = dyn_cast<object::ObjectFile>(&*ChildOrErr.get()))
|
||||
Fn(*O);
|
||||
else
|
||||
FailIfError(object::object_error::invalid_file_type);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
visitObjectFiles(std::string FileName,
|
||||
std::function<void(const object::ObjectFile &)> Fn) {
|
||||
ErrorOr<object::OwningBinary<object::Binary>> BinaryOrErr =
|
||||
object::createBinary(FileName);
|
||||
FailIfError(BinaryOrErr);
|
||||
|
||||
object::Binary &Binary = *BinaryOrErr.get().getBinary();
|
||||
if (object::Archive *A = dyn_cast<object::Archive>(&Binary))
|
||||
visitObjectFiles(*A, Fn);
|
||||
else if (object::ObjectFile *O = dyn_cast<object::ObjectFile>(&Binary))
|
||||
Fn(*O);
|
||||
else
|
||||
FailIfError(object::object_error::invalid_file_type);
|
||||
}
|
||||
|
||||
std::set<uint64_t> findSanitizerCovFunctions(std::string FileName) {
|
||||
std::set<uint64_t> Result;
|
||||
visitObjectFiles(FileName, [&](const object::ObjectFile &O) {
|
||||
auto Addrs = findSanitizerCovFunctions(O);
|
||||
Result.insert(Addrs.begin(), Addrs.end());
|
||||
});
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Locate addresses of all coverage points in a file. Coverage point
|
||||
// is defined as the 'address of instruction following __sanitizer_cov
|
||||
// call - 1'.
|
||||
std::set<uint64_t> getCoveragePoints(std::string FileName) {
|
||||
std::set<uint64_t> Result;
|
||||
|
||||
ErrorOr<object::OwningBinary<object::Binary>> BinaryOrErr =
|
||||
object::createBinary(FileName);
|
||||
FailIfError(BinaryOrErr);
|
||||
|
||||
object::Binary &Binary = *BinaryOrErr.get().getBinary();
|
||||
if (object::Archive *A = dyn_cast<object::Archive>(&Binary))
|
||||
getArchiveCoveragePoints(*A, &Result);
|
||||
else if (object::ObjectFile *O = dyn_cast<object::ObjectFile>(&Binary))
|
||||
getObjectCoveragePoints(*O, &Result);
|
||||
else
|
||||
FailIfError(object::object_error::invalid_file_type);
|
||||
|
||||
visitObjectFiles(FileName, [&](const object::ObjectFile &O) {
|
||||
getObjectCoveragePoints(O, &Result);
|
||||
});
|
||||
return Result;
|
||||
}
|
||||
|
||||
static std::unique_ptr<SpecialCaseList> createDefaultBlacklist() {
|
||||
if (!ClUseDefaultBlacklist)
|
||||
return std::unique_ptr<SpecialCaseList>();
|
||||
std::unique_ptr<MemoryBuffer> MB =
|
||||
MemoryBuffer::getMemBuffer(DefaultBlacklist);
|
||||
std::string Error;
|
||||
auto Blacklist = SpecialCaseList::create(MB.get(), Error);
|
||||
FailIfNotEmpty(Error);
|
||||
return Blacklist;
|
||||
}
|
||||
|
||||
static std::unique_ptr<SpecialCaseList> createUserBlacklist() {
|
||||
if (ClBlacklist.empty())
|
||||
return std::unique_ptr<SpecialCaseList>();
|
||||
|
||||
return SpecialCaseList::createOrDie({{ClBlacklist}});
|
||||
}
|
||||
|
||||
static void printFunctionLocs(const std::set<FunctionLoc> &FnLocs,
|
||||
raw_ostream &OS) {
|
||||
std::unique_ptr<SpecialCaseList> DefaultBlacklist = createDefaultBlacklist();
|
||||
std::unique_ptr<SpecialCaseList> UserBlacklist = createUserBlacklist();
|
||||
|
||||
for (const FunctionLoc &FnLoc : FnLocs) {
|
||||
if (DefaultBlacklist &&
|
||||
DefaultBlacklist->inSection("fun", FnLoc.FunctionName))
|
||||
continue;
|
||||
if (DefaultBlacklist &&
|
||||
DefaultBlacklist->inSection("src", FnLoc.Loc.FileName))
|
||||
continue;
|
||||
if (UserBlacklist && UserBlacklist->inSection("fun", FnLoc.FunctionName))
|
||||
continue;
|
||||
if (UserBlacklist && UserBlacklist->inSection("src", FnLoc.Loc.FileName))
|
||||
continue;
|
||||
|
||||
OS << stripPathPrefix(FnLoc.Loc.FileName) << ":" << FnLoc.Loc.Line << " "
|
||||
<< FnLoc.FunctionName << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string escapeHtml(const std::string &S) {
|
||||
std::string Result;
|
||||
Result.reserve(S.size());
|
||||
@ -438,28 +474,6 @@ static std::string escapeHtml(const std::string &S) {
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Computes a map file_name->{line_number}
|
||||
static std::map<std::string, std::set<int>>
|
||||
getFileLines(std::string ObjectFile, const std::set<uint64_t> &Addrs) {
|
||||
std::map<std::string, std::set<int>> FileLines;
|
||||
|
||||
auto Symbolizer(createSymbolizer());
|
||||
|
||||
// Fill in FileLines map.
|
||||
for (auto Addr : Addrs) {
|
||||
auto InliningInfo = Symbolizer->symbolizeInlinedCode(ObjectFile, Addr);
|
||||
FailIfError(InliningInfo);
|
||||
for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) {
|
||||
auto FrameInfo = InliningInfo->getFrame(I);
|
||||
SmallString<256> FileName(FrameInfo.FileName);
|
||||
sys::path::remove_dots(FileName, /* remove_dot_dot */ true);
|
||||
FileLines[FileName.str()].insert(FrameInfo.Line);
|
||||
}
|
||||
}
|
||||
|
||||
return FileLines;
|
||||
}
|
||||
|
||||
static ErrorOr<bool> isCoverageFile(std::string FileName) {
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
|
||||
MemoryBuffer::getFile(FileName);
|
||||
@ -547,83 +561,193 @@ class CoverageData {
|
||||
}
|
||||
}
|
||||
|
||||
void printReport(std::string ObjectFile, raw_ostream &OS) {
|
||||
// file_name -> set of covered lines;
|
||||
std::map<std::string, std::set<int>> CoveredFileLines =
|
||||
getFileLines(ObjectFile, *Addrs);
|
||||
std::map<std::string, std::set<int>> CoveragePoints =
|
||||
getFileLines(ObjectFile, getCoveragePoints(ObjectFile));
|
||||
|
||||
// TOC
|
||||
OS << "<ul>\n";
|
||||
for (auto It : CoveredFileLines) {
|
||||
auto FileName = It.first;
|
||||
OS << "<li><a href=\"#" << escapeHtml(FileName) << "\">"
|
||||
<< stripPathPrefix(FileName) << "</a></li>\n";
|
||||
}
|
||||
OS << "</ul>\n";
|
||||
|
||||
// Source
|
||||
for (auto It : CoveredFileLines) {
|
||||
auto FileName = It.first;
|
||||
auto Lines = It.second;
|
||||
auto CovLines = CoveragePoints[FileName];
|
||||
|
||||
OS << "<a name=\"" << escapeHtml(FileName) << "\"></a>\n";
|
||||
OS << "<h2>" << stripPathPrefix(FileName) << "</h2>\n";
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
|
||||
MemoryBuffer::getFile(FileName);
|
||||
if (!BufOrErr) {
|
||||
OS << "Error reading file: " << FileName << " : "
|
||||
<< BufOrErr.getError().message() << "("
|
||||
<< BufOrErr.getError().value() << ")\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
OS << "<pre>\n";
|
||||
for (line_iterator I = line_iterator(*BufOrErr.get(), false);
|
||||
!I.is_at_eof(); ++I) {
|
||||
OS << "<span ";
|
||||
if (Lines.find(I.line_number()) != Lines.end())
|
||||
OS << "class=covered";
|
||||
else if (CovLines.find(I.line_number()) != CovLines.end())
|
||||
OS << "class=notcovered";
|
||||
OS << ">";
|
||||
OS << escapeHtml(*I) << "</span>\n";
|
||||
}
|
||||
OS << "</pre>\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Print list of covered functions.
|
||||
// Line format: <file_name>:<line> <function_name>
|
||||
void printCoveredFunctions(std::string ObjectFile, raw_ostream &OS) {
|
||||
printFunctionLocs(computeFunctionLocs(ObjectFile, *Addrs), OS);
|
||||
}
|
||||
|
||||
// Print list of not covered functions.
|
||||
// Line format: <file_name>:<line> <function_name>
|
||||
void printNotCoveredFunctions(std::string ObjectFile, raw_ostream &OS) {
|
||||
std::set<FunctionLoc> AllFns =
|
||||
computeFunctionLocs(ObjectFile, getCoveragePoints(ObjectFile));
|
||||
std::set<FunctionLoc> CoveredFns = computeFunctionLocs(ObjectFile, *Addrs);
|
||||
|
||||
std::set<FunctionLoc> NotCoveredFns;
|
||||
std::set_difference(AllFns.begin(), AllFns.end(), CoveredFns.begin(),
|
||||
CoveredFns.end(),
|
||||
std::inserter(NotCoveredFns, NotCoveredFns.end()));
|
||||
printFunctionLocs(NotCoveredFns, OS);
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
explicit CoverageData(std::unique_ptr<std::set<uint64_t>> Addrs)
|
||||
: Addrs(std::move(Addrs)) {}
|
||||
|
||||
friend class CoverageDataWithObjectFile;
|
||||
|
||||
std::unique_ptr<std::set<uint64_t>> Addrs;
|
||||
};
|
||||
|
||||
// Coverage data translated into source code line-level information.
|
||||
// Fetches debug info in constructor and calculates various information per
|
||||
// request.
|
||||
class SourceCoverageData {
|
||||
public:
|
||||
enum LineStatus {
|
||||
// coverage information for the line is not available.
|
||||
// default value in maps.
|
||||
UNKNOWN = 0,
|
||||
// the line is fully covered.
|
||||
COVERED = 1,
|
||||
// the line is fully uncovered.
|
||||
NOT_COVERED = 2,
|
||||
// some points in the line a covered, some are not.
|
||||
MIXED = 3
|
||||
};
|
||||
|
||||
SourceCoverageData(std::string ObjectFile, const std::set<uint64_t> &Addrs) {
|
||||
std::set<uint64_t> AllCovPoints = getCoveragePoints(ObjectFile);
|
||||
|
||||
if (!std::includes(AllCovPoints.begin(), AllCovPoints.end(), Addrs.begin(),
|
||||
Addrs.end())) {
|
||||
Fail("Coverage points in binary and .sancov file do not match.");
|
||||
}
|
||||
|
||||
AllAddrInfo = getAddrInfo(ObjectFile, AllCovPoints, true);
|
||||
CovAddrInfo = getAddrInfo(ObjectFile, Addrs, true);
|
||||
}
|
||||
|
||||
// Compute number of functions hit/total in a file.
|
||||
// file_name -> <fn_coverage, all_fn_coverage>
|
||||
std::map<std::string, std::pair<size_t, size_t>> computeFileFnCoverage() {
|
||||
std::map<std::string, std::pair<size_t, size_t>> FileCoverage;
|
||||
auto AllCovPointsByFile =
|
||||
group_by(AllAddrInfo, [](const AddrInfo &AI) { return AI.FileName; });
|
||||
auto CovPointByFile =
|
||||
group_by(CovAddrInfo, [](const AddrInfo &AI) { return AI.FileName; });
|
||||
|
||||
for (auto P : AllCovPointsByFile) {
|
||||
const std::string &FileName = P.first;
|
||||
const auto &AllCovInfo = P.second;
|
||||
|
||||
auto AllFns = group_by(
|
||||
AllCovInfo, [](const AddrInfo &AI) { return AI.FunctionName; });
|
||||
size_t AllCoverage = AllFns.size();
|
||||
size_t Coverage = 0;
|
||||
|
||||
auto It = CovPointByFile.find(FileName);
|
||||
if (It != CovPointByFile.end()) {
|
||||
const auto &CovInfo = It->second;
|
||||
auto Fns = group_by(CovInfo,
|
||||
[](const AddrInfo &AI) { return AI.FunctionName; });
|
||||
Coverage = Fns.size();
|
||||
}
|
||||
FileCoverage[FileName] = std::make_pair(Coverage, AllCoverage);
|
||||
}
|
||||
return FileCoverage;
|
||||
}
|
||||
|
||||
// line_number -> line_status.
|
||||
typedef std::map<int, LineStatus> LineStatusMap;
|
||||
// file_name -> LineStatusMap
|
||||
typedef std::map<std::string, LineStatusMap> FileLineStatusMap;
|
||||
|
||||
// fills in the {file_name -> {line_no -> status}} map.
|
||||
FileLineStatusMap computeLineStatusMap() {
|
||||
FileLineStatusMap StatusMap;
|
||||
|
||||
auto AllLocs = group_by(AllAddrInfo, [](const AddrInfo &AI) {
|
||||
return FileLoc{AI.FileName, AI.Line};
|
||||
});
|
||||
auto CovLocs = group_by(CovAddrInfo, [](const AddrInfo &AI) {
|
||||
return FileLoc{AI.FileName, AI.Line};
|
||||
});
|
||||
|
||||
for (const auto &P : AllLocs) {
|
||||
const FileLoc &Loc = P.first;
|
||||
auto I = CovLocs.find(Loc);
|
||||
|
||||
if (I == CovLocs.end()) {
|
||||
StatusMap[Loc.FileName][Loc.Line] = NOT_COVERED;
|
||||
} else {
|
||||
StatusMap[Loc.FileName][Loc.Line] =
|
||||
(I->second.size() == P.second.size()) ? COVERED : MIXED;
|
||||
}
|
||||
}
|
||||
return StatusMap;
|
||||
}
|
||||
|
||||
std::set<FileFn> computeCoveredFunctions() const {
|
||||
std::set<FileFn> Fns;
|
||||
auto CovFns = group_by(CovAddrInfo, [](const AddrInfo &AI) {
|
||||
return FileFn{AI.FileName, AI.FunctionName};
|
||||
});
|
||||
|
||||
for (const auto &P : CovFns) {
|
||||
Fns.insert(P.first);
|
||||
}
|
||||
return Fns;
|
||||
}
|
||||
|
||||
std::set<FileFn> computeNotCoveredFunctions() const {
|
||||
std::set<FileFn> Fns;
|
||||
|
||||
auto AllFns = group_by(AllAddrInfo, [](const AddrInfo &AI) {
|
||||
return FileFn{AI.FileName, AI.FunctionName};
|
||||
});
|
||||
auto CovFns = group_by(CovAddrInfo, [](const AddrInfo &AI) {
|
||||
return FileFn{AI.FileName, AI.FunctionName};
|
||||
});
|
||||
|
||||
for (const auto &P : AllFns) {
|
||||
if (CovFns.find(P.first) == CovFns.end()) {
|
||||
Fns.insert(P.first);
|
||||
}
|
||||
}
|
||||
return Fns;
|
||||
}
|
||||
|
||||
typedef std::map<FileLoc, std::set<std::string>> FunctionLocs;
|
||||
// finds first line number in a file for each function.
|
||||
FunctionLocs resolveFunctions(const std::set<FileFn> &Fns) const {
|
||||
std::vector<AddrInfo> FnAddrs;
|
||||
for (const auto &AI : AllAddrInfo) {
|
||||
if (Fns.find(FileFn{AI.FileName, AI.FunctionName}) != Fns.end())
|
||||
FnAddrs.push_back(AI);
|
||||
}
|
||||
|
||||
auto GroupedAddrs = group_by(FnAddrs, [](const AddrInfo &AI) {
|
||||
return FnLoc{FileLoc{AI.FileName, AI.Line}, AI.FunctionName};
|
||||
});
|
||||
|
||||
FunctionLocs Result;
|
||||
std::string LastFileName;
|
||||
std::set<std::string> ProcessedFunctions;
|
||||
|
||||
for (const auto &P : GroupedAddrs) {
|
||||
const FnLoc &Loc = P.first;
|
||||
std::string FileName = Loc.Loc.FileName;
|
||||
std::string FunctionName = Loc.FunctionName;
|
||||
|
||||
if (LastFileName != FileName)
|
||||
ProcessedFunctions.clear();
|
||||
LastFileName = FileName;
|
||||
|
||||
if (!ProcessedFunctions.insert(FunctionName).second)
|
||||
continue;
|
||||
|
||||
Result[FileLoc{FileName, Loc.Loc.Line}].insert(FunctionName);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::set<std::string> files() const {
|
||||
std::set<std::string> Files;
|
||||
for (const auto &AI : AllAddrInfo) {
|
||||
Files.insert(AI.FileName);
|
||||
}
|
||||
return Files;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<AddrInfo> AllAddrInfo;
|
||||
std::vector<AddrInfo> CovAddrInfo;
|
||||
};
|
||||
|
||||
static void printFunctionLocs(const SourceCoverageData::FunctionLocs &FnLocs,
|
||||
raw_ostream &OS) {
|
||||
for (const auto &Fns : FnLocs) {
|
||||
for (const auto &Fn : Fns.second) {
|
||||
OS << stripPathPrefix(Fns.first.FileName) << ":" << Fns.first.Line << " "
|
||||
<< Fn << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Holder for coverage data + filename of corresponding object file.
|
||||
class CoverageDataWithObjectFile {
|
||||
class CoverageDataWithObjectFile : public CoverageData {
|
||||
public:
|
||||
static ErrorOr<std::unique_ptr<CoverageDataWithObjectFile>>
|
||||
readAndMerge(std::string ObjectFile,
|
||||
@ -638,25 +762,145 @@ public:
|
||||
|
||||
std::string object_file() const { return ObjectFile; }
|
||||
|
||||
// Print list of covered functions.
|
||||
// Line format: <file_name>:<line> <function_name>
|
||||
void printCoveredFunctions(raw_ostream &OS) const {
|
||||
Coverage->printCoveredFunctions(ObjectFile, OS);
|
||||
SourceCoverageData SCovData(ObjectFile, *Addrs);
|
||||
auto CoveredFns = SCovData.computeCoveredFunctions();
|
||||
printFunctionLocs(SCovData.resolveFunctions(CoveredFns), OS);
|
||||
}
|
||||
|
||||
// Print list of not covered functions.
|
||||
// Line format: <file_name>:<line> <function_name>
|
||||
void printNotCoveredFunctions(raw_ostream &OS) const {
|
||||
Coverage->printNotCoveredFunctions(ObjectFile, OS);
|
||||
SourceCoverageData SCovData(ObjectFile, *Addrs);
|
||||
auto NotCoveredFns = SCovData.computeNotCoveredFunctions();
|
||||
printFunctionLocs(SCovData.resolveFunctions(NotCoveredFns), OS);
|
||||
}
|
||||
|
||||
void printReport(raw_ostream &OS) const {
|
||||
Coverage->printReport(ObjectFile, OS);
|
||||
SourceCoverageData SCovData(ObjectFile, *Addrs);
|
||||
auto LineStatusMap = SCovData.computeLineStatusMap();
|
||||
|
||||
// file_name -> [file_fn].
|
||||
auto NotCoveredFns = SCovData.computeNotCoveredFunctions();
|
||||
auto NotCoveredFnMap = group_by(
|
||||
NotCoveredFns, [](const FileFn &FileFn) { return FileFn.FileName; });
|
||||
// file_loc -> set[function_name]
|
||||
auto NotCoveredFnByLoc = SCovData.resolveFunctions(NotCoveredFns);
|
||||
auto FileFnCoverage = SCovData.computeFileFnCoverage();
|
||||
|
||||
// TOC
|
||||
|
||||
// Covered Files.
|
||||
OS << "<details open><summary>Covered Files</summary>\n";
|
||||
OS << "<table>\n";
|
||||
OS << "<tr><th>File</th><th>Hit Fns %</th>";
|
||||
OS << "<th>Hit (Total) Fns</th></tr>\n";
|
||||
for (auto FileName : SCovData.files()) {
|
||||
std::pair<size_t, size_t> FC = FileFnCoverage[FileName];
|
||||
if (FC.first == 0)
|
||||
continue;
|
||||
size_t CovPct = FC.second == 0 ? 100 : 100 * FC.first / FC.second;
|
||||
|
||||
OS << "<tr><td><a href=\"#" << escapeHtml(FileName) << "\">"
|
||||
<< stripPathPrefix(FileName) << "</a></td>"
|
||||
<< "<td>" << CovPct << "%</td>"
|
||||
<< "<td>" << FC.first << " (" << FC.second << ")"
|
||||
<< "</tr>\n";
|
||||
}
|
||||
OS << "</table>\n";
|
||||
OS << "</details>\n";
|
||||
|
||||
// Not covered files.
|
||||
OS << "<details><summary>Not Covered Files</summary>\n";
|
||||
OS << "<table>\n";
|
||||
for (auto FileName : SCovData.files()) {
|
||||
std::pair<size_t, size_t> FC = FileFnCoverage[FileName];
|
||||
if (FC.first == 0)
|
||||
OS << "<tr><td>" << stripPathPrefix(FileName) << "</td>\n";
|
||||
}
|
||||
OS << "</table>\n";
|
||||
OS << "</details>\n";
|
||||
|
||||
// Source
|
||||
for (auto FileName : SCovData.files()) {
|
||||
std::pair<size_t, size_t> FC = FileFnCoverage[FileName];
|
||||
if (FC.first == 0)
|
||||
continue;
|
||||
OS << "<a name=\"" << escapeHtml(FileName) << "\"></a>\n";
|
||||
OS << "<h2>" << stripPathPrefix(FileName) << "</h2>\n";
|
||||
|
||||
auto NotCoveredFns = NotCoveredFnMap.find(FileName);
|
||||
if (NotCoveredFns != NotCoveredFnMap.end()) {
|
||||
OS << "<details open><summary>Not Covered Functions</summary>";
|
||||
OS << "<table>\n";
|
||||
for (auto FileFn : NotCoveredFns->second) {
|
||||
OS << "<tr><td>";
|
||||
OS << "<a href=\"#"
|
||||
<< escapeHtml(FileName + ":: " + FileFn.FunctionName) << "\">";
|
||||
OS << escapeHtml(FileFn.FunctionName) << "</a>";
|
||||
OS << "</td></tr>\n";
|
||||
}
|
||||
OS << "</table></details>\n";
|
||||
}
|
||||
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
|
||||
MemoryBuffer::getFile(FileName);
|
||||
if (!BufOrErr) {
|
||||
OS << "Error reading file: " << FileName << " : "
|
||||
<< BufOrErr.getError().message() << "("
|
||||
<< BufOrErr.getError().value() << ")\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
OS << "<pre>\n";
|
||||
const auto &LineStatuses = LineStatusMap[FileName];
|
||||
for (line_iterator I = line_iterator(*BufOrErr.get(), false);
|
||||
!I.is_at_eof(); ++I) {
|
||||
uint32_t Line = I.line_number();
|
||||
{ // generate anchors (if any);
|
||||
FileLoc Loc = FileLoc{FileName, Line};
|
||||
auto It = NotCoveredFnByLoc.find(Loc);
|
||||
if (It != NotCoveredFnByLoc.end()) {
|
||||
for (std::string Fn : It->second) {
|
||||
OS << "<a name=\"" << escapeHtml(FileName + ":: " + Fn)
|
||||
<< "\"></a>";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
OS << "<span ";
|
||||
auto LIT = LineStatuses.find(I.line_number());
|
||||
auto Status = (LIT != LineStatuses.end()) ? LIT->second
|
||||
: SourceCoverageData::UNKNOWN;
|
||||
switch (Status) {
|
||||
case SourceCoverageData::UNKNOWN:
|
||||
OS << "class=unknown";
|
||||
break;
|
||||
case SourceCoverageData::COVERED:
|
||||
OS << "class=covered";
|
||||
break;
|
||||
case SourceCoverageData::NOT_COVERED:
|
||||
OS << "class=notcovered";
|
||||
break;
|
||||
case SourceCoverageData::MIXED:
|
||||
OS << "class=mixed";
|
||||
break;
|
||||
}
|
||||
OS << ">";
|
||||
OS << escapeHtml(*I) << "</span>\n";
|
||||
}
|
||||
OS << "</pre>\n";
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CoverageDataWithObjectFile(std::string ObjectFile,
|
||||
std::unique_ptr<CoverageData> Coverage)
|
||||
: ObjectFile(std::move(ObjectFile)), Coverage(std::move(Coverage)) {}
|
||||
|
||||
: CoverageData(std::move(Coverage->Addrs)),
|
||||
ObjectFile(std::move(ObjectFile)) {}
|
||||
const std::string ObjectFile;
|
||||
const std::unique_ptr<CoverageData> Coverage;
|
||||
};
|
||||
|
||||
// Multiple coverage files data organized by object file.
|
||||
@ -687,13 +931,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Object file => list of corresponding coverage files.
|
||||
std::map<std::string, std::vector<std::string>> CoverageByObjFile;
|
||||
Regex SancovRegex("(.*)\\.[0-9]+\\.sancov");
|
||||
SmallVector<StringRef, 2> Components;
|
||||
|
||||
// Group coverage files by object file.
|
||||
for (const auto &FileName : CovFiles) {
|
||||
// Object file => list of corresponding coverage file names.
|
||||
auto CoverageByObjFile = group_by(CovFiles, [&](std::string FileName) {
|
||||
auto ShortFileName = llvm::sys::path::filename(FileName);
|
||||
auto Ok = SancovRegex.match(ShortFileName, &Components);
|
||||
if (!Ok) {
|
||||
@ -706,13 +948,24 @@ public:
|
||||
if (Iter == ObjFiles.end()) {
|
||||
Fail("Object file for coverage not found: " + FileName);
|
||||
}
|
||||
auto ObjectFile = Iter->second;
|
||||
CoverageByObjFile[ObjectFile].push_back(FileName);
|
||||
}
|
||||
return Iter->second;
|
||||
});
|
||||
|
||||
// Read coverage.
|
||||
std::vector<std::unique_ptr<CoverageDataWithObjectFile>> MergedCoverage;
|
||||
for (const auto &Pair : CoverageByObjFile) {
|
||||
if (findSanitizerCovFunctions(Pair.first).empty()) {
|
||||
for (auto FileName : Pair.second) {
|
||||
CovFiles.erase(FileName);
|
||||
}
|
||||
|
||||
errs()
|
||||
<< "Ignoring " << Pair.first
|
||||
<< " and its coverage because __sanitizer_cov* functions were not "
|
||||
"found.\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto DataOrError =
|
||||
CoverageDataWithObjectFile::readAndMerge(Pair.first, Pair.second);
|
||||
FailIfError(DataOrError);
|
||||
@ -745,6 +998,8 @@ public:
|
||||
OS << "<style>\n";
|
||||
OS << ".covered { background: #7F7; }\n";
|
||||
OS << ".notcovered { background: #F77; }\n";
|
||||
OS << "summary { font-weight: bold; }\n";
|
||||
OS << "details > summary + * { margin-left: 1em; }\n";
|
||||
OS << "</style>\n";
|
||||
OS << "<title>" << Title << "</title>\n";
|
||||
OS << "</head>\n";
|
||||
@ -752,14 +1007,6 @@ public:
|
||||
|
||||
// Title
|
||||
OS << "<h1>" << Title << "</h1>\n";
|
||||
OS << "<p>Coverage files: ";
|
||||
for (auto InputFile : CoverageFiles) {
|
||||
llvm::sys::fs::file_status Status;
|
||||
llvm::sys::fs::status(InputFile, Status);
|
||||
OS << stripPathPrefix(InputFile) << " ("
|
||||
<< Status.getLastModificationTime().str() << ") ";
|
||||
}
|
||||
OS << "</p>\n";
|
||||
|
||||
// Modules TOC.
|
||||
if (Coverage.size() > 1) {
|
||||
@ -780,6 +1027,17 @@ public:
|
||||
CovData->printReport(OS);
|
||||
}
|
||||
|
||||
// About
|
||||
OS << "<details><summary>About</summary>\n";
|
||||
OS << "Coverage files:<ul>";
|
||||
for (auto InputFile : CoverageFiles) {
|
||||
llvm::sys::fs::file_status Status;
|
||||
llvm::sys::fs::status(InputFile, Status);
|
||||
OS << "<li>" << stripPathPrefix(InputFile) << " ("
|
||||
<< Status.getLastModificationTime().str() << ")</li>\n";
|
||||
}
|
||||
OS << "</ul></details>\n";
|
||||
|
||||
OS << "</body>\n";
|
||||
OS << "</html>\n";
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user