From 1a6e1a01e429c6d188ae509f185804feabb9edc9 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 7 May 2020 14:08:23 -0400
Subject: [PATCH 109/315] FIXME: yet more on leak detection

---
 gcc/analyzer/analyzer.h       |   1 +
 gcc/analyzer/engine.cc        | 153 ++++++++++++++++++++++++++++++----
 gcc/analyzer/exploded-graph.h |  13 +--
 gcc/analyzer/program-state.cc |  34 +++++---
 gcc/analyzer/program-state.h  |   9 +-
 gcc/analyzer/region-model2.cc |  22 +++--
 gcc/analyzer/region-model2.h  |  17 ++--
 7 files changed, 187 insertions(+), 62 deletions(-)

diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 851068e169a..d15fbabdf24 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -74,6 +74,7 @@ class region_model2_manager;
 class store2_manager;
 class region_model2;
 class region_model2_context;
+  class impl_region_model2_context;
 class constraint_manager2;
 class equiv_class2;
 
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index a23120c4c9f..cb156916a07 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -197,25 +197,24 @@ impl_region_model2_context::warn (pending_diagnostic *d)
        m_stmt, m_stmt_finder, d);
 }
 
-#if 0
-int
-impl_region_model2_context::on_svalue_purge (svalue_id first_unused_sid,
-					    const svalue_id_map &map)
+void
+impl_region_model2_context::on_svalue2_leak (const svalue2 *sval)
+
 {
-  int total = 0;
   int sm_idx;
-  sm_state_map *smap;
-  FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+  sm_state_map2 *smap;
+  FOR_EACH_VEC_ELT (m_new_state->m_checker_states2, sm_idx, smap)
     {
       const state_machine &sm = m_ext_state.get_sm (sm_idx);
-      total += smap->on_svalue_purge (sm, sm_idx, first_unused_sid,
-				      map, this);
+      smap->on_svalue2_leak (sm, sm_idx, sval, this);
     }
+
+#if 0
+  // FIXME: what is/was this used for?
   if (m_change)
-    total += m_change->on_svalue_purge (first_unused_sid);
-  return total;
-}
+    m_change->on_svalue_purge (first_unused_sid);
 #endif
+}
 
 #if 0
 void
@@ -954,6 +953,123 @@ impl_region_model_context::on_unexpected_tree_code (tree t,
     m_new_state->m_valid = false;
 }
 
+// FIXME:
+/* Create an sm_context and use it to call SM's on_leak vfunc, so that
+   it can potentially complain about a leak of SVAL in the given STATE.  */
+
+void
+impl_region_model2_context::on_state_leak (const state_machine &sm,
+					   int sm_idx,
+					   const svalue2 *sval,
+					   state_machine::state_t state)
+{
+  logger * const logger = get_logger ();
+  LOG_SCOPE (logger);
+  if (logger)
+    {
+      logger->start_log_line ();
+      logger->log_partial ("considering leak of ");
+      sval->dump_to_pp (logger->get_printer (), true);
+      logger->end_log_line ();
+    }
+
+  if (!m_eg)
+    return;
+
+  /* m_old_state also needs to be non-NULL so that the sm_ctxt can look
+     up the old state of SVAL.  */
+  gcc_assert (m_old_state);
+
+#if 0
+  /* Don't report on sid leaking if it's equal to one of the used sids.
+     For example, given:
+       some_non_trivial_expression = malloc (sizeof (struct foo));
+     we have:
+       _1 = malloc;                         (void *)
+       some_non_trivial_expression = _1;    (struct foo *)
+     and at leak-detection time we may have:
+       sv5: {type: 'struct foo *', &r3}  (used)
+       sv6: {type: 'void *', &r3}         (unused)
+     where both point to the same region.  We don't want to report a
+     leak of sv6, so we reject the report due to its equality with sv5.  */
+  gcc_assert (m_new_state);
+  gcc_assert (!first_unused_sid.null_p ());
+  for (int i = 0; i < first_unused_sid.as_int (); i++)
+    {
+      svalue_id used_sid = svalue_id::from_int (i);
+
+      /* Use the "_without_cm" form of eval_condition, since
+	 we're half-way through purging - we don't want to introduce new
+	 equivalence classes into the constraint_manager for "sid" and
+	 for each of the used_sids.  */
+      const region_model &rm = *m_new_state->m_region_model;
+      tristate eq = rm.eval_condition_without_cm (dst_sid, EQ_EXPR, used_sid);
+      if (eq.is_true ())
+	{
+	  if (logger)
+	    logger->log ("rejecting leak of sv%i due to equality with sv%i",
+			 dst_sid.as_int (), used_sid.as_int ());
+	  return;
+	}
+    }
+#endif
+
+  /* SVAL has leaked within the new state: it is not used by any reachable
+     regions.
+     We need to convert it back to a tree, but since it's likely no regions
+     use it, FIXME we
+     have to use MAP to convert it back to an svalue_id within the old state.
+     We can then look that svalue_id up to locate regions and thus tree(s)
+     that use it.  */
+
+  auto_vec<path_var> leaked_pvs;
+  m_old_state->m_region_model2->get_path_vars_for_svalue2 (sval, &leaked_pvs);
+
+  if (leaked_pvs.length () < 1)
+    return;
+
+  /* Find "best" leaked tree.
+     Sort the leaks into most human-readable first, through
+     to least user-readable.  Given that we only emit one
+     leak per EC, this ought to ensure that we pick the most
+     user-readable description of each leaking EC.
+     This assumes that all vars in the EC have the same state.  */
+  leaked_pvs.qsort (readability_comparator);
+
+  tree leaked_tree = leaked_pvs[0].m_tree;
+  if (logger)
+    logger->log ("best leaked_tree: %qE", leaked_tree);
+
+  leak_stmt_finder stmt_finder (*m_eg, leaked_tree);
+  impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag,
+			   m_old_state, m_new_state,
+			   m_change,
+			   m_old_state->m_checker_states[sm_idx],
+			   m_new_state->m_checker_states[sm_idx],
+			   &stmt_finder);
+  gcc_assert (m_enode_for_diag);
+
+  /* Don't complain about leaks when returning from "main".  */
+  if (m_enode_for_diag->get_supernode ()
+      && m_enode_for_diag->get_supernode ()->return_p ())
+    {
+      tree fndecl = m_enode_for_diag->get_function ()->decl;
+      if (0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), "main"))
+	{
+	  if (logger)
+	    logger->log ("not reporting leak from main");
+	  return;
+	}
+    }
+
+  pending_diagnostic *pd = sm.on_leak (leaked_tree);
+  if (pd)
+    m_eg->get_diagnostic_manager ().add_diagnostic
+      (&sm, m_enode_for_diag, m_enode_for_diag->get_supernode (),
+       m_stmt, &stmt_finder,
+       leaked_tree, state, pd);
+}
+
 /* Implementation of region_model_context::on_condition vfunc.
    Notify all state machines about the condition, which could lead to
    state transitions.  */
@@ -3052,11 +3168,14 @@ exploded_graph::process_node (exploded_node *node)
 	    if (flags.m_terminate_path)
 	      return;
 
-	    impl_region_model2_context ctxt (*this, node,
-					     &old_state, &next_state, &change,
-					     stmt);
-	    program_state::detect_leaks2 (old_state, next_state,
-					  get_ext_state (), &ctxt);
+	    if (next_state.m_region_model2)
+	      {
+		impl_region_model2_context ctxt (*this, node,
+						 &old_state, &next_state, &change,
+						 stmt);
+		program_state::detect_leaks2 (old_state, next_state,
+					      get_ext_state (), &ctxt);
+	      }
 
 	    unsigned next_idx = stmt_idx + 1;
 	    program_point next_point
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index d60e523700f..eec4fd66ed9 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -117,23 +117,16 @@ class impl_region_model2_context : public region_model2_context
 			     logger *logger = NULL);
 
   void warn (pending_diagnostic *d) FINAL OVERRIDE;
-
-#if 0
-  int on_svalue_purge (svalue_id first_unused_sid,
-		       const svalue_id_map &map) FINAL OVERRIDE;
-#endif
+  void on_svalue2_leak (const svalue2 *) OVERRIDE;
   logger *get_logger () FINAL OVERRIDE
   {
     return m_logger.get_logger ();
   }
-#if 0
   void on_state_leak (const state_machine &sm,
 		      int sm_idx,
-		      svalue_id sid,
-		      svalue_id first_unused_sid,
-		      const svalue_id_map &map,
+		      const svalue2 *sval,
 		      state_machine::state_t state);
-
+#if 0
   void on_inherited_svalue (svalue_id parent_sid,
 			    svalue_id child_sid) FINAL OVERRIDE;
 
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 15014b73757..43ea2bd80eb 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -932,19 +932,26 @@ sm_state_map2::purge_for_unknown_fncall (const exploded_graph &eg,
 #endif
 }
 
-#if 0
-/* Purge any state for svalue_ids >= FIRST_UNUSED_SID.
+// FIXME:
+/* Purge any state for SVAL.
    If !SM::can_purge_p, then report the state as leaking,
-   using SM_IDX, CTXT, and MAP.
-   Return the number of states that were purged.  */
+   using FIXME.  */
 
-int
-sm_state_map2::on_svalue_purge (const state_machine &sm,
-			       int sm_idx,
-			       svalue_id first_unused_sid,
-			       const svalue_id_map &map,
-			       impl_region_model_context *ctxt)
+void
+sm_state_map2::on_svalue2_leak (const state_machine &sm,
+				int sm_idx,
+				const svalue2 *sval,
+				impl_region_model2_context *ctxt)
 {
+  if (state_machine::state_t state = get_state (sval))
+    {
+      if (!sm.can_purge_p (state))
+	ctxt->on_state_leak (sm, sm_idx, sval, state);
+      m_map.remove (sval);
+    }
+
+  // TODO
+#if 0
   /* TODO: ideally remove the slot directly; for now
      do it in two stages.  */
   auto_vec<svalue_id> to_remove;
@@ -977,8 +984,10 @@ sm_state_map2::on_svalue_purge (const state_machine &sm,
     m_map.remove (*dst_sid);
 
   return to_remove.length ();
+#endif
 }
 
+#if 0
 /* Set the state of CHILD_SID to that of PARENT_SID.  */
 
 void
@@ -1959,6 +1968,7 @@ program_state::detect_leaks2 (const program_state &src_state,
       const svalue2 *sval = (*iter);
       if (!dest_svalues.contains (sval))
 	{
+#if 0
 	  pretty_printer pp1, pp2, pp3;
 	  pp_format_decoder (&pp1) = default_tree_printer;
 	  pp_format_decoder (&pp2) = default_tree_printer;
@@ -1970,8 +1980,8 @@ program_state::detect_leaks2 (const program_state &src_state,
 		 pp_formatted_text (&pp1),
 		 pp_formatted_text (&pp2),
 		 pp_formatted_text (&pp3));
-	  // TODO: wire this up to the sm states
-	  //gcc_unreachable (); // TODO
+#endif
+	  ctxt->on_svalue2_leak (sval);
 	}
     }
 }
diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h
index 22bbd9f075f..ff4964b2e40 100644
--- a/gcc/analyzer/program-state.h
+++ b/gcc/analyzer/program-state.h
@@ -300,12 +300,11 @@ public:
 				 region_model2 *new_model,
 				 region_model2_context *ctxt);
 
+  void on_svalue2_leak (const state_machine &sm,
+			int sm_idx,
+			const svalue2 *sval,
+			impl_region_model2_context *ctxt);
 #if 0
-  int on_svalue_purge (const state_machine &sm,
-		       int sm_idx,
-		       svalue_id first_unused_sid,
-		       const svalue_id_map &map,
-		       impl_region_model_context *ctxt);
   void on_inherited_svalue (svalue_id parent_sid,
 			    svalue_id child_sid);
 
diff --git a/gcc/analyzer/region-model2.cc b/gcc/analyzer/region-model2.cc
index 91efadb15e3..f9c608c7288 100644
--- a/gcc/analyzer/region-model2.cc
+++ b/gcc/analyzer/region-model2.cc
@@ -4481,8 +4481,8 @@ region_model2::get_reachable_svalues (hash_set<const svalue2 *> *out)
   m_store.for_each_cluster (reachable_region2s::init_cluster_cb,
 			    &reachable_regions);
 
+#if 0
   /* Mark all locals in all frames (and what's reachable from them).  */
-#if 1
   for (const frame_region2 *iter_frame = get_current_frame ();
        iter_frame; iter_frame = iter_frame->get_calling_frame ())
     {
@@ -4508,6 +4508,7 @@ region_model2::get_reachable_svalues (hash_set<const svalue2 *> *out)
       // (and all parms/locals)
     }
 #else
+  /* Get regions for locals that have explicitly bound values.  */
   for (store2::cluster_map_t::iterator iter = m_store.begin ();
        iter != m_store.end (); ++iter)
     {
@@ -5864,23 +5865,30 @@ region_model2::get_representative_path_var (region2 *reg) const
   return path_var (NULL_TREE, 0);
 }
 #endif
-/* Locate all region2s that directly have value SID and append representative
+/* Locate all region2s that directly have value SVAL and append representative
    path_var instances for them into *OUT.  */
-#if 0
+/* FIXME: other ways of getting to the svalue e.g. for initial_svalue2s?
+   Also: this might not be the best human-readable description to use.  */
+
 void
-region_model2::get_path_vars_for_svalue2 (const svalue2 *sval, vec<path_var> *out) const
+region_model2::get_path_vars_for_svalue2 (const svalue2 *sval,
+					  auto_vec<path_var> *out) const
 {
+  return m_store.get_representative_path_vars (sval, out);
+
+#if 0
   unsigned i;
   region2 *region2;
   FOR_EACH_VEC_ELT (m_region2s, i, region2)
-    if (sid == region2->get_value_direct ())
+    if (sval == region2->get_value_direct ())
       {
-	path_var pv = get_representative_path_var (region2_id::from_int (i));
+	path_var pv = get_representative_path_var (sval);
 	if (pv.m_tree)
 	  out->safe_push (pv);
       }
-}
 #endif
+}
+
 /* Set DST_REG value to be a new unknown value of type TYPE.  */
 #if 0
 svalue2_id
diff --git a/gcc/analyzer/region-model2.h b/gcc/analyzer/region-model2.h
index b6f75090a10..1b5c567df2f 100644
--- a/gcc/analyzer/region-model2.h
+++ b/gcc/analyzer/region-model2.h
@@ -1888,15 +1888,17 @@ class region_model2
   tree get_representative_tree (const svalue2 *sval) const;
 #if 0
   path_var get_representative_path_var (region2_id rid) const;
-  void get_path_vars_for_svalue2 (svalue2_id sid, vec<path_var> *out) const;
+#endif
+  void get_path_vars_for_svalue2 (const svalue2 *sval,
+				  auto_vec<path_var> *out) const;
 
+#if 0
   void purge_unused_svalue2s (purge_stats *out,
 			     region_model2_context *ctxt,
 			     svalue2_id_set *known_used_sids = NULL);
   void purge_region2s (const region2_id_set &set,
 		      purge_stats *stats,
 		      logger *logger);
-
   unsigned get_num_svalue2s () const { return m_svalue2s.length (); }
   unsigned get_num_region2s () const { return m_region2s.length (); }
 #endif
@@ -2039,9 +2041,7 @@ class region_model2_context
      has purged (potentially for use in selftests).
      MAP has already been applied to the IDs, but is provided in case
      the client needs to figure out the old IDs.  */
-#if 0
-  virtual int on_svalue2_purge (const svalue2 *purged_value) = 0;
-#endif
+  virtual void on_svalue2_leak (const svalue2 *sval) = 0;
 
   virtual logger *get_logger () = 0;
 
@@ -2090,12 +2090,7 @@ class noop_region_model2_context : public region_model2_context
 {
 public:
   void warn (pending_diagnostic *) OVERRIDE {}
-#if 0
-  int on_svalue2_purge (const svalue2 *) OVERRIDE
-  {
-    return 0;
-  }
-#endif
+  void on_svalue2_leak (const svalue2 *) OVERRIDE {}
   logger *get_logger () OVERRIDE { return NULL; }
 #if 0
   void on_inherited_svalue2 (svalue2_id parent_sid ATTRIBUTE_UNUSED,
-- 
2.26.2

