Index: tools/sancov/sancov.cc =================================================================== --- tools/sancov/sancov.cc +++ tools/sancov/sancov.cc @@ -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 @@ "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 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 @@ Fail(Message); } +// --------- + +// Produces std::map> grouping input +// elements by FuncTy result. +template +static inline auto group_by(const RangeTy &R, FuncTy F) + -> std::map::type, + std::vector::type>> { + std::map::type, + std::vector::type>> + Result; + for (const auto &E : R) { + Result[F(E)].push_back(E); + } + return Result; +} + template static void readInts(const char *Start, const char *End, std::set *Ints) { @@ -155,8 +174,18 @@ 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 @@ new symbolize::LLVMSymbolizer(SymbolizerOptions)); } -// Compute [FileLoc -> FunctionName] map for given addresses. -static std::map -computeFunctionsMap(std::string ObjectFile, const std::set &Addrs) { - std::map Fns; +// A DILineInfo with address. +struct AddrInfo : public DILineInfo { + uint64_t Addr; - auto Symbolizer(createSymbolizer()); + AddrInfo(const DILineInfo &DI, uint64_t Addr) : DILineInfo(DI), Addr(Addr) { + FileName = normalizeFilename(FileName); + } - // 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; - } +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(); } +}; - return Fns; -} +class Blacklists { +public: + Blacklists() + : DefaultBlacklist(createDefaultBlacklist()), + UserBlacklist(createUserBlacklist()) {} + + 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; + } -// Compute functions for given addresses. It keeps only the first -// occurence of a function within a file. -std::set computeFunctionLocs(std::string ObjectFile, - const std::set &Addrs) { - std::map Fns = computeFunctionsMap(ObjectFile, Addrs); +private: + static std::unique_ptr createDefaultBlacklist() { + if (!ClUseDefaultBlacklist) + return std::unique_ptr(); + std::unique_ptr MB = + MemoryBuffer::getMemBuffer(DefaultBlacklistStr); + std::string Error; + auto Blacklist = SpecialCaseList::create(MB.get(), Error); + FailIfNotEmpty(Error); + return Blacklist; + } - std::set Result; - std::string LastFileName; - std::set ProcessedFunctions; + static std::unique_ptr createUserBlacklist() { + if (ClBlacklist.empty()) + return std::unique_ptr(); - for (const auto &P : Fns) { - std::string FileName = P.first.FileName; - std::string FunctionName = P.second; + return SpecialCaseList::createOrDie({{ClBlacklist}}); + } + std::unique_ptr DefaultBlacklist; + std::unique_ptr UserBlacklist; +}; - if (LastFileName != FileName) - ProcessedFunctions.clear(); - LastFileName = FileName; +// Collect all debug info for given addresses. +static std::vector getAddrInfo(std::string ObjectFile, + const std::set &Addrs, + bool InlinedCode) { + std::vector Result; + auto Symbolizer(createSymbolizer()); + Blacklists B; - if (!ProcessedFunctions.insert(FunctionName).second) + 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 @@ 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 @@ 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 @@ if (!SectSize) continue; - StringRef SectionName; - FailIfError(Section.getName(SectionName)); - StringRef BytesStr; FailIfError(Section.getContents(BytesStr)); ArrayRef Bytes(reinterpret_cast(BytesStr.data()), @@ -322,92 +383,67 @@ 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 *Addrs) { +static void +visitObjectFiles(const object::Archive &A, + std::function Fn) { for (auto &ErrorOrChild : A.children()) { FailIfError(ErrorOrChild); const object::Archive::Child &C = *ErrorOrChild; ErrorOr> ChildOrErr = C.getAsBinary(); FailIfError(ChildOrErr); - if (object::ObjectFile *O = - dyn_cast(&*ChildOrErr.get())) - getObjectCoveragePoints(*O, Addrs); + if (auto *O = dyn_cast(&*ChildOrErr.get())) + Fn(*O); else FailIfError(object::object_error::invalid_file_type); } } -// 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 getCoveragePoints(std::string FileName) { - std::set Result; - +static void +visitObjectFiles(std::string FileName, + std::function Fn) { ErrorOr> BinaryOrErr = object::createBinary(FileName); FailIfError(BinaryOrErr); object::Binary &Binary = *BinaryOrErr.get().getBinary(); if (object::Archive *A = dyn_cast(&Binary)) - getArchiveCoveragePoints(*A, &Result); + visitObjectFiles(*A, Fn); else if (object::ObjectFile *O = dyn_cast(&Binary)) - getObjectCoveragePoints(*O, &Result); + Fn(*O); else FailIfError(object::object_error::invalid_file_type); - - return Result; -} - -static std::unique_ptr createDefaultBlacklist() { - if (!ClUseDefaultBlacklist) - return std::unique_ptr(); - std::unique_ptr MB = - MemoryBuffer::getMemBuffer(DefaultBlacklist); - std::string Error; - auto Blacklist = SpecialCaseList::create(MB.get(), Error); - FailIfNotEmpty(Error); - return Blacklist; } -static std::unique_ptr createUserBlacklist() { - if (ClBlacklist.empty()) - return std::unique_ptr(); - - return SpecialCaseList::createOrDie({{ClBlacklist}}); +std::set findSanitizerCovFunctions(std::string FileName) { + std::set Result; + visitObjectFiles(FileName, [&](const object::ObjectFile &O) { + auto Addrs = findSanitizerCovFunctions(O); + Result.insert(Addrs.begin(), Addrs.end()); + }); + return Result; } -static void printFunctionLocs(const std::set &FnLocs, - raw_ostream &OS) { - std::unique_ptr DefaultBlacklist = createDefaultBlacklist(); - std::unique_ptr 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"; - } +// 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 getCoveragePoints(std::string FileName) { + std::set Result; + visitObjectFiles(FileName, [&](const object::ObjectFile &O) { + getObjectCoveragePoints(O, &Result); + }); + return Result; } static std::string escapeHtml(const std::string &S) { @@ -438,28 +474,6 @@ return Result; } -// Computes a map file_name->{line_number} -static std::map> -getFileLines(std::string ObjectFile, const std::set &Addrs) { - std::map> 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 isCoverageFile(std::string FileName) { ErrorOr> BufOrErr = MemoryBuffer::getFile(FileName); @@ -547,83 +561,193 @@ } } - void printReport(std::string ObjectFile, raw_ostream &OS) { - // file_name -> set of covered lines; - std::map> CoveredFileLines = - getFileLines(ObjectFile, *Addrs); - std::map> CoveragePoints = - getFileLines(ObjectFile, getCoveragePoints(ObjectFile)); +protected: + explicit CoverageData(std::unique_ptr> Addrs) + : Addrs(std::move(Addrs)) {} - // TOC - OS << "
    \n"; - for (auto It : CoveredFileLines) { - auto FileName = It.first; - OS << "
  • " - << stripPathPrefix(FileName) << "
  • \n"; + friend class CoverageDataWithObjectFile; + + std::unique_ptr> 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 &Addrs) { + std::set 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."); } - OS << "
\n"; - // Source - for (auto It : CoveredFileLines) { - auto FileName = It.first; - auto Lines = It.second; - auto CovLines = CoveragePoints[FileName]; + AllAddrInfo = getAddrInfo(ObjectFile, AllCovPoints, true); + CovAddrInfo = getAddrInfo(ObjectFile, Addrs, true); + } - OS << "\n"; - OS << "

" << stripPathPrefix(FileName) << "

\n"; - ErrorOr> BufOrErr = - MemoryBuffer::getFile(FileName); - if (!BufOrErr) { - OS << "Error reading file: " << FileName << " : " - << BufOrErr.getError().message() << "(" - << BufOrErr.getError().value() << ")\n"; - continue; + // Compute number of functions hit/total in a file. + // file_name -> + std::map> computeFileFnCoverage() { + std::map> 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; + } - OS << "
\n";
-      for (line_iterator I = line_iterator(*BufOrErr.get(), false);
-           !I.is_at_eof(); ++I) {
-        OS << "";
-        OS << escapeHtml(*I) << "\n";
+  // line_number -> line_status.
+  typedef std::map LineStatusMap;
+  // file_name -> LineStatusMap
+  typedef std::map 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;
       }
-      OS << "
\n"; } + return StatusMap; } - // Print list of covered functions. - // Line format: : - void printCoveredFunctions(std::string ObjectFile, raw_ostream &OS) { - printFunctionLocs(computeFunctionLocs(ObjectFile, *Addrs), OS); + std::set computeCoveredFunctions() const { + std::set 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; } - // Print list of not covered functions. - // Line format: : - void printNotCoveredFunctions(std::string ObjectFile, raw_ostream &OS) { - std::set AllFns = - computeFunctionLocs(ObjectFile, getCoveragePoints(ObjectFile)); - std::set CoveredFns = computeFunctionLocs(ObjectFile, *Addrs); + std::set computeNotCoveredFunctions() const { + std::set Fns; - std::set NotCoveredFns; - std::set_difference(AllFns.begin(), AllFns.end(), CoveredFns.begin(), - CoveredFns.end(), - std::inserter(NotCoveredFns, NotCoveredFns.end())); - printFunctionLocs(NotCoveredFns, OS); + 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; } -private: - explicit CoverageData(std::unique_ptr> Addrs) - : Addrs(std::move(Addrs)) {} + typedef std::map> FunctionLocs; + // finds first line number in a file for each function. + FunctionLocs resolveFunctions(const std::set &Fns) const { + std::vector FnAddrs; + for (const auto &AI : AllAddrInfo) { + if (Fns.find(FileFn{AI.FileName, AI.FunctionName}) != Fns.end()) + FnAddrs.push_back(AI); + } - std::unique_ptr> Addrs; + auto GroupedAddrs = group_by(FnAddrs, [](const AddrInfo &AI) { + return FnLoc{FileLoc{AI.FileName, AI.Line}, AI.FunctionName}; + }); + + FunctionLocs Result; + std::string LastFileName; + std::set 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 files() const { + std::set Files; + for (const auto &AI : AllAddrInfo) { + Files.insert(AI.FileName); + } + return Files; + } + +private: + std::vector AllAddrInfo; + std::vector 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> readAndMerge(std::string ObjectFile, @@ -638,25 +762,145 @@ std::string object_file() const { return ObjectFile; } + // Print list of covered functions. + // Line format: : 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: : 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 << "
Covered Files\n"; + OS << "\n"; + OS << ""; + OS << "\n"; + for (auto FileName : SCovData.files()) { + std::pair FC = FileFnCoverage[FileName]; + if (FC.first == 0) + continue; + size_t CovPct = FC.second == 0 ? 100 : 100 * FC.first / FC.second; + + OS << "" + << "" + << "\n"; + } + OS << "
FileHit Fns %Hit (Total) Fns
" + << stripPathPrefix(FileName) << "" << CovPct << "%" << FC.first << " (" << FC.second << ")" + << "
\n"; + OS << "
\n"; + + // Not covered files. + OS << "
Not Covered Files\n"; + OS << "\n"; + for (auto FileName : SCovData.files()) { + std::pair FC = FileFnCoverage[FileName]; + if (FC.first == 0) + OS << "\n"; + } + OS << "
" << stripPathPrefix(FileName) << "
\n"; + OS << "
\n"; + + // Source + for (auto FileName : SCovData.files()) { + std::pair FC = FileFnCoverage[FileName]; + if (FC.first == 0) + continue; + OS << "\n"; + OS << "

" << stripPathPrefix(FileName) << "

\n"; + + auto NotCoveredFns = NotCoveredFnMap.find(FileName); + if (NotCoveredFns != NotCoveredFnMap.end()) { + OS << "
Not Covered Functions"; + OS << "\n"; + for (auto FileFn : NotCoveredFns->second) { + OS << "\n"; + } + OS << "
"; + OS << ""; + OS << escapeHtml(FileFn.FunctionName) << ""; + OS << "
\n"; + } + + ErrorOr> BufOrErr = + MemoryBuffer::getFile(FileName); + if (!BufOrErr) { + OS << "Error reading file: " << FileName << " : " + << BufOrErr.getError().message() << "(" + << BufOrErr.getError().value() << ")\n"; + continue; + } + + OS << "
\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 << "";
+            };
+          }
+        }
+
+        OS << "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) << "\n";
+      }
+      OS << "
\n"; + } } private: CoverageDataWithObjectFile(std::string ObjectFile, std::unique_ptr 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 Coverage; }; // Multiple coverage files data organized by object file. @@ -687,13 +931,11 @@ } } - // Object file => list of corresponding coverage files. - std::map> CoverageByObjFile; Regex SancovRegex("(.*)\\.[0-9]+\\.sancov"); SmallVector 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 @@ 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> 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 @@ OS << "\n"; OS << "" << Title << "\n"; OS << "\n"; @@ -752,14 +1007,6 @@ // Title OS << "

" << Title << "

\n"; - OS << "

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

\n"; // Modules TOC. if (Coverage.size() > 1) { @@ -780,6 +1027,17 @@ CovData->printReport(OS); } + // About + OS << "
About\n"; + OS << "Coverage files:
    "; + for (auto InputFile : CoverageFiles) { + llvm::sys::fs::file_status Status; + llvm::sys::fs::status(InputFile, Status); + OS << "
  • " << stripPathPrefix(InputFile) << " (" + << Status.getLastModificationTime().str() << ")
  • \n"; + } + OS << "
\n"; + OS << "\n"; OS << "\n"; } @@ -798,6 +1056,7 @@ const std::string MainObjFile; std::vector> Coverage; const std::set CoverageFiles; + const std::set IgnoredCoverageFiles; }; } // namespace