diff options
Diffstat (limited to 'tools/sancov/sancov.cc')
-rw-r--r-- | tools/sancov/sancov.cc | 1025 |
1 files changed, 875 insertions, 150 deletions
diff --git a/tools/sancov/sancov.cc b/tools/sancov/sancov.cc index a07cdbe097a3b..55b03709dde34 100644 --- a/tools/sancov/sancov.cc +++ b/tools/sancov/sancov.cc @@ -11,10 +11,11 @@ // coverage. //===----------------------------------------------------------------------===// #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Twine.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCContext.h" -#include "llvm/MC/MCDisassembler.h" +#include "llvm/MC/MCDisassembler/MCDisassembler.h" #include "llvm/MC/MCInst.h" #include "llvm/MC/MCInstPrinter.h" #include "llvm/MC/MCInstrAnalysis.h" @@ -24,16 +25,20 @@ #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" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LineIterator.h" +#include "llvm/Support/MD5.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/Regex.h" #include "llvm/Support/Signals.h" #include "llvm/Support/SpecialCaseList.h" #include "llvm/Support/TargetRegistry.h" @@ -41,9 +46,11 @@ #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/raw_ostream.h" +#include <algorithm> #include <set> #include <stdio.h> #include <string> +#include <utility> #include <vector> using namespace llvm; @@ -54,29 +61,34 @@ namespace { enum ActionType { PrintAction, + PrintCovPointsAction, CoveredFunctionsAction, - NotCoveredFunctionsAction + NotCoveredFunctionsAction, + HtmlReportAction, + StatsAction }; cl::opt<ActionType> Action( cl::desc("Action (required)"), cl::Required, cl::values(clEnumValN(PrintAction, "print", "Print coverage addresses"), + clEnumValN(PrintCovPointsAction, "print-coverage-pcs", + "Print coverage instrumentation points addresses."), clEnumValN(CoveredFunctionsAction, "covered-functions", "Print all covered funcions."), clEnumValN(NotCoveredFunctionsAction, "not-covered-functions", "Print all not covered funcions."), + clEnumValN(HtmlReportAction, "html-report", + "Print HTML coverage report."), + clEnumValN(StatsAction, "print-coverage-stats", + "Print coverage statistics."), clEnumValEnd)); -static cl::list<std::string> ClInputFiles(cl::Positional, cl::OneOrMore, - cl::desc("<filenames...>")); +static cl::list<std::string> + ClInputFiles(cl::Positional, cl::OneOrMore, + cl::desc("(<binary file>|<.sancov file>)...")); -static cl::opt<std::string> - ClBinaryName("obj", cl::Required, - cl::desc("Path to object file to be symbolized")); - -static cl::opt<bool> - ClDemangle("demangle", cl::init(true), - cl::desc("Print demangled function name.")); +static cl::opt<bool> ClDemangle("demangle", cl::init(true), + cl::desc("Print demangled function name.")); static cl::opt<std::string> ClStripPathPrefix( "strip_path_prefix", cl::init(""), @@ -90,7 +102,9 @@ 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_.*\n" + "src:/usr/include/.*\n" + "src:.*/libc\\+\\+/.*\n"; // --------- FORMAT SPECIFICATION --------- @@ -103,7 +117,12 @@ 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"; + exit(1); +} static void FailIfError(std::error_code Error) { if (!Error) @@ -116,11 +135,21 @@ template <typename T> static void FailIfError(const ErrorOr<T> &E) { FailIfError(E.getError()); } -static void FailIfNotEmpty(const std::string &E) { - if (E.empty()) +static void FailIfError(Error Err) { + if (Err) { + logAllUnhandledErrors(std::move(Err), errs(), "Error: "); + exit(1); + } +} + +template <typename T> static void FailIfError(Expected<T> &E) { + FailIfError(E.takeError()); +} + +static void FailIfNotEmpty(const llvm::Twine &E) { + if (E.str().empty()) return; - errs() << "Error: " << E << "\n"; - exit(1); + Fail(E); } template <typename T> @@ -128,8 +157,24 @@ static void FailIfEmpty(const std::unique_ptr<T> &Ptr, const std::string &Message) { if (Ptr.get()) return; - errs() << "Error: " << Message << "\n"; - exit(1); + 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> @@ -149,8 +194,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); } @@ -167,53 +222,98 @@ std::string stripPathPrefix(std::string Path) { return Path.substr(Pos + ClStripPathPrefix.size()); } -// Compute [FileLoc -> FunctionName] map for given addresses. -static std::map<FileLoc, std::string> -computeFunctionsMap(const std::set<uint64_t> &Addrs) { - std::map<FileLoc, std::string> Fns; - +static std::unique_ptr<symbolize::LLVMSymbolizer> createSymbolizer() { symbolize::LLVMSymbolizer::Options SymbolizerOptions; SymbolizerOptions.Demangle = ClDemangle; SymbolizerOptions.UseSymbolTable = true; - symbolize::LLVMSymbolizer Symbolizer(SymbolizerOptions); + return std::unique_ptr<symbolize::LLVMSymbolizer>( + new symbolize::LLVMSymbolizer(SymbolizerOptions)); +} - // Fill in Fns map. - for (auto Addr : Addrs) { - auto InliningInfo = Symbolizer.symbolizeInlinedCode(ClBinaryName, 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; - } +// A DILineInfo with address. +struct AddrInfo : public DILineInfo { + uint64_t Addr; + + AddrInfo(const DILineInfo &DI, uint64_t Addr) : DILineInfo(DI), Addr(Addr) { + FileName = normalizeFilename(FileName); } - return Fns; -} +private: + static std::string normalizeFilename(const 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(const std::set<uint64_t> &Addrs) { - std::map<FileLoc, std::string> Fns = computeFunctionsMap(Addrs); +class Blacklists { +public: + Blacklists() + : DefaultBlacklist(createDefaultBlacklist()), + UserBlacklist(createUserBlacklist()) {} + + // AddrInfo contains normalized filename. It is important to check it rather + // than DILineInfo. + bool isBlacklisted(const AddrInfo &AI) { + if (DefaultBlacklist && DefaultBlacklist->inSection("fun", AI.FunctionName)) + return true; + if (DefaultBlacklist && DefaultBlacklist->inSection("src", AI.FileName)) + return true; + if (UserBlacklist && UserBlacklist->inSection("fun", AI.FunctionName)) + return true; + if (UserBlacklist && UserBlacklist->inSection("src", AI.FileName)) + return true; + return false; + } - std::set<FunctionLoc> Result; - std::string LastFileName; - std::set<std::string> ProcessedFunctions; +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; + } - for (const auto &P : Fns) { - std::string FileName = P.first.FileName; - std::string FunctionName = P.second; + static std::unique_ptr<SpecialCaseList> createUserBlacklist() { + if (ClBlacklist.empty()) + return std::unique_ptr<SpecialCaseList>(); - if (LastFileName != FileName) - ProcessedFunctions.clear(); - LastFileName = FileName; + return SpecialCaseList::createOrDie({{ClBlacklist}}); + } + std::unique_ptr<SpecialCaseList> DefaultBlacklist; + std::unique_ptr<SpecialCaseList> UserBlacklist; +}; - if (!ProcessedFunctions.insert(FunctionName).second) - continue; +// Collect all debug info for given addresses. +static std::vector<AddrInfo> getAddrInfo(const std::string &ObjectFile, + const std::set<uint64_t> &Addrs, + bool InlinedCode) { + std::vector<AddrInfo> Result; + auto Symbolizer(createSymbolizer()); + Blacklists B; - Result.insert(FunctionLoc{P.first, P.second}); + for (auto Addr : Addrs) { + auto LineInfo = Symbolizer->symbolizeCode(ObjectFile, Addr); + FailIfError(LineInfo); + auto LineAddrInfo = AddrInfo(*LineInfo, Addr); + if (B.isBlacklisted(LineAddrInfo)) + continue; + Result.push_back(LineAddrInfo); + if (InlinedCode) { + auto InliningInfo = Symbolizer->symbolizeInlinedCode(ObjectFile, Addr); + FailIfError(InliningInfo); + for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) { + auto FrameInfo = InliningInfo->getFrame(I); + auto FrameAddrInfo = AddrInfo(FrameInfo, Addr); + if (B.isBlacklisted(FrameAddrInfo)) + continue; + Result.push_back(FrameAddrInfo); + } + } } return Result; @@ -226,22 +326,20 @@ findSanitizerCovFunctions(const object::ObjectFile &O) { std::set<uint64_t> Result; for (const object::SymbolRef &Symbol : O.symbols()) { - ErrorOr<uint64_t> AddressOrErr = Symbol.getAddress(); - FailIfError(AddressOrErr); + Expected<uint64_t> AddressOrErr = Symbol.getAddress(); + FailIfError(errorToErrorCode(AddressOrErr.takeError())); - ErrorOr<StringRef> NameOrErr = Symbol.getName(); - FailIfError(NameOrErr); + Expected<StringRef> NameOrErr = Symbol.getName(); + FailIfError(errorToErrorCode(NameOrErr.takeError())); StringRef Name = NameOrErr.get(); 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()) - FailIfNotEmpty("__sanitizer_cov* functions not found"); - return Result; } @@ -284,8 +382,10 @@ 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()) { + for (object::SectionRef Section : O.sections()) { if (Section.isVirtual() || !Section.isText()) // llvm-objdump does the same. continue; uint64_t SectionAddr = Section.getAddress(); @@ -293,9 +393,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()), @@ -310,98 +407,170 @@ 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) { - 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); +static void +visitObjectFiles(const object::Archive &A, + function_ref<void(const object::ObjectFile &)> Fn) { + Error Err; + for (auto &C : A.children(Err)) { + Expected<std::unique_ptr<object::Binary>> ChildOrErr = C.getAsBinary(); + FailIfError(errorToErrorCode(ChildOrErr.takeError())); + if (auto *O = dyn_cast<object::ObjectFile>(&*ChildOrErr.get())) + Fn(*O); else FailIfError(object::object_error::invalid_file_type); } + FailIfError(std::move(Err)); } -// 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 = +static void +visitObjectFiles(const std::string &FileName, + function_ref<void(const object::ObjectFile &)> Fn) { + Expected<object::OwningBinary<object::Binary>> BinaryOrErr = object::createBinary(FileName); - FailIfError(BinaryOrErr); + if (!BinaryOrErr) + FailIfError(errorToErrorCode(BinaryOrErr.takeError())); object::Binary &Binary = *BinaryOrErr.get().getBinary(); if (object::Archive *A = dyn_cast<object::Archive>(&Binary)) - getArchiveCoveragePoints(*A, &Result); + visitObjectFiles(*A, Fn); else if (object::ObjectFile *O = dyn_cast<object::ObjectFile>(&Binary)) - getObjectCoveragePoints(*O, &Result); + Fn(*O); else FailIfError(object::object_error::invalid_file_type); +} +std::set<uint64_t> findSanitizerCovFunctions(const 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; } -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; +// 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(const std::string &FileName) { + std::set<uint64_t> Result; + visitObjectFiles(FileName, [&](const object::ObjectFile &O) { + getObjectCoveragePoints(O, &Result); + }); + return Result; +} + +static void printCovPoints(const std::string &ObjFile, raw_ostream &OS) { + for (uint64_t Addr : getCoveragePoints(ObjFile)) { + OS << "0x"; + OS.write_hex(Addr); + OS << "\n"; + } +} + +static std::string escapeHtml(const std::string &S) { + std::string Result; + Result.reserve(S.size()); + for (char Ch : S) { + switch (Ch) { + case '&': + Result.append("&"); + break; + case '\'': + Result.append("'"); + break; + case '"': + Result.append("""); + break; + case '<': + Result.append("<"); + break; + case '>': + Result.append(">"); + break; + default: + Result.push_back(Ch); + break; + } + } + return Result; } -static std::unique_ptr<SpecialCaseList> createUserBlacklist() { - if (ClBlacklist.empty()) - return std::unique_ptr<SpecialCaseList>(); +// Adds leading zeroes wrapped in 'lz' style. +// Leading zeroes help locate 000% coverage. +static std::string formatHtmlPct(size_t Pct) { + Pct = std::max(std::size_t{0}, std::min(std::size_t{100}, Pct)); - return SpecialCaseList::createOrDie({{ClBlacklist}}); + std::string Num = std::to_string(Pct); + std::string Zeroes(3 - Num.size(), '0'); + if (!Zeroes.empty()) + Zeroes = "<span class='lz'>" + Zeroes + "</span>"; + + return Zeroes + Num; } -static void printFunctionLocs(const std::set<FunctionLoc> &FnLocs, - raw_ostream &OS) { - std::unique_ptr<SpecialCaseList> DefaultBlacklist = createDefaultBlacklist(); - std::unique_ptr<SpecialCaseList> UserBlacklist = createUserBlacklist(); +static std::string anchorName(const std::string &Anchor) { + llvm::MD5 Hasher; + llvm::MD5::MD5Result Hash; + Hasher.update(Anchor); + Hasher.final(Hash); - 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; + SmallString<32> HexString; + llvm::MD5::stringifyResult(Hash, HexString); + return HexString.str().str(); +} - OS << stripPathPrefix(FnLoc.Loc.FileName) << ":" << FnLoc.Loc.Line << " " - << FnLoc.FunctionName << "\n"; +static ErrorOr<bool> isCoverageFile(const std::string &FileName) { + ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = + MemoryBuffer::getFile(FileName); + if (!BufOrErr) { + errs() << "Warning: " << BufOrErr.getError().message() << "(" + << BufOrErr.getError().value() + << "), filename: " << llvm::sys::path::filename(FileName) << "\n"; + return BufOrErr.getError(); + } + std::unique_ptr<MemoryBuffer> Buf = std::move(BufOrErr.get()); + if (Buf->getBufferSize() < 8) { + return false; } + const FileHeader *Header = + reinterpret_cast<const FileHeader *>(Buf->getBufferStart()); + return Header->Magic == BinCoverageMagic; +} + +struct CoverageStats { + CoverageStats() : AllPoints(0), CovPoints(0), AllFns(0), CovFns(0) {} + + size_t AllPoints; + size_t CovPoints; + size_t AllFns; + size_t CovFns; +}; + +static raw_ostream &operator<<(raw_ostream &OS, const CoverageStats &Stats) { + OS << "all-edges: " << Stats.AllPoints << "\n"; + OS << "cov-edges: " << Stats.CovPoints << "\n"; + OS << "all-functions: " << Stats.AllFns << "\n"; + OS << "cov-functions: " << Stats.CovFns << "\n"; + return OS; } class CoverageData { - public: +public: // Read single file coverage data. - static ErrorOr<std::unique_ptr<CoverageData>> read(std::string FileName) { + static ErrorOr<std::unique_ptr<CoverageData>> + read(const std::string &FileName) { ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = MemoryBuffer::getFile(FileName); if (!BufOrErr) @@ -471,37 +640,570 @@ class CoverageData { } } +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) + : 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 coverage points hit/total in a file. + // file_name -> <coverage, all_coverage> + std::map<std::string, std::pair<size_t, size_t>> computeFileCoverage() { + std::map<std::string, std::pair<size_t, size_t>> FileCoverage; + auto AllCovPointsByFile = + group_by(AllAddrInfo, [](const AddrInfo &AI) { return AI.FileName; }); + auto CovPointsByFile = + group_by(CovAddrInfo, [](const AddrInfo &AI) { return AI.FileName; }); + + for (const auto &P : AllCovPointsByFile) { + const std::string &FileName = P.first; + + FileCoverage[FileName] = + std::make_pair(CovPointsByFile[FileName].size(), + AllCovPointsByFile[FileName].size()); + } + 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> computeAllFunctions() const { + std::set<FileFn> Fns; + for (const auto &AI : AllAddrInfo) { + Fns.insert(FileFn{AI.FileName, AI.FunctionName}); + } + return Fns; + } + + 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; + } + + // Compute % coverage for each function. + std::map<FileFn, int> computeFunctionsCoverage() const { + std::map<FileFn, int> FnCoverage; + 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) { + FileFn F = P.first; + FnCoverage[F] = CovFns[F].size() * 100 / P.second.size(); + } + + return FnCoverage; + } + + 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; + + auto FLoc = FileLoc{FileName, Loc.Loc.Line}; + Result[FLoc].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; + } + + void collectStats(CoverageStats *Stats) const { + Stats->AllPoints += AllCovPoints.size(); + Stats->AllFns += computeAllFunctions().size(); + Stats->CovFns += computeCoveredFunctions().size(); + } + +private: + const std::set<uint64_t> AllCovPoints; + + 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 : public CoverageData { +public: + static ErrorOr<std::unique_ptr<CoverageDataWithObjectFile>> + readAndMerge(const std::string &ObjectFile, + const std::vector<std::string> &FileNames) { + auto MergedDataOrError = CoverageData::readAndMerge(FileNames); + if (!MergedDataOrError) + return MergedDataOrError.getError(); + return std::unique_ptr<CoverageDataWithObjectFile>( + new CoverageDataWithObjectFile(ObjectFile, + std::move(MergedDataOrError.get()))); + } + + std::string object_file() const { return ObjectFile; } + // Print list of covered functions. // Line format: <file_name>:<line> <function_name> - void printCoveredFunctions(raw_ostream &OS) { - printFunctionLocs(computeFunctionLocs(*Addrs), OS); + void printCoveredFunctions(raw_ostream &OS) const { + 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) { - std::set<FunctionLoc> AllFns = - computeFunctionLocs(getCoveragePoints(ClBinaryName)); - std::set<FunctionLoc> CoveredFns = computeFunctionLocs(*Addrs); + void printNotCoveredFunctions(raw_ostream &OS) const { + SourceCoverageData SCovData(ObjectFile, *Addrs); + auto NotCoveredFns = SCovData.computeNotCoveredFunctions(); + printFunctionLocs(SCovData.resolveFunctions(NotCoveredFns), OS); + } + + void printReport(raw_ostream &OS) const { + SourceCoverageData SCovData(ObjectFile, *Addrs); + auto LineStatusMap = SCovData.computeLineStatusMap(); + + std::set<FileFn> AllFns = SCovData.computeAllFunctions(); + // file_loc -> set[function_name] + auto AllFnsByLoc = SCovData.resolveFunctions(AllFns); + auto FileCoverage = SCovData.computeFileCoverage(); + + auto FnCoverage = SCovData.computeFunctionsCoverage(); + auto FnCoverageByFile = + group_by(FnCoverage, [](const std::pair<FileFn, int> &FileFn) { + return FileFn.first.FileName; + }); + + // TOC + + size_t NotCoveredFilesCount = 0; + std::set<std::string> Files = SCovData.files(); + + // Covered Files. + OS << "<details open><summary>Touched Files</summary>\n"; + OS << "<table>\n"; + OS << "<tr><th>File</th><th>Coverage %</th>"; + OS << "<th>Hit (Total) Fns</th></tr>\n"; + for (const auto &FileName : Files) { + std::pair<size_t, size_t> FC = FileCoverage[FileName]; + if (FC.first == 0) { + NotCoveredFilesCount++; + continue; + } + size_t CovPct = FC.second == 0 ? 100 : 100 * FC.first / FC.second; + + OS << "<tr><td><a href=\"#" << anchorName(FileName) << "\">" + << stripPathPrefix(FileName) << "</a></td>" + << "<td>" << formatHtmlPct(CovPct) << "%</td>" + << "<td>" << FC.first << " (" << FC.second << ")" + << "</tr>\n"; + } + OS << "</table>\n"; + OS << "</details>\n"; + + // Not covered files. + if (NotCoveredFilesCount) { + OS << "<details><summary>Not Touched Files</summary>\n"; + OS << "<table>\n"; + for (const auto &FileName : Files) { + std::pair<size_t, size_t> FC = FileCoverage[FileName]; + if (FC.first == 0) + OS << "<tr><td>" << stripPathPrefix(FileName) << "</td>\n"; + } + OS << "</table>\n"; + OS << "</details>\n"; + } else { + OS << "<p>Congratulations! All source files are touched.</p>\n"; + } + + // Source + for (const auto &FileName : Files) { + std::pair<size_t, size_t> FC = FileCoverage[FileName]; + if (FC.first == 0) + continue; + OS << "<a name=\"" << anchorName(FileName) << "\"></a>\n"; + OS << "<h2>" << stripPathPrefix(FileName) << "</h2>\n"; + OS << "<details open><summary>Function Coverage</summary>"; + OS << "<div class='fnlist'>\n"; + + auto &FileFnCoverage = FnCoverageByFile[FileName]; + + for (const auto &P : FileFnCoverage) { + std::string FunctionName = P.first.FunctionName; + + OS << "<div class='fn' style='order: " << P.second << "'>"; + OS << "<span class='pct'>" << formatHtmlPct(P.second) + << "%</span> "; + OS << "<span class='name'><a href=\"#" + << anchorName(FileName + "::" + FunctionName) << "\">"; + OS << escapeHtml(FunctionName) << "</a></span>"; + OS << "</div>\n"; + } + OS << "</div></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 = AllFnsByLoc.find(Loc); + if (It != AllFnsByLoc.end()) { + for (const std::string &Fn : It->second) { + OS << "<a name=\"" << anchorName(FileName + "::" + Fn) + << "\"></a>"; + }; + } + } - std::set<FunctionLoc> NotCoveredFns; - std::set_difference(AllFns.begin(), AllFns.end(), CoveredFns.begin(), - CoveredFns.end(), - std::inserter(NotCoveredFns, NotCoveredFns.end())); - printFunctionLocs(NotCoveredFns, OS); + 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"; + } + } + + void collectStats(CoverageStats *Stats) const { + Stats->CovPoints += Addrs->size(); + + SourceCoverageData SCovData(ObjectFile, *Addrs); + SCovData.collectStats(Stats); } private: - explicit CoverageData(std::unique_ptr<std::set<uint64_t>> Addrs) - : Addrs(std::move(Addrs)) {} + CoverageDataWithObjectFile(std::string ObjectFile, + std::unique_ptr<CoverageData> Coverage) + : CoverageData(std::move(Coverage->Addrs)), + ObjectFile(std::move(ObjectFile)) {} + const std::string ObjectFile; +}; - std::unique_ptr<std::set<uint64_t>> Addrs; +// Multiple coverage files data organized by object file. +class CoverageDataSet { +public: + static ErrorOr<std::unique_ptr<CoverageDataSet>> + readCmdArguments(std::vector<std::string> FileNames) { + // Short name => file name. + std::map<std::string, std::string> ObjFiles; + std::string FirstObjFile; + std::set<std::string> CovFiles; + + // Partition input values into coverage/object files. + for (const auto &FileName : FileNames) { + auto ErrorOrIsCoverage = isCoverageFile(FileName); + if (!ErrorOrIsCoverage) + continue; + if (ErrorOrIsCoverage.get()) { + CovFiles.insert(FileName); + } else { + auto ShortFileName = llvm::sys::path::filename(FileName); + if (ObjFiles.find(ShortFileName) != ObjFiles.end()) { + Fail("Duplicate binary file with a short name: " + ShortFileName); + } + + ObjFiles[ShortFileName] = FileName; + if (FirstObjFile.empty()) + FirstObjFile = FileName; + } + } + + Regex SancovRegex("(.*)\\.[0-9]+\\.sancov"); + SmallVector<StringRef, 2> Components; + + // 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) { + Fail("Can't match coverage file name against " + "<module_name>.<pid>.sancov pattern: " + + FileName); + } + + auto Iter = ObjFiles.find(Components[1]); + if (Iter == ObjFiles.end()) { + Fail("Object file for coverage not found: " + FileName); + } + return Iter->second; + }); + + // Read coverage. + std::vector<std::unique_ptr<CoverageDataWithObjectFile>> MergedCoverage; + for (const auto &Pair : CoverageByObjFile) { + if (findSanitizerCovFunctions(Pair.first).empty()) { + for (const 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); + MergedCoverage.push_back(std::move(DataOrError.get())); + } + + return std::unique_ptr<CoverageDataSet>( + new CoverageDataSet(FirstObjFile, &MergedCoverage, CovFiles)); + } + + void printCoveredFunctions(raw_ostream &OS) const { + for (const auto &Cov : Coverage) { + Cov->printCoveredFunctions(OS); + } + } + + void printNotCoveredFunctions(raw_ostream &OS) const { + for (const auto &Cov : Coverage) { + Cov->printNotCoveredFunctions(OS); + } + } + + void printStats(raw_ostream &OS) const { + CoverageStats Stats; + for (const auto &Cov : Coverage) { + Cov->collectStats(&Stats); + } + OS << Stats; + } + + void printReport(raw_ostream &OS) const { + auto Title = + (llvm::sys::path::filename(MainObjFile) + " Coverage Report").str(); + + OS << "<html>\n"; + OS << "<head>\n"; + + // Stylesheet + OS << "<style>\n"; + OS << ".covered { background: #7F7; }\n"; + OS << ".notcovered { background: #F77; }\n"; + OS << ".mixed { background: #FF7; }\n"; + OS << "summary { font-weight: bold; }\n"; + OS << "details > summary + * { margin-left: 1em; }\n"; + OS << ".fnlist { display: flex; flex-flow: column nowrap; }\n"; + OS << ".fn { display: flex; flex-flow: row nowrap; }\n"; + OS << ".pct { width: 3em; text-align: right; margin-right: 1em; }\n"; + OS << ".name { flex: 2; }\n"; + OS << ".lz { color: lightgray; }\n"; + OS << "</style>\n"; + OS << "<title>" << Title << "</title>\n"; + OS << "</head>\n"; + OS << "<body>\n"; + + // Title + OS << "<h1>" << Title << "</h1>\n"; + + // Modules TOC. + if (Coverage.size() > 1) { + for (const auto &CovData : Coverage) { + OS << "<li><a href=\"#module_" << anchorName(CovData->object_file()) + << "\">" << llvm::sys::path::filename(CovData->object_file()) + << "</a></li>\n"; + } + } + + for (const auto &CovData : Coverage) { + if (Coverage.size() > 1) { + OS << "<h2>" << llvm::sys::path::filename(CovData->object_file()) + << "</h2>\n"; + } + OS << "<a name=\"module_" << anchorName(CovData->object_file()) + << "\"></a>\n"; + CovData->printReport(OS); + } + + // About + OS << "<details><summary>About</summary>\n"; + OS << "Coverage files:<ul>"; + for (const 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"; + } + + bool empty() const { return Coverage.empty(); } + +private: + explicit CoverageDataSet( + const std::string &MainObjFile, + std::vector<std::unique_ptr<CoverageDataWithObjectFile>> *Data, + const std::set<std::string> &CoverageFiles) + : MainObjFile(MainObjFile), CoverageFiles(CoverageFiles) { + Data->swap(this->Coverage); + } + + const std::string MainObjFile; + std::vector<std::unique_ptr<CoverageDataWithObjectFile>> Coverage; + const std::set<std::string> CoverageFiles; }; + } // namespace int main(int argc, char **argv) { // Print stack trace if we signal out. - sys::PrintStackTraceOnErrorSignal(); + sys::PrintStackTraceOnErrorSignal(argv[0]); PrettyStackTraceProgram X(argc, argv); llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. @@ -511,23 +1213,46 @@ int main(int argc, char **argv) { cl::ParseCommandLineOptions(argc, argv, "Sanitizer Coverage Processing Tool"); - auto CovData = CoverageData::readAndMerge(ClInputFiles); - FailIfError(CovData); - - switch (Action) { - case PrintAction: { + // -print doesn't need object files. + if (Action == PrintAction) { + auto CovData = CoverageData::readAndMerge(ClInputFiles); + FailIfError(CovData); CovData.get()->printAddrs(outs()); return 0; + } else if (Action == PrintCovPointsAction) { + // -print-coverage-points doesn't need coverage files. + for (const std::string &ObjFile : ClInputFiles) { + printCovPoints(ObjFile, outs()); + } + return 0; + } + + auto CovDataSet = CoverageDataSet::readCmdArguments(ClInputFiles); + FailIfError(CovDataSet); + + if (CovDataSet.get()->empty()) { + Fail("No coverage files specified."); } + + switch (Action) { case CoveredFunctionsAction: { - CovData.get()->printCoveredFunctions(outs()); + CovDataSet.get()->printCoveredFunctions(outs()); return 0; } case NotCoveredFunctionsAction: { - CovData.get()->printNotCoveredFunctions(outs()); + CovDataSet.get()->printNotCoveredFunctions(outs()); return 0; } + case HtmlReportAction: { + CovDataSet.get()->printReport(outs()); + return 0; + } + case StatsAction: { + CovDataSet.get()->printStats(outs()); + return 0; + } + case PrintAction: + case PrintCovPointsAction: + llvm_unreachable("unsupported action"); } - - llvm_unreachable("unsupported action"); } |