Rework flushing of diagnostics to PathDiagnosticConsumer.  Now all the reports are batched up before being flushed
to the underlying consumer implementation.  This allows us to unique reports across analyses to multiple functions (which
shows up with inlining).

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@148997 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h
index fba5692..427c47a 100644
--- a/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h
+++ b/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h
@@ -48,20 +48,18 @@
 class PathDiagnosticConsumer {
   virtual void anchor();
 public:
-  PathDiagnosticConsumer() {}
+  PathDiagnosticConsumer() : flushed(false) {}
+  virtual ~PathDiagnosticConsumer();
 
-  virtual ~PathDiagnosticConsumer() {}
-  
-  virtual void
-  FlushDiagnostics(SmallVectorImpl<std::string> *FilesMade = 0) = 0;
-  
-  void FlushDiagnostics(SmallVectorImpl<std::string> &FilesMade) {
-    FlushDiagnostics(&FilesMade);
-  }
-  
+  void FlushDiagnostics(SmallVectorImpl<std::string> *FilesMade);
+
+  virtual void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
+                                    SmallVectorImpl<std::string> *FilesMade)
+                                    = 0;
+
   virtual StringRef getName() const = 0;
   
-  void HandlePathDiagnostic(const PathDiagnostic* D);
+  void HandlePathDiagnostic(PathDiagnostic *D);
 
   enum PathGenerationScheme { Minimal, Extensive };
   virtual PathGenerationScheme getGenerationScheme() const { return Minimal; }
@@ -70,10 +68,8 @@
   virtual bool useVerboseDescription() const { return true; }
 
 protected:
-  /// The actual logic for handling path diagnostics, as implemented
-  /// by subclasses of PathDiagnosticConsumer.
-  virtual void HandlePathDiagnosticImpl(const PathDiagnostic* D) = 0;
-
+  bool flushed;
+  llvm::FoldingSet<PathDiagnostic> Diags;
 };
 
 //===----------------------------------------------------------------------===//
@@ -597,6 +593,8 @@
   }
   
   void Profile(llvm::FoldingSetNodeID &ID) const;
+  
+  void FullProfile(llvm::FoldingSetNodeID &ID) const;
 };  
 
 } // end GR namespace
diff --git a/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h b/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h
index d4408ae..b7294ab 100644
--- a/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h
+++ b/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h
@@ -138,7 +138,7 @@
   
   void FlushDiagnostics() {
     if (PD.get())
-      PD->FlushDiagnostics();
+      PD->FlushDiagnostics(0);
   }
 
   unsigned getMaxNodes() const { return MaxNodes; }
diff --git a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
index 0c4e427..933e15c 100644
--- a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
+++ b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
@@ -39,15 +39,13 @@
   llvm::sys::Path Directory, FilePrefix;
   bool createdDir, noDir;
   const Preprocessor &PP;
-  std::vector<const PathDiagnostic*> BatchedDiags;
 public:
   HTMLDiagnostics(const std::string& prefix, const Preprocessor &pp);
 
   virtual ~HTMLDiagnostics() { FlushDiagnostics(NULL); }
 
-  virtual void FlushDiagnostics(SmallVectorImpl<std::string> *FilesMade);
-
-  virtual void HandlePathDiagnosticImpl(const PathDiagnostic* D);
+  virtual void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
+                                    SmallVectorImpl<std::string> *FilesMade);
 
   virtual StringRef getName() const {
     return "HTMLDiagnostics";
@@ -88,30 +86,13 @@
 // Report processing.
 //===----------------------------------------------------------------------===//
 
-void HTMLDiagnostics::HandlePathDiagnosticImpl(const PathDiagnostic* D) {
-  if (!D)
-    return;
-
-  if (D->empty()) {
-    delete D;
-    return;
+void HTMLDiagnostics::FlushDiagnosticsImpl(
+  std::vector<const PathDiagnostic *> &Diags,
+  SmallVectorImpl<std::string> *FilesMade) {
+  for (std::vector<const PathDiagnostic *>::iterator it = Diags.begin(),
+       et = Diags.end(); it != et; ++it) {
+    ReportDiag(**it, FilesMade);
   }
-
-  const_cast<PathDiagnostic*>(D)->flattenLocations();
-  BatchedDiags.push_back(D);
-}
-
-void
-HTMLDiagnostics::FlushDiagnostics(SmallVectorImpl<std::string> *FilesMade)
-{
-  while (!BatchedDiags.empty()) {
-    const PathDiagnostic* D = BatchedDiags.back();
-    BatchedDiags.pop_back();
-    ReportDiag(*D, FilesMade);
-    delete D;
-  }
-
-  BatchedDiags.clear();
 }
 
 void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
diff --git a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp
index e398bae..c0bb180 100644
--- a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp
+++ b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp
@@ -84,11 +84,122 @@
 
 void PathDiagnosticConsumer::anchor() { }
 
-void PathDiagnosticConsumer::HandlePathDiagnostic(const PathDiagnostic *D) {
-  // For now this simply forwards to HandlePathDiagnosticImpl.  In the future
-  // we can use this indirection to control for multi-threaded access to
-  // the PathDiagnosticConsumer from multiple bug reporters.
-  HandlePathDiagnosticImpl(D);
+PathDiagnosticConsumer::~PathDiagnosticConsumer() {
+  // Delete the contents of the FoldingSet if it isn't empty already.
+  for (llvm::FoldingSet<PathDiagnostic>::iterator it =
+       Diags.begin(), et = Diags.end() ; it != et ; ++it) {
+    delete &*it;
+  }
+}
+
+void PathDiagnosticConsumer::HandlePathDiagnostic(PathDiagnostic *D) {
+  if (!D)
+    return;
+  
+  if (D->empty()) {
+    delete D;
+    return;
+  }
+  
+  // We need to flatten the locations (convert Stmt* to locations) because
+  // the referenced statements may be freed by the time the diagnostics
+  // are emitted.
+  D->flattenLocations();
+
+  // Profile the node to see if we already have something matching it
+  llvm::FoldingSetNodeID profile;
+  D->Profile(profile);
+  void *InsertPos = 0;
+
+  if (PathDiagnostic *orig = Diags.FindNodeOrInsertPos(profile, InsertPos)) {
+    // Keep the PathDiagnostic with the shorter path.
+    if (orig->size() <= D->size()) {
+      bool shouldKeepOriginal = true;
+      if (orig->size() == D->size()) {
+        // Here we break ties in a fairly arbitrary, but deterministic, way.
+        llvm::FoldingSetNodeID fullProfile, fullProfileOrig;
+        D->FullProfile(fullProfile);
+        orig->FullProfile(fullProfileOrig);
+        if (fullProfile.ComputeHash() < fullProfileOrig.ComputeHash())
+          shouldKeepOriginal = false;
+      }
+
+      if (shouldKeepOriginal) {
+        delete D;
+        return;
+      }
+    }
+    Diags.RemoveNode(orig);
+    delete orig;
+  }
+  
+  Diags.InsertNode(D);
+}
+
+
+namespace {
+struct CompareDiagnostics {
+  // Compare if 'X' is "<" than 'Y'.
+  bool operator()(const PathDiagnostic *X, const PathDiagnostic *Y) const {
+    // First compare by location
+    const FullSourceLoc &XLoc = X->getLocation().asLocation();
+    const FullSourceLoc &YLoc = Y->getLocation().asLocation();
+    if (XLoc < YLoc)
+      return true;
+    if (XLoc != YLoc)
+      return false;
+    
+    // Next, compare by bug type.
+    StringRef XBugType = X->getBugType();
+    StringRef YBugType = Y->getBugType();
+    if (XBugType < YBugType)
+      return true;
+    if (XBugType != YBugType)
+      return false;
+    
+    // Next, compare by bug description.
+    StringRef XDesc = X->getDescription();
+    StringRef YDesc = Y->getDescription();
+    if (XDesc < YDesc)
+      return true;
+    if (XDesc != YDesc)
+      return false;
+    
+    // FIXME: Further refine by comparing PathDiagnosticPieces?
+    return false;    
+  }  
+};  
+}
+
+void
+PathDiagnosticConsumer::FlushDiagnostics(SmallVectorImpl<std::string> *Files) {
+  if (flushed)
+    return;
+  
+  flushed = true;
+  
+  std::vector<const PathDiagnostic *> BatchDiags;
+  for (llvm::FoldingSet<PathDiagnostic>::iterator it = Diags.begin(),
+       et = Diags.end(); it != et; ++it) {
+    BatchDiags.push_back(&*it);
+  }
+  
+  // Clear out the FoldingSet.
+  Diags.clear();
+
+  // Sort the diagnostics so that they are always emitted in a deterministic
+  // order.
+  if (!BatchDiags.empty())
+    std::sort(BatchDiags.begin(), BatchDiags.end(), CompareDiagnostics());
+  
+  FlushDiagnosticsImpl(BatchDiags, Files);
+
+  // Delete the flushed diagnostics.
+  for (std::vector<const PathDiagnostic *>::iterator it = BatchDiags.begin(),
+       et = BatchDiags.end(); it != et; ++it) {
+    const PathDiagnostic *D = *it;
+    delete D;
+  }
 }
 
 //===----------------------------------------------------------------------===//
@@ -366,13 +477,17 @@
 }
 
 void PathDiagnostic::Profile(llvm::FoldingSetNodeID &ID) const {
-  ID.AddInteger(Size);
+  if (Size)
+    getLocation().Profile(ID);
   ID.AddString(BugType);
   ID.AddString(Desc);
   ID.AddString(Category);
+}
+
+void PathDiagnostic::FullProfile(llvm::FoldingSetNodeID &ID) const {
+  Profile(ID);
   for (const_iterator I = begin(), E = end(); I != E; ++I)
     ID.Add(*I);
-  
   for (meta_iterator I = meta_begin(), E = meta_end(); I != E; ++I)
     ID.AddString(*I);
 }
diff --git a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
index 22fabbf..18eed8a 100644
--- a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
+++ b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
@@ -25,43 +25,9 @@
 
 typedef llvm::DenseMap<FileID, unsigned> FIDMap;
 
-namespace {
-struct CompareDiagnostics {
-  // Compare if 'X' is "<" than 'Y'.
-  bool operator()(const PathDiagnostic *X, const PathDiagnostic *Y) const {
-    // First compare by location
-    const FullSourceLoc &XLoc = X->getLocation().asLocation();
-    const FullSourceLoc &YLoc = Y->getLocation().asLocation();
-    if (XLoc < YLoc)
-      return true;
-    if (XLoc != YLoc)
-      return false;
-    
-    // Next, compare by bug type.
-    StringRef XBugType = X->getBugType();
-    StringRef YBugType = Y->getBugType();
-    if (XBugType < YBugType)
-      return true;
-    if (XBugType != YBugType)
-      return false;
-    
-    // Next, compare by bug description.
-    StringRef XDesc = X->getDescription();
-    StringRef YDesc = Y->getDescription();
-    if (XDesc < YDesc)
-      return true;
-    if (XDesc != YDesc)
-      return false;
-    
-    // FIXME: Further refine by comparing PathDiagnosticPieces?
-    return false;    
-  }  
-};  
-}
 
 namespace {
   class PlistDiagnostics : public PathDiagnosticConsumer {
-    std::vector<const PathDiagnostic*> BatchedDiags;
     const std::string OutputFile;
     const LangOptions &LangOpts;
     llvm::OwningPtr<PathDiagnosticConsumer> SubPD;
@@ -70,11 +36,10 @@
     PlistDiagnostics(const std::string& prefix, const LangOptions &LangOpts,
                      PathDiagnosticConsumer *subPD);
 
-    ~PlistDiagnostics() { FlushDiagnostics(NULL); }
+    virtual ~PlistDiagnostics() {}
 
-    void FlushDiagnostics(SmallVectorImpl<std::string> *FilesMade);
-    
-    void HandlePathDiagnosticImpl(const PathDiagnostic* D);
+    void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
+                              SmallVectorImpl<std::string> *FilesMade);
     
     virtual StringRef getName() const {
       return "PlistDiagnostics";
@@ -321,46 +286,20 @@
   }
 }
 
-void PlistDiagnostics::HandlePathDiagnosticImpl(const PathDiagnostic* D) {
-  if (!D)
-    return;
-
-  if (D->empty()) {
-    delete D;
-    return;
-  }
-
-  // We need to flatten the locations (convert Stmt* to locations) because
-  // the referenced statements may be freed by the time the diagnostics
-  // are emitted.
-  const_cast<PathDiagnostic*>(D)->flattenLocations();
-  BatchedDiags.push_back(D);
-}
-
-void PlistDiagnostics::FlushDiagnostics(SmallVectorImpl<std::string>
-                                        *FilesMade) {
-  
-  if (flushed)
-    return;
-  
-  flushed = true;
-  
-  // Sort the diagnostics so that they are always emitted in a deterministic
-  // order.
-  if (!BatchedDiags.empty())
-    std::sort(BatchedDiags.begin(), BatchedDiags.end(), CompareDiagnostics()); 
-
+void PlistDiagnostics::FlushDiagnosticsImpl(
+                                    std::vector<const PathDiagnostic *> &Diags,
+                                    SmallVectorImpl<std::string> *FilesMade) {
   // Build up a set of FIDs that we use by scanning the locations and
   // ranges of the diagnostics.
   FIDMap FM;
   SmallVector<FileID, 10> Fids;
   const SourceManager* SM = 0;
 
-  if (!BatchedDiags.empty())
-    SM = &(*BatchedDiags.begin())->begin()->getLocation().getManager();
+  if (!Diags.empty())
+    SM = &(*Diags.begin())->begin()->getLocation().getManager();
 
-  for (std::vector<const PathDiagnostic*>::iterator DI = BatchedDiags.begin(),
-       DE = BatchedDiags.end(); DI != DE; ++DI) {
+  for (std::vector<const PathDiagnostic*>::iterator DI = Diags.begin(),
+       DE = Diags.end(); DI != DE; ++DI) {
 
     const PathDiagnostic *D = *DI;
 
@@ -406,16 +345,13 @@
        " <key>diagnostics</key>\n"
        " <array>\n";
 
-  for (std::vector<const PathDiagnostic*>::iterator DI=BatchedDiags.begin(),
-       DE = BatchedDiags.end(); DI!=DE; ++DI) {
+  for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(),
+       DE = Diags.end(); DI!=DE; ++DI) {
 
     o << "  <dict>\n"
          "   <key>path</key>\n";
 
     const PathDiagnostic *D = *DI;
-    // Create an owning smart pointer for 'D' just so that we auto-free it
-    // when we exit this method.
-    llvm::OwningPtr<PathDiagnostic> OwnedD(const_cast<PathDiagnostic*>(D));
 
     o << "   <array>\n";
 
@@ -438,9 +374,10 @@
 
     // Output the diagnostic to the sub-diagnostic client, if any.
     if (SubPD) {
-      SubPD->HandlePathDiagnostic(OwnedD.take());
+      std::vector<const PathDiagnostic *> SubDiags;
+      SubDiags.push_back(D);
       SmallVector<std::string, 1> SubFilesMade;
-      SubPD->FlushDiagnostics(SubFilesMade);
+      SubPD->FlushDiagnosticsImpl(SubDiags, &SubFilesMade);
 
       if (!SubFilesMade.empty()) {
         o << "  <key>" << SubPD->getName() << "_files</key>\n";
@@ -462,6 +399,4 @@
   
   if (FilesMade)
     FilesMade->push_back(OutputFile);
-  
-  BatchedDiags.clear();
 }
diff --git a/lib/StaticAnalyzer/Core/TextPathDiagnostics.cpp b/lib/StaticAnalyzer/Core/TextPathDiagnostics.cpp
index 3543f7f..0c6b228 100644
--- a/lib/StaticAnalyzer/Core/TextPathDiagnostics.cpp
+++ b/lib/StaticAnalyzer/Core/TextPathDiagnostics.cpp
@@ -31,9 +31,8 @@
   TextPathDiagnostics(const std::string& output, DiagnosticsEngine &diag)
     : OutputFile(output), Diag(diag) {}
 
-  void HandlePathDiagnosticImpl(const PathDiagnostic* D);
-
-  void FlushDiagnostics(SmallVectorImpl<std::string> *FilesMade) { }
+  void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
+                            SmallVectorImpl<std::string> *FilesMade);
   
   virtual StringRef getName() const {
     return "TextPathDiagnostics";
@@ -53,18 +52,17 @@
   return new TextPathDiagnostics(out, PP.getDiagnostics());
 }
 
-void TextPathDiagnostics::HandlePathDiagnosticImpl(const PathDiagnostic* D) {
-  if (!D)
-    return;
-
-  if (D->empty()) {
-    delete D;
-    return;
-  }
-
-  for (PathDiagnostic::const_iterator I=D->begin(), E=D->end(); I != E; ++I) {
-    unsigned diagID = Diag.getDiagnosticIDs()->getCustomDiagID(
-                                           DiagnosticIDs::Note, I->getString());
-    Diag.Report(I->getLocation().asLocation(), diagID);
+void TextPathDiagnostics::FlushDiagnosticsImpl(
+                              std::vector<const PathDiagnostic *> &Diags,
+                              SmallVectorImpl<std::string> *FilesMade) {
+  for (std::vector<const PathDiagnostic *>::iterator it = Diags.begin(),
+       et = Diags.end(); it != et; ++it) {
+    const PathDiagnostic *D = *it;
+    for (PathDiagnostic::const_iterator I=D->begin(), E=D->end(); I != E; ++I) {
+      unsigned diagID =
+        Diag.getDiagnosticIDs()->getCustomDiagID(DiagnosticIDs::Note,
+                                                 I->getString());
+      Diag.Report(I->getLocation().asLocation(), diagID);
+    }
   }
 }
diff --git a/test/Analysis/inline-unique-reports.c b/test/Analysis/inline-unique-reports.c
new file mode 100644
index 0000000..15b1e0b
--- /dev/null
+++ b/test/Analysis/inline-unique-reports.c
@@ -0,0 +1,139 @@
+// RUN: %clang --analyze %s -Xclang -analyzer-inline-call -o %t > /dev/null 2>&1
+// RUN: FileCheck -input-file %t %s
+
+static inline bug(int *p) {
+  *p = 0xDEADBEEF;
+}
+
+void test_bug_1() {
+  int *p = 0;
+  bug(p);
+}
+
+void test_bug_2() {
+  int *p = 0;
+  bug(p);
+}
+
+// CHECK: <?xml version="1.0" encoding="UTF-8"?>
+// CHECK: <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+// CHECK: <plist version="1.0">
+// CHECK: <dict>
+// CHECK:  <key>files</key>
+// CHECK:  <array>
+// CHECK:  </array>
+// CHECK:  <key>diagnostics</key>
+// CHECK:  <array>
+// CHECK:   <dict>
+// CHECK:    <key>path</key>
+// CHECK:    <array>
+// CHECK:     <dict>
+// CHECK:      <key>kind</key><string>control</string>
+// CHECK:      <key>edges</key>
+// CHECK:       <array>
+// CHECK:        <dict>
+// CHECK:         <key>start</key>
+// CHECK:          <array>
+// CHECK:           <dict>
+// CHECK:            <key>line</key><integer>5</integer>
+// CHECK:            <key>col</key><integer>3</integer>
+// CHECK:            <key>file</key><integer>0</integer>
+// CHECK:           </dict>
+// CHECK:           <dict>
+// CHECK:            <key>line</key><integer>5</integer>
+// CHECK:            <key>col</key><integer>3</integer>
+// CHECK:            <key>file</key><integer>0</integer>
+// CHECK:           </dict>
+// CHECK:          </array>
+// CHECK:         <key>end</key>
+// CHECK:          <array>
+// CHECK:           <dict>
+// CHECK:            <key>line</key><integer>9</integer>
+// CHECK:            <key>col</key><integer>12</integer>
+// CHECK:            <key>file</key><integer>0</integer>
+// CHECK:           </dict>
+// CHECK:           <dict>
+// CHECK:            <key>line</key><integer>9</integer>
+// CHECK:            <key>col</key><integer>12</integer>
+// CHECK:            <key>file</key><integer>0</integer>
+// CHECK:           </dict>
+// CHECK:          </array>
+// CHECK:        </dict>
+// CHECK:       </array>
+// CHECK:     </dict>
+// CHECK:     <dict>
+// CHECK:      <key>kind</key><string>control</string>
+// CHECK:      <key>edges</key>
+// CHECK:       <array>
+// CHECK:        <dict>
+// CHECK:         <key>start</key>
+// CHECK:          <array>
+// CHECK:           <dict>
+// CHECK:            <key>line</key><integer>9</integer>
+// CHECK:            <key>col</key><integer>12</integer>
+// CHECK:            <key>file</key><integer>0</integer>
+// CHECK:           </dict>
+// CHECK:           <dict>
+// CHECK:            <key>line</key><integer>9</integer>
+// CHECK:            <key>col</key><integer>12</integer>
+// CHECK:            <key>file</key><integer>0</integer>
+// CHECK:           </dict>
+// CHECK:          </array>
+// CHECK:         <key>end</key>
+// CHECK:          <array>
+// CHECK:           <dict>
+// CHECK:            <key>line</key><integer>5</integer>
+// CHECK:            <key>col</key><integer>3</integer>
+// CHECK:            <key>file</key><integer>0</integer>
+// CHECK:           </dict>
+// CHECK:           <dict>
+// CHECK:            <key>line</key><integer>5</integer>
+// CHECK:            <key>col</key><integer>4</integer>
+// CHECK:            <key>file</key><integer>0</integer>
+// CHECK:           </dict>
+// CHECK:          </array>
+// CHECK:        </dict>
+// CHECK:       </array>
+// CHECK:     </dict>
+// CHECK:     <dict>
+// CHECK:      <key>kind</key><string>event</string>
+// CHECK:      <key>location</key>
+// CHECK:      <dict>
+// CHECK:       <key>line</key><integer>5</integer>
+// CHECK:       <key>col</key><integer>3</integer>
+// CHECK:       <key>file</key><integer>0</integer>
+// CHECK:      </dict>
+// CHECK:      <key>ranges</key>
+// CHECK:      <array>
+// CHECK:        <array>
+// CHECK:         <dict>
+// CHECK:          <key>line</key><integer>5</integer>
+// CHECK:          <key>col</key><integer>4</integer>
+// CHECK:          <key>file</key><integer>0</integer>
+// CHECK:         </dict>
+// CHECK:         <dict>
+// CHECK:          <key>line</key><integer>5</integer>
+// CHECK:          <key>col</key><integer>4</integer>
+// CHECK:          <key>file</key><integer>0</integer>
+// CHECK:         </dict>
+// CHECK:        </array>
+// CHECK:      </array>
+// CHECK:      <key>extended_message</key>
+// CHECK:      <string>Dereference of null pointer (loaded from variable &apos;p&apos;)</string>
+// CHECK:      <key>message</key>
+// CHECK: <string>Dereference of null pointer (loaded from variable &apos;p&apos;)</string>
+// CHECK:     </dict>
+// CHECK:    </array>
+// CHECK:    <key>description</key><string>Dereference of null pointer (loaded from variable &apos;p&apos;)</string>
+// CHECK:    <key>category</key><string>Logic error</string>
+// CHECK:    <key>type</key><string>Dereference of null pointer</string>
+// CHECK:   <key>location</key>
+// CHECK:   <dict>
+// CHECK:    <key>line</key><integer>5</integer>
+// CHECK:    <key>col</key><integer>3</integer>
+// CHECK:    <key>file</key><integer>0</integer>
+// CHECK:   </dict>
+// CHECK:   </dict>
+// CHECK:  </array>
+// CHECK: </dict>
+// CHECK: </plist>