[llvm] r307421 - [llvm-pdbutil] Improve diff mode.

Zachary Turner via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 7 11:45:38 PDT 2017


Author: zturner
Date: Fri Jul  7 11:45:37 2017
New Revision: 307421

URL: http://llvm.org/viewvc/llvm-project?rev=307421&view=rev
Log:
[llvm-pdbutil] Improve diff mode.

We're getting to the point that some MS tools (e.g. DIA) can recognize
our PDBs but others (e.g. link.exe) cannot. I think the way forward is
to improve our tooling to help us find differences more easily. For
example, if we can compile the same program with clang-cl and cl and
have a tool tell us all the places where the PDBs differ, this could
tell us what we're doing wrong. It's tricky though, because there are a
lot of "benign" differences in a PDB. For example, if the string table
in one PDB consists of "foo" followed by "bar" and in the other PDB it
consists of "bar" followed by "foo", this is not necessarily a critical
difference, as long as the uses of these strings also refer to the
correct location. On the other hand, if the second PDB doesn't even
contain the string "foo" at all, this is a critical difference.

diff mode has been in llvm-pdbutil for quite a while, but because of the
above challenge along with some others, it's been hard to make it
useful. I think this patch addresses that. It looks for all the same
things, but it now prints the output in tabular format (carefully
formatted and aligned into tables and fields), and it highlights
critical differences in red, non-critical differences in yellow, and
identical fields in green.  This makes it easy to spot the places we
differ, and the general concept of outputting arbitrary fields in
tabular format can be extended to provide analysis into many of the
different types of information that show up in a PDB.

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

Added:
    llvm/trunk/tools/llvm-pdbutil/DiffPrinter.cpp
    llvm/trunk/tools/llvm-pdbutil/DiffPrinter.h
Modified:
    llvm/trunk/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h
    llvm/trunk/include/llvm/DebugInfo/PDB/Native/PDBStringTable.h
    llvm/trunk/lib/DebugInfo/PDB/Native/PDBStringTable.cpp
    llvm/trunk/tools/llvm-pdbutil/CMakeLists.txt
    llvm/trunk/tools/llvm-pdbutil/Diff.cpp
    llvm/trunk/tools/llvm-pdbutil/FormatUtil.cpp
    llvm/trunk/tools/llvm-pdbutil/FormatUtil.h

Modified: llvm/trunk/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h?rev=307421&r1=307420&r2=307421&view=diff
==============================================================================
--- llvm/trunk/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h (original)
+++ llvm/trunk/include/llvm/DebugInfo/PDB/Native/NamedStreamMap.h Fri Jul  7 11:45:37 2017
@@ -44,7 +44,7 @@ public:
   bool get(StringRef Stream, uint32_t &StreamNo) const;
   void set(StringRef Stream, uint32_t StreamNo);
   void remove(StringRef Stream);
-
+  const StringMap<uint32_t> &getStringMap() const { return Mapping; }
   iterator_range<StringMapConstIterator<uint32_t>> entries() const;
 
 private:

Modified: llvm/trunk/include/llvm/DebugInfo/PDB/Native/PDBStringTable.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/DebugInfo/PDB/Native/PDBStringTable.h?rev=307421&r1=307420&r2=307421&view=diff
==============================================================================
--- llvm/trunk/include/llvm/DebugInfo/PDB/Native/PDBStringTable.h (original)
+++ llvm/trunk/include/llvm/DebugInfo/PDB/Native/PDBStringTable.h Fri Jul  7 11:45:37 2017
@@ -56,7 +56,6 @@ private:
   const PDBStringTableHeader *Header = nullptr;
   codeview::DebugStringTableSubsectionRef Strings;
   FixedStreamArray<support::ulittle32_t> IDs;
-  uint32_t ByteSize = 0;
   uint32_t NameCount = 0;
 };
 

Modified: llvm/trunk/lib/DebugInfo/PDB/Native/PDBStringTable.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/DebugInfo/PDB/Native/PDBStringTable.cpp?rev=307421&r1=307420&r2=307421&view=diff
==============================================================================
--- llvm/trunk/lib/DebugInfo/PDB/Native/PDBStringTable.cpp (original)
+++ llvm/trunk/lib/DebugInfo/PDB/Native/PDBStringTable.cpp Fri Jul  7 11:45:37 2017
@@ -21,7 +21,7 @@ using namespace llvm;
 using namespace llvm::support;
 using namespace llvm::pdb;
 
-uint32_t PDBStringTable::getByteSize() const { return ByteSize; }
+uint32_t PDBStringTable::getByteSize() const { return Header->ByteSize; }
 uint32_t PDBStringTable::getNameCount() const { return NameCount; }
 uint32_t PDBStringTable::getHashVersion() const { return Header->HashVersion; }
 uint32_t PDBStringTable::getSignature() const { return Header->Signature; }

Modified: llvm/trunk/tools/llvm-pdbutil/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/llvm-pdbutil/CMakeLists.txt?rev=307421&r1=307420&r2=307421&view=diff
==============================================================================
--- llvm/trunk/tools/llvm-pdbutil/CMakeLists.txt (original)
+++ llvm/trunk/tools/llvm-pdbutil/CMakeLists.txt Fri Jul  7 11:45:37 2017
@@ -11,6 +11,7 @@ add_llvm_tool(llvm-pdbutil
   Analyze.cpp
   BytesOutputStyle.cpp
   Diff.cpp
+  DiffPrinter.cpp
   DumpOutputStyle.cpp
   llvm-pdbutil.cpp
   FormatUtil.cpp

Modified: llvm/trunk/tools/llvm-pdbutil/Diff.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/llvm-pdbutil/Diff.cpp?rev=307421&r1=307420&r2=307421&view=diff
==============================================================================
--- llvm/trunk/tools/llvm-pdbutil/Diff.cpp (original)
+++ llvm/trunk/tools/llvm-pdbutil/Diff.cpp Fri Jul  7 11:45:37 2017
@@ -9,9 +9,14 @@
 
 #include "Diff.h"
 
+#include "DiffPrinter.h"
+#include "FormatUtil.h"
 #include "StreamUtil.h"
 #include "llvm-pdbutil.h"
 
+#include "llvm/ADT/StringSet.h"
+
+#include "llvm/DebugInfo/PDB/Native/DbiStream.h"
 #include "llvm/DebugInfo/PDB/Native/Formatters.h"
 #include "llvm/DebugInfo/PDB/Native/InfoStream.h"
 #include "llvm/DebugInfo/PDB/Native/PDBFile.h"
@@ -49,47 +54,6 @@ template <> struct format_provider<PdbRa
 
 template <typename R> using ValueOfRange = llvm::detail::ValueOfRange<R>;
 
-template <typename Range, typename Comp>
-static void set_differences(Range &&R1, Range &&R2,
-                            SmallVectorImpl<ValueOfRange<Range>> *OnlyLeft,
-                            SmallVectorImpl<ValueOfRange<Range>> *OnlyRight,
-                            SmallVectorImpl<ValueOfRange<Range>> *Intersection,
-                            Comp Comparator) {
-
-  std::sort(R1.begin(), R1.end(), Comparator);
-  std::sort(R2.begin(), R2.end(), Comparator);
-
-  if (OnlyLeft) {
-    OnlyLeft->reserve(R1.size());
-    auto End = std::set_difference(R1.begin(), R1.end(), R2.begin(), R2.end(),
-                                   OnlyLeft->begin(), Comparator);
-    OnlyLeft->set_size(std::distance(OnlyLeft->begin(), End));
-  }
-  if (OnlyRight) {
-    OnlyLeft->reserve(R2.size());
-    auto End = std::set_difference(R2.begin(), R2.end(), R1.begin(), R1.end(),
-                                   OnlyRight->begin(), Comparator);
-    OnlyRight->set_size(std::distance(OnlyRight->begin(), End));
-  }
-  if (Intersection) {
-    Intersection->reserve(std::min(R1.size(), R2.size()));
-    auto End = std::set_intersection(R1.begin(), R1.end(), R2.begin(), R2.end(),
-                                     Intersection->begin(), Comparator);
-    Intersection->set_size(std::distance(Intersection->begin(), End));
-  }
-}
-
-template <typename Range>
-static void
-set_differences(Range &&R1, Range &&R2,
-                SmallVectorImpl<ValueOfRange<Range>> *OnlyLeft,
-                SmallVectorImpl<ValueOfRange<Range>> *OnlyRight,
-                SmallVectorImpl<ValueOfRange<Range>> *Intersection = nullptr) {
-  std::less<ValueOfRange<Range>> Comp;
-  set_differences(std::forward<Range>(R1), std::forward<Range>(R2), OnlyLeft,
-                  OnlyRight, Intersection, Comp);
-}
-
 DiffStyle::DiffStyle(PDBFile &File1, PDBFile &File2)
     : File1(File1), File2(File2) {}
 
@@ -136,300 +100,382 @@ Error DiffStyle::dump() {
   return Error::success();
 }
 
-template <typename T>
-static bool diffAndPrint(StringRef Label, PDBFile &File1, PDBFile &File2, T V1,
-                         T V2) {
-  if (V1 == V2) {
-    outs() << formatv("  {0}: No differences detected!\n", Label);
-    return false;
-  }
-
-  outs().indent(2) << Label << "\n";
-  outs().indent(4) << formatv("{0}: {1}\n", File1.getFilePath(), V1);
-  outs().indent(4) << formatv("{0}: {1}\n", File2.getFilePath(), V2);
-  return true;
-}
-
-template <typename T>
-static bool diffAndPrint(StringRef Label, PDBFile &File1, PDBFile &File2,
-                         ArrayRef<T> V1, ArrayRef<T> V2) {
-  if (V1 == V2) {
-    outs() << formatv("  {0}: No differences detected!\n", Label);
-    return false;
-  }
-
-  outs().indent(2) << Label << "\n";
-  outs().indent(4) << formatv("{0}: {1}\n", File1.getFilePath(),
-                              make_range(V1.begin(), V1.end()));
-  outs().indent(4) << formatv("{0}: {1}\n", File2.getFilePath(),
-                              make_range(V2.begin(), V2.end()));
-  return true;
-}
-
-template <typename T>
-static bool printSymmetricDifferences(PDBFile &File1, PDBFile &File2,
-                                      T &&OnlyRange1, T &&OnlyRange2,
-                                      StringRef Label) {
-  bool HasDiff = false;
-  if (!OnlyRange1.empty()) {
-    HasDiff = true;
-    outs() << formatv("  {0} {1}(s) only in ({2})\n", OnlyRange1.size(), Label,
-                      File1.getFilePath());
-    for (const auto &Item : OnlyRange1)
-      outs() << formatv("    {0}\n", Label, Item);
-  }
-  if (!OnlyRange2.empty()) {
-    HasDiff = true;
-    outs() << formatv("  {0} {1}(s) only in ({2})\n", OnlyRange2.size(),
-                      File2.getFilePath());
-    for (const auto &Item : OnlyRange2)
-      outs() << formatv("    {0}\n", Item);
-  }
-  return HasDiff;
+static std::string shortFilePath(StringRef Path, uint32_t Width) {
+  if (Path.size() <= Width)
+    return Path;
+  Path = Path.take_back(Width - 3);
+  return std::string("...") + Path.str();
 }
 
 Error DiffStyle::diffSuperBlock() {
-  outs() << "MSF Super Block: Searching for differences...\n";
-  bool Diffs = false;
-
-  Diffs |= diffAndPrint("Block Size", File1, File2, File1.getBlockSize(),
-                        File2.getBlockSize());
-  Diffs |= diffAndPrint("Block Count", File1, File2, File1.getBlockCount(),
-                        File2.getBlockCount());
-  Diffs |= diffAndPrint("Unknown 1", File1, File2, File1.getUnknown1(),
-                        File2.getUnknown1());
-  if (!Diffs)
-    outs() << "MSF Super Block: No differences detected...\n";
+  DiffPrinter D(2, "MSF Super Block", 16, 20, outs());
+  D.printExplicit("File", DiffResult::UNSPECIFIED,
+                  shortFilePath(File1.getFilePath(), 18),
+                  shortFilePath(File2.getFilePath(), 18));
+  D.print("Block Size", File1.getBlockSize(), File2.getBlockSize());
+  D.print("Block Count", File1.getBlockCount(), File2.getBlockCount());
+  D.print("Unknown 1", File1.getUnknown1(), File2.getUnknown1());
+  D.print("Directory Size", File1.getNumDirectoryBytes(),
+          File2.getNumDirectoryBytes());
   return Error::success();
 }
 
 Error DiffStyle::diffStreamDirectory() {
+  DiffPrinter D(2, "Stream Directory", 30, 20, outs());
+  D.printExplicit("File", DiffResult::UNSPECIFIED,
+                  shortFilePath(File1.getFilePath(), 18),
+                  shortFilePath(File2.getFilePath(), 18));
+
   SmallVector<std::string, 32> P;
   SmallVector<std::string, 32> Q;
-  discoverStreamPurposes(File1, P);
-  discoverStreamPurposes(File2, Q);
-  outs() << "Stream Directory: Searching for differences...\n";
-
-  bool HasDifferences = false;
+  discoverStreamPurposes(File1, P, 28);
+  discoverStreamPurposes(File2, Q, 28);
+  D.print("Stream Count", File1.getNumStreams(), File2.getNumStreams());
   auto PI = to_vector<32>(enumerate(P));
   auto QI = to_vector<32>(enumerate(Q));
 
-  typedef decltype(PI) ContainerType;
-  typedef typename ContainerType::value_type value_type;
-
-  auto Comparator = [](const value_type &I1, const value_type &I2) {
-    return I1.value() < I2.value();
-  };
-
-  decltype(PI) OnlyP;
-  decltype(QI) OnlyQ;
-  decltype(PI) Common;
-
-  set_differences(PI, QI, &OnlyP, &OnlyQ, &Common, Comparator);
-
-  if (!OnlyP.empty()) {
-    HasDifferences = true;
-    outs().indent(2) << formatv("{0} Stream(s) only in ({1})\n", OnlyP.size(),
-                                File1.getFilePath());
-    for (auto &Item : OnlyP) {
-      outs().indent(4) << formatv("Stream {0} - {1}\n", Item.index(),
-                                  Item.value());
+  // Scan all streams in the left hand side, looking for ones that are also
+  // in the right.  Each time we find one, remove it.  When we're done, Q
+  // should contain all the streams that are in the right but not in the left.
+  for (const auto &P : PI) {
+    typedef decltype(PI) ContainerType;
+    typedef typename ContainerType::value_type value_type;
+
+    auto Iter = llvm::find_if(
+        QI, [P](const value_type &V) { return V.value() == P.value(); });
+
+    if (Iter == QI.end()) {
+      D.printExplicit(P.value(), DiffResult::DIFFERENT, P.index(),
+                      "(not present)");
+      continue;
     }
-  }
 
-  if (!OnlyQ.empty()) {
-    HasDifferences = true;
-    outs().indent(2) << formatv("{0} Streams(s) only in ({1})\n", OnlyQ.size(),
-                                File2.getFilePath());
-    for (auto &Item : OnlyQ) {
-      outs().indent(4) << formatv("Stream {0} - {1}\n", Item.index(),
-                                  Item.value());
-    }
+    D.print<EquivalentDiffProvider>(P.value(), P.index(), Iter->index());
+    QI.erase(Iter);
   }
-  if (!Common.empty()) {
-    outs().indent(2) << formatv("Found {0} common streams.  Searching for "
-                                "intra-stream differences.\n",
-                                Common.size());
-    bool HasCommonDifferences = false;
-    for (const auto &Left : Common) {
-      // Left was copied from the first range so its index refers to a stream
-      // index in the first file.  Find the corresponding stream index in the
-      // second file.
-      auto Range =
-          std::equal_range(QI.begin(), QI.end(), Left,
-                           [](const value_type &L, const value_type &R) {
-                             return L.value() < R.value();
-                           });
-      const auto &Right = *Range.first;
-      assert(Left.value() == Right.value());
-      uint32_t LeftSize = File1.getStreamByteSize(Left.index());
-      uint32_t RightSize = File2.getStreamByteSize(Right.index());
-      if (LeftSize != RightSize) {
-        HasDifferences = true;
-        HasCommonDifferences = true;
-        outs().indent(4) << formatv("{0} ({1}: {2} bytes, {3}: {4} bytes)\n",
-                                    Left.value(), File1.getFilePath(), LeftSize,
-                                    File2.getFilePath(), RightSize);
-      }
-    }
-    if (!HasCommonDifferences)
-      outs().indent(2) << "Common Streams:  No differences detected!\n";
+
+  for (const auto &Q : QI) {
+    D.printExplicit(Q.value(), DiffResult::DIFFERENT, "(not present)",
+                    Q.index());
   }
-  if (!HasDifferences)
-    outs() << "Stream Directory: No differences detected!\n";
 
   return Error::success();
 }
 
 Error DiffStyle::diffStringTable() {
+  DiffPrinter D(2, "String Table", 30, 20, outs());
+  D.printExplicit("File", DiffResult::UNSPECIFIED,
+                  shortFilePath(File1.getFilePath(), 18),
+                  shortFilePath(File2.getFilePath(), 18));
+
   auto ExpectedST1 = File1.getStringTable();
   auto ExpectedST2 = File2.getStringTable();
-  outs() << "String Table: Searching for differences...\n";
   bool Has1 = !!ExpectedST1;
   bool Has2 = !!ExpectedST2;
-  if (!(Has1 && Has2)) {
-    // If one has a string table and the other doesn't, we can print less
-    // output.
-    if (Has1 != Has2) {
-      if (Has1) {
-        outs() << formatv("  {0}: ({1} strings)\n", File1.getFilePath(),
-                          ExpectedST1->getNameCount());
-        outs() << formatv("  {0}: (string table not present)\n",
-                          File2.getFilePath());
-      } else {
-        outs() << formatv("  {0}: (string table not present)\n",
-                          File1.getFilePath());
-        outs() << formatv("  {0}: ({1})\n", File2.getFilePath(),
-                          ExpectedST2->getNameCount());
-      }
-    }
+  std::string Count1 = Has1 ? llvm::utostr(ExpectedST1->getNameCount())
+                            : "(string table not present)";
+  std::string Count2 = Has2 ? llvm::utostr(ExpectedST2->getNameCount())
+                            : "(string table not present)";
+  D.print("Number of Strings", Count1, Count2);
+
+  if (!Has1 || !Has2) {
     consumeError(ExpectedST1.takeError());
     consumeError(ExpectedST2.takeError());
     return Error::success();
   }
 
-  bool HasDiff = false;
   auto &ST1 = *ExpectedST1;
   auto &ST2 = *ExpectedST2;
 
-  if (ST1.getByteSize() != ST2.getByteSize()) {
-    outs() << "  Stream Size\n";
-    outs() << formatv("    {0} - {1} byte(s)\n", File1.getFilePath(),
-                      ST1.getByteSize());
-    outs() << formatv("    {0} - {1} byte(s)\n", File2.getFilePath(),
-                      ST2.getByteSize());
-    outs() << formatv("    Difference: {0} bytes\n",
-                      AbsoluteDifference(ST1.getByteSize(), ST2.getByteSize()));
-    HasDiff = true;
-  }
-  HasDiff |= diffAndPrint("Hash Version", File1, File2, ST1.getHashVersion(),
-                          ST1.getHashVersion());
-  HasDiff |= diffAndPrint("Signature", File1, File2, ST1.getSignature(),
-                          ST1.getSignature());
+  D.print("Hash Version", ST1.getHashVersion(), ST2.getHashVersion());
+  D.print("Byte Size", ST1.getByteSize(), ST2.getByteSize());
+  D.print("Signature", ST1.getSignature(), ST2.getSignature());
 
   // Both have a valid string table, dive in and compare individual strings.
 
   auto IdList1 = ST1.name_ids();
   auto IdList2 = ST2.name_ids();
-  std::vector<StringRef> Strings1, Strings2;
-  Strings1.reserve(IdList1.size());
-  Strings2.reserve(IdList2.size());
+  StringSet<> LS;
+  StringSet<> RS;
+  uint32_t Empty1 = 0;
+  uint32_t Empty2 = 0;
   for (auto ID : IdList1) {
     auto S = ST1.getStringForID(ID);
     if (!S)
       return S.takeError();
-    Strings1.push_back(*S);
+    if (S->empty())
+      ++Empty1;
+    else
+      LS.insert(*S);
   }
   for (auto ID : IdList2) {
     auto S = ST2.getStringForID(ID);
     if (!S)
       return S.takeError();
-    Strings2.push_back(*S);
+    if (S->empty())
+      ++Empty2;
+    else
+      RS.insert(*S);
+  }
+  D.print("Empty Strings", Empty1, Empty2);
+
+  for (const auto &S : LS) {
+    auto R = RS.find(S.getKey());
+    std::string Truncated = truncateStringMiddle(S.getKey(), 28);
+    uint32_t I = cantFail(ST1.getIDForString(S.getKey()));
+    if (R == RS.end()) {
+      D.printExplicit(Truncated, DiffResult::DIFFERENT, I, "(not present)");
+      continue;
+    }
+
+    uint32_t J = cantFail(ST2.getIDForString(R->getKey()));
+    D.print<EquivalentDiffProvider>(Truncated, I, J);
+    RS.erase(R);
   }
 
-  SmallVector<StringRef, 64> OnlyP;
-  SmallVector<StringRef, 64> OnlyQ;
-  auto End1 = std::remove(Strings1.begin(), Strings1.end(), "");
-  auto End2 = std::remove(Strings2.begin(), Strings2.end(), "");
-  uint32_t Empty1 = std::distance(End1, Strings1.end());
-  uint32_t Empty2 = std::distance(End2, Strings2.end());
-  Strings1.erase(End1, Strings1.end());
-  Strings2.erase(End2, Strings2.end());
-  set_differences(Strings1, Strings2, &OnlyP, &OnlyQ);
-  printSymmetricDifferences(File1, File2, OnlyP, OnlyQ, "String");
-
-  if (Empty1 != Empty2) {
-    PDBFile &MoreF = (Empty1 > Empty2) ? File1 : File2;
-    PDBFile &LessF = (Empty1 < Empty2) ? File1 : File2;
-    uint32_t Difference = AbsoluteDifference(Empty1, Empty2);
-    outs() << formatv("  {0} had {1} more empty strings than {2}\n",
-                      MoreF.getFilePath(), Difference, LessF.getFilePath());
+  for (const auto &S : RS) {
+    auto L = LS.find(S.getKey());
+    std::string Truncated = truncateStringMiddle(S.getKey(), 28);
+    uint32_t J = cantFail(ST2.getIDForString(S.getKey()));
+    if (L == LS.end()) {
+      D.printExplicit(Truncated, DiffResult::DIFFERENT, "(not present)", J);
+      continue;
+    }
+
+    uint32_t I = cantFail(ST1.getIDForString(L->getKey()));
+    D.print<EquivalentDiffProvider>(Truncated, I, J);
   }
-  if (!HasDiff)
-    outs() << "String Table: No differences detected!\n";
   return Error::success();
 }
 
 Error DiffStyle::diffFreePageMap() { return Error::success(); }
 
 Error DiffStyle::diffInfoStream() {
+  DiffPrinter D(2, "PDB Stream", 22, 40, outs());
+  D.printExplicit("File", DiffResult::UNSPECIFIED,
+                  shortFilePath(File1.getFilePath(), 38),
+                  shortFilePath(File2.getFilePath(), 38));
+
   auto ExpectedInfo1 = File1.getPDBInfoStream();
   auto ExpectedInfo2 = File2.getPDBInfoStream();
 
-  outs() << "PDB Stream: Searching for differences...\n";
   bool Has1 = !!ExpectedInfo1;
   bool Has2 = !!ExpectedInfo2;
   if (!(Has1 && Has2)) {
-    if (Has1 != Has2)
-      outs() << formatv("{0} does not have a PDB Stream!\n",
-                        Has1 ? File1.getFilePath() : File2.getFilePath());
-    consumeError(ExpectedInfo2.takeError());
+    std::string L = Has1 ? "(present)" : "(not present)";
+    std::string R = Has2 ? "(present)" : "(not present)";
+    D.print("Stream", L, R);
+
+    consumeError(ExpectedInfo1.takeError());
     consumeError(ExpectedInfo2.takeError());
     return Error::success();
   }
 
-  bool HasDiff = false;
   auto &IS1 = *ExpectedInfo1;
   auto &IS2 = *ExpectedInfo2;
-  if (IS1.getStreamSize() != IS2.getStreamSize()) {
-    outs() << "  Stream Size\n";
-    outs() << formatv("    {0} - {1} byte(s)\n", File1.getFilePath(),
-                      IS1.getStreamSize());
-    outs() << formatv("    {0} - {1} byte(s)\n", File2.getFilePath(),
-                      IS2.getStreamSize());
-    outs() << formatv(
-        "    Difference: {0} bytes\n",
-        AbsoluteDifference(IS1.getStreamSize(), IS2.getStreamSize()));
-    HasDiff = true;
-  }
-  HasDiff |= diffAndPrint("Age", File1, File2, IS1.getAge(), IS2.getAge());
-  HasDiff |= diffAndPrint("Guid", File1, File2, IS1.getGuid(), IS2.getGuid());
-  HasDiff |= diffAndPrint("Signature", File1, File2, IS1.getSignature(),
-                          IS2.getSignature());
-  HasDiff |=
-      diffAndPrint("Version", File1, File2, IS1.getVersion(), IS2.getVersion());
-  HasDiff |= diffAndPrint("Features", File1, File2, IS1.getFeatureSignatures(),
-                          IS2.getFeatureSignatures());
-  HasDiff |= diffAndPrint("Named Stream Byte Size", File1, File2,
-                          IS1.getNamedStreamMapByteSize(),
-                          IS2.getNamedStreamMapByteSize());
-  SmallVector<StringRef, 4> NS1;
-  SmallVector<StringRef, 4> NS2;
-  for (const auto &X : IS1.getNamedStreams().entries())
-    NS1.push_back(X.getKey());
-  for (const auto &X : IS2.getNamedStreams().entries())
-    NS2.push_back(X.getKey());
-  SmallVector<StringRef, 4> OnlyP;
-  SmallVector<StringRef, 4> OnlyQ;
-  set_differences(NS1, NS2, &OnlyP, &OnlyQ);
-  printSymmetricDifferences(File1, File2, OnlyP, OnlyQ, "Named Streams");
-  if (!HasDiff)
-    outs() << "PDB Stream: No differences detected!\n";
-
+  D.print("Stream Size", IS1.getStreamSize(), IS2.getStreamSize());
+  D.print("Age", IS1.getAge(), IS2.getAge());
+  D.print("Guid", IS1.getGuid(), IS2.getGuid());
+  D.print("Signature", IS1.getSignature(), IS2.getSignature());
+  D.print("Version", IS1.getVersion(), IS2.getVersion());
+  D.diffUnorderedArray("Feature", IS1.getFeatureSignatures(),
+                       IS2.getFeatureSignatures());
+  D.print("Named Stream Size", IS1.getNamedStreamMapByteSize(),
+          IS2.getNamedStreamMapByteSize());
+  StringMap<uint32_t> NSL = IS1.getNamedStreams().getStringMap();
+  StringMap<uint32_t> NSR = IS2.getNamedStreams().getStringMap();
+  D.diffUnorderedMap<EquivalentDiffProvider>("Named Stream", NSL, NSR);
   return Error::success();
 }
 
-Error DiffStyle::diffDbiStream() { return Error::success(); }
+struct StreamNumberProvider {
+  static DiffResult compare(uint16_t L, uint16_t R) {
+    if (L == R)
+      return DiffResult::IDENTICAL;
+    bool LP = L != kInvalidStreamIndex;
+    bool RP = R != kInvalidStreamIndex;
+    if (LP != RP)
+      return DiffResult::DIFFERENT;
+    return DiffResult::EQUIVALENT;
+  }
+
+  static std::string format(uint16_t SN) {
+    if (SN == kInvalidStreamIndex)
+      return "(not present)";
+    return formatv("{0}", SN).str();
+  }
+};
+
+struct ModiProvider {
+  DiffResult compare(Optional<uint32_t> L, Optional<uint32_t> R) {
+    if (L == R)
+      return DiffResult::IDENTICAL;
+    if (L.hasValue() != R.hasValue())
+      return DiffResult::DIFFERENT;
+    return DiffResult::EQUIVALENT;
+  }
+
+  std::string format(Optional<uint32_t> Modi) {
+    if (!Modi.hasValue())
+      return "(not present)";
+    return formatv("{0}", *Modi).str();
+  }
+};
+
+struct StringProvider {
+  DiffResult compare(StringRef L, StringRef R) {
+    IdenticalDiffProvider I;
+    return I.compare(L, R);
+  }
+
+  std::string format(StringRef S) {
+    if (S.empty())
+      return "(empty)";
+    return S;
+  }
+};
+
+static std::vector<std::pair<uint32_t, DbiModuleDescriptor>>
+getModuleDescriptors(const DbiModuleList &ML) {
+  std::vector<std::pair<uint32_t, DbiModuleDescriptor>> List;
+  List.reserve(ML.getModuleCount());
+  for (uint32_t I = 0; I < ML.getModuleCount(); ++I)
+    List.emplace_back(I, ML.getModuleDescriptor(I));
+  return List;
+}
+
+Error DiffStyle::diffDbiStream() {
+  DiffPrinter D(2, "DBI Stream", 40, 30, outs());
+  D.printExplicit("File", DiffResult::UNSPECIFIED,
+                  shortFilePath(File1.getFilePath(), 38),
+                  shortFilePath(File2.getFilePath(), 38));
+
+  auto ExpectedDbi1 = File1.getPDBDbiStream();
+  auto ExpectedDbi2 = File2.getPDBDbiStream();
+
+  bool Has1 = !!ExpectedDbi1;
+  bool Has2 = !!ExpectedDbi2;
+  if (!(Has1 && Has2)) {
+    std::string L = Has1 ? "(present)" : "(not present)";
+    std::string R = Has2 ? "(present)" : "(not present)";
+    D.print("Stream", L, R);
+
+    consumeError(ExpectedDbi1.takeError());
+    consumeError(ExpectedDbi2.takeError());
+    return Error::success();
+  }
+
+  auto &DL = *ExpectedDbi1;
+  auto &DR = *ExpectedDbi2;
+
+  D.print("Dbi Version", (uint32_t)DL.getDbiVersion(),
+          (uint32_t)DR.getDbiVersion());
+  D.print("Age", DL.getAge(), DR.getAge());
+  D.print("Machine", (uint16_t)DL.getMachineType(),
+          (uint16_t)DR.getMachineType());
+  D.print("Flags", DL.getFlags(), DR.getFlags());
+  D.print("Build Major", DL.getBuildMajorVersion(), DR.getBuildMajorVersion());
+  D.print("Build Minor", DL.getBuildMinorVersion(), DR.getBuildMinorVersion());
+  D.print("Build Number", DL.getBuildNumber(), DR.getBuildNumber());
+  D.print("PDB DLL Version", DL.getPdbDllVersion(), DR.getPdbDllVersion());
+  D.print("PDB DLL RBLD", DL.getPdbDllRbld(), DR.getPdbDllRbld());
+  D.print<StreamNumberProvider>("DBG (FPO)",
+                                DL.getDebugStreamIndex(DbgHeaderType::FPO),
+                                DR.getDebugStreamIndex(DbgHeaderType::FPO));
+  D.print<StreamNumberProvider>(
+      "DBG (Exception)", DL.getDebugStreamIndex(DbgHeaderType::Exception),
+      DR.getDebugStreamIndex(DbgHeaderType::Exception));
+  D.print<StreamNumberProvider>("DBG (Fixup)",
+                                DL.getDebugStreamIndex(DbgHeaderType::Fixup),
+                                DR.getDebugStreamIndex(DbgHeaderType::Fixup));
+  D.print<StreamNumberProvider>(
+      "DBG (OmapToSrc)", DL.getDebugStreamIndex(DbgHeaderType::OmapToSrc),
+      DR.getDebugStreamIndex(DbgHeaderType::OmapToSrc));
+  D.print<StreamNumberProvider>(
+      "DBG (OmapFromSrc)", DL.getDebugStreamIndex(DbgHeaderType::OmapFromSrc),
+      DR.getDebugStreamIndex(DbgHeaderType::OmapFromSrc));
+  D.print<StreamNumberProvider>(
+      "DBG (SectionHdr)", DL.getDebugStreamIndex(DbgHeaderType::SectionHdr),
+      DR.getDebugStreamIndex(DbgHeaderType::SectionHdr));
+  D.print<StreamNumberProvider>(
+      "DBG (TokenRidMap)", DL.getDebugStreamIndex(DbgHeaderType::TokenRidMap),
+      DR.getDebugStreamIndex(DbgHeaderType::TokenRidMap));
+  D.print<StreamNumberProvider>("DBG (Xdata)",
+                                DL.getDebugStreamIndex(DbgHeaderType::Xdata),
+                                DR.getDebugStreamIndex(DbgHeaderType::Xdata));
+  D.print<StreamNumberProvider>("DBG (Pdata)",
+                                DL.getDebugStreamIndex(DbgHeaderType::Pdata),
+                                DR.getDebugStreamIndex(DbgHeaderType::Pdata));
+  D.print<StreamNumberProvider>("DBG (NewFPO)",
+                                DL.getDebugStreamIndex(DbgHeaderType::NewFPO),
+                                DR.getDebugStreamIndex(DbgHeaderType::NewFPO));
+  D.print<StreamNumberProvider>(
+      "DBG (SectionHdrOrig)",
+      DL.getDebugStreamIndex(DbgHeaderType::SectionHdrOrig),
+      DR.getDebugStreamIndex(DbgHeaderType::SectionHdrOrig));
+  D.print<StreamNumberProvider>("Globals Stream",
+                                DL.getGlobalSymbolStreamIndex(),
+                                DR.getGlobalSymbolStreamIndex());
+  D.print<StreamNumberProvider>("Publics Stream",
+                                DL.getPublicSymbolStreamIndex(),
+                                DR.getPublicSymbolStreamIndex());
+  D.print<StreamNumberProvider>("Symbol Records", DL.getSymRecordStreamIndex(),
+                                DR.getSymRecordStreamIndex());
+  D.print("Has CTypes", DL.hasCTypes(), DR.hasCTypes());
+  D.print("Is Incrementally Linked", DL.isIncrementallyLinked(),
+          DR.isIncrementallyLinked());
+  D.print("Is Stripped", DL.isStripped(), DR.isStripped());
+  const DbiModuleList &ML = DL.modules();
+  const DbiModuleList &MR = DR.modules();
+  D.print("Module Count", ML.getModuleCount(), MR.getModuleCount());
+  D.print("Source File Count", ML.getSourceFileCount(),
+          MR.getSourceFileCount());
+  auto MDL = getModuleDescriptors(ML);
+  auto MDR = getModuleDescriptors(MR);
+  // Scan all module descriptors from the left, and look for corresponding
+  // module descriptors on the right.
+  for (const auto &L : MDL) {
+    D.printFullRow(
+        truncateQuotedNameFront("Module", L.second.getModuleName(), 70));
+
+    auto Iter = llvm::find_if(
+        MDR, [&L](const std::pair<uint32_t, DbiModuleDescriptor> &R) {
+          return R.second.getModuleName().equals_lower(
+              L.second.getModuleName());
+        });
+    if (Iter == MDR.end()) {
+      // We didn't find this module at all on the right.  Just print one row
+      // and continue.
+      D.print<ModiProvider>("- Modi", L.first, None);
+      continue;
+    }
+
+    // We did find this module.  Go through and compare each field.
+    const auto &R = *Iter;
+    D.print<ModiProvider>("- Modi", L.first, R.first);
+    D.print<StringProvider>("- Obj File Name",
+                            shortFilePath(L.second.getObjFileName(), 28),
+                            shortFilePath(R.second.getObjFileName(), 28));
+    D.print<StreamNumberProvider>("- Debug Stream",
+                                  L.second.getModuleStreamIndex(),
+                                  R.second.getModuleStreamIndex());
+    D.print("- C11 Byte Size", L.second.getC11LineInfoByteSize(),
+            R.second.getC11LineInfoByteSize());
+    D.print("- C13 Byte Size", L.second.getC13LineInfoByteSize(),
+            R.second.getC13LineInfoByteSize());
+    D.print("- # of files", L.second.getNumberOfFiles(),
+            R.second.getNumberOfFiles());
+    D.print("- Pdb File Path Index", L.second.getPdbFilePathNameIndex(),
+            R.second.getPdbFilePathNameIndex());
+    D.print("- Source File Name Index", L.second.getSourceFileNameIndex(),
+            R.second.getSourceFileNameIndex());
+    D.print("- Symbol Byte Size", L.second.getSymbolDebugInfoByteSize(),
+            R.second.getSymbolDebugInfoByteSize());
+    MDR.erase(Iter);
+  }
+
+  return Error::success();
+}
 
 Error DiffStyle::diffSectionContribs() { return Error::success(); }
 

Added: llvm/trunk/tools/llvm-pdbutil/DiffPrinter.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/llvm-pdbutil/DiffPrinter.cpp?rev=307421&view=auto
==============================================================================
--- llvm/trunk/tools/llvm-pdbutil/DiffPrinter.cpp (added)
+++ llvm/trunk/tools/llvm-pdbutil/DiffPrinter.cpp Fri Jul  7 11:45:37 2017
@@ -0,0 +1,106 @@
+
+#include "DiffPrinter.h"
+
+#include "llvm/Support/FormatAdapters.h"
+
+using namespace llvm;
+using namespace llvm::pdb;
+
+static void setColor(llvm::raw_ostream &OS, DiffResult Result) {
+  switch (Result) {
+  case DiffResult::IDENTICAL:
+    OS.changeColor(raw_ostream::Colors::GREEN, false);
+    break;
+  case DiffResult::EQUIVALENT:
+    OS.changeColor(raw_ostream::Colors::YELLOW, true);
+    break;
+  default:
+    OS.changeColor(raw_ostream::Colors::RED, false);
+    break;
+  }
+}
+
+DiffPrinter::DiffPrinter(uint32_t Indent, StringRef Header,
+                         uint32_t PropertyWidth, uint32_t FieldWidth,
+                         raw_ostream &Stream)
+    : Indent(Indent), PropertyWidth(PropertyWidth), FieldWidth(FieldWidth),
+      OS(Stream) {
+  printHeaderRow();
+  printFullRow(Header);
+}
+
+DiffPrinter::~DiffPrinter() {}
+
+void DiffPrinter::printFullRow(StringRef Text) {
+  newLine();
+  printField(Text, DiffResult::UNSPECIFIED, AlignStyle::Center,
+             PropertyWidth + 1 + FieldWidth + 1 + FieldWidth);
+  printSeparatorRow();
+}
+
+void DiffPrinter::printSeparatorRow() {
+  newLine();
+  OS << formatv("{0}", fmt_repeat('-', PropertyWidth));
+  OS << '+';
+  OS << formatv("{0}", fmt_repeat('-', FieldWidth));
+  OS << '+';
+  OS << formatv("{0}", fmt_repeat('-', FieldWidth));
+  OS << '|';
+}
+
+void DiffPrinter::printHeaderRow() {
+  newLine('-');
+  OS << formatv("{0}", fmt_repeat('-', PropertyWidth + 2 * FieldWidth + 3));
+}
+
+void DiffPrinter::newLine(char InitialChar) {
+  OS << "\n";
+  OS.indent(Indent) << InitialChar;
+}
+
+void DiffPrinter::printExplicit(StringRef Property, DiffResult C,
+                                StringRef Left, StringRef Right) {
+  newLine();
+  printField(Property, DiffResult::UNSPECIFIED, AlignStyle::Right,
+             PropertyWidth);
+  printField(Left, C, AlignStyle::Center, FieldWidth);
+  printField(Right, C, AlignStyle::Center, FieldWidth);
+  printSeparatorRow();
+}
+
+void DiffPrinter::printSame(StringRef Property, StringRef Value) {
+  newLine();
+  printField(Property, DiffResult::UNSPECIFIED, AlignStyle::Right,
+             PropertyWidth);
+  printField(Value, DiffResult::IDENTICAL, AlignStyle::Center,
+             FieldWidth + 1 + FieldWidth);
+  printSeparatorRow();
+}
+
+void DiffPrinter::printDifferent(StringRef Property, StringRef Left,
+                                 StringRef Right) {
+  newLine();
+  printField(Property, DiffResult::UNSPECIFIED, AlignStyle::Right,
+             PropertyWidth);
+  printField(Left, DiffResult::DIFFERENT, AlignStyle::Center, FieldWidth);
+  printField(Right, DiffResult::DIFFERENT, AlignStyle::Center, FieldWidth);
+  printSeparatorRow();
+}
+
+void DiffPrinter::printField(StringRef Value, DiffResult C, AlignStyle Style,
+                             uint32_t Width) {
+  if (Style == AlignStyle::Right)
+    --Width;
+
+  std::string FormattedItem =
+      formatv("{0}", fmt_align(Value, Style, Width)).str();
+  if (C != DiffResult::UNSPECIFIED) {
+    setColor(OS, C);
+    OS << FormattedItem;
+    OS.resetColor();
+  } else
+    OS << FormattedItem;
+  if (Style == AlignStyle::Right)
+    OS << ' ';
+  OS << '|';
+}

Added: llvm/trunk/tools/llvm-pdbutil/DiffPrinter.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/llvm-pdbutil/DiffPrinter.h?rev=307421&view=auto
==============================================================================
--- llvm/trunk/tools/llvm-pdbutil/DiffPrinter.h (added)
+++ llvm/trunk/tools/llvm-pdbutil/DiffPrinter.h Fri Jul  7 11:45:37 2017
@@ -0,0 +1,158 @@
+//===- DiffPrinter.h ------------------------------------------ *- C++ --*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TOOLS_LLVMPDBDUMP_DIFFPRINTER_H
+#define LLVM_TOOLS_LLVMPDBDUMP_DIFFPRINTER_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <list>
+#include <unordered_set>
+
+namespace llvm {
+namespace pdb {
+
+class PDBFile;
+
+enum class DiffResult { UNSPECIFIED, IDENTICAL, EQUIVALENT, DIFFERENT };
+
+struct IdenticalDiffProvider {
+  template <typename T, typename U>
+  DiffResult compare(const T &Left, const U &Right) {
+    return (Left == Right) ? DiffResult::IDENTICAL : DiffResult::DIFFERENT;
+  }
+
+  template <typename T> std::string format(const T &Item) {
+    return formatv("{0}", Item).str();
+  }
+};
+
+struct EquivalentDiffProvider {
+  template <typename T, typename U>
+  DiffResult compare(const T &Left, const U &Right) {
+    return (Left == Right) ? DiffResult::IDENTICAL : DiffResult::EQUIVALENT;
+  }
+
+  template <typename T> std::string format(const T &Item) {
+    return formatv("{0}", Item).str();
+  }
+};
+
+class DiffPrinter {
+public:
+  DiffPrinter(uint32_t Indent, StringRef Header, uint32_t PropertyWidth,
+              uint32_t FieldWidth, raw_ostream &Stream);
+  ~DiffPrinter();
+
+  template <typename T, typename U> struct Identical {};
+
+  template <typename Provider = IdenticalDiffProvider, typename T, typename U>
+  void print(StringRef Property, const T &Left, const U &Right,
+             Provider P = Provider()) {
+    std::string L = P.format(Left);
+    std::string R = P.format(Right);
+
+    DiffResult Result = P.compare(Left, Right);
+    printExplicit(Property, Result, L, R);
+  }
+
+  void printExplicit(StringRef Property, DiffResult C, StringRef Left,
+                     StringRef Right);
+
+  template <typename T, typename U>
+  void printExplicit(StringRef Property, DiffResult C, const T &Left,
+                     const U &Right) {
+    std::string L = formatv("{0}", Left).str();
+    std::string R = formatv("{0}", Right).str();
+    printExplicit(Property, C, StringRef(L), StringRef(R));
+  }
+
+  template <typename T, typename U>
+  void diffUnorderedArray(StringRef Property, ArrayRef<T> Left,
+                          ArrayRef<U> Right) {
+    std::unordered_set<T> LS(Left.begin(), Left.end());
+    std::unordered_set<U> RS(Right.begin(), Right.end());
+    std::string Count1 = formatv("{0} element(s)", Left.size());
+    std::string Count2 = formatv("{0} element(s)", Right.size());
+    print(std::string(Property) + "s (set)", Count1, Count2);
+    for (const auto &L : LS) {
+      auto Iter = RS.find(L);
+      std::string Text = formatv("{0}", L).str();
+      if (Iter == RS.end()) {
+        print(Property, Text, "(not present)");
+        continue;
+      }
+      print(Property, Text, Text);
+      RS.erase(Iter);
+    }
+    for (const auto &R : RS) {
+      auto Iter = LS.find(R);
+      std::string Text = formatv("{0}", R).str();
+      if (Iter == LS.end()) {
+        print(Property, "(not present)", Text);
+        continue;
+      }
+      print(Property, Text, Text);
+    }
+  }
+
+  template <typename ValueProvider = IdenticalDiffProvider, typename T,
+            typename U>
+  void diffUnorderedMap(StringRef Property, const StringMap<T> &Left,
+                        const StringMap<U> &Right,
+                        ValueProvider P = ValueProvider()) {
+    StringMap<U> RightCopy(Right);
+
+    std::string Count1 = formatv("{0} element(s)", Left.size());
+    std::string Count2 = formatv("{0} element(s)", Right.size());
+    print(std::string(Property) + "s (map)", Count1, Count2);
+
+    for (const auto &L : Left) {
+      auto Iter = RightCopy.find(L.getKey());
+      if (Iter == RightCopy.end()) {
+        printExplicit(L.getKey(), DiffResult::DIFFERENT, L.getValue(),
+                      "(not present)");
+        continue;
+      }
+
+      print(L.getKey(), L.getValue(), Iter->getValue(), P);
+      RightCopy.erase(Iter);
+    }
+
+    for (const auto &R : RightCopy) {
+      printExplicit(R.getKey(), DiffResult::DIFFERENT, "(not present)",
+                    R.getValue());
+    }
+  }
+
+  void printFullRow(StringRef Text);
+
+private:
+  void printSame(StringRef Property, StringRef Value);
+  void printDifferent(StringRef Property, StringRef Left, StringRef Right);
+
+  void printHeaderRow();
+  void printSeparatorRow();
+  void newLine(char InitialChar = '|');
+  void printField(StringRef Value, DiffResult C, AlignStyle Style,
+                  uint32_t Width);
+
+  uint32_t Indent;
+  uint32_t PropertyWidth;
+  uint32_t FieldWidth;
+  raw_ostream &OS;
+};
+} // namespace pdb
+} // namespace llvm
+
+#endif

Modified: llvm/trunk/tools/llvm-pdbutil/FormatUtil.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/llvm-pdbutil/FormatUtil.cpp?rev=307421&r1=307420&r2=307421&view=diff
==============================================================================
--- llvm/trunk/tools/llvm-pdbutil/FormatUtil.cpp (original)
+++ llvm/trunk/tools/llvm-pdbutil/FormatUtil.cpp Fri Jul  7 11:45:37 2017
@@ -26,6 +26,17 @@ std::string llvm::pdb::truncateStringBac
   return std::string(S) + std::string("...");
 }
 
+std::string llvm::pdb::truncateStringMiddle(StringRef S, uint32_t MaxLen) {
+  if (MaxLen == 0 || S.size() <= MaxLen || S.size() <= 3)
+    return S;
+
+  assert(MaxLen >= 3);
+  uint32_t FinalLen = std::min<size_t>(S.size(), MaxLen - 3);
+  StringRef Front = S.take_front(FinalLen / 2);
+  StringRef Back = S.take_back(Front.size());
+  return std::string(Front) + std::string("...") + std::string(Back);
+}
+
 std::string llvm::pdb::truncateStringFront(StringRef S, uint32_t MaxLen) {
   if (MaxLen == 0 || S.size() <= MaxLen || S.size() <= 3)
     return S;

Modified: llvm/trunk/tools/llvm-pdbutil/FormatUtil.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/llvm-pdbutil/FormatUtil.h?rev=307421&r1=307420&r2=307421&view=diff
==============================================================================
--- llvm/trunk/tools/llvm-pdbutil/FormatUtil.h (original)
+++ llvm/trunk/tools/llvm-pdbutil/FormatUtil.h Fri Jul  7 11:45:37 2017
@@ -23,6 +23,7 @@ namespace llvm {
 namespace pdb {
 
 std::string truncateStringBack(StringRef S, uint32_t MaxLen);
+std::string truncateStringMiddle(StringRef S, uint32_t MaxLen);
 std::string truncateStringFront(StringRef S, uint32_t MaxLen);
 std::string truncateQuotedNameFront(StringRef Label, StringRef Name,
                                     uint32_t MaxLen);




More information about the llvm-commits mailing list