]> granicus.if.org Git - clang/commitdiff
Rework flushing of diagnostics to PathDiagnosticConsumer. Now all the reports are...
authorTed Kremenek <kremenek@apple.com>
Wed, 25 Jan 2012 23:47:14 +0000 (23:47 +0000)
committerTed Kremenek <kremenek@apple.com>
Wed, 25 Jan 2012 23:47:14 +0000 (23:47 +0000)
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

include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h
include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h
lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
lib/StaticAnalyzer/Core/PathDiagnostic.cpp
lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
lib/StaticAnalyzer/Core/TextPathDiagnostics.cpp
test/Analysis/inline-unique-reports.c [new file with mode: 0644]

index fba5692d05e867d6873c092e3707c5c96282a8aa..427c47aa849dacdfe712056caf529c82cdd067e3 100644 (file)
@@ -48,20 +48,18 @@ class PathDiagnostic;
 class PathDiagnosticConsumer {
   virtual void anchor();
 public:
-  PathDiagnosticConsumer() {}
+  PathDiagnosticConsumer() : flushed(false) {}
+  virtual ~PathDiagnosticConsumer();
+
+  void FlushDiagnostics(SmallVectorImpl<std::string> *FilesMade);
+
+  virtual void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
+                                    SmallVectorImpl<std::string> *FilesMade)
+                                    = 0;
 
-  virtual ~PathDiagnosticConsumer() {}
-  
-  virtual void
-  FlushDiagnostics(SmallVectorImpl<std::string> *FilesMade = 0) = 0;
-  
-  void FlushDiagnostics(SmallVectorImpl<std::string> &FilesMade) {
-    FlushDiagnostics(&FilesMade);
-  }
-  
   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 @@ public:
   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 @@ public:
   }
   
   void Profile(llvm::FoldingSetNodeID &ID) const;
+  
+  void FullProfile(llvm::FoldingSetNodeID &ID) const;
 };  
 
 } // end GR namespace
index d4408aef115b9be6da27a1311ce4e2a787aea834..b7294abb52b5edbe12e51c717d5a0a4d385738bc 100644 (file)
@@ -138,7 +138,7 @@ public:
   
   void FlushDiagnostics() {
     if (PD.get())
-      PD->FlushDiagnostics();
+      PD->FlushDiagnostics(0);
   }
 
   unsigned getMaxNodes() const { return MaxNodes; }
index 0c4e4277e4d6569478b03af7e5b584d7fec86ca8..933e15c0dec789d259b6212eeaebafb5179bb619 100644 (file)
@@ -39,15 +39,13 @@ class HTMLDiagnostics : public PathDiagnosticConsumer {
   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 @@ ento::createHTMLDiagnosticConsumer(const std::string& prefix,
 // 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,
index e398bae60fc46ec301a461f167b03c65f80d52de..c0bb180c82e5fded65524fa55576bb410b938f58 100644 (file)
@@ -84,11 +84,122 @@ PathDiagnostic::PathDiagnostic(StringRef bugtype, StringRef desc,
 
 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 PathDiagnosticMacroPiece::Profile(llvm::FoldingSetNodeID &ID) const {
 }
 
 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);
 }
index 22fabbf7ae7548d5bae525a48f49cd8605740e0c..18eed8a0f997a7895879976c6c4836d15233c6ed 100644 (file)
@@ -25,43 +25,9 @@ using namespace ento;
 
 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 @@ namespace {
     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 @@ static void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P,
   }
 }
 
-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 @@ void PlistDiagnostics::FlushDiagnostics(SmallVectorImpl<std::string>
        " <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 @@ void PlistDiagnostics::FlushDiagnostics(SmallVectorImpl<std::string>
 
     // 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 @@ void PlistDiagnostics::FlushDiagnostics(SmallVectorImpl<std::string>
   
   if (FilesMade)
     FilesMade->push_back(OutputFile);
-  
-  BatchedDiags.clear();
 }
index 3543f7ff13dc4c5e58aea4a4917c3b01bf300b23..0c6b2282741a53d49ca442184a5109d3cbce6b1f 100644 (file)
@@ -31,9 +31,8 @@ public:
   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 @@ ento::createTextPathDiagnosticConsumer(const std::string& out,
   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 (file)
index 0000000..15b1e0b
--- /dev/null
@@ -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>