Эх сурвалжийг харах

[Analyzer] Add support for displaying cross-file diagnostic paths in HTML output

This change adds support for cross-file diagnostic paths in html output. If the
diagnostic path is not cross-file, there is no change in the output.

Patch by Vlad Tsyrklevich!

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

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@309968 91177308-0d34-0410-b5e6-96231b3b80d8
Devin Coughlin 8 жил өмнө
parent
commit
896feaf77b

+ 3 - 2
include/clang/StaticAnalyzer/Core/Analyses.def

@@ -28,9 +28,10 @@ ANALYSIS_CONSTRAINTS(Z3Constraints, "z3", "Use Z3 contraint solver", CreateZ3Con
 #define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN)
 #define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN)
 #endif
 #endif
 
 
-ANALYSIS_DIAGNOSTICS(HTML,  "html",  "Output analysis results using HTML",   createHTMLDiagnosticConsumer)
+ANALYSIS_DIAGNOSTICS(HTML, "html", "Output analysis results using HTML", createHTMLDiagnosticConsumer)
+ANALYSIS_DIAGNOSTICS(HTML_SINGLE_FILE, "html-single-file", "Output analysis results using HTML (not allowing for multi-file bugs)", createHTMLSingleFileDiagnosticConsumer)
 ANALYSIS_DIAGNOSTICS(PLIST, "plist", "Output analysis results using Plists", createPlistDiagnosticConsumer)
 ANALYSIS_DIAGNOSTICS(PLIST, "plist", "Output analysis results using Plists", createPlistDiagnosticConsumer)
-ANALYSIS_DIAGNOSTICS(PLIST_MULTI_FILE, "plist-multi-file", "Output analysis results using Plists (allowing for mult-file bugs)", createPlistMultiFileDiagnosticConsumer)
+ANALYSIS_DIAGNOSTICS(PLIST_MULTI_FILE, "plist-multi-file", "Output analysis results using Plists (allowing for multi-file bugs)", createPlistMultiFileDiagnosticConsumer)
 ANALYSIS_DIAGNOSTICS(PLIST_HTML, "plist-html", "Output analysis results using HTML wrapped with Plists", createPlistHTMLDiagnosticConsumer)
 ANALYSIS_DIAGNOSTICS(PLIST_HTML, "plist-html", "Output analysis results using HTML wrapped with Plists", createPlistHTMLDiagnosticConsumer)
 ANALYSIS_DIAGNOSTICS(TEXT, "text", "Text output of analysis results", createTextPathDiagnosticConsumer)
 ANALYSIS_DIAGNOSTICS(TEXT, "text", "Text output of analysis results", createTextPathDiagnosticConsumer)
 
 

+ 5 - 0
lib/Rewrite/HTMLRewrite.cpp

@@ -289,6 +289,11 @@ void html::AddHeaderFooterInternalBuiltinCSS(Rewriter &R, FileID FID,
       " body { color:#000000; background-color:#ffffff }\n"
       " body { color:#000000; background-color:#ffffff }\n"
       " body { font-family:Helvetica, sans-serif; font-size:10pt }\n"
       " body { font-family:Helvetica, sans-serif; font-size:10pt }\n"
       " h1 { font-size:14pt }\n"
       " h1 { font-size:14pt }\n"
+      " .FileName { margin-top: 5px; margin-bottom: 5px; display: inline; }\n"
+      " .FileNav { margin-left: 5px; margin-right: 5px; display: inline; }\n"
+      " .FileNav a { text-decoration:none; font-size: larger; }\n"
+      " .divider { margin-top: 30px; margin-bottom: 30px; height: 15px; }\n"
+      " .divider { background-color: gray; }\n"
       " .code { border-collapse:collapse; width:100%; }\n"
       " .code { border-collapse:collapse; width:100%; }\n"
       " .code { font-family: \"Monospace\", monospace; font-size:10pt }\n"
       " .code { font-family: \"Monospace\", monospace; font-size:10pt }\n"
       " .code { line-height: 1.2em }\n"
       " .code { line-height: 1.2em }\n"

+ 209 - 105
lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp

@@ -44,8 +44,12 @@ class HTMLDiagnostics : public PathDiagnosticConsumer {
   bool createdDir, noDir;
   bool createdDir, noDir;
   const Preprocessor &PP;
   const Preprocessor &PP;
   AnalyzerOptions &AnalyzerOpts;
   AnalyzerOptions &AnalyzerOpts;
+  const bool SupportsCrossFileDiagnostics;
 public:
 public:
-  HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string& prefix, const Preprocessor &pp);
+  HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts,
+                  const std::string& prefix,
+                  const Preprocessor &pp,
+                  bool supportsMultipleFiles);
 
 
   ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); }
   ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); }
 
 
@@ -56,6 +60,10 @@ public:
     return "HTMLDiagnostics";
     return "HTMLDiagnostics";
   }
   }
 
 
+  bool supportsCrossFileDiagnostics() const override {
+    return SupportsCrossFileDiagnostics;
+  }
+
   unsigned ProcessMacroPiece(raw_ostream &os,
   unsigned ProcessMacroPiece(raw_ostream &os,
                              const PathDiagnosticMacroPiece& P,
                              const PathDiagnosticMacroPiece& P,
                              unsigned num);
                              unsigned num);
@@ -69,21 +77,47 @@ public:
 
 
   void ReportDiag(const PathDiagnostic& D,
   void ReportDiag(const PathDiagnostic& D,
                   FilesMade *filesMade);
                   FilesMade *filesMade);
+
+  // Generate the full HTML report
+  std::string GenerateHTML(const PathDiagnostic& D, Rewriter &R,
+                           const SourceManager& SMgr, const PathPieces& path,
+                           const char *declName);
+
+  // Add HTML header/footers to file specified by FID
+  void FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
+                    const SourceManager& SMgr, const PathPieces& path,
+                    FileID FID, const FileEntry *Entry, const char *declName);
+
+  // Rewrite the file specified by FID with HTML formatting.
+  void RewriteFile(Rewriter &R, const SourceManager& SMgr,
+                   const PathPieces& path, FileID FID);
 };
 };
 
 
 } // end anonymous namespace
 } // end anonymous namespace
 
 
 HTMLDiagnostics::HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts,
 HTMLDiagnostics::HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts,
                                  const std::string& prefix,
                                  const std::string& prefix,
-                                 const Preprocessor &pp)
-    : Directory(prefix), createdDir(false), noDir(false), PP(pp), AnalyzerOpts(AnalyzerOpts) {
-}
+                                 const Preprocessor &pp,
+                                 bool supportsMultipleFiles)
+    : Directory(prefix),
+      createdDir(false),
+      noDir(false),
+      PP(pp),
+      AnalyzerOpts(AnalyzerOpts),
+      SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
 
 
 void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
 void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
                                         PathDiagnosticConsumers &C,
                                         PathDiagnosticConsumers &C,
                                         const std::string& prefix,
                                         const std::string& prefix,
                                         const Preprocessor &PP) {
                                         const Preprocessor &PP) {
-  C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP));
+  C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, true));
+}
+
+void ento::createHTMLSingleFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
+                                                  PathDiagnosticConsumers &C,
+                                                  const std::string& prefix,
+                                                  const Preprocessor &PP) {
+  C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, false));
 }
 }
 
 
 //===----------------------------------------------------------------------===//
 //===----------------------------------------------------------------------===//
@@ -121,24 +155,24 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
   // First flatten out the entire path to make it easier to use.
   // First flatten out the entire path to make it easier to use.
   PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false);
   PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false);
 
 
-  // The path as already been prechecked that all parts of the path are
-  // from the same file and that it is non-empty.
-  const SourceManager &SMgr = path.front()->getLocation().getManager();
+  // The path as already been prechecked that the path is non-empty.
   assert(!path.empty());
   assert(!path.empty());
-  FileID FID =
-    path.front()->getLocation().asLocation().getExpansionLoc().getFileID();
-  assert(FID.isValid());
+  const SourceManager &SMgr = path.front()->getLocation().getManager();
 
 
   // Create a new rewriter to generate HTML.
   // Create a new rewriter to generate HTML.
   Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts());
   Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts());
 
 
+  // The file for the first path element is considered the main report file, it
+  // will usually be equivalent to SMgr.getMainFileID(); however, it might be a
+  // header when -analyzer-opt-analyze-headers is used.
+  FileID ReportFile = path.front()->getLocation().asLocation().getExpansionLoc().getFileID();
+
   // Get the function/method name
   // Get the function/method name
   SmallString<128> declName("unknown");
   SmallString<128> declName("unknown");
   int offsetDecl = 0;
   int offsetDecl = 0;
   if (const Decl *DeclWithIssue = D.getDeclWithIssue()) {
   if (const Decl *DeclWithIssue = D.getDeclWithIssue()) {
-      if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) {
+      if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue))
           declName = ND->getDeclName().getAsString();
           declName = ND->getDeclName().getAsString();
-      }
 
 
       if (const Stmt *Body = DeclWithIssue->getBody()) {
       if (const Stmt *Body = DeclWithIssue->getBody()) {
           // Retrieve the relative position of the declaration which will be used
           // Retrieve the relative position of the declaration which will be used
@@ -151,49 +185,144 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
       }
       }
   }
   }
 
 
-  // Process the path.
-  // Maintain the counts of extra note pieces separately.
-  unsigned TotalPieces = path.size();
-  unsigned TotalNotePieces =
-      std::count_if(path.begin(), path.end(),
-                    [](const std::shared_ptr<PathDiagnosticPiece> &p) {
-                      return isa<PathDiagnosticNotePiece>(*p);
-                    });
+  std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str());
+  if (report.empty()) {
+    llvm::errs() << "warning: no diagnostics generated for main file.\n";
+    return;
+  }
 
 
-  unsigned TotalRegularPieces = TotalPieces - TotalNotePieces;
-  unsigned NumRegularPieces = TotalRegularPieces;
-  unsigned NumNotePieces = TotalNotePieces;
+  // Create a path for the target HTML file.
+  int FD;
+  SmallString<128> Model, ResultPath;
 
 
-  for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) {
-    if (isa<PathDiagnosticNotePiece>(I->get())) {
-      // This adds diagnostic bubbles, but not navigation.
-      // Navigation through note pieces would be added later,
-      // as a separate pass through the piece list.
-      HandlePiece(R, FID, **I, NumNotePieces, TotalNotePieces);
-      --NumNotePieces;
-    } else {
-      HandlePiece(R, FID, **I, NumRegularPieces, TotalRegularPieces);
-      --NumRegularPieces;
-    }
+  if (!AnalyzerOpts.shouldWriteStableReportFilename()) {
+      llvm::sys::path::append(Model, Directory, "report-%%%%%%.html");
+      if (std::error_code EC =
+          llvm::sys::fs::make_absolute(Model)) {
+          llvm::errs() << "warning: could not make '" << Model
+                       << "' absolute: " << EC.message() << '\n';
+        return;
+      }
+      if (std::error_code EC =
+          llvm::sys::fs::createUniqueFile(Model, FD, ResultPath)) {
+          llvm::errs() << "warning: could not create file in '" << Directory
+                       << "': " << EC.message() << '\n';
+          return;
+      }
+
+  } else {
+      int i = 1;
+      std::error_code EC;
+      do {
+          // Find a filename which is not already used
+          const FileEntry* Entry = SMgr.getFileEntryForID(ReportFile);
+          std::stringstream filename;
+          Model = "";
+          filename << "report-"
+                   << llvm::sys::path::filename(Entry->getName()).str()
+                   << "-" << declName.c_str()
+                   << "-" << offsetDecl
+                   << "-" << i << ".html";
+          llvm::sys::path::append(Model, Directory,
+                                  filename.str());
+          EC = llvm::sys::fs::openFileForWrite(Model,
+                                               FD,
+                                               llvm::sys::fs::F_RW |
+                                               llvm::sys::fs::F_Excl);
+          if (EC && EC != llvm::errc::file_exists) {
+              llvm::errs() << "warning: could not create file '" << Model
+                           << "': " << EC.message() << '\n';
+              return;
+          }
+          i++;
+      } while (EC);
   }
   }
 
 
-  // Add line numbers, header, footer, etc.
+  llvm::raw_fd_ostream os(FD, true);
 
 
-  // unsigned FID = R.getSourceMgr().getMainFileID();
-  html::EscapeText(R, FID);
-  html::AddLineNumbers(R, FID);
+  if (filesMade)
+    filesMade->addDiagnostic(D, getName(),
+                             llvm::sys::path::filename(ResultPath));
 
 
-  // If we have a preprocessor, relex the file and syntax highlight.
-  // We might not have a preprocessor if we come from a deserialized AST file,
-  // for example.
+  // Emit the HTML to disk.
+  os << report;
+}
 
 
-  html::SyntaxHighlight(R, FID, PP);
-  html::HighlightMacros(R, FID, PP);
+std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R,
+    const SourceManager& SMgr, const PathPieces& path, const char *declName) {
+
+  // Rewrite source files as HTML for every new file the path crosses
+  std::vector<FileID> FileIDs;
+  for (auto I : path) {
+    FileID FID = I->getLocation().asLocation().getExpansionLoc().getFileID();
+    if (std::find(FileIDs.begin(), FileIDs.end(), FID) != FileIDs.end())
+      continue;
+
+    FileIDs.push_back(FID);
+    RewriteFile(R, SMgr, path, FID);
+  }
+
+  if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) {
+    // Prefix file names, anchor tags, and nav cursors to every file
+    for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) {
+      std::string s;
+      llvm::raw_string_ostream os(s);
+
+      if (I != FileIDs.begin())
+        os << "<hr class=divider>\n";
+
+      os << "<div id=File" << I->getHashValue() << ">\n";
 
 
-  // Get the full directory name of the analyzed file.
+      // Left nav arrow
+      if (I != FileIDs.begin())
+        os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue()
+           << "\">&#x2190;</a></div>";
 
 
-  const FileEntry* Entry = SMgr.getFileEntryForID(FID);
+      os << "<h4 class=FileName>" << SMgr.getFileEntryForID(*I)->getName()
+         << "</h4>\n";
 
 
+      // Right nav arrow
+      if (I + 1 != E)
+        os << "<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue()
+           << "\">&#x2192;</a></div>";
+
+      os << "</div>\n";
+
+      R.InsertTextBefore(SMgr.getLocForStartOfFile(*I), os.str());
+    }
+
+    // Append files to the main report file in the order they appear in the path
+    for (auto I : llvm::make_range(FileIDs.begin() + 1, FileIDs.end())) {
+      std::string s;
+      llvm::raw_string_ostream os(s);
+
+      const RewriteBuffer *Buf = R.getRewriteBufferFor(I);
+      for (auto BI : *Buf)
+        os << BI;
+
+      R.InsertTextAfter(SMgr.getLocForEndOfFile(FileIDs[0]), os.str());
+    }
+  }
+
+  const RewriteBuffer *Buf = R.getRewriteBufferFor(FileIDs[0]);
+  if (!Buf)
+    return "";
+
+  // Add CSS, header, and footer.
+  const FileEntry* Entry = SMgr.getFileEntryForID(FileIDs[0]);
+  FinalizeHTML(D, R, SMgr, path, FileIDs[0], Entry, declName);
+
+  std::string file;
+  llvm::raw_string_ostream os(file);
+  for (auto BI : *Buf)
+    os << BI;
+
+  return os.str();
+}
+
+void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
+    const SourceManager& SMgr, const PathPieces& path, FileID FID,
+    const FileEntry *Entry, const char *declName) {
   // This is a cludge; basically we want to append either the full
   // This is a cludge; basically we want to append either the full
   // working directory if we have no directory information.  This is
   // working directory if we have no directory information.  This is
   // a work in progress.
   // a work in progress.
@@ -306,73 +435,48 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
     R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
     R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
   }
   }
 
 
-  // Add CSS, header, and footer.
-
   html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName());
   html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName());
+}
 
 
-  // Get the rewrite buffer.
-  const RewriteBuffer *Buf = R.getRewriteBufferFor(FID);
-
-  if (!Buf) {
-    llvm::errs() << "warning: no diagnostics generated for main file.\n";
-    return;
-  }
-
-  // Create a path for the target HTML file.
-  int FD;
-  SmallString<128> Model, ResultPath;
+void HTMLDiagnostics::RewriteFile(Rewriter &R, const SourceManager& SMgr,
+    const PathPieces& path, FileID FID) {
+  // Process the path.
+  // Maintain the counts of extra note pieces separately.
+  unsigned TotalPieces = path.size();
+  unsigned TotalNotePieces =
+      std::count_if(path.begin(), path.end(),
+                    [](const std::shared_ptr<PathDiagnosticPiece> &p) {
+                      return isa<PathDiagnosticNotePiece>(*p);
+                    });
 
 
-  if (!AnalyzerOpts.shouldWriteStableReportFilename()) {
-      llvm::sys::path::append(Model, Directory, "report-%%%%%%.html");
-      if (std::error_code EC =
-          llvm::sys::fs::make_absolute(Model)) {
-          llvm::errs() << "warning: could not make '" << Model
-                       << "' absolute: " << EC.message() << '\n';
-        return;
-      }
-      if (std::error_code EC =
-          llvm::sys::fs::createUniqueFile(Model, FD, ResultPath)) {
-          llvm::errs() << "warning: could not create file in '" << Directory
-                       << "': " << EC.message() << '\n';
-          return;
-      }
+  unsigned TotalRegularPieces = TotalPieces - TotalNotePieces;
+  unsigned NumRegularPieces = TotalRegularPieces;
+  unsigned NumNotePieces = TotalNotePieces;
 
 
-  } else {
-      int i = 1;
-      std::error_code EC;
-      do {
-          // Find a filename which is not already used
-          std::stringstream filename;
-          Model = "";
-          filename << "report-"
-                   << llvm::sys::path::filename(Entry->getName()).str()
-                   << "-" << declName.c_str()
-                   << "-" << offsetDecl
-                   << "-" << i << ".html";
-          llvm::sys::path::append(Model, Directory,
-                                  filename.str());
-          EC = llvm::sys::fs::openFileForWrite(Model,
-                                               FD,
-                                               llvm::sys::fs::F_RW |
-                                               llvm::sys::fs::F_Excl);
-          if (EC && EC != llvm::errc::file_exists) {
-              llvm::errs() << "warning: could not create file '" << Model
-                           << "': " << EC.message() << '\n';
-              return;
-          }
-          i++;
-      } while (EC);
+  for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) {
+    if (isa<PathDiagnosticNotePiece>(I->get())) {
+      // This adds diagnostic bubbles, but not navigation.
+      // Navigation through note pieces would be added later,
+      // as a separate pass through the piece list.
+      HandlePiece(R, FID, **I, NumNotePieces, TotalNotePieces);
+      --NumNotePieces;
+    } else {
+      HandlePiece(R, FID, **I, NumRegularPieces, TotalRegularPieces);
+      --NumRegularPieces;
+    }
   }
   }
 
 
-  llvm::raw_fd_ostream os(FD, true);
+  // Add line numbers, header, footer, etc.
 
 
-  if (filesMade)
-    filesMade->addDiagnostic(D, getName(),
-                             llvm::sys::path::filename(ResultPath));
+  html::EscapeText(R, FID);
+  html::AddLineNumbers(R, FID);
 
 
-  // Emit the HTML to disk.
-  for (RewriteBuffer::iterator I = Buf->begin(), E = Buf->end(); I!=E; ++I)
-      os << *I;
+  // If we have a preprocessor, relex the file and syntax highlight.
+  // We might not have a preprocessor if we come from a deserialized AST file,
+  // for example.
+
+  html::SyntaxHighlight(R, FID, PP);
+  html::HighlightMacros(R, FID, PP);
 }
 }
 
 
 void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID,
 void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID,

+ 0 - 12
test/Analysis/diagnostics/diag-cross-file-boundaries.c

@@ -1,12 +0,0 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s
-// RUN: %clang_analyze_cc1 -analyzer-checker=core -analyzer-output=html -o PR12421.html %s 2>&1 | FileCheck %s
-
-// Test for PR12421
-#include "diag-cross-file-boundaries.h"
-
-int main(){
-  f();
-  return 0;
-}
-
-// CHECK: warning: Path diagnostic report is not generated.

+ 14 - 0
test/Analysis/html-diag-singlefile.c

@@ -0,0 +1,14 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core -analyzer-output=html-single-file -o D30406.html %s 2>&1 | FileCheck %s
+
+// Check that single file HTML output does not process multi-file diagnostics.
+// (This used to test for PR12421, before the introduction of the html-single-file format)
+
+#include "html-diag-singlefile.h"
+
+int main(){
+  f();
+  return 0;
+}
+
+// CHECK: warning: Path diagnostic report is not generated.

+ 0 - 0
test/Analysis/diagnostics/diag-cross-file-boundaries.h → test/Analysis/html-diag-singlefile.h


+ 10 - 0
test/Analysis/html-diags-analyze-headers.c

@@ -0,0 +1,10 @@
+// RUN: mkdir -p %t.dir
+// RUN: %clang_analyze_cc1 -analyzer-opt-analyze-headers -analyzer-output=html -analyzer-checker=core -o %t.dir %s
+// RUN: ls %t.dir | grep report
+// RUN: rm -rf %t.dir
+
+// This tests that we emit HTML diagnostics for reports in headers when the
+// analyzer is run with -analyzer-opt-analyze-headers. This was handled
+// incorrectly in the first iteration of D30406.
+
+#include "html-diags-analyze-headers.h"

+ 5 - 0
test/Analysis/html-diags-analyze-headers.h

@@ -0,0 +1,5 @@
+#include "html-diags-multifile.h"
+
+void test_call_macro() {
+  has_bug(0);
+}

+ 2 - 3
test/Analysis/html-diags-multifile.c

@@ -1,10 +1,9 @@
 // RUN: mkdir -p %t.dir
 // RUN: mkdir -p %t.dir
 // RUN: %clang_analyze_cc1 -analyzer-output=html -analyzer-checker=core -o %t.dir %s
 // RUN: %clang_analyze_cc1 -analyzer-output=html -analyzer-checker=core -o %t.dir %s
-// RUN: ls %t.dir | not grep report
+// RUN: ls %t.dir | grep report
 // RUN: rm -fR %t.dir
 // RUN: rm -fR %t.dir
 
 
-// This tests that we do not currently emit HTML diagnostics for reports that
-// cross file boundaries.
+// This tests that we emit HTML diagnostics for reports that cross file boundaries.
 
 
 #include "html-diags-multifile.h"
 #include "html-diags-multifile.h"
 
 

+ 6 - 0
test/Analysis/html-diags.c

@@ -3,6 +3,12 @@
 // RUN: %clang_analyze_cc1 -analyzer-output=html -analyzer-checker=core -o %T/dir %s
 // RUN: %clang_analyze_cc1 -analyzer-output=html -analyzer-checker=core -o %T/dir %s
 // RUN: ls %T/dir | grep report
 // RUN: ls %T/dir | grep report
 
 
+// D30406: Test new html-single-file output
+// RUN: rm -fR %T/dir
+// RUN: mkdir %T/dir
+// RUN: %clang_analyze_cc1 -analyzer-output=html-single-file -analyzer-checker=core -o %T/dir %s
+// RUN: ls %T/dir | grep report
+
 // PR16547: Test relative paths
 // PR16547: Test relative paths
 // RUN: cd %T/dir
 // RUN: cd %T/dir
 // RUN: %clang_analyze_cc1 -analyzer-output=html -analyzer-checker=core -o testrelative %s
 // RUN: %clang_analyze_cc1 -analyzer-output=html -analyzer-checker=core -o testrelative %s

+ 7 - 0
test/Coverage/html-diagnostics.c

@@ -1,11 +1,18 @@
 // RUN: rm -rf %t
 // RUN: rm -rf %t
 // RUN: %clang_cc1 -analyze -analyzer-output=html -analyzer-checker=core -o %t %s
 // RUN: %clang_cc1 -analyze -analyzer-output=html -analyzer-checker=core -o %t %s
 // RUN: find %t -name "*.html" -exec cat "{}" ";" | FileCheck %s
 // RUN: find %t -name "*.html" -exec cat "{}" ";" | FileCheck %s
+//
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -analyze -analyzer-output=html-single-file -analyzer-checker=core -o %t %s
+// RUN: find %t -name "*.html" -exec cat "{}" ";" | FileCheck %s
 
 
 // REQUIRES: staticanalyzer
 // REQUIRES: staticanalyzer
 
 
 // CHECK: <h3>Annotated Source Code</h3>
 // CHECK: <h3>Annotated Source Code</h3>
 
 
+// Make sure it's not generated as a multi-file HTML output
+// CHECK-NOT: <h4 class=FileName>{{.*}}
+
 // Without tweaking expr, the expr would hit to the line below
 // Without tweaking expr, the expr would hit to the line below
 // emitted to the output as comment.
 // emitted to the output as comment.
 // CHECK: {{[D]ereference of null pointer}}
 // CHECK: {{[D]ereference of null pointer}}

+ 21 - 0
test/Coverage/html-multifile-diagnostics.c

@@ -0,0 +1,21 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -analyze -analyzer-output=html -analyzer-checker=core -o %t %s
+// RUN: find %t -name "*.html" -exec cat "{}" ";" | FileCheck %s
+
+// REQUIRES: staticanalyzer
+
+// CHECK: <h3>Annotated Source Code</h3>
+
+// Make sure it's generated as multi-file HTML output
+// CHECK: <h4 class=FileName>{{.*}}html-multifile-diagnostics.c</h4>
+// CHECK: <h4 class=FileName>{{.*}}html-multifile-diagnostics.h</h4>
+
+// Without tweaking expr, the expr would hit to the line below
+// emitted to the output as comment.
+// CHECK: {{[D]ereference of null pointer}}
+
+#include "html-multifile-diagnostics.h"
+
+void f0() {
+  f1((int*)0);
+}

+ 3 - 0
test/Coverage/html-multifile-diagnostics.h

@@ -0,0 +1,3 @@
+void f1(int *ptr) {
+  *ptr = 0;
+}

+ 0 - 7
www/analyzer/open_projects.html

@@ -107,13 +107,6 @@ mailing list</a> to notify other members of the community.</p>
 
 
   <li>Bug Reporting 
   <li>Bug Reporting 
   <ul>
   <ul>
-    <li>Add support for displaying cross-file diagnostic paths in HTML output
-    (used by <tt>scan-build</tt>).
-    <p>Currently <tt>scan-build</tt> output does not display reports that span 
-    multiple files. The main problem is that we do not have a good format to
-    display such paths in HTML output. <i>(Difficulty: Medium)</i> </p>
-    </li>
-    
     <li>Refactor path diagnostic generation in <a href="http://clang.llvm.org/doxygen/BugReporter_8cpp_source.html">BugReporter.cpp</a>.
     <li>Refactor path diagnostic generation in <a href="http://clang.llvm.org/doxygen/BugReporter_8cpp_source.html">BugReporter.cpp</a>.
     <p>It would be great to have more code reuse between "Minimal" and 
     <p>It would be great to have more code reuse between "Minimal" and 
     "Extensive" PathDiagnostic generation algorithms. One idea is to create an 
     "Extensive" PathDiagnostic generation algorithms. One idea is to create an