[llvm] r282639 - [sancov] introducing symbolized coverage files (.symcov)

Mike Aizatsky via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 28 14:39:29 PDT 2016


Author: aizatsky
Date: Wed Sep 28 16:39:28 2016
New Revision: 282639

URL: http://llvm.org/viewvc/llvm-project?rev=282639&view=rev
Log:
[sancov] introducing symbolized coverage files (.symcov)

Summary:
Answering any meaningful questions about .sancov files requires
accessing symbol information from the corresponding binary.

This change introduces a separate intermediate data structure and
format: symbolized coverage. It contains all symbol information that
is required to answer common queries:
- merging
- coverd/uncovered files and functions
- line status.

Also removing the html report functionality from sancov: generated
HTML files are too huge, and a different approach is required.
Maintaining this half-working approach in the C++ is painful.

Differential Revision: https://reviews.llvm.org/D24947

Added:
    llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.0.symcov
    llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.1.symcov
    llvm/trunk/test/tools/sancov/merge.test
    llvm/trunk/test/tools/sancov/symbolize.test
Removed:
    llvm/trunk/test/tools/sancov/html-report.test
Modified:
    llvm/trunk/include/llvm/DebugInfo/DIContext.h
    llvm/trunk/tools/sancov/sancov.cc

Modified: llvm/trunk/include/llvm/DebugInfo/DIContext.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/DebugInfo/DIContext.h?rev=282639&r1=282638&r2=282639&view=diff
==============================================================================
--- llvm/trunk/include/llvm/DebugInfo/DIContext.h (original)
+++ llvm/trunk/include/llvm/DebugInfo/DIContext.h Wed Sep 28 16:39:28 2016
@@ -42,6 +42,10 @@ struct DILineInfo {
   bool operator!=(const DILineInfo &RHS) const {
     return !(*this == RHS);
   }
+  bool operator<(const DILineInfo &RHS) const {
+    return std::tie(FileName, FunctionName, Line, Column) <
+           std::tie(RHS.FileName, RHS.FunctionName, RHS.Line, RHS.Column);
+  }
 };
 
 typedef SmallVector<std::pair<uint64_t, DILineInfo>, 16> DILineInfoTable;

Added: llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.0.symcov
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.0.symcov?rev=282639&view=auto
==============================================================================
--- llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.0.symcov (added)
+++ llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.0.symcov Wed Sep 28 16:39:28 2016
@@ -0,0 +1,25 @@
+{
+  "covered-points" : ["4e132b", "4e1472", "4e1520", "4e1553", "4e1586"],
+  "binary-hash" : "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5",
+  "point-symbol-info" : {
+    "test/tools/sancov/Inputs/foo.cpp" : {
+      "foo()" : {
+        "4e178c" : "5:0"
+      }
+    },
+    "test/tools/sancov/Inputs/test.cpp" : {
+      "bar(std::string)" : {
+        "4e132b" : "12:0"
+      },
+      "main" : {
+        "4e1472" : "14:0",
+        "4e14c2" : "16:9",
+        "4e1520" : "17:5",
+        "4e1553" : "17:5",
+        "4e1586" : "17:5",
+        "4e1635" : "19:1",
+        "4e1690" : "17:5"
+      }
+    }
+  }
+}

Added: llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.1.symcov
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.1.symcov?rev=282639&view=auto
==============================================================================
--- llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.1.symcov (added)
+++ llvm/trunk/test/tools/sancov/Inputs/test-linux_x86_64.1.symcov Wed Sep 28 16:39:28 2016
@@ -0,0 +1,25 @@
+{
+  "covered-points" : ["4e132b", "4e1472", "4e14c2", "4e1520", "4e1553", "4e1586", "4e178c"],
+  "binary-hash" : "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5",
+  "point-symbol-info" : {
+    "test/tools/sancov/Inputs/foo.cpp" : {
+      "foo()" : {
+        "4e178c" : "5:0"
+      }
+    },
+    "test/tools/sancov/Inputs/test.cpp" : {
+      "bar(std::string)" : {
+        "4e132b" : "12:0"
+      },
+      "main" : {
+        "4e1472" : "14:0",
+        "4e14c2" : "16:9",
+        "4e1520" : "17:5",
+        "4e1553" : "17:5",
+        "4e1586" : "17:5",
+        "4e1635" : "19:1",
+        "4e1690" : "17:5"
+      }
+    }
+  }
+}

Removed: llvm/trunk/test/tools/sancov/html-report.test
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/html-report.test?rev=282638&view=auto
==============================================================================
--- llvm/trunk/test/tools/sancov/html-report.test (original)
+++ llvm/trunk/test/tools/sancov/html-report.test (removed)
@@ -1,6 +0,0 @@
-REQUIRES: x86_64-linux
-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: {{.*/Inputs/test.cpp}}
-

Added: llvm/trunk/test/tools/sancov/merge.test
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/merge.test?rev=282639&view=auto
==============================================================================
--- llvm/trunk/test/tools/sancov/merge.test (added)
+++ llvm/trunk/test/tools/sancov/merge.test Wed Sep 28 16:39:28 2016
@@ -0,0 +1,64 @@
+REQUIRES: x86_64-linux
+RUN: sancov -merge %p/Inputs/test-linux_x86_64.0.symcov| FileCheck --check-prefix=MERGE1 %s
+RUN: sancov -merge %p/Inputs/test-linux_x86_64.0.symcov %p/Inputs/test-linux_x86_64.1.symcov| FileCheck --check-prefix=MERGE2 %s
+
+MERGE1: {
+MERGE1-NEXT:   "covered-points" : ["4e132b", "4e1472", "4e1520", "4e1553", "4e1586"],
+MERGE1-NEXT:   "binary-hash" : "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5",
+MERGE1-NEXT:   "point-symbol-info" : {
+MERGE1-NEXT:     "test/tools/sancov/Inputs/foo.cpp" : {
+MERGE1-NEXT:       "foo()" : {
+MERGE1-NEXT:         "4e178c" : "5:0"
+MERGE1-NEXT:       }
+MERGE1-NEXT:     },
+MERGE1-NEXT:     "test/tools/sancov/Inputs/test.cpp" : {
+MERGE1-NEXT:       "bar(std::string)" : {
+MERGE1-NEXT:         "4e132b" : "12:0"
+MERGE1-NEXT:       },
+MERGE1-NEXT:       "main" : {
+MERGE1-NEXT:         "4e1472" : "14:0",
+MERGE1-NEXT:         "4e14c2" : "16:9",
+MERGE1-NEXT:         "4e1520" : "17:5",
+MERGE1-NEXT:         "4e1553" : "17:5",
+MERGE1-NEXT:         "4e1586" : "17:5",
+MERGE1-NEXT:         "4e1635" : "19:1",
+MERGE1-NEXT:         "4e1690" : "17:5"
+MERGE1-NEXT:       }
+MERGE1-NEXT:     }
+MERGE1-NEXT:   }
+MERGE1-NEXT: }
+
+MERGE2: {
+MERGE2-NEXT:   "covered-points" : ["BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e132b", "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1472", "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e14c2", "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1520", "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1553", "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1586", "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e178c"],
+MERGE2-NEXT:   "point-symbol-info" : {
+MERGE2-NEXT:     "test/tools/sancov/Inputs/foo.cpp" : {
+MERGE2-NEXT:      "foo()" : {
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e178c" : "5:0",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e178c" : "5:0"
+MERGE2-NEXT:       }
+MERGE2-NEXT:     },
+MERGE2-NEXT:     "test/tools/sancov/Inputs/test.cpp" : {
+MERGE2-NEXT:       "bar(std::string)" : {
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e132b" : "12:0",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e132b" : "12:0"
+MERGE2-NEXT:       },
+MERGE2-NEXT:       "main" : {
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1472" : "14:0",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e14c2" : "16:9",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1520" : "17:5",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1553" : "17:5",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1586" : "17:5",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1635" : "19:1",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1690" : "17:5",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1472" : "14:0",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e14c2" : "16:9",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1520" : "17:5",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1553" : "17:5",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1586" : "17:5",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1635" : "19:1",
+MERGE2-NEXT:         "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5:4e1690" : "17:5"
+MERGE2-NEXT:       }
+MERGE2-NEXT:     }
+MERGE2-NEXT:   }
+MERGE2-NEXT: }
+

Added: llvm/trunk/test/tools/sancov/symbolize.test
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/tools/sancov/symbolize.test?rev=282639&view=auto
==============================================================================
--- llvm/trunk/test/tools/sancov/symbolize.test (added)
+++ llvm/trunk/test/tools/sancov/symbolize.test Wed Sep 28 16:39:28 2016
@@ -0,0 +1,29 @@
+REQUIRES: x86_64-linux
+RUN: sancov -symbolize -strip_path_prefix="llvm/" %p/Inputs/test-linux_x86_64 %p/Inputs/test-linux_x86_64.0.sancov | FileCheck %s
+
+CHECK: {
+CHECK-NEXT:  "covered-points" : ["4e132b", "4e1472", "4e1520", "4e1553", "4e1586"],
+CHECK-NEXT:  "binary-hash" : "BB3CDD5045AED83906F6ADCC1C4DAF7E2596A6B5",
+CHECK-NEXT:  "point-symbol-info" : {
+CHECK-NEXT:    "test/tools/sancov/Inputs/foo.cpp" : {
+CHECK-NEXT:      "foo()" : {
+CHECK-NEXT:        "4e178c" : "5:0"
+CHECK-NEXT:     }
+CHECK-NEXT:   },
+CHECK-NEXT:   "test/tools/sancov/Inputs/test.cpp" : {
+CHECK-NEXT:     "bar(std::string)" : {
+CHECK-NEXT:       "4e132b" : "12:0"
+CHECK-NEXT:      },
+CHECK-NEXT:      "main" : {
+CHECK-NEXT:        "4e1472" : "14:0",
+CHECK-NEXT:        "4e14c2" : "16:9",
+CHECK-NEXT:        "4e1520" : "17:5",
+CHECK-NEXT:        "4e1553" : "17:5",
+CHECK-NEXT:        "4e1586" : "17:5",
+CHECK-NEXT:        "4e1635" : "19:1",
+CHECK-NEXT:        "4e1690" : "17:5"
+CHECK-NEXT:      }
+CHECK-NEXT:    }
+CHECK-NEXT:  }
+CHECK-NEXT:}
+

Modified: llvm/trunk/tools/sancov/sancov.cc
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/sancov/sancov.cc?rev=282639&r1=282638&r2=282639&view=diff
==============================================================================
--- llvm/trunk/tools/sancov/sancov.cc (original)
+++ llvm/trunk/tools/sancov/sancov.cc Wed Sep 28 16:39:28 2016
@@ -11,6 +11,7 @@
 // coverage.
 //===----------------------------------------------------------------------===//
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/DebugInfo/Symbolize/Symbolize.h"
 #include "llvm/MC/MCAsmInfo.h"
@@ -41,11 +42,14 @@
 #include "llvm/Support/Path.h"
 #include "llvm/Support/PrettyStackTrace.h"
 #include "llvm/Support/Regex.h"
+#include "llvm/Support/SHA1.h"
 #include "llvm/Support/Signals.h"
+#include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/SpecialCaseList.h"
 #include "llvm/Support/TargetRegistry.h"
 #include "llvm/Support/TargetSelect.h"
 #include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/YAMLParser.h"
 #include "llvm/Support/raw_ostream.h"
 
 #include <algorithm>
@@ -62,28 +66,33 @@ namespace {
 // --------- COMMAND LINE FLAGS ---------
 
 enum ActionType {
-  PrintAction,
-  PrintCovPointsAction,
   CoveredFunctionsAction,
-  NotCoveredFunctionsAction,
   HtmlReportAction,
-  StatsAction
+  MergeAction,
+  NotCoveredFunctionsAction,
+  PrintAction,
+  PrintCovPointsAction,
+  StatsAction,
+  SymbolizeAction
 };
 
 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));
+    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(StatsAction, "print-coverage-stats",
+                   "Print coverage statistics."),
+        clEnumValN(HtmlReportAction, "html-report",
+                   "REMOVED. Use -symbolize & symcov-report-server.py."),
+        clEnumValN(SymbolizeAction, "symbolize",
+                   "Produces a symbolized JSON report from binary report."),
+        clEnumValN(MergeAction, "merge", "Merges reports."), clEnumValEnd));
 
 static cl::list<std::string>
     ClInputFiles(cl::Positional, cl::OneOrMore,
@@ -119,66 +128,99 @@ static const uint32_t BinCoverageMagic =
 static const uint32_t Bitness32 = 0xFFFFFF32;
 static const uint32_t Bitness64 = 0xFFFFFF64;
 
+static Regex SancovFileRegex("(.*)\\.[0-9]+\\.sancov");
+static Regex SymcovFileRegex(".*\\.symcov");
+
+// --------- MAIN DATASTRUCTURES ----------
+
+// Contents of .sancov file: list of coverage point addresses that were
+// executed.
+struct RawCoverage {
+  explicit RawCoverage(std::unique_ptr<std::set<uint64_t>> Addrs)
+      : Addrs(std::move(Addrs)) {}
+
+  // Read binary .sancov file.
+  static ErrorOr<std::unique_ptr<RawCoverage>>
+  read(const std::string &FileName);
+
+  std::unique_ptr<std::set<uint64_t>> Addrs;
+};
+
+// Coverage point has an opaque Id and corresponds to multiple source locations.
+struct CoveragePoint {
+  explicit CoveragePoint(const std::string &Id) : Id(Id) {}
+
+  std::string Id;
+  SmallVector<DILineInfo, 1> Locs;
+};
+
+// Symcov file content: set of covered Ids plus information about all available
+// coverage points.
+struct SymbolizedCoverage {
+  // Read json .symcov file.
+  static std::unique_ptr<SymbolizedCoverage> read(const std::string &InputFile);
+
+  std::set<std::string> CoveredIds;
+  std::string BinaryHash;
+  std::vector<CoveragePoint> Points;
+};
+
+struct CoverageStats {
+  size_t AllPoints;
+  size_t CovPoints;
+  size_t AllFns;
+  size_t CovFns;
+};
+
 // --------- ERROR HANDLING ---------
 
-static void Fail(const llvm::Twine &E) {
+static void fail(const llvm::Twine &E) {
   errs() << "Error: " << E << "\n";
   exit(1);
 }
 
-static void FailIfError(std::error_code Error) {
+static void failIf(bool B, const llvm::Twine &E) {
+  if (B)
+    fail(E);
+}
+
+static void failIfError(std::error_code Error) {
   if (!Error)
     return;
   errs() << "Error: " << Error.message() << "(" << Error.value() << ")\n";
   exit(1);
 }
 
-template <typename T> static void FailIfError(const ErrorOr<T> &E) {
-  FailIfError(E.getError());
+template <typename T> static void failIfError(const ErrorOr<T> &E) {
+  failIfError(E.getError());
 }
 
-static void FailIfError(Error Err) {
+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());
+template <typename T> static void failIfError(Expected<T> &E) {
+  failIfError(E.takeError());
 }
 
-static void FailIfNotEmpty(const llvm::Twine &E) {
+static void failIfNotEmpty(const llvm::Twine &E) {
   if (E.str().empty())
     return;
-  Fail(E);
+  fail(E);
 }
 
 template <typename T>
-static void FailIfEmpty(const std::unique_ptr<T> &Ptr,
+static void failIfEmpty(const std::unique_ptr<T> &Ptr,
                         const std::string &Message) {
   if (Ptr.get())
     return;
-  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;
+  fail(Message);
 }
 
+// ----------- Coverage I/O ----------
 template <typename T>
 static void readInts(const char *Start, const char *End,
                      std::set<uint64_t> *Ints) {
@@ -187,34 +229,321 @@ static void readInts(const char *Start,
   std::copy(S, E, std::inserter(*Ints, Ints->end()));
 }
 
-struct FileLoc {
-  bool operator<(const FileLoc &RHS) const {
-    return std::tie(FileName, Line) < std::tie(RHS.FileName, RHS.Line);
+ErrorOr<std::unique_ptr<RawCoverage>>
+RawCoverage::read(const 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() << '\n';
+    return make_error_code(errc::illegal_byte_sequence);
   }
+  const FileHeader *Header =
+      reinterpret_cast<const FileHeader *>(Buf->getBufferStart());
 
-  std::string FileName;
-  uint32_t Line;
-};
+  if (Header->Magic != BinCoverageMagic) {
+    errs() << "Wrong magic: " << Header->Magic << '\n';
+    return make_error_code(errc::illegal_byte_sequence);
+  }
 
-struct FileFn {
-  bool operator<(const FileFn &RHS) const {
-    return std::tie(FileName, FunctionName) <
-           std::tie(RHS.FileName, RHS.FunctionName);
+  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 << '\n';
+    return make_error_code(errc::illegal_byte_sequence);
   }
 
-  std::string FileName;
-  std::string FunctionName;
-};
+  return std::unique_ptr<RawCoverage>(new RawCoverage(std::move(Addrs)));
+}
 
-struct FnLoc {
-  bool operator<(const FnLoc &RHS) const {
-    return std::tie(Loc, FunctionName) < std::tie(RHS.Loc, RHS.FunctionName);
+// Print coverage addresses.
+raw_ostream &operator<<(raw_ostream &OS, const RawCoverage &CoverageData) {
+  for (auto Addr : *CoverageData.Addrs) {
+    OS << "0x";
+    OS.write_hex(Addr);
+    OS << "\n";
   }
+  return OS;
+}
 
-  FileLoc Loc;
-  std::string FunctionName;
+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;
+}
+
+// Helper for writing out JSON. Handles indents and commas using
+// scope variables for objects and arrays.
+class JSONWriter {
+public:
+  JSONWriter(raw_ostream &Out) : OS(Out) {}
+  JSONWriter(const JSONWriter &) = delete;
+  ~JSONWriter() { OS << "\n"; }
+
+  void operator<<(StringRef S) { printJSONStringLiteral(S, OS); }
+
+  // Helper RAII class to output JSON objects.
+  class Object {
+  public:
+    Object(JSONWriter *W, raw_ostream &OS) : W(W), OS(OS) {
+      OS << "{";
+      W->Indent++;
+    }
+    Object(const Object &) = delete;
+    ~Object() {
+      W->Indent--;
+      OS << "\n";
+      W->indent();
+      OS << "}";
+    }
+
+    void key(StringRef Key) {
+      Index++;
+      if (Index > 0)
+        OS << ",";
+      OS << "\n";
+      W->indent();
+      printJSONStringLiteral(Key, OS);
+      OS << " : ";
+    }
+
+  private:
+    JSONWriter *W;
+    raw_ostream &OS;
+    int Index = -1;
+  };
+
+  std::unique_ptr<Object> object() { return make_unique<Object>(this, OS); }
+
+  // Helper RAII class to output JSON arrays.
+  class Array {
+  public:
+    Array(raw_ostream &OS) : OS(OS) { OS << "["; }
+    Array(const Array &) = delete;
+    ~Array() { OS << "]"; }
+    void next() {
+      Index++;
+      if (Index > 0)
+        OS << ", ";
+    }
+
+  private:
+    raw_ostream &OS;
+    int Index = -1;
+  };
+
+  std::unique_ptr<Array> array() { return make_unique<Array>(OS); }
+
+private:
+  void indent() { OS.indent(Indent * 2); }
+
+  static void printJSONStringLiteral(StringRef S, raw_ostream &OS) {
+    if (S.find('"') == std::string::npos) {
+      OS << "\"" << S << "\"";
+      return;
+    }
+    OS << "\"";
+    for (char Ch : S.bytes()) {
+      if (Ch == '"')
+        OS << "\\";
+      OS << Ch;
+    }
+    OS << "\"";
+  }
+
+  raw_ostream &OS;
+  int Indent = 0;
 };
 
+// Output symbolized information for coverage points in JSON.
+// Format:
+// {
+//   '<file_name>' : {
+//     '<function_name>' : {
+//       '<point_id'> : '<line_number>:'<column_number'.
+//          ....
+//       }
+//    }
+// }
+static void operator<<(JSONWriter &W,
+                       const std::vector<CoveragePoint> &Points) {
+  // Group points by file.
+  auto ByFile(W.object());
+  std::map<std::string, std::vector<const CoveragePoint *>> PointsByFile;
+  for (const auto &Point : Points) {
+    for (const DILineInfo &Loc : Point.Locs) {
+      PointsByFile[Loc.FileName].push_back(&Point);
+    }
+  }
+
+  for (const auto &P : PointsByFile) {
+    std::string FileName = P.first;
+    ByFile->key(FileName);
+
+    // Group points by function.
+    auto ByFn(W.object());
+    std::map<std::string, std::vector<const CoveragePoint *>> PointsByFn;
+    for (auto PointPtr : P.second) {
+      for (const DILineInfo &Loc : PointPtr->Locs) {
+        PointsByFn[Loc.FunctionName].push_back(PointPtr);
+      }
+    }
+
+    for (const auto &P : PointsByFn) {
+      std::string FunctionName = P.first;
+      ByFn->key(FunctionName);
+
+      // Output <point_id> : "<line>:<col>".
+      auto ById(W.object());
+      for (const CoveragePoint *Point : P.second) {
+        for (const auto &Loc : Point->Locs) {
+          if (Loc.FileName != FileName || Loc.FunctionName != FunctionName)
+            continue;
+
+          ById->key(Point->Id);
+          W << (utostr(Loc.Line) + ":" + utostr(Loc.Column));
+        }
+      }
+    }
+  }
+}
+
+static void operator<<(JSONWriter &W, const SymbolizedCoverage &C) {
+  auto O(W.object());
+
+  {
+    O->key("covered-points");
+    auto PointsArray(W.array());
+
+    for (const auto &P : C.CoveredIds) {
+      PointsArray->next();
+      W << P;
+    }
+  }
+
+  {
+    if (!C.BinaryHash.empty()) {
+      O->key("binary-hash");
+      W << C.BinaryHash;
+    }
+  }
+
+  {
+    O->key("point-symbol-info");
+    W << C.Points;
+  }
+}
+
+static std::string parseScalarString(yaml::Node *N) {
+  SmallString<64> StringStorage;
+  yaml::ScalarNode *S = dyn_cast<yaml::ScalarNode>(N);
+  failIf(!S, "expected string");
+  return S->getValue(StringStorage);
+}
+
+std::unique_ptr<SymbolizedCoverage>
+SymbolizedCoverage::read(const std::string &InputFile) {
+  auto Coverage(make_unique<SymbolizedCoverage>());
+
+  std::map<std::string, CoveragePoint> Points;
+  ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
+      MemoryBuffer::getFile(InputFile);
+  failIfError(BufOrErr);
+
+  SourceMgr SM;
+  yaml::Stream S(**BufOrErr, SM);
+
+  yaml::document_iterator DI = S.begin();
+  failIf(DI == S.end(), "empty document: " + InputFile);
+  yaml::Node *Root = DI->getRoot();
+  failIf(!Root, "expecting root node: " + InputFile);
+  yaml::MappingNode *Top = dyn_cast<yaml::MappingNode>(Root);
+  failIf(!Top, "expecting mapping node: " + InputFile);
+
+  for (auto &KVNode : *Top) {
+    auto Key = parseScalarString(KVNode.getKey());
+
+    if (Key == "covered-points") {
+      yaml::SequenceNode *Points =
+          dyn_cast<yaml::SequenceNode>(KVNode.getValue());
+      failIf(!Points, "expected array: " + InputFile);
+
+      for (auto I = Points->begin(), E = Points->end(); I != E; ++I) {
+        Coverage->CoveredIds.insert(parseScalarString(&*I));
+      }
+    } else if (Key == "binary-hash") {
+      Coverage->BinaryHash = parseScalarString(KVNode.getValue());
+    } else if (Key == "point-symbol-info") {
+      yaml::MappingNode *PointSymbolInfo =
+          dyn_cast<yaml::MappingNode>(KVNode.getValue());
+      failIf(!PointSymbolInfo, "expected mapping node: " + InputFile);
+
+      for (auto &FileKVNode : *PointSymbolInfo) {
+        auto Filename = parseScalarString(FileKVNode.getKey());
+
+        yaml::MappingNode *FileInfo =
+            dyn_cast<yaml::MappingNode>(FileKVNode.getValue());
+        failIf(!FileInfo, "expected mapping node: " + InputFile);
+
+        for (auto &FunctionKVNode : *FileInfo) {
+          auto FunctionName = parseScalarString(FunctionKVNode.getKey());
+
+          yaml::MappingNode *FunctionInfo =
+              dyn_cast<yaml::MappingNode>(FunctionKVNode.getValue());
+          failIf(!FunctionInfo, "expected mapping node: " + InputFile);
+
+          for (auto &PointKVNode : *FunctionInfo) {
+            auto PointId = parseScalarString(PointKVNode.getKey());
+            auto Loc = parseScalarString(PointKVNode.getValue());
+
+            size_t ColonPos = Loc.find(':');
+            failIf(ColonPos == std::string::npos, "expected ':': " + InputFile);
+
+            auto LineStr = Loc.substr(0, ColonPos);
+            auto ColStr = Loc.substr(ColonPos + 1, Loc.size());
+
+            if (Points.find(PointId) == Points.end())
+              Points.insert(std::make_pair(PointId, CoveragePoint(PointId)));
+
+            DILineInfo LineInfo;
+            LineInfo.FileName = Filename;
+            LineInfo.FunctionName = FunctionName;
+            char *End;
+            LineInfo.Line = std::strtoul(LineStr.c_str(), &End, 10);
+            LineInfo.Column = std::strtoul(ColStr.c_str(), &End, 10);
+
+            CoveragePoint *CoveragePoint = &Points.find(PointId)->second;
+            CoveragePoint->Locs.push_back(LineInfo);
+          }
+        }
+      }
+    } else {
+      errs() << "Ignoring unknown key: " << Key << "\n";
+    }
+  }
+
+  for (auto &KV : Points) {
+    Coverage->Points.push_back(KV.second);
+  }
+
+  return Coverage;
+}
+
+// ---------- MAIN FUNCTIONALITY ----------
+
 std::string stripPathPrefix(std::string Path) {
   if (ClStripPathPrefix.empty())
     return Path;
@@ -232,21 +561,11 @@ static std::unique_ptr<symbolize::LLVMSy
       new symbolize::LLVMSymbolizer(SymbolizerOptions));
 }
 
-// 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);
-  }
-
-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();
-  }
-};
+static std::string normalizeFilename(const std::string &FileName) {
+  SmallString<256> S(FileName);
+  sys::path::remove_dots(S, /* remove_dot_dot */ true);
+  return stripPathPrefix(S.str().str());
+}
 
 class Blacklists {
 public:
@@ -254,16 +573,14 @@ public:
       : 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))
+  bool isBlacklisted(const DILineInfo &I) {
+    if (DefaultBlacklist && DefaultBlacklist->inSection("fun", I.FunctionName))
       return true;
-    if (DefaultBlacklist && DefaultBlacklist->inSection("src", AI.FileName))
+    if (DefaultBlacklist && DefaultBlacklist->inSection("src", I.FileName))
       return true;
-    if (UserBlacklist && UserBlacklist->inSection("fun", AI.FunctionName))
+    if (UserBlacklist && UserBlacklist->inSection("fun", I.FunctionName))
       return true;
-    if (UserBlacklist && UserBlacklist->inSection("src", AI.FileName))
+    if (UserBlacklist && UserBlacklist->inSection("src", I.FileName))
       return true;
     return false;
   }
@@ -276,7 +593,7 @@ private:
         MemoryBuffer::getMemBuffer(DefaultBlacklistStr);
     std::string Error;
     auto Blacklist = SpecialCaseList::create(MB.get(), Error);
-    FailIfNotEmpty(Error);
+    failIfNotEmpty(Error);
     return Blacklist;
   }
 
@@ -290,32 +607,43 @@ private:
   std::unique_ptr<SpecialCaseList> UserBlacklist;
 };
 
-// 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;
+static std::vector<CoveragePoint>
+getCoveragePoints(const std::string &ObjectFile,
+                  const std::set<uint64_t> &Addrs, bool InlinedCode) {
+  std::vector<CoveragePoint> Result;
   auto Symbolizer(createSymbolizer());
   Blacklists B;
 
   for (auto Addr : Addrs) {
+    std::set<DILineInfo> Infos; // deduplicate debug info.
+
     auto LineInfo = Symbolizer->symbolizeCode(ObjectFile, Addr);
-    FailIfError(LineInfo);
-    auto LineAddrInfo = AddrInfo(*LineInfo, Addr);
-    if (B.isBlacklisted(LineAddrInfo))
+    failIfError(LineInfo);
+    LineInfo->FileName = normalizeFilename(LineInfo->FileName);
+    if (B.isBlacklisted(*LineInfo))
       continue;
-    Result.push_back(LineAddrInfo);
+
+    auto Id = utohexstr(Addr, true);
+    auto Point = CoveragePoint(Id);
+    Infos.insert(*LineInfo);
+    Point.Locs.push_back(*LineInfo);
+
     if (InlinedCode) {
       auto InliningInfo = Symbolizer->symbolizeInlinedCode(ObjectFile, Addr);
-      FailIfError(InliningInfo);
+      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))
+        FrameInfo.FileName = normalizeFilename(FrameInfo.FileName);
+        if (B.isBlacklisted(FrameInfo))
           continue;
-        Result.push_back(FrameAddrInfo);
+        if (Infos.find(FrameInfo) == Infos.end()) {
+          Infos.insert(FrameInfo);
+          Point.Locs.push_back(FrameInfo);
+        }
       }
     }
+
+    Result.push_back(Point);
   }
 
   return Result;
@@ -353,7 +681,7 @@ static void findMachOIndirectCovFunction
             if (IndirectSymbol < Symtab.nsyms) {
               object::SymbolRef Symbol = *(O.getSymbolByIndex(IndirectSymbol));
               Expected<StringRef> Name = Symbol.getName();
-              FailIfError(Name);
+              failIfError(Name);
               if (isCoveragePointSymbol(Name.get())) {
                 Result->insert(Addr);
               }
@@ -376,11 +704,11 @@ findSanitizerCovFunctions(const object::
 
   for (const object::SymbolRef &Symbol : O.symbols()) {
     Expected<uint64_t> AddressOrErr = Symbol.getAddress();
-    FailIfError(AddressOrErr);
+    failIfError(AddressOrErr);
     uint64_t Address = AddressOrErr.get();
 
     Expected<StringRef> NameOrErr = Symbol.getName();
-    FailIfError(NameOrErr);
+    failIfError(NameOrErr);
     StringRef Name = NameOrErr.get();
 
     if (!(Symbol.getFlags() & object::BasicSymbolRef::SF_Undefined) &&
@@ -394,11 +722,11 @@ findSanitizerCovFunctions(const object::
          CO->export_directories()) {
       uint32_t RVA;
       std::error_code EC = Export.getExportRVA(RVA);
-      FailIfError(EC);
+      failIfError(EC);
 
       StringRef Name;
       EC = Export.getSymbolName(Name);
-      FailIfError(EC);
+      failIfError(EC);
 
       if (isCoveragePointSymbol(Name))
         Result.insert(CO->getImageBase() + RVA);
@@ -423,36 +751,36 @@ static void getObjectCoveragePoints(cons
 
   std::string Error;
   const Target *TheTarget = TargetRegistry::lookupTarget(TripleName, Error);
-  FailIfNotEmpty(Error);
+  failIfNotEmpty(Error);
 
   std::unique_ptr<const MCSubtargetInfo> STI(
       TheTarget->createMCSubtargetInfo(TripleName, "", ""));
-  FailIfEmpty(STI, "no subtarget info for target " + TripleName);
+  failIfEmpty(STI, "no subtarget info for target " + TripleName);
 
   std::unique_ptr<const MCRegisterInfo> MRI(
       TheTarget->createMCRegInfo(TripleName));
-  FailIfEmpty(MRI, "no register info for target " + TripleName);
+  failIfEmpty(MRI, "no register info for target " + TripleName);
 
   std::unique_ptr<const MCAsmInfo> AsmInfo(
       TheTarget->createMCAsmInfo(*MRI, TripleName));
-  FailIfEmpty(AsmInfo, "no asm info for target " + TripleName);
+  failIfEmpty(AsmInfo, "no asm info for target " + TripleName);
 
   std::unique_ptr<const MCObjectFileInfo> MOFI(new MCObjectFileInfo);
   MCContext Ctx(AsmInfo.get(), MRI.get(), MOFI.get());
   std::unique_ptr<MCDisassembler> DisAsm(
       TheTarget->createMCDisassembler(*STI, Ctx));
-  FailIfEmpty(DisAsm, "no disassembler info for target " + TripleName);
+  failIfEmpty(DisAsm, "no disassembler info for target " + TripleName);
 
   std::unique_ptr<const MCInstrInfo> MII(TheTarget->createMCInstrInfo());
-  FailIfEmpty(MII, "no instruction info for target " + TripleName);
+  failIfEmpty(MII, "no instruction info for target " + TripleName);
 
   std::unique_ptr<const MCInstrAnalysis> MIA(
       TheTarget->createMCInstrAnalysis(MII.get()));
-  FailIfEmpty(MIA, "no instruction analysis info for target " + TripleName);
+  failIfEmpty(MIA, "no instruction analysis info for target " + TripleName);
 
   auto SanCovAddrs = findSanitizerCovFunctions(O);
   if (SanCovAddrs.empty())
-    Fail("__sanitizer_cov* functions not found");
+    fail("__sanitizer_cov* functions not found");
 
   for (object::SectionRef Section : O.sections()) {
     if (Section.isVirtual() || !Section.isText()) // llvm-objdump does the same.
@@ -463,7 +791,7 @@ static void getObjectCoveragePoints(cons
       continue;
 
     StringRef BytesStr;
-    FailIfError(Section.getContents(BytesStr));
+    failIfError(Section.getContents(BytesStr));
     ArrayRef<uint8_t> Bytes(reinterpret_cast<const uint8_t *>(BytesStr.data()),
                             BytesStr.size());
 
@@ -494,13 +822,13 @@ visitObjectFiles(const object::Archive &
   Error Err;
   for (auto &C : A.children(Err)) {
     Expected<std::unique_ptr<object::Binary>> ChildOrErr = C.getAsBinary();
-    FailIfError(ChildOrErr);
+    failIfError(ChildOrErr);
     if (auto *O = dyn_cast<object::ObjectFile>(&*ChildOrErr.get()))
       Fn(*O);
     else
-      FailIfError(object::object_error::invalid_file_type);
+      failIfError(object::object_error::invalid_file_type);
   }
-  FailIfError(std::move(Err));
+  failIfError(std::move(Err));
 }
 
 static void
@@ -509,7 +837,7 @@ visitObjectFiles(const std::string &File
   Expected<object::OwningBinary<object::Binary>> BinaryOrErr =
       object::createBinary(FileName);
   if (!BinaryOrErr)
-    FailIfError(BinaryOrErr);
+    failIfError(BinaryOrErr);
 
   object::Binary &Binary = *BinaryOrErr.get().getBinary();
   if (object::Archive *A = dyn_cast<object::Archive>(&Binary))
@@ -517,10 +845,11 @@ visitObjectFiles(const std::string &File
   else if (object::ObjectFile *O = dyn_cast<object::ObjectFile>(&Binary))
     Fn(*O);
   else
-    FailIfError(object::object_error::invalid_file_type);
+    failIfError(object::object_error::invalid_file_type);
 }
 
-std::set<uint64_t> findSanitizerCovFunctions(const std::string &FileName) {
+static std::set<uint64_t>
+findSanitizerCovFunctions(const std::string &FileName) {
   std::set<uint64_t> Result;
   visitObjectFiles(FileName, [&](const object::ObjectFile &O) {
     auto Addrs = findSanitizerCovFunctions(O);
@@ -532,7 +861,7 @@ std::set<uint64_t> findSanitizerCovFunct
 // 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) {
+static std::set<uint64_t> findCoveragePointAddrs(const std::string &FileName) {
   std::set<uint64_t> Result;
   visitObjectFiles(FileName, [&](const object::ObjectFile &O) {
     getObjectCoveragePoints(O, &Result);
@@ -541,66 +870,18 @@ std::set<uint64_t> getCoveragePoints(con
 }
 
 static void printCovPoints(const std::string &ObjFile, raw_ostream &OS) {
-  for (uint64_t Addr : getCoveragePoints(ObjFile)) {
+  for (uint64_t Addr : findCoveragePointAddrs(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;
-}
-
-// 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));
-
-  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 std::string anchorName(const std::string &Anchor) {
-  llvm::MD5 Hasher;
-  llvm::MD5::MD5Result Hash;
-  Hasher.update(Anchor);
-  Hasher.final(Hash);
-
-  SmallString<32> HexString;
-  llvm::MD5::stringifyResult(Hash, HexString);
-  return HexString.str().str();
-}
-
 static ErrorOr<bool> isCoverageFile(const std::string &FileName) {
+  auto ShortFileName = llvm::sys::path::filename(FileName);
+  if (!SancovFileRegex.match(ShortFileName))
+    return false;
+
   ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
       MemoryBuffer::getFile(FileName);
   if (!BufOrErr) {
@@ -618,490 +899,187 @@ static ErrorOr<bool> isCoverageFile(cons
   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;
+static bool isSymbolizedCoverageFile(const std::string &FileName) {
+  auto ShortFileName = llvm::sys::path::filename(FileName);
+  return SymcovFileRegex.match(ShortFileName);
 }
 
-class CoverageData {
-public:
-  // Read single file coverage data.
-  static ErrorOr<std::unique_ptr<CoverageData>>
-  read(const 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() << '\n';
-      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 << '\n';
-      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 << '\n';
-      return make_error_code(errc::illegal_byte_sequence);
-    }
-
-    return std::unique_ptr<CoverageData>(new CoverageData(std::move(Addrs)));
-  }
-
-  // Merge multiple coverage data together.
-  static std::unique_ptr<CoverageData>
-  merge(const std::vector<std::unique_ptr<CoverageData>> &Covs) {
-    auto Addrs = llvm::make_unique<std::set<uint64_t>>();
-
-    for (const auto &Cov : Covs)
-      Addrs->insert(Cov->Addrs->begin(), Cov->Addrs->end());
-
-    return std::unique_ptr<CoverageData>(new CoverageData(std::move(Addrs)));
-  }
-
-  // Read list of files and merges their coverage info.
-  static ErrorOr<std::unique_ptr<CoverageData>>
-  readAndMerge(const std::vector<std::string> &FileNames) {
-    std::vector<std::unique_ptr<CoverageData>> Covs;
-    for (const auto &FileName : FileNames) {
-      auto Cov = read(FileName);
-      if (!Cov)
-        return Cov.getError();
-      Covs.push_back(std::move(Cov.get()));
-    }
-    return merge(Covs);
-  }
+static std::unique_ptr<SymbolizedCoverage>
+symbolize(const RawCoverage &Data, const std::string ObjectFile) {
+  auto Coverage = make_unique<SymbolizedCoverage>();
 
-  // Print coverage addresses.
-  void printAddrs(raw_ostream &OS) {
-    for (auto Addr : *Addrs) {
-      OS << "0x";
-      OS.write_hex(Addr);
-      OS << "\n";
-    }
+  ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
+      MemoryBuffer::getFile(ObjectFile);
+  failIfError(BufOrErr);
+  SHA1 Hasher;
+  Hasher.update((*BufOrErr)->getBuffer());
+  Coverage->BinaryHash = toHex(Hasher.final());
+
+  for (uint64_t Addr : *Data.Addrs) {
+    Coverage->CoveredIds.insert(utohexstr(Addr, true));
+  }
+
+  std::set<uint64_t> AllAddrs = findCoveragePointAddrs(ObjectFile);
+  if (!std::includes(AllAddrs.begin(), AllAddrs.end(), Data.Addrs->begin(),
+                     Data.Addrs->end())) {
+    fail("Coverage points in binary and .sancov file do not match.");
   }
+  Coverage->Points = getCoveragePoints(ObjectFile, AllAddrs, true);
+  return Coverage;
+}
 
-protected:
-  explicit CoverageData(std::unique_ptr<std::set<uint64_t>> Addrs)
-      : Addrs(std::move(Addrs)) {}
-
-  friend class CoverageDataWithObjectFile;
+struct FileFn {
+  bool operator<(const FileFn &RHS) const {
+    return std::tie(FileName, FunctionName) <
+           std::tie(RHS.FileName, RHS.FunctionName);
+  }
 
-  std::unique_ptr<std::set<uint64_t>> Addrs;
+  std::string FileName;
+  std::string FunctionName;
 };
 
-// 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;
-      }
+static std::set<FileFn>
+computeFunctions(const std::vector<CoveragePoint> &Points) {
+  std::set<FileFn> Fns;
+  for (const auto &Point : Points) {
+    for (const auto &Loc : Point.Locs) {
+      Fns.insert(FileFn{Loc.FileName, Loc.FunctionName});
     }
-    return StatusMap;
   }
+  return Fns;
+}
 
-  std::set<FileFn> computeAllFunctions() const {
-    std::set<FileFn> Fns;
-    for (const auto &AI : AllAddrInfo) {
-      Fns.insert(FileFn{AI.FileName, AI.FunctionName});
-    }
-    return Fns;
-  }
+static std::set<FileFn>
+computeNotCoveredFunctions(const SymbolizedCoverage &Coverage) {
+  auto Fns = computeFunctions(Coverage.Points);
 
-  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 &Point : Coverage.Points) {
+    if (Coverage.CoveredIds.find(Point.Id) == Coverage.CoveredIds.end())
+      continue;
 
-    for (const auto &P : CovFns) {
-      Fns.insert(P.first);
+    for (const auto &Loc : Point.Locs) {
+      Fns.erase(FileFn{Loc.FileName, Loc.FunctionName});
     }
-    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;
-  }
+  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};
-    });
+static std::set<FileFn>
+computeCoveredFunctions(const SymbolizedCoverage &Coverage) {
+  auto AllFns = computeFunctions(Coverage.Points);
+  std::set<FileFn> Result;
 
-    auto CovFns = group_by(CovAddrInfo, [](const AddrInfo &AI) {
-      return FileFn{AI.FileName, AI.FunctionName};
-    });
+  for (const auto &Point : Coverage.Points) {
+    if (Coverage.CoveredIds.find(Point.Id) == Coverage.CoveredIds.end())
+      continue;
 
-    for (const auto &P : AllFns) {
-      FileFn F = P.first;
-      FnCoverage[F] = CovFns[F].size() * 100 / P.second.size();
+    for (const auto &Loc : Point.Locs) {
+      Result.insert(FileFn{Loc.FileName, Loc.FunctionName});
     }
-
-    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;
+  return Result;
+}
 
-      if (!ProcessedFunctions.insert(FunctionName).second)
+typedef std::map<FileFn, std::pair<uint32_t, uint32_t>> FunctionLocs;
+// finds first location in a file for each function.
+static FunctionLocs resolveFunctions(const SymbolizedCoverage &Coverage,
+                                     const std::set<FileFn> &Fns) {
+  FunctionLocs Result;
+  for (const auto &Point : Coverage.Points) {
+    for (const auto &Loc : Point.Locs) {
+      FileFn Fn = FileFn{Loc.FileName, Loc.FunctionName};
+      if (Fns.find(Fn) == Fns.end())
         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);
+      auto P = std::make_pair(Loc.Line, Loc.Column);
+      auto I = Result.find(Fn);
+      if (I == Result.end() || I->second > P) {
+        Result[Fn] = P;
+      }
     }
-    return Files;
-  }
-
-  void collectStats(CoverageStats *Stats) const {
-    Stats->AllPoints += AllCovPoints.size();
-    Stats->AllFns += computeAllFunctions().size();
-    Stats->CovFns += computeCoveredFunctions().size();
   }
+  return Result;
+}
 
-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";
-    }
+static void printFunctionLocs(const FunctionLocs &FnLocs, raw_ostream &OS) {
+  for (const auto &P : FnLocs) {
+    OS << stripPathPrefix(P.first.FileName) << ":" << P.second.first << " "
+       << P.first.FunctionName << "\n";
+  }
+}
+CoverageStats computeStats(const SymbolizedCoverage &Coverage) {
+  CoverageStats Stats = {Coverage.Points.size(), Coverage.CoveredIds.size(),
+                         computeFunctions(Coverage.Points).size(),
+                         computeCoveredFunctions(Coverage).size()};
+  return Stats;
+}
+
+// Print list of covered functions.
+// Line format: <file_name>:<line> <function_name>
+static void printCoveredFunctions(const SymbolizedCoverage &CovData,
+                                  raw_ostream &OS) {
+  auto CoveredFns = computeCoveredFunctions(CovData);
+  printFunctionLocs(resolveFunctions(CovData, CoveredFns), OS);
+}
+
+// Print list of not covered functions.
+// Line format: <file_name>:<line> <function_name>
+static void printNotCoveredFunctions(const SymbolizedCoverage &CovData,
+                                     raw_ostream &OS) {
+  auto NotCoveredFns = computeNotCoveredFunctions(CovData);
+  printFunctionLocs(resolveFunctions(CovData, NotCoveredFns), OS);
+}
+
+// Read list of files and merges their coverage info.
+static void readAndPrintRawCoverage(const std::vector<std::string> &FileNames,
+                                    raw_ostream &OS) {
+  std::vector<std::unique_ptr<RawCoverage>> Covs;
+  for (const auto &FileName : FileNames) {
+    auto Cov = RawCoverage::read(FileName);
+    if (!Cov)
+      continue;
+    OS << *Cov.get();
   }
 }
 
-// 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) 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) 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;
+static std::unique_ptr<SymbolizedCoverage>
+merge(const std::vector<std::unique_ptr<SymbolizedCoverage>> &Coverages) {
+  auto Result = make_unique<SymbolizedCoverage>();
 
-      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";
+  for (size_t I = 0; I < Coverages.size(); ++I) {
+    const SymbolizedCoverage &Coverage = *Coverages[I];
+    std::string Prefix;
+    if (Coverages.size() > 1) {
+      // prefix is not needed when there's only one file.
+      Prefix =
+          (Coverage.BinaryHash.size() ? Coverage.BinaryHash : utostr(I)) + ":";
     }
 
-    // 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>";
-            };
-          }
-        }
+    for (const auto &Id : Coverage.CoveredIds) {
+      Result->CoveredIds.insert(Prefix + Id);
+    }
 
-        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";
+    for (const auto &CovPoint : Coverage.Points) {
+      CoveragePoint NewPoint(CovPoint);
+      NewPoint.Id = Prefix + CovPoint.Id;
+      Result->Points.push_back(NewPoint);
     }
   }
 
-  void collectStats(CoverageStats *Stats) const {
-    Stats->CovPoints += Addrs->size();
-
-    SourceCoverageData SCovData(ObjectFile, *Addrs);
-    SCovData.collectStats(Stats);
+  if (Coverages.size() == 1) {
+    Result->BinaryHash = Coverages[0]->BinaryHash;
   }
 
-private:
-  CoverageDataWithObjectFile(std::string ObjectFile,
-                             std::unique_ptr<CoverageData> Coverage)
-      : CoverageData(std::move(Coverage->Addrs)),
-        ObjectFile(std::move(ObjectFile)) {}
-  const std::string ObjectFile;
-};
+  return Result;
+}
 
-// Multiple coverage files data organized by object file.
-class CoverageDataSet {
-public:
-  static ErrorOr<std::unique_ptr<CoverageDataSet>>
-  readCmdArguments(std::vector<std::string> FileNames) {
+static std::unique_ptr<SymbolizedCoverage>
+readSymbolizeAndMergeCmdArguments(std::vector<std::string> FileNames) {
+  std::vector<std::unique_ptr<SymbolizedCoverage>> Coverages;
+
+  {
     // Short name => file name.
     std::map<std::string, std::string> ObjFiles;
     std::string FirstObjFile;
@@ -1109,6 +1087,10 @@ public:
 
     // Partition input values into coverage/object files.
     for (const auto &FileName : FileNames) {
+      if (isSymbolizedCoverageFile(FileName)) {
+        Coverages.push_back(SymbolizedCoverage::read(FileName));
+      }
+
       auto ErrorOrIsCoverage = isCoverageFile(FileName);
       if (!ErrorOrIsCoverage)
         continue;
@@ -1117,7 +1099,7 @@ public:
       } else {
         auto ShortFileName = llvm::sys::path::filename(FileName);
         if (ObjFiles.find(ShortFileName) != ObjFiles.end()) {
-          Fail("Duplicate binary file with a short name: " + ShortFileName);
+          fail("Duplicate binary file with a short name: " + ShortFileName);
         }
 
         ObjFiles[ShortFileName] = FileName;
@@ -1126,28 +1108,28 @@ public:
       }
     }
 
-    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) {
+    std::map<std::string, std::vector<std::string>> CoverageByObjFile;
+    for (const auto &FileName : CovFiles) {
       auto ShortFileName = llvm::sys::path::filename(FileName);
-      auto Ok = SancovRegex.match(ShortFileName, &Components);
+      auto Ok = SancovFileRegex.match(ShortFileName, &Components);
       if (!Ok) {
-        Fail("Can't match coverage file name against "
+        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);
+        fail("Object file for coverage not found: " + FileName);
       }
-      return Iter->second;
-    });
 
-    // Read coverage.
-    std::vector<std::unique_ptr<CoverageDataWithObjectFile>> MergedCoverage;
+      CoverageByObjFile[Iter->second].push_back(FileName);
+    };
+
+    // Read raw coverage and symbolize it.
     for (const auto &Pair : CoverageByObjFile) {
       if (findSanitizerCovFunctions(Pair.first).empty()) {
         for (const auto &FileName : Pair.second) {
@@ -1161,132 +1143,34 @@ public:
         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 std::string &CoverageFile : Pair.second) {
+        auto DataOrError = RawCoverage::read(CoverageFile);
+        failIfError(DataOrError);
+        Coverages.push_back(symbolize(*DataOrError.get(), Pair.first));
       }
     }
-
-    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;
-};
+  return merge(Coverages);
+}
 
 } // namespace
 
-int main(int argc, char **argv) {
+int main(int Argc, char **Argv) {
   // Print stack trace if we signal out.
-  sys::PrintStackTraceOnErrorSignal(argv[0]);
-  PrettyStackTraceProgram X(argc, argv);
+  sys::PrintStackTraceOnErrorSignal(Argv[0]);
+  PrettyStackTraceProgram X(Argc, Argv);
   llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
 
   llvm::InitializeAllTargetInfos();
   llvm::InitializeAllTargetMCs();
   llvm::InitializeAllDisassemblers();
 
-  cl::ParseCommandLineOptions(argc, argv, "Sanitizer Coverage Processing Tool");
+  cl::ParseCommandLineOptions(Argc, Argv, "Sanitizer Coverage Processing Tool");
 
   // -print doesn't need object files.
   if (Action == PrintAction) {
-    auto CovData = CoverageData::readAndMerge(ClInputFiles);
-    FailIfError(CovData);
-    CovData.get()->printAddrs(outs());
+    readAndPrintRawCoverage(ClInputFiles, outs());
     return 0;
   } else if (Action == PrintCovPointsAction) {
     // -print-coverage-points doesn't need coverage files.
@@ -1296,30 +1180,32 @@ int main(int argc, char **argv) {
     return 0;
   }
 
-  auto CovDataSet = CoverageDataSet::readCmdArguments(ClInputFiles);
-  FailIfError(CovDataSet);
-
-  if (CovDataSet.get()->empty()) {
-    Fail("No coverage files specified.");
-  }
+  auto Coverage = readSymbolizeAndMergeCmdArguments(ClInputFiles);
+  failIf(!Coverage, "No valid coverage files given.");
 
   switch (Action) {
   case CoveredFunctionsAction: {
-    CovDataSet.get()->printCoveredFunctions(outs());
+    printCoveredFunctions(*Coverage, outs());
     return 0;
   }
   case NotCoveredFunctionsAction: {
-    CovDataSet.get()->printNotCoveredFunctions(outs());
+    printNotCoveredFunctions(*Coverage, outs());
     return 0;
   }
-  case HtmlReportAction: {
-    CovDataSet.get()->printReport(outs());
+  case StatsAction: {
+    outs() << computeStats(*Coverage);
     return 0;
   }
-  case StatsAction: {
-    CovDataSet.get()->printStats(outs());
+  case MergeAction:
+  case SymbolizeAction: { // merge & symbolize are synonims.
+    JSONWriter W(outs());
+    W << *Coverage;
     return 0;
   }
+  case HtmlReportAction:
+    errs() << "-html-report option is removed: "
+              "use -symbolize & symcov-report-server.py instead\n";
+    return 1;
   case PrintAction:
   case PrintCovPointsAction:
     llvm_unreachable("unsupported action");




More information about the llvm-commits mailing list