From 61b95f4824cd2ccfb30e5379a112c62a3ebee33d Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 9 Apr 2019 16:56:06 -0400
Subject: [PATCH 188/217] FIXME: impl-3: initial impl of adding events to path

---
 gcc/analyzer/impl-3.cc | 564 +++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 500 insertions(+), 64 deletions(-)

diff --git a/gcc/analyzer/impl-3.cc b/gcc/analyzer/impl-3.cc
index a0dd665..1f25136 100644
--- a/gcc/analyzer/impl-3.cc
+++ b/gcc/analyzer/impl-3.cc
@@ -46,6 +46,44 @@
 
 namespace impl_3 {
 
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+char *print (const char *fmt, ...)
+{
+  /* FIXME: all this cloning is likely to be expensive.  */
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+  char *result = xstrdup (pp_formatted_text (pp));
+
+  pp_clear_output_area (pp);
+
+  delete pp;
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
 class engine;
 
 /* FIXME.  */
@@ -87,27 +125,59 @@ class ec3_state : public extra_ec_state
   // merging of ECs???
 };
 
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+enum event_kind
+{
+  EK_DEBUG,
+  EK_STMT,
+  EK_FUNCTION_ENTRY,
+  EK_STATE_CHANGE,
+  EK_EDGE,
+  EK_WARNING
+};
+
+static const char *
+event_kind_to_string (enum event_kind ek)
+{
+  switch (ek)
+    {
+    default:
+      gcc_unreachable ();
+    case EK_DEBUG:
+      return "EK_DEBUG";
+    case EK_STMT:
+      return "EK_STMT";
+    case EK_FUNCTION_ENTRY:
+      return "EK_FUNCTION_ENTRY";
+    case EK_STATE_CHANGE:
+      return "EK_STATE_CHANGE";
+    case EK_EDGE:
+      return "EK_EDGE";
+    case EK_WARNING:
+      return "EK_WARNING";
+    }
+}
+
 /* FIXME.  */
 
-class checker3_event : public simple_diagnostic_event //diagnostic_event
+class checker3_event : public diagnostic_event
 {
 public:
-  checker3_event (location_t loc, tree fndecl, int depth, const char *desc)
-  : simple_diagnostic_event (loc, fndecl, depth, desc)
+  checker3_event (enum event_kind kind,
+		  location_t loc, tree fndecl, int depth)
+    : m_kind (kind), m_loc (loc), m_fndecl (fndecl), m_depth (depth),
+      m_pending_diagnostic (NULL), m_emission_id ()
   {
   }
-  // FIXME: for now
 
-  checker3_event *clone () const
-  {
-    label_text desc = get_desc ();
-    checker3_event *result
-      = new checker3_event (get_location (), get_fndecl (),
-			    get_stack_depth (), desc.m_buffer);
-    // TODO: make virtual (if we add subclasses)
-    desc.maybe_free ();
-    return result;
-  }
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+
+  virtual checker3_event *clone () const = 0;
 
   std::string to_str () const
   {
@@ -122,8 +192,301 @@ public:
     result += ")";
     return result;
   }
+
+  void prepare_for_emission (pending_diagnostic *pd, event_id_t emission_id)
+  {
+    m_pending_diagnostic = pd;
+    m_emission_id = emission_id;
+
+    /* Call get_desc now, to ensure that any side-effects take place
+       before pending_diagnostic::emit is called.
+       In particular, state_change_event::get_desc can call
+       pending_diagnostic::describe_critical_state_change;
+       free_of_non_heap can use this to tweak the message.
+       (Would be neater to simply capture this within the state).  */
+    label_text desc = get_desc ();
+    desc.maybe_free ();
+  }
+
+  enum event_kind m_kind;
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  pending_diagnostic *m_pending_diagnostic;
+  event_id_t m_emission_id; // only set once all pruning has occurred
 };
 
+class debug_event : public checker3_event
+{
+public:
+  debug_event (location_t loc, tree fndecl, int depth,
+	      const char *desc)
+  : checker3_event (EK_DEBUG, loc, fndecl, depth),
+    m_desc (xstrdup (desc))
+  {
+  }
+  ~debug_event ()
+  {
+    free (m_desc);
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new debug_event (m_loc, m_fndecl, m_depth, m_desc);
+  }
+
+private:
+  char *m_desc;
+};
+
+class statement_event : public checker3_event
+{
+public:
+  statement_event (const gimple *stmt, tree fndecl, int depth)
+  : checker3_event (EK_STMT, gimple_location (stmt), fndecl, depth),
+    m_stmt (stmt)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    pretty_printer pp;
+    pp_string (&pp, "stmt: ");
+    // FIXME: const_cast!
+    pp_gimple_stmt_1 (&pp, const_cast <gimple *> (m_stmt) , 0, (dump_flags_t)0);
+    return label_text::take (xstrdup (pp_formatted_text (&pp)));
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new statement_event (m_stmt, m_fndecl, m_depth);
+  }
+
+  const gimple *m_stmt;
+};
+
+class function_entry_event : public checker3_event
+{
+public:
+  function_entry_event (location_t loc, tree fndecl, int depth)
+  : checker3_event (EK_FUNCTION_ENTRY, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::take (print ("entry to %qE", m_fndecl));
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new function_entry_event (m_loc, m_fndecl, m_depth);
+  }
+
+  // FIXME
+};
+
+class state_change_event : public checker3_event
+{
+public:
+  state_change_event (const supernode *node, const gimple *stmt,
+		      int stack_depth,
+		      const state_machine &sm,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin)
+  : checker3_event (EK_STATE_CHANGE,
+		   stmt->location, node->m_fun->decl,
+		   stack_depth),
+    m_node (node), m_stmt (stmt), m_sm (sm),
+    m_var (var), m_from (from), m_to (to),
+    m_origin (origin)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    if (m_origin)
+      return label_text::take
+	(print ("state of %qE: %qs -> %qs (origin: %qE)",
+		m_var,
+		m_sm.get_state_name (m_from),
+		m_sm.get_state_name (m_to),
+		m_origin));
+    else
+      {
+	/* If there is no origin, then presumably this
+	   state change came directly from a pattern match.
+	   Attempt to generate a nicer human-readable description.
+	   For greatest precision-of-wording, give the pending diagnostic
+	   a chance to describe this state change (in terms of the
+	   diagnostic).
+	   Note that we only have a pending_diagnostic set on the event once
+	   the diagnostic is about to being emitted, so the description for
+	   an event can change.  */
+	if (m_pending_diagnostic)
+	  {
+	    label_text custom_desc
+	      = m_pending_diagnostic->describe_critical_state_change
+	      (//diagnostic_context *ctxt,
+	       m_var, m_from, m_to, m_emission_id);
+	    if (custom_desc.m_buffer)
+	      {
+		if (flag_analyzer_verbose_state_changes)
+		  {
+		    /* Append debug version.  */
+		    label_text result = label_text::take
+		      (print ("%s (state of %qE: %qs -> %qs, origin: NULL)",
+			      custom_desc.m_buffer,
+			      m_var,
+			      m_sm.get_state_name (m_from),
+			      m_sm.get_state_name (m_to)));
+		    custom_desc.maybe_free ();
+		    return result;
+		  }
+		else
+		  return custom_desc;
+	      }
+	  }
+
+	/* Fallback description.  */
+	return label_text::take
+	  (print ("state of %qE: %qs -> %qs (origin: NULL)",
+		  m_var,
+		  m_sm.get_state_name (m_from),
+		  m_sm.get_state_name (m_to)));
+      }
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new state_change_event (m_node, m_stmt, m_depth,
+				   m_sm, m_var, m_from, m_to, m_origin);
+  }
+
+  const supernode *m_node;
+  const gimple *m_stmt;
+  const state_machine &m_sm;
+  tree m_var;
+  state_machine::state_t m_from;
+  state_machine::state_t m_to;
+  tree m_origin;
+};
+
+class edge_event : public checker3_event
+{
+public:
+  edge_event (const superedge *edge,
+	      location_t loc, tree fndecl, int depth)
+  : checker3_event (EK_EDGE,
+		   loc, fndecl, depth),
+    m_edge (edge)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    switch (m_edge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	{
+	  char *edge_desc = m_edge->get_description ();
+	  if (strlen (edge_desc) > 0)
+	    {
+	      label_text result
+		= label_text::take (print ("taking %qs edge SN:%i -> SN:%i",
+					   edge_desc,
+					   m_edge->m_src->m_index,
+					   m_edge->m_dest->m_index));
+	      free (edge_desc);
+	      return result;
+	    }
+	  else
+	    {
+	      free (edge_desc);
+	      return label_text::take (print ("taking edge SN:%i -> SN:%i",
+					      m_edge->m_src->m_index,
+					      m_edge->m_dest->m_index));
+	    }
+	}
+      case SUPEREDGE_CALL:
+	{
+	  return label_text::take (print ("calling %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_RETURN:
+	{
+	  return label_text::take (print ("returning to %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	return label_text::take (// TODO: possibly a higher-level description?
+				 print ("taking edge SN:%i -> SN:%i",
+					m_edge->m_src->m_index,
+					m_edge->m_dest->m_index));
+      }
+  }
+
+  const cfg_superedge& get_cfg_superedge () const
+  {
+    gcc_assert (m_edge->m_kind == SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_cfg_superedge ();
+  }
+
+  const call_superedge& get_call_superedge () const
+  {
+    gcc_assert (m_edge->m_kind != SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_call_superedge ();
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new edge_event (m_edge, m_loc, m_fndecl, m_depth);
+  }
+
+  const superedge *m_edge;
+};
+
+/* FIXME: a repeat of the warning message at the end of the path.  */
+
+class warning_event : public checker3_event
+{
+public:
+  warning_event (location_t loc, tree fndecl, int depth,
+		 const char *msg)
+  : checker3_event (EK_WARNING, loc, fndecl, depth),
+    m_msg (xstrdup (msg))
+  {
+  }
+  ~warning_event () { free (m_msg); }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_msg);
+  }
+
+  warning_event *clone () const FINAL OVERRIDE
+  {
+    return new warning_event (m_loc, m_fndecl, m_depth, m_msg);
+  }
+
+  char *m_msg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
 /* FIXME.  */
 
 class checker3_path : public diagnostic_path
@@ -171,6 +534,11 @@ public:
     return *m_events[idx];
   }
 
+  void add_event (checker3_event *event)
+  {
+    m_events.safe_push (event);
+  }
+
   const supernode *get_end_node () const
   {
     gcc_assert (m_links.length () > 0);
@@ -187,35 +555,7 @@ public:
     return get_sg ().get_edge_for_return (cedge);
   }
 
-  /* Record that we've reached NODE, via EDGE.  EDGE may be NULL,
-     to cover entrypoints to the analysis.
-     Update m_active_calls to reflect call and return edges.  */
-  void add_link (const superedge *edge, const supernode *node)
-  {
-    m_links.safe_push (link (edge, node));
-    if (edge)
-      {
-	switch (edge->m_kind)
-	  {
-	  default:
-	    break;
-	  case SUPEREDGE_CALL:
-	    {
-	      cgraph_edge *cedge = edge->get_callgraph_edge ();
-	      gcc_assert (cedge);
-	      m_active_calls.safe_push (cedge);
-	    }
-	    break;
-	  case SUPEREDGE_RETURN:
-	    {
-	      cgraph_edge *top_cedge = m_active_calls.pop ();
-	      cgraph_edge *edge_cedge = edge->get_callgraph_edge ();
-	      gcc_assert (edge_cedge == top_cedge);
-	    }
-	    break;
-	  }
-      }
-  }
+  void add_link (const superedge *edge, const supernode *node);
 
   int get_stack_depth () const { return m_ps.m_stack_depth; }
 
@@ -234,6 +574,46 @@ public:
   const supergraph &get_sg () const;
   const state_machine &get_sm () const;
 
+  int get_verbosity () const;
+
+  void add_final_event (const supernode *node, const gimple *stmt,
+			tree var, state_machine::state_t state,
+			pending_diagnostic *pd)
+  {
+    char *msg = NULL;
+    label_text ev_desc = pd->describe_final_event (var, state);
+    if (ev_desc.m_buffer)
+      {
+	if (flag_analyzer_verbose_state_changes)
+	  {
+	    msg = print ("%s (%qE is in state %qs)", ev_desc.m_buffer,
+			 var, get_sm ().get_state_name (state));
+	    ev_desc.maybe_free ();
+	  }
+	else
+	  msg = ev_desc.take_or_copy ();
+      }
+    else
+      {
+	msg = print ("here (%qE is in state %qs)", var,
+		     get_sm ().get_state_name (state));
+	// FIXME: ideally would show the full message here, too?
+      }
+    checker3_event *end_of_path
+      = new warning_event (stmt->location,
+			   node->m_fun->decl, get_stack_depth (),
+			   msg);
+    add_event (end_of_path);
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd)
+  {
+    checker3_event *e;
+    int i;
+    FOR_EACH_VEC_ELT (m_events, i, e)
+      e->prepare_for_emission (pd, event_id_t (i));
+  }
+
   ////////////////////////////////////////////////////////////////////////////
 
   engine *m_engine;
@@ -340,7 +720,13 @@ public:
 		    m_engine->m_sm.get_state_name (to));
     state_machine::state_t baseline = m_path->get_baseline_state (var);
     if (from == baseline)
-      m_path->set_state (var, baseline, to);
+      {
+	m_path->set_state (var, baseline, to);
+	checker3_event *event
+	  = new state_change_event (node, stmt, m_path->get_stack_depth (),
+				    m_engine->m_sm, var, from, to, origin);
+	m_path->add_event (event);
+      }
   }
 
   void warn_for_state (const supernode *node, const gimple *stmt,
@@ -356,8 +742,13 @@ public:
     state_machine::state_t baseline = m_path->get_baseline_state (var);
     if (state == baseline)
       {
+	checker3_path emission_path (*m_path);
+	emission_path.add_final_event (node, stmt, var, state, d);
+	emission_path.prepare_for_emission (d);
+	// TODO: potentially we should filter it
+
 	gcc_rich_location rich_loc (stmt->location);
-	rich_loc.set_path (m_path);
+	rich_loc.set_path (&emission_path);
 
 	auto_cfun sentinel (node->m_fun);
 	d->emit (&rich_loc);
@@ -464,6 +855,52 @@ checker3_path::to_str () const
   return result;
 }
 
+/* Record that we've reached NODE, via EDGE.  EDGE may be NULL,
+   to cover entrypoints to the analysis.
+   Update m_active_calls to reflect call and return edges.  */
+
+void
+checker3_path::add_link (const superedge *edge, const supernode *node)
+{
+  m_links.safe_push (link (edge, node));
+  if (edge)
+    {
+      switch (edge->m_kind)
+	{
+	default:
+	  break;
+	case SUPEREDGE_CALL:
+	  {
+	    cgraph_edge *cedge = edge->get_callgraph_edge ();
+	    gcc_assert (cedge);
+	    m_active_calls.safe_push (cedge);
+	  }
+	  break;
+	case SUPEREDGE_RETURN:
+	  {
+	    cgraph_edge *top_cedge = m_active_calls.pop ();
+	    cgraph_edge *edge_cedge = edge->get_callgraph_edge ();
+	    gcc_assert (edge_cedge == top_cedge);
+	  }
+	  break;
+	}
+    }
+  if (get_verbosity () > 1)
+    {
+      add_event (new debug_event (node->get_start_location (),
+				  node->m_fun->decl,
+				  get_stack_depth (),
+				  print ("SN: %i", node->m_index)));
+    }
+  if (get_verbosity () > 0 && node->entry_p ())
+    {
+      add_event
+	(new function_entry_event (node->get_start_location (),
+				   node->m_fun->decl,
+				   get_stack_depth ()));
+    }
+}
+
 state_machine::state_t
 checker3_path::get_baseline_state (tree expr)
 {
@@ -502,6 +939,12 @@ checker3_path::get_sm () const
   return m_engine->m_sm;
 }
 
+int
+checker3_path::get_verbosity () const
+{
+  return m_engine->m_verbosity;
+}
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* FIXME.  */
@@ -591,30 +1034,17 @@ engine::traverse (checker3_path path)
       /* We need to clone the backtrace here so if the transition
 	 writes into the path each edge has its own copy.  */
 
-#if 0
-      checker_event *event
+      checker3_event *event
 	= new edge_event (succ,
 			  (last_stmt
 			   ? last_stmt->location
 			   : UNKNOWN_LOCATION),
 			  node->m_fun->decl,
-			  new_b.m_path.m_stack_depth);
-      new_b.m_path.add_event (event);
-
-      sm_instance per_edge_new_sm_inst = sm_inst;
-      engine_sm_context ctxt (this, new_b,
-			      &per_edge_new_sm_inst,
-			      &sm_inst,
-			      &new_b.m_path, &new_ps);
-      m_sm.on_edge (&ctxt, last_stmt, succ);
-#endif
+			  path.get_stack_depth ()); // use old stack depth
+      new_path.add_event (event);
 
-#if 0
-      /* This could have updated the state.  Make sure this effect
-	 is recorded in the cache.  */
-      add_blk_edges_to_cache (node, per_edge_new_sm_inst,
-			      per_block_old_sm_inst);
-#endif
+      engine_sm_context ctxt (this, &new_path);
+      m_sm.on_edge (&ctxt, last_stmt, succ);
 
       new_path.add_link (succ, succ->m_dest);
       traverse (new_path);
@@ -638,12 +1068,18 @@ engine::on_stmt (checker3_path *path, const gimple *stmt)
 
   const supernode *node = path->get_end_node ();
 
-  // TODO
+  checker3_event *event
+    = new statement_event (stmt,
+			   node->m_fun->decl,
+			   path->get_stack_depth ());
+  path->add_event (event);
 
   engine_sm_context ctxt (this, path);
   m_sm.on_stmt (&ctxt, node, stmt);
   m_on_stmt_calls++;
 
+  // TODO
+
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
     {
       /* Debugging/test support.  */
-- 
1.8.5.3

