[analyzer] Provide an option to dump generated exploded graphs to a given file.
authorGeorge Karpenkov <ekarpenkov@apple.com>
Fri, 28 Sep 2018 18:49:21 +0000 (18:49 +0000)
committerGeorge Karpenkov <ekarpenkov@apple.com>
Fri, 28 Sep 2018 18:49:21 +0000 (18:49 +0000)
Dumping graphs instead of opening them is often very useful,
e.g. for transfer or converting to SVG.

Basic sanity check for generated exploded graphs.

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

llvm-svn: 343352

clang/include/clang/Driver/CC1Options.td
clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h
clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
clang/lib/Frontend/CompilerInvocation.cpp
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
clang/test/Analysis/dump_egraph.c [new file with mode: 0644]

index 511dded..e4fd133 100644 (file)
@@ -80,6 +80,9 @@ def trim_egraph : Flag<["-"], "trim-egraph">,
   HelpText<"Only show error-related paths in the analysis graph">;
 def analyzer_viz_egraph_graphviz : Flag<["-"], "analyzer-viz-egraph-graphviz">,
   HelpText<"Display exploded graph using GraphViz">;
+def analyzer_dump_egraph : Separate<["-"], "analyzer-dump-egraph">,
+  HelpText<"Dump exploded graph to the specified file">;
+def analyzer_dump_egraph_EQ : Joined<["-"], "analyzer-dump-egraph=">, Alias<analyzer_dump_egraph>;
 
 def analyzer_inline_max_stack_depth : Separate<["-"], "analyzer-inline-max-stack-depth">,
   HelpText<"Bound on stack depth while inlining (4 by default)">;
index 67de75b..715cc2b 100644 (file)
@@ -140,6 +140,9 @@ public:
 
   std::string AnalyzeSpecificFunction;
 
+  /// File path to which the exploded graph should be dumped.
+  std::string DumpExplodedGraphTo;
+
   /// Store full compiler invocation for reproducible instructions in the
   /// generated report.
   std::string FullCompilerInvocation;
index a8a273b..c9c2b4f 100644 (file)
@@ -204,6 +204,18 @@ public:
   void enqueueEndOfPath(ExplodedNodeSet &S);
   void GenerateCallExitNode(ExplodedNode *N);
 
+
+  /// Dump graph to the specified filename.
+  /// If filename is empty, generate a temporary one.
+  /// \return The filename the graph is written into.
+  std::string DumpGraph(bool trim = false, StringRef Filename="");
+
+  /// Dump the graph consisting of the given nodes to a specified filename.
+  /// Generate a temporary filename if it's not provided.
+  /// \return The filename the graph is written into.
+  std::string DumpGraph(ArrayRef<const ExplodedNode *> Nodes,
+                        StringRef Filename = "");
+
   /// Visualize the ExplodedGraph created by executing the simulation.
   void ViewGraph(bool trim = false);
 
index 55745b1..782c796 100644 (file)
@@ -284,6 +284,7 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args,
 
   Opts.visualizeExplodedGraphWithGraphViz =
     Args.hasArg(OPT_analyzer_viz_egraph_graphviz);
+  Opts.DumpExplodedGraphTo = Args.getLastArgValue(OPT_analyzer_dump_egraph);
   Opts.NoRetryExhausted = Args.hasArg(OPT_analyzer_disable_retry_exhausted);
   Opts.AnalyzeAll = Args.hasArg(OPT_analyzer_opt_analyze_headers);
   Opts.AnalyzerDisplayProgress = Args.hasArg(OPT_analyzer_display_progress);
index 90f8920..c19f056 100644 (file)
@@ -3058,6 +3058,23 @@ struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits {
 
 void ExprEngine::ViewGraph(bool trim) {
 #ifndef NDEBUG
+  std::string Filename = DumpGraph(trim);
+  llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT);
+#endif
+  llvm::errs() << "Warning: viewing graph requires assertions" << "\n";
+}
+
+
+void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode*> Nodes) {
+#ifndef NDEBUG
+  std::string Filename = DumpGraph(Nodes);
+  llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT);
+#endif
+  llvm::errs() << "Warning: viewing graph requires assertions" << "\n";
+}
+
+std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) {
+#ifndef NDEBUG
   if (trim) {
     std::vector<const ExplodedNode *> Src;
 
@@ -3067,22 +3084,30 @@ void ExprEngine::ViewGraph(bool trim) {
       const auto *N = const_cast<ExplodedNode *>(EI->begin()->getErrorNode());
       if (N) Src.push_back(N);
     }
-
-    ViewGraph(Src);
+    return DumpGraph(Src, Filename);
   } else {
-    llvm::ViewGraph(&G, "ExprEngine");
+    return llvm::WriteGraph(&G, "ExprEngine", /*ShortNames=*/false,
+                     /*Title=*/"Exploded Graph", /*Filename=*/Filename);
   }
 #endif
+  llvm::errs() << "Warning: dumping graph requires assertions" << "\n";
+  return "";
 }
 
-void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode*> Nodes) {
+std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes,
+                                  StringRef Filename) {
 #ifndef NDEBUG
   std::unique_ptr<ExplodedGraph> TrimmedG(G.trim(Nodes));
 
   if (!TrimmedG.get()) {
     llvm::errs() << "warning: Trimmed ExplodedGraph is empty.\n";
   } else {
-    llvm::ViewGraph(TrimmedG.get(), "TrimmedExprEngine");
+    return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine",
+                            /*ShortNames=*/false,
+                            /*Title=*/"Trimmed Exploded Graph",
+                            /*Filename=*/Filename);
   }
 #endif
+  llvm::errs() << "Warning: dumping graph requires assertions" << "\n";
+  return "";
 }
index d42e3a9..ad17dae 100644 (file)
@@ -745,6 +745,9 @@ void AnalysisConsumer::ActionExprEngine(Decl *D, bool ObjCGCEnabled,
   Eng.ExecuteWorkList(Mgr->getAnalysisDeclContextManager().getStackFrame(D),
                       Mgr->options.getMaxNodesPerTopLevelFunction());
 
+  if (!Mgr->options.DumpExplodedGraphTo.empty())
+    Eng.DumpGraph(Mgr->options.TrimGraph, Mgr->options.DumpExplodedGraphTo);
+
   // Visualize the exploded graph.
   if (Mgr->options.visualizeExplodedGraphWithGraphViz)
     Eng.ViewGraph(Mgr->options.TrimGraph);
diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c
new file mode 100644 (file)
index 0000000..70b7e1f
--- /dev/null
@@ -0,0 +1,15 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core -analyzer-dump-egraph=%t.dot %s
+// RUN: cat %t.dot | FileCheck %s
+// REQUIRES: asserts
+
+int getJ();
+
+int foo() {
+  int *x = 0;
+  return *x;
+}
+
+// CHECK: digraph "Exploded Graph" {
+// CHECK: Edge: (B2, B1)
+// CHECK: Block Entrance: B1
+// CHECK: Bug report attached