[llvm-cov] Add an option which maps the location of source directories on another machine to your local copies

Summary:
This patch adds the -path-equivalence option (example: llvm-cov show -path-equivalence=/origin/path,/local/path) which maps the source code path from one machine to another when using `llvm-cov show`. This is similar to the -filename-equivalence option, but doesn't require you to specify all the source files on the command line.

This allows you to generate the coverage data on one machine (e.g. in a CI system), and then use llvm-cov on another machine where you have the same code base on a different path.

Reviewers: vsk

Reviewed By: vsk

Subscribers: llvm-commits

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

llvm-svn: 310827
diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp
index 8afae22..e5878df 100644
--- a/llvm/tools/llvm-cov/CodeCoverage.cpp
+++ b/llvm/tools/llvm-cov/CodeCoverage.cpp
@@ -94,6 +94,10 @@
   /// \brief Load the coverage mapping data. Return nullptr if an error occurred.
   std::unique_ptr<CoverageMapping> load();
 
+  /// \brief Create a mapping from files in the Coverage data to local copies
+  /// (path-equivalence).
+  void remapPathNames(const CoverageMapping &Coverage);
+
   /// \brief Remove input source files which aren't mapped by \p Coverage.
   void removeUnmappedInputs(const CoverageMapping &Coverage);
 
@@ -125,13 +129,14 @@
   /// A list of input source files.
   std::vector<std::string> SourceFiles;
 
-  /// Whether or not we're in -filename-equivalence mode.
-  bool CompareFilenamesOnly;
-
-  /// In -filename-equivalence mode, this maps absolute paths from the
-  /// coverage mapping data to input source files.
+  /// In -path-equivalence mode, this maps the absolute paths from the coverage
+  /// mapping data to the input source files.
   StringMap<std::string> RemappedFilenames;
 
+  /// The coverage data path to be remapped from, and the source path to be
+  /// remapped to, when using -path-equivalence.
+  Optional<std::pair<std::string, std::string>> PathRemapping;
+
   /// The architecture the coverage mapping data targets.
   std::vector<StringRef> CoverageArches;
 
@@ -171,24 +176,20 @@
 }
 
 void CodeCoverageTool::addCollectedPath(const std::string &Path) {
-  if (CompareFilenamesOnly) {
-    SourceFiles.emplace_back(Path);
-  } else {
-    SmallString<128> EffectivePath(Path);
-    if (std::error_code EC = sys::fs::make_absolute(EffectivePath)) {
-      error(EC.message(), Path);
-      return;
-    }
-    sys::path::remove_dots(EffectivePath, /*remove_dot_dots=*/true);
-    SourceFiles.emplace_back(EffectivePath.str());
+  SmallString<128> EffectivePath(Path);
+  if (std::error_code EC = sys::fs::make_absolute(EffectivePath)) {
+    error(EC.message(), Path);
+    return;
   }
+  sys::path::remove_dots(EffectivePath, /*remove_dot_dots=*/true);
+  SourceFiles.emplace_back(EffectivePath.str());
 }
 
 void CodeCoverageTool::collectPaths(const std::string &Path) {
   llvm::sys::fs::file_status Status;
   llvm::sys::fs::status(Path, Status);
   if (!llvm::sys::fs::exists(Status)) {
-    if (CompareFilenamesOnly)
+    if (PathRemapping)
       addCollectedPath(Path);
     else
       error("Missing source file", Path);
@@ -348,6 +349,8 @@
   if (Mismatched)
     warning(utostr(Mismatched) + " functions have mismatched data");
 
+  remapPathNames(*Coverage);
+
   if (!SourceFiles.empty())
     removeUnmappedInputs(*Coverage);
 
@@ -356,33 +359,58 @@
   return Coverage;
 }
 
+void CodeCoverageTool::remapPathNames(const CoverageMapping &Coverage) {
+  if (!PathRemapping)
+    return;
+
+  // Convert remapping paths to native paths with trailing seperators.
+  auto nativeWithTrailing = [](StringRef Path) -> std::string {
+    if (Path.empty())
+      return "";
+    SmallString<128> NativePath;
+    sys::path::native(Path, NativePath);
+    if (!sys::path::is_separator(NativePath.back()))
+      NativePath += sys::path::get_separator();
+    return NativePath.c_str();
+  };
+  std::string RemapFrom = nativeWithTrailing(PathRemapping->first);
+  std::string RemapTo = nativeWithTrailing(PathRemapping->second);
+
+  // Create a mapping from coverage data file paths to local paths.
+  for (StringRef Filename : Coverage.getUniqueSourceFiles()) {
+    SmallString<128> NativeFilename;
+    sys::path::native(Filename, NativeFilename);
+    if (NativeFilename.startswith(RemapFrom)) {
+      RemappedFilenames[Filename] =
+          RemapTo + NativeFilename.substr(RemapFrom.size()).str();
+    }
+  }
+
+  // Convert input files from local paths to coverage data file paths.
+  StringMap<std::string> InvRemappedFilenames;
+  for (const auto &RemappedFilename : RemappedFilenames)
+    InvRemappedFilenames[RemappedFilename.getValue()] = RemappedFilename.getKey();
+
+  for (std::string &Filename : SourceFiles) {
+    SmallString<128> NativeFilename;
+    sys::path::native(Filename, NativeFilename);
+    auto CovFileName = InvRemappedFilenames.find(NativeFilename);
+    if (CovFileName != InvRemappedFilenames.end())
+      Filename = CovFileName->second;
+  }
+}
+
 void CodeCoverageTool::removeUnmappedInputs(const CoverageMapping &Coverage) {
   std::vector<StringRef> CoveredFiles = Coverage.getUniqueSourceFiles();
 
   auto UncoveredFilesIt = SourceFiles.end();
-  if (!CompareFilenamesOnly) {
-    // The user may have specified source files which aren't in the coverage
-    // mapping. Filter these files away.
-    UncoveredFilesIt = std::remove_if(
-        SourceFiles.begin(), SourceFiles.end(), [&](const std::string &SF) {
-          return !std::binary_search(CoveredFiles.begin(), CoveredFiles.end(),
-                                     SF);
-        });
-  } else {
-    for (auto &SF : SourceFiles) {
-      StringRef SFBase = sys::path::filename(SF);
-      for (const auto &CF : CoveredFiles) {
-        if (SFBase == sys::path::filename(CF)) {
-          RemappedFilenames[CF] = SF;
-          SF = CF;
-          break;
-        }
-      }
-    }
-    UncoveredFilesIt = std::remove_if(
-        SourceFiles.begin(), SourceFiles.end(),
-        [&](const std::string &SF) { return !RemappedFilenames.count(SF); });
-  }
+  // The user may have specified source files which aren't in the coverage
+  // mapping. Filter these files away.
+  UncoveredFilesIt = std::remove_if(
+      SourceFiles.begin(), SourceFiles.end(), [&](const std::string &SF) {
+        return !std::binary_search(CoveredFiles.begin(), CoveredFiles.end(),
+                                   SF);
+      });
 
   SourceFiles.erase(UncoveredFilesIt, SourceFiles.end());
 }
@@ -521,10 +549,10 @@
                             "HTML output")),
       cl::init(CoverageViewOptions::OutputFormat::Text));
 
-  cl::opt<bool> FilenameEquivalence(
-      "filename-equivalence", cl::Optional,
-      cl::desc("Treat source files as equivalent to paths in the coverage data "
-               "when the file names match, even if the full paths do not"));
+  cl::opt<std::string> PathRemap(
+      "path-equivalence", cl::Optional,
+      cl::desc("<from>,<to> Map coverage data paths to local source file "
+               "paths"));
 
   cl::OptionCategory FilteringCategory("Function filtering options");
 
@@ -573,7 +601,6 @@
   auto commandLineParser = [&, this](int argc, const char **argv) -> int {
     cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
     ViewOpts.Debug = DebugDump;
-    CompareFilenamesOnly = FilenameEquivalence;
 
     if (!CovFilename.empty())
       ObjectFilenames.emplace_back(CovFilename);
@@ -598,6 +625,12 @@
       break;
     }
 
+    // If path-equivalence was given and is a comma seperated pair then set
+    // PathRemapping.
+    auto EquivPair = StringRef(PathRemap).split(',');
+    if (!(EquivPair.first.empty() && EquivPair.second.empty()))
+      PathRemapping = EquivPair;
+
     // If a demangler is supplied, check if it exists and register it.
     if (DemanglerOpts.size()) {
       auto DemanglerPathOrErr = sys::findProgramByName(DemanglerOpts[0]);