llvm-cov: Implement the preserve-paths flag
authorJustin Bogner <mail@justinbogner.com>
Tue, 4 Feb 2014 10:45:02 +0000 (10:45 +0000)
committerJustin Bogner <mail@justinbogner.com>
Tue, 4 Feb 2014 10:45:02 +0000 (10:45 +0000)
Until now, when a path in a gcno file included a directory, we would
emit our .gcov file in that directory, whereas gcov always emits the
file in the current directory. In doing so, this implements gcov's
strange name-mangling -p flag, which is needed to avoid clobbering
files when two with the same name exist in different directories.

The path mangling is a bit ugly and only handles unix-like paths, but
it's simple, and it doesn't make any guesses as to how it should
behave outside of what gcov documents. If we decide this should be
cross platform later, we can consider the compatibility implications
then.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@200754 91177308-0d34-0410-b5e6-96231b3b80d8

14 files changed:
include/llvm/Support/GCOV.h
lib/IR/GCOV.cpp
test/tools/llvm-cov/Inputs/test_-b.output
test/tools/llvm-cov/Inputs/test_-f.output
test/tools/llvm-cov/Inputs/test_no_gcda.output
test/tools/llvm-cov/Inputs/test_no_options.output
test/tools/llvm-cov/Inputs/test_no_preserve_paths.output [new file with mode: 0644]
test/tools/llvm-cov/Inputs/test_paths.cpp.gcov [new file with mode: 0644]
test/tools/llvm-cov/Inputs/test_paths.gcda [new file with mode: 0644]
test/tools/llvm-cov/Inputs/test_paths.gcno [new file with mode: 0644]
test/tools/llvm-cov/Inputs/test_paths.h.gcov [new file with mode: 0644]
test/tools/llvm-cov/Inputs/test_preserve_paths.output [new file with mode: 0644]
test/tools/llvm-cov/llvm-cov.test
tools/llvm-cov/llvm-cov.cpp

index 4e7920b8e94847ab26d685425a9d3af4b7c6fbfb..aeac4555cee05b29e49748eff54952bd47ccbf7d 100644 (file)
@@ -37,14 +37,15 @@ namespace GCOV {
 
 /// GCOVOptions - A struct for passing gcov options between functions.
 struct GCOVOptions {
-  GCOVOptions(bool A, bool B, bool C, bool F, bool U) :
-    AllBlocks(A), BranchInfo(B), BranchCount(C), FuncCoverage(F), UncondBranch(U)
-  {}
+  GCOVOptions(bool A, bool B, bool C, bool F, bool P, bool U)
+      : AllBlocks(A), BranchInfo(B), BranchCount(C), FuncCoverage(F),
+        PreservePaths(P), UncondBranch(U) {}
 
   bool AllBlocks;
   bool BranchInfo;
   bool BranchCount;
   bool FuncCoverage;
+  bool PreservePaths;
   bool UncondBranch;
 };
 
@@ -401,8 +402,13 @@ private:
   StringMap<LineData> LineInfo;
   uint32_t RunCount;
   uint32_t ProgramCount;
-  SmallVector<GCOVCoverage, 4> FileCoverages;
-  MapVector<const GCOVFunction *, GCOVCoverage> FuncCoverages;
+
+  typedef SmallVector<std::pair<std::string, GCOVCoverage>, 4>
+      FileCoverageList;
+  typedef MapVector<const GCOVFunction *, GCOVCoverage> FuncCoverageMap;
+
+  FileCoverageList FileCoverages;
+  FuncCoverageMap FuncCoverages;
 };
 
 }
index 466828113b8f7861494bae1d1536830606d11971..45ef78e078ae31839218f91ef0d9c73020de8eb2 100644 (file)
 #include "llvm/ADT/OwningPtr.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/Format.h"
+#include "llvm/Support/FileSystem.h"
 #include "llvm/Support/GCOV.h"
 #include "llvm/Support/MemoryObject.h"
+#include "llvm/Support/Path.h"
 #include "llvm/Support/system_error.h"
 #include <algorithm>
 using namespace llvm;
@@ -429,6 +431,42 @@ static raw_ostream &operator<<(raw_ostream &OS, const formatBranchInfo &FBI) {
   return OS;
 }
 
+/// Convert a path to a gcov filename. If PreservePaths is true, this
+/// translates "/" to "#", ".." to "^", and drops ".", to match gcov.
+static std::string mangleCoveragePath(StringRef Filename, bool PreservePaths) {
+  if (!PreservePaths)
+    return (sys::path::filename(Filename) + ".gcov").str();
+
+  // This behaviour is defined by gcov in terms of text replacements, so it's
+  // not likely to do anything useful on filesystems with different textual
+  // conventions.
+  llvm::SmallString<256> Result("");
+  StringRef::iterator I, S, E;
+  for (I = S = Filename.begin(), E = Filename.end(); I != E; ++I) {
+    if (*I != '/')
+      continue;
+
+    if (I - S == 1 && *S == '.') {
+      // ".", the current directory, is skipped.
+    } else if (I - S == 2 && *S == '.' && *(S + 1) == '.') {
+      // "..", the parent directory, is replaced with "^".
+      Result.append("^#");
+    } else {
+      if (S < I)
+        // Leave other components intact,
+        Result.append(S, I);
+      // And separate with "#".
+      Result.push_back('#');
+    }
+    S = I + 1;
+  }
+
+  if (S < I)
+    Result.append(S, I);
+  Result.append(".gcov");
+  return Result.str();
+}
+
 /// print -  Print source files with collected line count information.
 void FileInfo::print(StringRef GCNOFile, StringRef GCDAFile) {
   for (StringMap<LineData>::const_iterator I = LineInfo.begin(),
@@ -441,9 +479,10 @@ void FileInfo::print(StringRef GCNOFile, StringRef GCDAFile) {
     }
     StringRef AllLines = Buff->getBuffer();
 
-    std::string CovFilename = Filename.str() + ".gcov";
+    std::string CoveragePath = mangleCoveragePath(Filename,
+                                                  Options.PreservePaths);
     std::string ErrorInfo;
-    raw_fd_ostream OS(CovFilename.c_str(), ErrorInfo);
+    raw_fd_ostream OS(CoveragePath.c_str(), ErrorInfo);
     if (!ErrorInfo.empty())
       errs() << ErrorInfo << "\n";
 
@@ -555,7 +594,7 @@ void FileInfo::print(StringRef GCNOFile, StringRef GCDAFile) {
         }
       }
     }
-    FileCoverages.push_back(FileCoverage);
+    FileCoverages.push_back(std::make_pair(CoveragePath, FileCoverage));
   }
 
   // FIXME: There is no way to detect calls given current instrumentation.
@@ -656,8 +695,8 @@ void FileInfo::printCoverage(const GCOVCoverage &Coverage) const {
 
 // printFuncCoverage - Print per-function coverage info.
 void FileInfo::printFuncCoverage() const {
-  for (MapVector<const GCOVFunction *, GCOVCoverage>::const_iterator I =
-         FuncCoverages.begin(), E = FuncCoverages.end(); I != E; ++I) {
+  for (FuncCoverageMap::const_iterator I = FuncCoverages.begin(),
+                                       E = FuncCoverages.end(); I != E; ++I) {
     const GCOVCoverage &Coverage = I->second;
     outs() << "Function '" << Coverage.Name << "'\n";
     printCoverage(Coverage);
@@ -667,12 +706,12 @@ void FileInfo::printFuncCoverage() const {
 
 // printFileCoverage - Print per-file coverage info.
 void FileInfo::printFileCoverage() const {
-  for (SmallVectorImpl<GCOVCoverage>::const_iterator I =
-         FileCoverages.begin(), E = FileCoverages.end(); I != E; ++I) {
-    const GCOVCoverage &Coverage = *I;
+  for (FileCoverageList::const_iterator I = FileCoverages.begin(),
+                                        E = FileCoverages.end(); I != E; ++I) {
+    const std::string &Filename = I->first;
+    const GCOVCoverage &Coverage = I->second;
     outs() << "File '" << Coverage.Name << "'\n";
     printCoverage(Coverage);
-    outs() << Coverage.Name << ":creating '" << Coverage.Name
-           << ".gcov'\n\n";
+    outs() << Coverage.Name << ":creating '" << Filename << "'\n\n";
   }
 }
index 4003ce8e692ffc8d7b1c01f46483104f05b49c1d..515987d45ea64ba93f90a2fd0a4de9dff99de1bb 100644 (file)
@@ -9,5 +9,5 @@ File './test.h'
 Lines executed:100.00% of 1
 No branches
 No calls
-./test.h:creating './test.h.gcov'
+./test.h:creating 'test.h.gcov'
 
index 9e98d888803909cf046c59e6870fd8f285ee8597..d97aa18174a0fc197dc10c3e2b28b37aac0befdc 100644 (file)
@@ -34,5 +34,5 @@ test.cpp:creating 'test.cpp.gcov'
 
 File './test.h'
 Lines executed:100.00% of 1
-./test.h:creating './test.h.gcov'
+./test.h:creating 'test.h.gcov'
 
index 69adce209b7c7b26d138cf58d3b1432280c8678b..e994be7291094c908dc538458ff84c2b6c09ed41 100644 (file)
@@ -4,5 +4,5 @@ test.cpp:creating 'test.cpp.gcov'
 
 File './test.h'
 Lines executed:0.00% of 1
-./test.h:creating './test.h.gcov'
+./test.h:creating 'test.h.gcov'
 
index 93ea726720a5990c5c07b3594f144609844c6918..8be8c1c210af821b4dc84f4a6bb67d3eb42c6d44 100644 (file)
@@ -4,5 +4,5 @@ test.cpp:creating 'test.cpp.gcov'
 
 File './test.h'
 Lines executed:100.00% of 1
-./test.h:creating './test.h.gcov'
+./test.h:creating 'test.h.gcov'
 
diff --git a/test/tools/llvm-cov/Inputs/test_no_preserve_paths.output b/test/tools/llvm-cov/Inputs/test_no_preserve_paths.output
new file mode 100644 (file)
index 0000000..ada0c36
--- /dev/null
@@ -0,0 +1,8 @@
+File 'srcdir/./nested_dir/../test.h'
+Lines executed:100.00% of 1
+srcdir/./nested_dir/../test.h:creating 'test.h.gcov'
+
+File 'srcdir/./nested_dir/../test.cpp'
+Lines executed:84.21% of 38
+srcdir/./nested_dir/../test.cpp:creating 'test.cpp.gcov'
+
diff --git a/test/tools/llvm-cov/Inputs/test_paths.cpp.gcov b/test/tools/llvm-cov/Inputs/test_paths.cpp.gcov
new file mode 100644 (file)
index 0000000..3982ddf
--- /dev/null
@@ -0,0 +1,79 @@
+        -:    0:Source:srcdir/./nested_dir/../test.cpp
+        -:    0:Graph:test_paths.gcno
+        -:    0:Data:test_paths.gcda
+        -:    0:Runs:3
+        -:    0:Programs:1
+        -:    1:#include "test.h"
+        -:    2:#include <cstdlib>
+        -:    3:
+        -:    4:bool on = false;
+        -:    5:int len = 42;
+        -:    6:double grid[10][10] = {0};
+        -:    7:const char * hello = "world";
+        -:    8:const char * world = "hello";
+        -:    9:
+12884901888:   10:void A::B() {}
+        -:   11:
+    #####:   12:void useless() {}
+        -:   13:
+        -:   14:double more_useless() {
+    #####:   15:  return 0;
+        -:   16:}
+        -:   17:
+        -:   18:int foo() {
+        3:   19:  on = true;
+        3:   20:  return 3;
+        -:   21:}
+        -:   22:
+        -:   23:int bar() {
+    #####:   24:  len--;
+    #####:   25:  return foo() + 45;
+        -:   26:}
+        -:   27:
+       12:   28:void assign(int ii, int jj) {
+       12:   29:  grid[ii][jj] = (ii+1) * (jj+1);
+       12:   30:}
+        -:   31:
+        -:   32:void initialize_grid() {
+       21:   33:  for (int ii = 0; ii < 2; ii++)
+       36:   34:    for (int jj = 0; jj < 2; jj++)
+       18:   35:      assign(ii, jj);
+        3:   36:}
+        -:   37:
+        -:   38:int main() {
+        3:   39:  initialize_grid();
+        -:   40:
+        3:   41:  int a = 2;
+        3:   42:  on = rand() % 2;
+        3:   43:  if (on) {
+        3:   44:    foo();
+        3:   45:    ++a;
+        3:   46:  } else {
+    #####:   47:    bar();
+    #####:   48:    a += rand();
+        -:   49:  }
+        -:   50:
+       66:   51:  for (int ii = 0; ii < 10; ++ii) {
+       30:   52:    switch (rand() % 5) {
+        -:   53:      case 0:
+        6:   54:        a += rand();
+        6:   55:        break;
+        -:   56:      case 1:
+        -:   57:      case 2:
+        3:   58:        a += rand() / rand();
+        3:   59:        break;
+        -:   60:      case 3:
+        9:   61:        a -= rand();
+        9:   62:        break;
+        -:   63:      default:
+       12:   64:        a = -1;
+       12:   65:    }
+       30:   66:  }
+        -:   67:
+        3:   68:  A thing;
+25769803782:   69:  for (uint64_t ii = 0; ii < 4294967296; ++ii)
+12884901888:   70:    thing.B();
+        -:   71:
+        3:   72:  return a + 8 + grid[2][3] + len;
+        -:   73:  return more_useless();
+        -:   74:}
diff --git a/test/tools/llvm-cov/Inputs/test_paths.gcda b/test/tools/llvm-cov/Inputs/test_paths.gcda
new file mode 100644 (file)
index 0000000..7e2cf9e
Binary files /dev/null and b/test/tools/llvm-cov/Inputs/test_paths.gcda differ
diff --git a/test/tools/llvm-cov/Inputs/test_paths.gcno b/test/tools/llvm-cov/Inputs/test_paths.gcno
new file mode 100644 (file)
index 0000000..aada974
Binary files /dev/null and b/test/tools/llvm-cov/Inputs/test_paths.gcno differ
diff --git a/test/tools/llvm-cov/Inputs/test_paths.h.gcov b/test/tools/llvm-cov/Inputs/test_paths.h.gcov
new file mode 100644 (file)
index 0000000..95e90ca
--- /dev/null
@@ -0,0 +1,8 @@
+        -:    0:Source:srcdir/./nested_dir/../test.h
+        -:    0:Graph:test_paths.gcno
+        -:    0:Data:test_paths.gcda
+        -:    0:Runs:3
+        -:    0:Programs:1
+        6:    1:struct A {
+        -:    2:  virtual void B();
+        -:    3:};
diff --git a/test/tools/llvm-cov/Inputs/test_preserve_paths.output b/test/tools/llvm-cov/Inputs/test_preserve_paths.output
new file mode 100644 (file)
index 0000000..5331972
--- /dev/null
@@ -0,0 +1,8 @@
+File 'srcdir/./nested_dir/../test.h'
+Lines executed:100.00% of 1
+srcdir/./nested_dir/../test.h:creating 'srcdir#nested_dir#^#test.h.gcov'
+
+File 'srcdir/./nested_dir/../test.cpp'
+Lines executed:84.21% of 38
+srcdir/./nested_dir/../test.cpp:creating 'srcdir#nested_dir#^#test.cpp.gcov'
+
index 239108240e0d982d1ed64cd0467c614615143ebb..c52a9815229d0ed5c2ada2fd60ae2cccb2ce03e5 100644 (file)
@@ -21,6 +21,18 @@ RUN: llvm-cov -o objdir test.c | diff -u test_no_options.output -
 RUN: diff -aub test_objdir.cpp.gcov test.cpp.gcov
 RUN: diff -aub test_objdir.h.gcov test.h.gcov
 
+# Preserve paths. This mangles the output filenames.
+RUN: mkdir -p %t/srcdir/nested_dir
+RUN: cp test.cpp test.h %t/srcdir
+RUN: llvm-cov -p test_paths.cpp | diff -u test_preserve_paths.output -
+RUN: diff -aub test_paths.cpp.gcov srcdir#nested_dir#^#test.cpp.gcov
+RUN: diff -aub test_paths.h.gcov srcdir#nested_dir#^#test.h.gcov
+
+# Don't preserve paths. Same results as preserve paths, but no mangling.
+RUN: llvm-cov test_paths.cpp | diff -u test_no_preserve_paths.output -
+RUN: diff -aub test_paths.cpp.gcov test.cpp.gcov
+RUN: diff -aub test_paths.h.gcov test.h.gcov
+
 # Function summaries. This changes stdout, but not the gcov files.
 RUN: llvm-cov test.c -f | diff -u test_-f.output -
 RUN: diff -aub test_no_options.cpp.gcov test.cpp.gcov
index 61bee43d82052670e5be9483ab6010c00c9adb2c..d7162c46882d9bec025244e6b79bb6286b1a02fd 100644 (file)
@@ -47,6 +47,10 @@ static cl::opt<std::string> ObjectDir("o", cl::value_desc("DIR"), cl::init(""),
                                       cl::desc("Search for objects in DIR"));
 static cl::alias ObjectDirA("object-directory", cl::aliasopt(ObjectDir));
 
+static cl::opt<bool> PreservePaths("p", cl::init(false),
+                                   cl::desc("Preserve path components"));
+static cl::alias PreservePathsA("preserve-paths", cl::aliasopt(PreservePaths));
+
 static cl::opt<bool> UncondBranch("u", cl::init(false),
                                   cl::desc("Display unconditional branch info "
                                            "(requires -b)"));
@@ -113,7 +117,7 @@ int main(int argc, char **argv) {
     GF.dump();
 
   GCOVOptions Options(AllBlocks, BranchProb, BranchCount, FuncSummary,
-                      UncondBranch);
+                      PreservePaths, UncondBranch);
   FileInfo FI(Options);
   GF.collectLineCounts(FI);
   FI.print(InputGCNO, InputGCDA);