From 0e47d295b6c3852e55833afbfb4479f69bdbd5e3 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 26 Nov 2019 13:03:40 -0500
Subject: [PATCH 64/75] FIXME: WIP on -fdump-analyer-sm-dot experiment

---
 gcc/analyzer/analyzer.opt |  4 +++
 gcc/analyzer/engine.cc    | 15 +++++++++
 gcc/analyzer/sm-malloc.cc | 50 ++++++++++++++++++++++++++++-
 gcc/analyzer/sm.cc        | 66 +++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/sm.h         | 12 +++++++
 5 files changed, 146 insertions(+), 1 deletion(-)

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 3311e97b343..cadc51bd864 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -182,4 +182,8 @@ fdump-analyzer-supergraph
 Common RejectNegative Var(flag_dump_analyzer_supergraph)
 Dump the analyzer supergraph to a SRCFILE.supergraph.dot file.
 
+fdump-analyer-sm-dot
+Common RejectNegative Var(flag_dump_analyzer_sm_dot)
+Dump the analyzer state machines to NAME.dot files.
+
 ; This comment is to ensure we retain the blank line above.
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 0736428906e..d8e31847d98 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -3554,6 +3554,21 @@ impl_run_checkers (logger *logger)
 	logger->log ("checkers[%i]: %s", i, sm->get_name ());
     }
 
+  if (flag_dump_analyzer_sm_dot)
+    {
+      auto_client_timevar tv ("-fdump-analyer-sm-dot");
+      int i;
+      state_machine *sm;
+      FOR_EACH_VEC_ELT (checkers, i, sm)
+	{
+	  char *filename = concat (sm->get_name (), ".dot", NULL);
+	  FILE *outf = fopen (filename, "w");
+	  sm->dump_dot_to_file (outf);
+	  fclose (outf);
+	  free (filename);
+	}
+    }
+
   /* Extrinsic state shared by nodes in the graph.  */
   const extrinsic_state ext_state (checkers);
 
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 9f4cf16cc66..63816797dc8 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -65,6 +65,8 @@ public:
   bool can_purge_p (state_t s) const FINAL OVERRIDE;
   pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
 
+  void for_each_transition (transition_visitor *) const FINAL OVERRIDE;
+
   /* Start state.  */
   state_t m_start;
 
@@ -598,7 +600,7 @@ malloc_state_machine::malloc_state_machine (logger *logger)
   m_null = add_state ("null");
   m_nonnull = add_state ("nonnull");
   m_freed = add_state ("freed");
-  m_non_heap = add_state ("non-heap");
+  m_non_heap = add_state ("non_heap");
   m_stop = add_state ("stop");
 }
 
@@ -810,6 +812,52 @@ malloc_state_machine::on_leak (tree var) const
   return new malloc_leak (*this, var);
 }
 
+/* FIXME.  */
+
+void
+malloc_state_machine::for_each_transition (transition_visitor *v) const
+{
+  v->on_transition (m_start, m_unchecked, "on 'X=malloc(...);'");
+  v->on_transition (m_start, m_unchecked, "on 'X=calloc(...);'");
+  v->on_transition (m_start, m_non_heap, "on 'X=alloca(...);'");
+  v->on_transition (m_start, m_non_heap, "on 'X=__builtin_alloca(...);'");
+
+  /* On "free".  */
+  v->on_transition (m_start, m_freed, "on 'free(X);'");
+  v->on_transition (m_unchecked, m_freed, "on 'free(X);'");
+  v->on_transition (m_nonnull, m_freed, "on 'free(X);'");
+  v->on_transition (m_freed, m_stop, "on 'free(X);'", "double-free");
+  v->on_transition (m_non_heap, m_stop, "on 'free(X);'", "free of non-heap");
+
+  /* Handle "__attribute__((nonnull))".   */
+  v->on_transition (m_unchecked, m_nonnull,
+		    "on 'FN(X)' with __attribute__((nonnull))",
+		    "possible NULL arg");
+  v->on_transition (m_null, m_stop,
+		    "on 'FN(X)' with __attribute__((nonnull))",
+		    "NULL arg");
+
+  /* is_zero_assignment.  */
+  v->on_transition (m_start, m_null, "on 'X = 0;'");
+  v->on_transition (m_unchecked, m_null, "on 'X = 0;'");
+  v->on_transition (m_nonnull, m_null, "on 'X = 0;'");
+  v->on_transition (m_freed, m_null, "on 'X = 0;'");
+
+  v->on_transition (m_start, m_non_heap, "on 'X = &EXPR;'");
+
+  /* Handle dereferences.  */
+  v->on_transition (m_unchecked, m_nonnull, "on '*X'", "possible NULL deref'");
+  v->on_transition (m_null, m_stop, "on '*X'", "NULL deref");
+  v->on_transition (m_freed, m_stop, "on '*X'", "use after free'");
+
+  /* on_condition.  */
+  v->on_transition (m_unchecked, m_nonnull, "on 'X != 0'");
+  v->on_transition (m_unchecked, m_null, "on 'X == 0'");
+
+  v->on_transition (m_unchecked, m_stop, "on leak", "leak");;
+  v->on_transition (m_nonnull, m_stop, "on leak", "leak");
+}
+
 } // anonymous namespace
 
 /* Internal interface to this file. */
diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
index 03d65ffed3d..dddad606471 100644
--- a/gcc/analyzer/sm.cc
+++ b/gcc/analyzer/sm.cc
@@ -28,6 +28,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "options.h"
 #include "function.h"
 #include "diagnostic-core.h"
+#include "pretty-print.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/analyzer-logging.h"
 #include "analyzer/sm.h"
@@ -89,6 +90,71 @@ state_machine::validate (state_t s) const
   gcc_assert (s < m_state_names.length ());
 }
 
+/* FIXME.  */
+
+void
+state_machine::dump_dot_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "digraph \"%s\" {", m_name);
+  pp_newline (pp);
+
+  pp_string (pp, "  /* STATES. */");
+  pp_newline (pp);
+
+  unsigned i;
+  const char *state_name;
+  FOR_EACH_VEC_ELT (m_state_names, i, state_name)
+    {
+      pp_printf (pp, "  %s;", state_name);
+      pp_newline (pp);
+    }
+
+  class visitor : public state_machine::transition_visitor
+  {
+  public:
+    visitor (const state_machine &sm, pretty_printer *pp)
+    : m_sm (sm), m_pp (pp) {}
+
+    void on_transition (state_machine::state_t from,
+			state_machine::state_t to,
+			const char *trigger,
+			const char *warning = NULL) FINAL OVERRIDE
+    {
+      if (warning)
+	pp_printf (m_pp, "  %s -> %s [label=\"%s: Warn('%s')\"];",
+		   m_sm.get_state_name (from),
+		   m_sm.get_state_name (to),
+		   trigger, warning);
+      else
+	pp_printf (m_pp, "  %s -> %s [label=\"%s\"];",
+		   m_sm.get_state_name (from),
+		   m_sm.get_state_name (to),
+		   trigger);
+      pp_newline (m_pp);
+    }
+
+  private:
+    const state_machine &m_sm;
+    pretty_printer *m_pp;
+  };
+  visitor v (*this, pp);
+  for_each_transition (&v);
+
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* FIXME.  */
+
+void
+state_machine::dump_dot_to_file (FILE *outf) const
+{
+  pretty_printer pp;
+  pp.buffer->stream = outf;
+  dump_dot_to_pp (&pp);
+  pp_flush (&pp);
+}
+
 /* Create instances of the various state machines, each using LOGGER,
    and populate OUT with them.  */
 
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index fb49630f073..f5c8e540566 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -78,6 +78,18 @@ public:
 
   void validate (state_t s) const;
 
+  void dump_dot_to_pp (pretty_printer *pp) const;
+  void dump_dot_to_file (FILE *) const;
+
+  class transition_visitor
+  {
+  public:
+    virtual void on_transition (state_t from, state_t to,
+				const char *trigger,
+				const char *warning = NULL) = 0;
+  };
+  virtual void for_each_transition (transition_visitor *) const {} // = 0
+
 protected:
   state_t add_state (const char *name);
 
-- 
2.21.0

