[llvm] r260628 - [sancov] improved object files handling.

Mike Aizatsky via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 11 16:29:07 PST 2016


Author: aizatsky
Date: Thu Feb 11 18:29:07 2016
New Revision: 260628

URL: http://llvm.org/viewvc/llvm-project?rev=260628&view=rev
Log:
[sancov] improved object files handling.

Multi-dso programs result in multiple coverage files dumped of the form
'<module_name>.<pid>.sancov'. When analyzing these coverage files it is
important to use correct corresponding object file.

This change removes the "-obj" sancov flag and lets user specify object
file names alongside coverage files. Sancov tool would match them using
<module_name> part of coverage file and short file name of the object
file.

Corresponding changes:
- compiler-rt: http://reviews.llvm.org/D17171
- docs: http://reviews.llvm.org/D17175

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

Added:
    llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.0.sancov
      - copied, changed from r260627, llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.sancov
    llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.1.sancov
      - copied, changed from r260627, llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64-1.sancov
Removed:
    llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64-1.sancov
    llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.sancov
Modified:
    llvm/trunk/test/tools/sancov/blacklist.test
    llvm/trunk/test/tools/sancov/covered_functions.test
    llvm/trunk/test/tools/sancov/html-report.test
    llvm/trunk/test/tools/sancov/not_covered_functions.test
    llvm/trunk/test/tools/sancov/print.test
    llvm/trunk/tools/sancov/sancov.cc

Removed: llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64-1.sancov
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64-1.sancov?rev=260627&view=auto
==============================================================================
Binary files llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64-1.sancov (original) and llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64-1.sancov (removed) differ

Copied: llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.0.sancov (from r260627, llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.sancov)
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.0.sancov?p2=llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.0.sancov&p1=llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.sancov&r1=260627&r2=260628&rev=260628&view=diff
==============================================================================
    (empty)

Copied: llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.1.sancov (from r260627, llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64-1.sancov)
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.1.sancov?p2=llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.1.sancov&p1=llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64-1.sancov&r1=260627&r2=260628&rev=260628&view=diff
==============================================================================
    (empty)

Removed: llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.sancov
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.sancov?rev=260627&view=auto
==============================================================================
Binary files llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.sancov (original) and llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.sancov (removed) differ

Modified: llvm/trunk/test/tools/sancov/blacklist.test
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/blacklist.test?rev=260628&r1=260627&r2=260628&view=diff
==============================================================================
--- llvm/trunk/test/tools/sancov/blacklist.test (original)
+++ llvm/trunk/test/tools/sancov/blacklist.test Thu Feb 11 18:29:07 2016
@@ -1,5 +1,5 @@
 REQUIRES: x86_64-linux
-RUN: sancov -obj %p/Inputs/test-linux_x86_64 -covered-functions -blacklist %p/Inputs/blacklist.txt %p/Inputs/test-linux_x86_64.sancov | FileCheck %s
+RUN: sancov -covered-functions -blacklist %p/Inputs/blacklist.txt %p/Inputs/test-linux_x86_64 %p/Inputs/test-linux_x86_64.0.sancov | FileCheck %s
 
 CHECK-NOT: Inputs{{[/\\]}}test.cpp:12 bar(std::string)
 CHECK: Inputs{{[/\\]}}test.cpp:14 main

Modified: llvm/trunk/test/tools/sancov/covered_functions.test
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/covered_functions.test?rev=260628&r1=260627&r2=260628&view=diff
==============================================================================
--- llvm/trunk/test/tools/sancov/covered_functions.test (original)
+++ llvm/trunk/test/tools/sancov/covered_functions.test Thu Feb 11 18:29:07 2016
@@ -1,7 +1,7 @@
 REQUIRES: x86_64-linux
-RUN: sancov -obj %p/Inputs/test-linux_x86_64 -covered-functions %p/Inputs/test-linux_x86_64.sancov | FileCheck %s
-RUN: sancov -obj %p/Inputs/test-linux_x86_64 -covered-functions -strip_path_prefix=Inputs/ %p/Inputs/test-linux_x86_64.sancov | FileCheck --check-prefix=STRIP_PATH %s
-RUN: sancov -obj %p/Inputs/test-linux_x86_64 -demangle=0 -covered-functions %p/Inputs/test-linux_x86_64.sancov | FileCheck --check-prefix=NO_DEMANGLE %s
+RUN: sancov -covered-functions %p/Inputs/test-linux_x86_64 %p/Inputs/test-linux_x86_64.0.sancov | FileCheck %s
+RUN: sancov -covered-functions -strip_path_prefix=Inputs/ %p/Inputs/test-linux_x86_64 %p/Inputs/test-linux_x86_64.0.sancov | FileCheck --check-prefix=STRIP_PATH %s
+RUN: sancov -demangle=0 -covered-functions %p/Inputs/test-linux_x86_64 %p/Inputs/test-linux_x86_64.0.sancov | FileCheck --check-prefix=NO_DEMANGLE %s
 
 CHECK: Inputs{{[/\\]}}test.cpp:12 bar(std::string)
 CHECK: Inputs{{[/\\]}}test.cpp:14 main

Modified: llvm/trunk/test/tools/sancov/html-report.test
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/html-report.test?rev=260628&r1=260627&r2=260628&view=diff
==============================================================================
--- llvm/trunk/test/tools/sancov/html-report.test (original)
+++ llvm/trunk/test/tools/sancov/html-report.test Thu Feb 11 18:29:07 2016
@@ -1,5 +1,5 @@
 REQUIRES: x86_64-linux
-RUN: sancov -obj %p/Inputs/test-linux_x86_64 -html-report %p/Inputs/test-linux_x86_64.sancov | FileCheck %s
+RUN: sancov -html-report %p/Inputs/test-linux_x86_64 %p/Inputs/test-linux_x86_64.0.sancov | FileCheck %s
 
 // It's very difficult to test html report. Do basic smoke check.
 CHECK: {{<a name=".*/Inputs/test.cpp">}}

Modified: llvm/trunk/test/tools/sancov/not_covered_functions.test
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/not_covered_functions.test?rev=260628&r1=260627&r2=260628&view=diff
==============================================================================
--- llvm/trunk/test/tools/sancov/not_covered_functions.test (original)
+++ llvm/trunk/test/tools/sancov/not_covered_functions.test Thu Feb 11 18:29:07 2016
@@ -1,6 +1,6 @@
 REQUIRES: x86_64-linux
-RUN: sancov -obj %p/Inputs/test-linux_x86_64 -not-covered-functions %p/Inputs/test-linux_x86_64.sancov | FileCheck %s
-RUN: sancov -obj %p/Inputs/test-linux_x86_64 -not-covered-functions %p/Inputs/test-linux_x86_64-1.sancov | FileCheck --check-prefix=CHECK1 --allow-empty %s
+RUN: sancov -not-covered-functions %p/Inputs/test-linux_x86_64 %p/Inputs/test-linux_x86_64.0.sancov | FileCheck %s
+RUN: sancov -not-covered-functions %p/Inputs/test-linux_x86_64 %p/Inputs/test-linux_x86_64.1.sancov | FileCheck --check-prefix=CHECK1 --allow-empty %s
 
 CHECK: Inputs{{[/\\]}}foo.cpp:5 foo()
 CHECK-NOT: {{.*__sanitizer.*}}

Modified: llvm/trunk/test/tools/sancov/print.test
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/print.test?rev=260628&r1=260627&r2=260628&view=diff
==============================================================================
--- llvm/trunk/test/tools/sancov/print.test (original)
+++ llvm/trunk/test/tools/sancov/print.test Thu Feb 11 18:29:07 2016
@@ -1,5 +1,5 @@
 REQUIRES: x86_64-linux
-RUN: sancov -obj %p/Inputs/test-linux_x86_64 -print %p/Inputs/test-linux_x86_64.sancov | FileCheck %s
+RUN: sancov -print %p/Inputs/test-linux_x86_64.0.sancov | FileCheck %s
 
 CHECK: 0x4dbe2b
 CHECK: 0x4dbf72

Modified: llvm/trunk/tools/sancov/sancov.cc
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/sancov/sancov.cc?rev=260628&r1=260627&r2=260628&view=diff
==============================================================================
--- llvm/trunk/tools/sancov/sancov.cc (original)
+++ llvm/trunk/tools/sancov/sancov.cc Thu Feb 11 18:29:07 2016
@@ -11,6 +11,7 @@
 // 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"
@@ -34,6 +35,7 @@
 #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"
@@ -44,6 +46,7 @@
 #include <set>
 #include <stdio.h>
 #include <string>
+#include <utility>
 #include <vector>
 
 using namespace llvm;
@@ -70,12 +73,9 @@ cl::opt<ActionType> Action(
                           "Print HTML coverage report."),
                clEnumValEnd));
 
-static cl::list<std::string> ClInputFiles(cl::Positional, cl::OneOrMore,
-                                          cl::desc("<filenames...>"));
-
-static cl::opt<std::string>
-    ClBinaryName("obj", cl::Required,
-                 cl::desc("Path to object file to be symbolized"));
+static cl::list<std::string>
+    ClInputFiles(cl::Positional, cl::OneOrMore,
+                 cl::desc("(<binary file>|<.sancov file>)..."));
 
 static cl::opt<bool>
     ClDemangle("demangle", cl::init(true),
@@ -108,6 +108,11 @@ static const uint32_t Bitness64 = 0xFFFF
 
 // ---------
 
+static void Fail(const llvm::Twine &E) {
+  errs() << "Error: " << E << "\n";
+  exit(1);
+}
+
 static void FailIfError(std::error_code Error) {
   if (!Error)
     return;
@@ -119,11 +124,10 @@ template <typename T> static void FailIf
   FailIfError(E.getError());
 }
 
-static void FailIfNotEmpty(const std::string &E) {
-  if (E.empty())
+static void FailIfNotEmpty(const llvm::Twine &E) {
+  if (E.str().empty())
     return;
-  errs() << "Error: " << E << "\n";
-  exit(1);
+  Fail(E);
 }
 
 template <typename T>
@@ -131,8 +135,7 @@ static void FailIfEmpty(const std::uniqu
                         const std::string &Message) {
   if (Ptr.get())
     return;
-  errs() << "Error: " << Message << "\n";
-  exit(1);
+  Fail(Message);
 }
 
 template <typename T>
@@ -180,14 +183,14 @@ static std::unique_ptr<symbolize::LLVMSy
 
 // Compute [FileLoc -> FunctionName] map for given addresses.
 static std::map<FileLoc, std::string>
-computeFunctionsMap(const std::set<uint64_t> &Addrs) {
+computeFunctionsMap(std::string ObjectFile, const std::set<uint64_t> &Addrs) {
   std::map<FileLoc, std::string> Fns;
 
   auto Symbolizer(createSymbolizer());
 
   // Fill in Fns map.
   for (auto Addr : Addrs) {
-    auto InliningInfo = Symbolizer->symbolizeInlinedCode(ClBinaryName, Addr);
+    auto InliningInfo = Symbolizer->symbolizeInlinedCode(ObjectFile, Addr);
     FailIfError(InliningInfo);
     for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) {
       auto FrameInfo = InliningInfo->getFrame(I);
@@ -203,8 +206,9 @@ computeFunctionsMap(const std::set<uint6
 
 // 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);
+std::set<FunctionLoc> computeFunctionLocs(std::string ObjectFile,
+                                          const std::set<uint64_t> &Addrs) {
+  std::map<FileLoc, std::string> Fns = computeFunctionsMap(ObjectFile, Addrs);
 
   std::set<FunctionLoc> Result;
   std::string LastFileName;
@@ -248,7 +252,7 @@ findSanitizerCovFunctions(const object::
   }
 
   if (Result.empty())
-    FailIfNotEmpty("__sanitizer_cov* functions not found");
+    Fail("__sanitizer_cov* functions not found");
 
   return Result;
 }
@@ -436,14 +440,14 @@ static std::string escapeHtml(const std:
 
 // Computes a map file_name->{line_number}
 static std::map<std::string, std::set<int>>
-getFileLines(const std::set<uint64_t> &Addrs) {
+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(ClBinaryName, Addr);
+    auto InliningInfo = Symbolizer->symbolizeInlinedCode(ObjectFile, Addr);
     FailIfError(InliningInfo);
     for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) {
       auto FrameInfo = InliningInfo->getFrame(I);
@@ -456,44 +460,58 @@ getFileLines(const std::set<uint64_t> &A
   return FileLines;
 }
 
+static ErrorOr<bool> isCoverageFile(std::string FileName) {
+  ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
+      MemoryBuffer::getFile(FileName);
+  if (!BufOrErr)
+    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;
+}
+
 class CoverageData {
  public:
   // Read single file coverage data.
-  static ErrorOr<std::unique_ptr<CoverageData>> read(std::string FileName) {
-    ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
-        MemoryBuffer::getFile(FileName);
-    if (!BufOrErr)
-      return BufOrErr.getError();
-    std::unique_ptr<MemoryBuffer> Buf = std::move(BufOrErr.get());
-    if (Buf->getBufferSize() < 8) {
-      errs() << "File too small (<8): " << Buf->getBufferSize();
-      return make_error_code(errc::illegal_byte_sequence);
-    }
-    const FileHeader *Header =
-        reinterpret_cast<const FileHeader *>(Buf->getBufferStart());
-
-    if (Header->Magic != BinCoverageMagic) {
-      errs() << "Wrong magic: " << Header->Magic;
-      return make_error_code(errc::illegal_byte_sequence);
-    }
-
-    auto Addrs = llvm::make_unique<std::set<uint64_t>>();
-
-    switch (Header->Bitness) {
-    case Bitness64:
-      readInts<uint64_t>(Buf->getBufferStart() + 8, Buf->getBufferEnd(),
-                         Addrs.get());
-      break;
-    case Bitness32:
-      readInts<uint32_t>(Buf->getBufferStart() + 8, Buf->getBufferEnd(),
-                         Addrs.get());
-      break;
-    default:
-      errs() << "Unsupported bitness: " << Header->Bitness;
-      return make_error_code(errc::illegal_byte_sequence);
-    }
+   static ErrorOr<std::unique_ptr<CoverageData>> read(std::string FileName) {
+     ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
+         MemoryBuffer::getFile(FileName);
+     if (!BufOrErr)
+       return BufOrErr.getError();
+     std::unique_ptr<MemoryBuffer> Buf = std::move(BufOrErr.get());
+     if (Buf->getBufferSize() < 8) {
+       errs() << "File too small (<8): " << Buf->getBufferSize();
+       return make_error_code(errc::illegal_byte_sequence);
+     }
+     const FileHeader *Header =
+         reinterpret_cast<const FileHeader *>(Buf->getBufferStart());
+
+     if (Header->Magic != BinCoverageMagic) {
+       errs() << "Wrong magic: " << Header->Magic;
+       return make_error_code(errc::illegal_byte_sequence);
+     }
+
+     auto Addrs = llvm::make_unique<std::set<uint64_t>>();
+
+     switch (Header->Bitness) {
+     case Bitness64:
+       readInts<uint64_t>(Buf->getBufferStart() + 8, Buf->getBufferEnd(),
+                          Addrs.get());
+       break;
+     case Bitness32:
+       readInts<uint32_t>(Buf->getBufferStart() + 8, Buf->getBufferEnd(),
+                          Addrs.get());
+       break;
+     default:
+       errs() << "Unsupported bitness: " << Header->Bitness;
+       return make_error_code(errc::illegal_byte_sequence);
+     }
 
-    return std::unique_ptr<CoverageData>(new CoverageData(std::move(Addrs)));
+     return std::unique_ptr<CoverageData>(new CoverageData(std::move(Addrs)));
   }
 
   // Merge multiple coverage data together.
@@ -529,37 +547,12 @@ class CoverageData {
     }
   }
 
-  void printReport(raw_ostream &OS) {
+  void printReport(std::string ObjectFile, raw_ostream &OS) {
     // file_name -> set of covered lines;
     std::map<std::string, std::set<int>> CoveredFileLines =
-        getFileLines(*Addrs);
+        getFileLines(ObjectFile, *Addrs);
     std::map<std::string, std::set<int>> CoveragePoints =
-        getFileLines(getCoveragePoints(ClBinaryName));
-
-    std::string Title = stripPathPrefix(ClBinaryName) + " Coverage Report";
-
-    OS << "<html>\n";
-    OS << "<head>\n";
-
-    // Stylesheet
-    OS << "<style>\n";
-    OS << ".covered { background: #7F7; }\n";
-    OS << ".notcovered { background: #F77; }\n";
-    OS << "</style>\n";
-    OS << "<title>" << Title << "</title>\n";
-    OS << "</head>\n";
-    OS << "<body>\n";
-
-    // Title
-    OS << "<h1>" << Title << "</h1>\n";
-    OS << "<p>Coverage files: ";
-    for (auto InputFile : ClInputFiles) {
-      llvm::sys::fs::file_status Status;
-      llvm::sys::fs::status(InputFile, Status);
-      OS << stripPathPrefix(InputFile) << " ("
-         << Status.getLastModificationTime().str() << ")";
-    }
-    OS << "</p>\n";
+        getFileLines(ObjectFile, getCoveragePoints(ObjectFile));
 
     // TOC
     OS << "<ul>\n";
@@ -576,7 +569,7 @@ class CoverageData {
       auto Lines = It.second;
       auto CovLines = CoveragePoints[FileName];
 
-      OS << "<a name=\"" << escapeHtml(FileName) << "\"> </a>\n";
+      OS << "<a name=\"" << escapeHtml(FileName) << "\"></a>\n";
       OS << "<h2>" << stripPathPrefix(FileName) << "</h2>\n";
       ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
           MemoryBuffer::getFile(FileName);
@@ -600,23 +593,20 @@ class CoverageData {
       }
       OS << "</pre>\n";
     }
-
-    OS << "</body>\n";
-    OS << "</html>\n";
   }
 
   // Print list of covered functions.
   // Line format: <file_name>:<line> <function_name>
-  void printCoveredFunctions(raw_ostream &OS) {
-    printFunctionLocs(computeFunctionLocs(*Addrs), OS);
+  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(raw_ostream &OS) {
+  void printNotCoveredFunctions(std::string ObjectFile, raw_ostream &OS) {
     std::set<FunctionLoc> AllFns =
-        computeFunctionLocs(getCoveragePoints(ClBinaryName));
-    std::set<FunctionLoc> CoveredFns = computeFunctionLocs(*Addrs);
+        computeFunctionLocs(ObjectFile, getCoveragePoints(ObjectFile));
+    std::set<FunctionLoc> CoveredFns = computeFunctionLocs(ObjectFile, *Addrs);
 
     std::set<FunctionLoc> NotCoveredFns;
     std::set_difference(AllFns.begin(), AllFns.end(), CoveredFns.begin(),
@@ -631,6 +621,185 @@ private:
 
   std::unique_ptr<std::set<uint64_t>> Addrs;
 };
+
+// Holder for coverage data + filename of corresponding object file.
+class CoverageDataWithObjectFile {
+public:
+  static ErrorOr<std::unique_ptr<CoverageDataWithObjectFile>>
+  readAndMerge(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; }
+
+  void printCoveredFunctions(raw_ostream &OS) const {
+    Coverage->printCoveredFunctions(ObjectFile, OS);
+  }
+
+  void printNotCoveredFunctions(raw_ostream &OS) const {
+    Coverage->printNotCoveredFunctions(ObjectFile, OS);
+  }
+
+  void printReport(raw_ostream &OS) const {
+    Coverage->printReport(ObjectFile, OS);
+  }
+
+private:
+  CoverageDataWithObjectFile(std::string ObjectFile,
+                             std::unique_ptr<CoverageData> Coverage)
+      : ObjectFile(std::move(ObjectFile)), Coverage(std::move(Coverage)) {}
+
+  const std::string ObjectFile;
+  const std::unique_ptr<CoverageData> Coverage;
+};
+
+// 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);
+      FailIfError(ErrorOrIsCoverage);
+      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;
+      }
+    }
+
+    // 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) {
+      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);
+      }
+      auto ObjectFile = Iter->second;
+      CoverageByObjFile[ObjectFile].push_back(FileName);
+    }
+
+    // Read coverage.
+    std::vector<std::unique_ptr<CoverageDataWithObjectFile>> MergedCoverage;
+    for (const auto &Pair : CoverageByObjFile) {
+      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 printReport(raw_ostream &OS) const {
+    std::string Title = stripPathPrefix(MainObjFile) + " Coverage Report";
+
+    OS << "<html>\n";
+    OS << "<head>\n";
+
+    // Stylesheet
+    OS << "<style>\n";
+    OS << ".covered { background: #7F7; }\n";
+    OS << ".notcovered { background: #F77; }\n";
+    OS << "</style>\n";
+    OS << "<title>" << Title << "</title>\n";
+    OS << "</head>\n";
+    OS << "<body>\n";
+
+    // 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) {
+      for (const auto &CovData : Coverage) {
+        OS << "<li><a href=\"#module_" << escapeHtml(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_" << escapeHtml(CovData->object_file())
+         << "\"></a>\n";
+      CovData->printReport(OS);
+    }
+
+    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) {
@@ -645,27 +814,35 @@ 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;
   }
+
+  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: {
-    CovData.get()->printReport(outs());
+    CovDataSet.get()->printReport(outs());
     return 0;
   }
+  case PrintAction:
+    llvm_unreachable("unsupported action");
   }
-
-  llvm_unreachable("unsupported action");
 }




More information about the llvm-commits mailing list