]> granicus.if.org Git - clang/commitdiff
[analyzer] Ensure that PathDiagnostics profile the same regardless of path.
authorJordan Rose <jordan_rose@apple.com>
Fri, 31 Aug 2012 00:36:26 +0000 (00:36 +0000)
committerJordan Rose <jordan_rose@apple.com>
Fri, 31 Aug 2012 00:36:26 +0000 (00:36 +0000)
PathDiagnostics are actually profiled and uniqued independently of the
path on which the bug occurred. This is used to merge diagnostics that
refer to the same issue along different paths, as well as by the plist
diagnostics to reference files created by the HTML diagnostics.

However, there are two problems with the current implementation:

1) The bug description is included in the profile, but some
   PathDiagnosticConsumers prefer abbreviated descriptions and some
   prefer verbose descriptions. Fixed by including both descriptions in
   the PathDiagnostic objects and always using the verbose one in the profile.

2) The "minimal" path generation scheme provides extra information about
   which events came from macros that the "extensive" scheme does not.
   This resulted not only in different locations for the plist and HTML
   diagnostics, but also in diagnostics being uniqued in the plist output
   but not in the HTML output. Fixed by storing the "end path" location
   explicitly in the PathDiagnostic object, rather than trying to find the
   last piece of the path when the diagnostic is requested.

This should hopefully finish unsticking our internal buildbot.

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

include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h
include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h
lib/StaticAnalyzer/Core/BugReporter.cpp
lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
lib/StaticAnalyzer/Core/PathDiagnostic.cpp
lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
lib/StaticAnalyzer/Core/TextPathDiagnostics.cpp
lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
test/Analysis/plist-html-macros.c [new file with mode: 0644]

index c8581e2e23188e0e2f872d44751dee56ab950660..0e1d15253c3c2f08574f28de10f476094dad6746 100644 (file)
@@ -163,8 +163,10 @@ public:
 
   const StringRef getDescription() const { return Description; }
 
-  const StringRef getShortDescription() const {
-    return ShortDescription.empty() ? Description : ShortDescription;
+  const StringRef getShortDescription(bool UseFallback = true) const {
+    if (ShortDescription.empty() && UseFallback)
+      return Description;
+    return ShortDescription;
   }
 
   /// Indicates whether or not any path pruning should take place
index ad8a0694b41bb869024c082d9ff4726d92346b64..dc275f08cc80fa0914307e25e506cfd8da6e8b84 100644 (file)
@@ -97,7 +97,6 @@ public:
   virtual PathGenerationScheme getGenerationScheme() const { return Minimal; }
   virtual bool supportsLogicalOpControlFlow() const { return false; }
   virtual bool supportsAllBlockEdges() const { return false; }
-  virtual bool useVerboseDescription() const { return true; }
   
   /// Return true if the PathDiagnosticConsumer supports individual
   /// PathDiagnostics that span multiple files.
@@ -653,14 +652,22 @@ public:
 class PathDiagnostic : public llvm::FoldingSetNode {
   const Decl *DeclWithIssue;
   std::string BugType;
-  std::string Desc;
+  std::string VerboseDesc;
+  std::string ShortDesc;
   std::string Category;
   std::deque<std::string> OtherDesc;
+  PathDiagnosticLocation Loc;
   PathPieces pathImpl;
   llvm::SmallVector<PathPieces *, 3> pathStack;
   
   PathDiagnostic(); // Do not implement.
 public:
+  PathDiagnostic(const Decl *DeclWithIssue, StringRef bugtype,
+                 StringRef verboseDesc, StringRef shortDesc,
+                 StringRef category);
+
+  ~PathDiagnostic();
+  
   const PathPieces &path;
 
   /// Return the path currently used by builders for constructing the 
@@ -683,16 +690,24 @@ public:
   void popActivePath() { if (!pathStack.empty()) pathStack.pop_back(); }
 
   bool isWithinCall() const { return !pathStack.empty(); }
-  
-  //  PathDiagnostic();
-  PathDiagnostic(const Decl *DeclWithIssue,
-                 StringRef bugtype,
-                 StringRef desc,
-                 StringRef category);
 
-  ~PathDiagnostic();
+  void setEndOfPath(PathDiagnosticPiece *EndPiece) {
+    assert(!Loc.isValid() && "End location already set!");
+    Loc = EndPiece->getLocation();
+    assert(Loc.isValid() && "Invalid location for end-of-path piece");
+    getActivePath().push_back(EndPiece);
+  }
 
-  StringRef getDescription() const { return Desc; }
+  void resetPath() {
+    pathStack.clear();
+    pathImpl.clear();
+    Loc = PathDiagnosticLocation();
+  }
+  
+  StringRef getVerboseDescription() const { return VerboseDesc; }
+  StringRef getShortDescription() const {
+    return ShortDesc.empty() ? VerboseDesc : ShortDesc;
+  }
   StringRef getBugType() const { return BugType; }
   StringRef getCategory() const { return Category; }
 
@@ -706,15 +721,27 @@ public:
   meta_iterator meta_end() const { return OtherDesc.end(); }
   void addMeta(StringRef s) { OtherDesc.push_back(s); }
 
-  PathDiagnosticLocation getLocation() const;
+  PathDiagnosticLocation getLocation() const {
+    assert(Loc.isValid() && "No end-of-path location set yet!");
+    return Loc;
+  }
 
   void flattenLocations() {
+    Loc.flatten();
     for (PathPieces::iterator I = pathImpl.begin(), E = pathImpl.end(); 
          I != E; ++I) (*I)->flattenLocations();
   }
-  
+
+  /// Profiles the diagnostic, independent of the path it references.
+  ///
+  /// This can be used to merge diagnostics that refer to the same issue
+  /// along different paths.
   void Profile(llvm::FoldingSetNodeID &ID) const;
-  
+
+  /// Profiles the diagnostic, including its path.
+  ///
+  /// Two diagnostics with the same issue along different paths will generate
+  /// different profiles.
   void FullProfile(llvm::FoldingSetNodeID &ID) const;
 };  
 
index 1866a27f728b5346631dfc0d6d32866b1f5441af..68cc7d88b1a817ea8b8aad7a39c51c76477adc9b 100644 (file)
@@ -1898,7 +1898,7 @@ void GRBugReporter::GeneratePathDiagnostic(PathDiagnostic& PD,
        visitors.push_back((*I)->clone());
 
     // Clear out the active path from any previous work.
-    PD.getActivePath().clear();
+    PD.resetPath();
     originalReportConfigToken = R->getConfigurationChangeToken();
 
     // Generate the very last diagnostic piece - the piece is visible before 
@@ -1915,7 +1915,7 @@ void GRBugReporter::GeneratePathDiagnostic(PathDiagnostic& PD,
     if (!LastPiece)
       LastPiece = BugReporterVisitor::getDefaultEndPath(PDB, N, *R);
     if (LastPiece)
-      PD.getActivePath().push_back(LastPiece);
+      PD.setEndOfPath(LastPiece);
     else
       return;
 
@@ -2106,9 +2106,8 @@ void BugReporter::FlushReport(BugReport *exampleReport,
   OwningPtr<PathDiagnostic>
     D(new PathDiagnostic(exampleReport->getDeclWithIssue(),
                          exampleReport->getBugType().getName(),
-                         PD.useVerboseDescription()
-                         ? exampleReport->getDescription() 
-                         : exampleReport->getShortDescription(),
+                         exampleReport->getDescription(),
+                         exampleReport->getShortDescription(/*Fallback=*/false),
                          BT.getCategory()));
 
   // Generate the full path diagnostic, using the generation scheme
@@ -2128,7 +2127,7 @@ void BugReporter::FlushReport(BugReport *exampleReport,
     llvm::tie(Beg, End) = exampleReport->getRanges();
     for ( ; Beg != End; ++Beg)
       piece->addRange(*Beg);
-    D->getActivePath().push_back(piece);
+    D->setEndOfPath(piece);
   }
 
   // Get the meta data.
index 46adfbd795a882be6bce902f21f4cb18629d9962..211bcb91240985481ac4305e319a79e017c8b16e 100644 (file)
@@ -189,7 +189,7 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
       << (*path.rbegin())->getLocation().asLocation().getExpansionColumnNumber()
       << "</a></td></tr>\n"
          "<tr><td class=\"rowname\">Description:</td><td>"
-      << D.getDescription() << "</td></tr>\n";
+      << D.getVerboseDescription() << "</td></tr>\n";
 
     // Output any other meta data.
 
@@ -209,15 +209,15 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
     std::string s;
     llvm::raw_string_ostream os(s);
 
-    const std::string& BugDesc = D.getDescription();
+    StringRef BugDesc = D.getVerboseDescription();
     if (!BugDesc.empty())
       os << "\n<!-- BUGDESC " << BugDesc << " -->\n";
 
-    const std::string& BugType = D.getBugType();
+    StringRef BugType = D.getBugType();
     if (!BugType.empty())
       os << "\n<!-- BUGTYPE " << BugType << " -->\n";
 
-    const std::string& BugCategory = D.getCategory();
+    StringRef BugCategory = D.getCategory();
     if (!BugCategory.empty())
       os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n";
 
index 97ef906cac36479d033dff03282b59f2da2389a4..d010a668d91550e64596073598bfc61b50b0dddc 100644 (file)
@@ -104,11 +104,12 @@ void PathPieces::flattenTo(PathPieces &Primary, PathPieces &Current,
 PathDiagnostic::~PathDiagnostic() {}
 
 PathDiagnostic::PathDiagnostic(const Decl *declWithIssue,
-                               StringRef bugtype, StringRef desc,
-                               StringRef category)
+                               StringRef bugtype, StringRef verboseDesc,
+                               StringRef shortDesc, StringRef category)
   : DeclWithIssue(declWithIssue),
     BugType(StripTrailingDots(bugtype)),
-    Desc(StripTrailingDots(desc)),
+    VerboseDesc(StripTrailingDots(verboseDesc)),
+    ShortDesc(StripTrailingDots(shortDesc)),
     Category(StripTrailingDots(category)),
     path(pathImpl) {}
 
@@ -227,8 +228,8 @@ struct CompareDiagnostics {
       return false;
     
     // Next, compare by bug description.
-    StringRef XDesc = X->getDescription();
-    StringRef YDesc = Y->getDescription();
+    StringRef XDesc = X->getVerboseDescription();
+    StringRef YDesc = Y->getVerboseDescription();
     if (XDesc < YDesc)
       return true;
     if (XDesc != YDesc)
@@ -271,18 +272,11 @@ void PathDiagnosticConsumer::FlushDiagnostics(
   }
 }
 
-static void ProfileDiagnostic(const PathDiagnostic &PD,
-                              llvm::FoldingSetNodeID &NodeID) {
-  NodeID.AddString(PD.getBugType());
-  NodeID.AddString(PD.getDescription());
-  NodeID.Add(PD.getLocation());
-}
-
 void PathDiagnosticConsumer::FilesMade::addDiagnostic(const PathDiagnostic &PD,
                                                       StringRef ConsumerName,
                                                       StringRef FileName) {
   llvm::FoldingSetNodeID NodeID;
-  ProfileDiagnostic(PD, NodeID);
+  NodeID.Add(PD);
   void *InsertPos;
   PDFileEntry *Entry = FindNodeOrInsertPos(NodeID, InsertPos);
   if (!Entry) {
@@ -303,7 +297,7 @@ void PathDiagnosticConsumer::FilesMade::addDiagnostic(const PathDiagnostic &PD,
 PathDiagnosticConsumer::PDFileEntry::ConsumerFiles *
 PathDiagnosticConsumer::FilesMade::getFiles(const PathDiagnostic &PD) {
   llvm::FoldingSetNodeID NodeID;
-  ProfileDiagnostic(PD, NodeID);
+  NodeID.Add(PD);
   void *InsertPos;
   PDFileEntry *Entry = FindNodeOrInsertPos(NodeID, InsertPos);
   if (!Entry)
@@ -627,24 +621,6 @@ void PathDiagnosticLocation::flatten() {
   }
 }
 
-PathDiagnosticLocation PathDiagnostic::getLocation() const {
-  assert(path.size() > 0 &&
-         "getLocation() requires a non-empty PathDiagnostic.");
-  
-  PathDiagnosticPiece *p = path.rbegin()->getPtr();
-  
-  while (true) {
-    if (PathDiagnosticCallPiece *cp = dyn_cast<PathDiagnosticCallPiece>(p)) {
-      assert(!cp->path.empty());
-      p = cp->path.rbegin()->getPtr();
-      continue;
-    }
-    break;
-  }
-  
-  return p->getLocation();
-}
-
 //===----------------------------------------------------------------------===//
 // Manipulation of PathDiagnosticCallPieces.
 //===----------------------------------------------------------------------===//
@@ -793,10 +769,9 @@ void PathDiagnosticMacroPiece::Profile(llvm::FoldingSetNodeID &ID) const {
 }
 
 void PathDiagnostic::Profile(llvm::FoldingSetNodeID &ID) const {
-  if (!path.empty())
-    getLocation().Profile(ID);
+  ID.Add(getLocation());
   ID.AddString(BugType);
-  ID.AddString(Desc);
+  ID.AddString(VerboseDesc);
   ID.AddString(Category);
 }
 
index c1c46c293a78a47469be9acfce164309b3972a27..0a534bf7c0a689ce1f669061a05c0da6837ae406 100644 (file)
@@ -47,7 +47,6 @@ namespace {
     PathGenerationScheme getGenerationScheme() const { return Extensive; }
     bool supportsLogicalOpControlFlow() const { return true; }
     bool supportsAllBlockEdges() const { return true; }
-    virtual bool useVerboseDescription() const { return false; }
     virtual bool supportsCrossFileDiagnostics() const {
       return SupportsCrossFileDiagnostics;
     }
@@ -443,7 +442,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl(
 
     // Output the bug type and bug category.
     o << "   <key>description</key>";
-    EmitString(o, D->getDescription()) << '\n';
+    EmitString(o, D->getShortDescription()) << '\n';
     o << "   <key>category</key>";
     EmitString(o, D->getCategory()) << '\n';
     o << "   <key>type</key>";
index 66bf4bb222d1760cd8c787239d8a918ac268bfe2..e09f4e365344a45569f85e6d12cfec5f7805008c 100644 (file)
@@ -41,7 +41,6 @@ public:
   PathGenerationScheme getGenerationScheme() const { return Minimal; }
   bool supportsLogicalOpControlFlow() const { return true; }
   bool supportsAllBlockEdges() const { return true; }
-  virtual bool useVerboseDescription() const { return true; }
   virtual bool supportsCrossFileDiagnostics() const { return true; }
 };
 
index bd643bab0374df2aacf64f914b3ced261bff189e..53747d4b4db3e3360ee98b55a25fa2f5a1005cd6 100644 (file)
@@ -78,7 +78,6 @@ public:
   ClangDiagPathDiagConsumer(DiagnosticsEngine &Diag) : Diag(Diag) {}
   virtual ~ClangDiagPathDiagConsumer() {}
   virtual StringRef getName() const { return "ClangDiags"; }
-  virtual bool useVerboseDescription() const { return false; }
   virtual PathGenerationScheme getGenerationScheme() const { return None; }
 
   void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
@@ -86,7 +85,7 @@ public:
     for (std::vector<const PathDiagnostic*>::iterator I = Diags.begin(),
          E = Diags.end(); I != E; ++I) {
       const PathDiagnostic *PD = *I;
-      StringRef desc = PD->getDescription();
+      StringRef desc = PD->getShortDescription();
       SmallString<512> TmpStr;
       llvm::raw_svector_ostream Out(TmpStr);
       for (StringRef::iterator I=desc.begin(), E=desc.end(); I!=E; ++I) {
diff --git a/test/Analysis/plist-html-macros.c b/test/Analysis/plist-html-macros.c
new file mode 100644 (file)
index 0000000..954aab0
--- /dev/null
@@ -0,0 +1,31 @@
+// REQUIRES: shell
+// RUN: %clang_cc1 -analyze -analyzer-checker=core -verify %s
+// (sanity check)
+
+// RUN: rm -rf %t.dir
+// RUN: mkdir -p %t.dir
+// RUN: %clang_cc1 -analyze -analyzer-checker=core -analyzer-output=plist-html -o %t.dir/index.plist %s
+// RUN: ls %t.dir | grep \\.html | count 1
+// RUN: grep \\.html %t.dir/index.plist | count 1
+
+// This tests two things: that the two calls to null_deref below are coalesced
+// into a single bug by both the plist and HTML diagnostics, and that the plist
+// diagnostics have a reference to the HTML diagnostics. (It would be nice to
+// check more carefully that the two actually match, but that's hard to write
+// in a lit RUN line.)
+
+#define CALL_FN(a) null_deref(a)
+
+void null_deref(int *a) {
+  if (a)
+    return;
+  *a = 1; // expected-warning{{null}}
+}
+
+void test1() {
+  CALL_FN(0);
+}
+
+void test2(int *p) {
+  CALL_FN(p);
+}