The analyzer's initial worklist was only populated with non-static
functions in the TU (along with those that look promising for call
summaries).  Hence some static functions that were never explicitly
called but could be called via function pointers were not being
analyzed.

This patch remedies this by ensuring that functions that escape as
function pointers get added to the worklist, if they haven't been
already.  Another fix would be to simply analyze all functions that
we have a body for, but too much of the testsuite relies on static
test functions not being directly analyzed.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to master as r11-3840-gaf66094d037793773eb8a49597866457f2f6a104.

gcc/analyzer/ChangeLog:
        PR analyzer/97258
        * engine.cc (impl_region_model_context::on_escaped_function): New
        vfunc.
        (exploded_graph::add_function_entry): Use m_functions_with_enodes
        to implement idempotency.
        (add_any_callbacks): New.
        (exploded_graph::build_initial_worklist): Use the above to find
        callbacks that are reachable from global initializers.
        (exploded_graph::on_escaped_function): New.
        * exploded-graph.h
        (impl_region_model_context::on_escaped_function): New decl.
        (exploded_graph::on_escaped_function): New decl.
        (exploded_graph::m_functions_with_enodes): New field.
        * region-model-reachability.cc
        (reachable_regions::reachable_regions): Replace "store" param with
        "model" param; use it to initialize m_model.
        (reachable_regions::add): When getting the svalue for the region,
        call get_store_value on the model rather than using an initial
        value.
        (reachable_regions::mark_escaped_clusters): Add ctxt param and
        use it to call on_escaped_function when a function_region escapes.
        * region-model-reachability.h
        (reachable_regions::reachable_regions): Replace "store" param with
        "model" param.
        (reachable_regions::mark_escaped_clusters): Add ctxt param.
        (reachable_regions::m_model): New field.
        * region-model.cc (region_model::handle_unrecognized_call): Update
        for change in reachable_regions ctor.
        (region_model::handle_unrecognized_call): Pass ctxt to
        mark_escaped_clusters.
        (region_model::get_reachable_svalues): Update for change in
        reachable_regions ctor.
        (region_model::get_initial_value_for_global): Read-only variables
        keep their initial values.
        * region-model.h (region_model_context::on_escaped_function): New
        vfunc.
        (noop_region_model_context::on_escaped_function): New.

gcc/testsuite/ChangeLog:
        PR analyzer/97258
        * gcc.dg/analyzer/callbacks-1.c: New test.
        * gcc.dg/analyzer/callbacks-2.c: New test.
        * gcc.dg/analyzer/callbacks-3.c: New test.
---
 gcc/analyzer/engine.cc                      | 70 +++++++++++++++++++++
 gcc/analyzer/exploded-graph.h               |  8 +++
 gcc/analyzer/region-model-reachability.cc   | 19 ++++--
 gcc/analyzer/region-model-reachability.h    |  8 ++-
 gcc/analyzer/region-model.cc                | 13 ++--
 gcc/analyzer/region-model.h                 |  5 ++
 gcc/testsuite/gcc.dg/analyzer/callbacks-1.c | 25 ++++++++
 gcc/testsuite/gcc.dg/analyzer/callbacks-2.c | 22 +++++++
 gcc/testsuite/gcc.dg/analyzer/callbacks-3.c | 19 ++++++
 9 files changed, 175 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/callbacks-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/callbacks-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/callbacks-3.c

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 0e79254ad60..65d7495f26f 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -143,6 +143,12 @@ impl_region_model_context::on_unknown_change (const svalue 
*sval,
     smap->on_unknown_change (sval, is_mutable, m_ext_state);
 }
 
+void
+impl_region_model_context::on_escaped_function (tree fndecl)
+{
+  m_eg->on_escaped_function (fndecl);
+}
+
 /* class setjmp_svalue : public svalue.  */
 
 /* Implementation of svalue::accept vfunc for setjmp_svalue.  */
@@ -1931,6 +1937,15 @@ exploded_graph::~exploded_graph ()
 exploded_node *
 exploded_graph::add_function_entry (function *fun)
 {
+  /* Be idempotent.  */
+  if (m_functions_with_enodes.contains (fun))
+    {
+      logger * const logger = get_logger ();
+       if (logger)
+       logger->log ("entrypoint for %qE already exists", fun->decl);
+      return NULL;
+    }
+
   program_point point = program_point::from_function_entry (m_sg, fun);
   program_state state (m_ext_state);
   state.push_frame (m_ext_state, fun);
@@ -1942,6 +1957,9 @@ exploded_graph::add_function_entry (function *fun)
   /* We should never fail to add such a node.  */
   gcc_assert (enode);
   add_edge (m_origin, enode, NULL);
+
+  m_functions_with_enodes.add (fun);
+
   return enode;
 }
 
@@ -2261,6 +2279,18 @@ toplevel_function_p (cgraph_node *node, function *fun, 
logger *logger)
   return true;
 }
 
+/* Callback for walk_tree for finding callbacks within initializers;
+   ensure they are treated as possible entrypoints to the analysis.  */
+
+static tree
+add_any_callbacks (tree *tp, int *, void *data)
+{
+  exploded_graph *eg = (exploded_graph *)data;
+  if (TREE_CODE (*tp) == FUNCTION_DECL)
+    eg->on_escaped_function (*tp);
+  return NULL_TREE;
+}
+
 /* Add initial nodes to EG, with entrypoints for externally-callable
    functions.  */
 
@@ -2286,6 +2316,19 @@ exploded_graph::build_initial_worklist ()
          logger->log ("did not create enode for %qE entrypoint", fun->decl);
       }
   }
+
+  /* Find callbacks that are reachable from global initializers.  */
+  varpool_node *vpnode;
+  FOR_EACH_VARIABLE (vpnode)
+    {
+      tree decl = vpnode->decl;
+      if (!TREE_PUBLIC (decl))
+       continue;
+      tree init = DECL_INITIAL (decl);
+      if (!init)
+       continue;
+      walk_tree (&init, add_any_callbacks, this, NULL);
+    }
 }
 
 /* The main loop of the analysis.
@@ -3923,6 +3966,33 @@ exploded_graph::get_node_by_index (int idx) const
   return enode;
 }
 
+/* Ensure that there is an exploded_node for a top-level call to FNDECL.  */
+
+void
+exploded_graph::on_escaped_function (tree fndecl)
+{
+  logger * const logger = get_logger ();
+  LOG_FUNC_1 (logger, "%qE", fndecl);
+
+  cgraph_node *cgnode = cgraph_node::get (fndecl);
+  if (!cgnode)
+    return;
+
+  function *fun = cgnode->get_fun ();
+  if (!fun)
+    return;
+
+  exploded_node *enode = add_function_entry (fun);
+  if (logger)
+    {
+      if (enode)
+       logger->log ("created EN %i for %qE entrypoint",
+                    enode->m_index, fun->decl);
+      else
+       logger->log ("did not create enode for %qE entrypoint", fun->decl);
+    }
+}
+
 /* A collection of classes for visualizing the callgraph in .dot form
    (as represented in the supergraph).  */
 
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index a6ca4b9a99d..b207b1d0762 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -66,6 +66,8 @@ class impl_region_model_context : public region_model_context
   void on_unexpected_tree_code (tree t,
                                const dump_location_t &loc) FINAL OVERRIDE;
 
+  void on_escaped_function (tree fndecl) FINAL OVERRIDE;
+
   exploded_graph *m_eg;
   log_user m_logger;
   const exploded_node *m_enode_for_diag;
@@ -799,6 +801,8 @@ public:
     return m_worklist.get_scc_id (node);
   }
 
+  void on_escaped_function (tree fndecl);
+
 private:
   void print_bar_charts (pretty_printer *pp) const;
 
@@ -845,6 +849,10 @@ private:
   call_string_data_map_t m_per_call_string_data;
 
   auto_vec<int> m_PK_AFTER_SUPERNODE_per_snode;
+
+  /* Functions with a top-level enode, to make add_function_entry
+     be idempotent, for use in handling callbacks.  */
+  hash_set<function *> m_functions_with_enodes;
 };
 
 /* A path within an exploded_graph: a sequence of edges.  */
diff --git a/gcc/analyzer/region-model-reachability.cc 
b/gcc/analyzer/region-model-reachability.cc
index c1b3b2db630..3a6b312a8df 100644
--- a/gcc/analyzer/region-model-reachability.cc
+++ b/gcc/analyzer/region-model-reachability.cc
@@ -58,9 +58,9 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
-reachable_regions::reachable_regions (store *store,
+reachable_regions::reachable_regions (region_model *model,
                                      region_model_manager *mgr)
-: m_store (store), m_mgr (mgr),
+: m_model (model), m_store (model->get_store ()), m_mgr (mgr),
   m_reachable_base_regs (), m_mutable_base_regs ()
 {
 }
@@ -135,7 +135,7 @@ reachable_regions::add (const region *reg, bool is_mutable)
   if (binding_cluster *bind_cluster = m_store->get_cluster (base_reg))
     bind_cluster->for_each_value (handle_sval_cb, this);
   else
-    handle_sval (m_mgr->get_or_create_initial_value (base_reg));
+    handle_sval (m_model->get_store_value (reg));
 }
 
 void
@@ -206,17 +206,24 @@ reachable_regions::handle_parm (const svalue *sval, tree 
param_type)
     }
 }
 
-/* Update m_store to mark the clusters that were found to be mutable
-   as having escaped.  */
+/* Update the store to mark the clusters that were found to be mutable
+   as having escaped.
+   Notify CTXT about escaping function_decls.  */
 
 void
-reachable_regions::mark_escaped_clusters ()
+reachable_regions::mark_escaped_clusters (region_model_context *ctxt)
 {
+  gcc_assert (ctxt);
   for (hash_set<const region *>::iterator iter = m_mutable_base_regs.begin ();
        iter != m_mutable_base_regs.end (); ++iter)
     {
       const region *base_reg = *iter;
       m_store->mark_as_escaped (base_reg);
+
+      /* If we have a function that's escaped, potentially add
+        it to the worklist.  */
+      if (const function_region *fn_reg = base_reg->dyn_cast_function_region 
())
+       ctxt->on_escaped_function (fn_reg->get_fndecl ());
     }
 }
 
diff --git a/gcc/analyzer/region-model-reachability.h 
b/gcc/analyzer/region-model-reachability.h
index aba1a58ec7f..fe305b0ce78 100644
--- a/gcc/analyzer/region-model-reachability.h
+++ b/gcc/analyzer/region-model-reachability.h
@@ -35,7 +35,7 @@ namespace ana {
 class reachable_regions
 {
 public:
-  reachable_regions (store *store, region_model_manager *mgr);
+  reachable_regions (region_model *model, region_model_manager *mgr);
 
   /* Callback called for each cluster when initializing this object.  */
   static void init_cluster_cb (const region *base_reg,
@@ -59,8 +59,9 @@ public:
   void handle_parm (const svalue *sval, tree param_type);
 
   /* Update the store to mark the clusters that were found to be mutable
-     as having escaped.  */
-  void mark_escaped_clusters ();
+     as having escaped.
+     Notify CTXT about escaping function_decls.  */
+  void mark_escaped_clusters (region_model_context *ctxt);
 
   /* Iteration over reachable base regions.  */
   hash_set<const region *>::iterator begin ()
@@ -94,6 +95,7 @@ public:
   DEBUG_FUNCTION void dump () const;
 
 private:
+  region_model *m_model;
   store *m_store;
   region_model_manager *m_mgr;
 
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 480f25a3a4b..922e0361e59 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -836,7 +836,7 @@ region_model::handle_unrecognized_call (const gcall *call,
 {
   tree fndecl = get_fndecl_for_call (call, ctxt);
 
-  reachable_regions reachable_regs (&m_store, m_mgr);
+  reachable_regions reachable_regs (this, m_mgr);
 
   /* Determine the reachable regions and their mutability.  */
   {
@@ -884,7 +884,7 @@ region_model::handle_unrecognized_call (const gcall *call,
     }
 
   /* Mark any clusters that have escaped.  */
-  reachable_regs.mark_escaped_clusters ();
+  reachable_regs.mark_escaped_clusters (ctxt);
 
   /* Update bindings for all clusters that have escaped, whether above,
      or previously.  */
@@ -904,7 +904,7 @@ void
 region_model::get_reachable_svalues (svalue_set *out,
                                     const svalue *extra_sval)
 {
-  reachable_regions reachable_regs (&m_store, m_mgr);
+  reachable_regions reachable_regs (this, m_mgr);
 
   /* Add globals and regions that already escaped in previous
      unknown calls.  */
@@ -1333,14 +1333,17 @@ region_model::get_initial_value_for_global (const 
region *reg) const
      an unknown value if an unknown call has occurred, unless this is
      static to-this-TU and hasn't escaped.  Globals that have escaped
      are explicitly tracked, so we shouldn't hit this case for them.  */
-  if (m_store.called_unknown_fn_p () && TREE_PUBLIC (decl))
+  if (m_store.called_unknown_fn_p ()
+      && TREE_PUBLIC (decl)
+      && !TREE_READONLY (decl))
     return m_mgr->get_or_create_unknown_svalue (reg->get_type ());
 
   /* If we are on a path from the entrypoint from "main" and we have a
      global decl defined in this TU that hasn't been touched yet, then
      the initial value of REG can be taken from the initialization value
      of the decl.  */
-  if (called_from_main_p () && !DECL_EXTERNAL (decl))
+  if ((called_from_main_p () && !DECL_EXTERNAL (decl))
+      || TREE_READONLY (decl))
     {
       /* Get the initializer value for base_reg.  */
       const svalue *base_reg_init
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 234ca16bcef..5ad4a492f4f 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -2795,6 +2795,9 @@ class region_model_context
      know how to handle the tree code of T at LOC.  */
   virtual void on_unexpected_tree_code (tree t,
                                        const dump_location_t &loc) = 0;
+
+  /* Hook for clients to be notified when a function_decl escapes.  */
+  virtual void on_escaped_function (tree fndecl) = 0;
 };
 
 /* A "do nothing" subclass of region_model_context.  */
@@ -2821,6 +2824,8 @@ public:
   {
   }
   void on_unexpected_tree_code (tree, const dump_location_t &) OVERRIDE {}
+
+  void on_escaped_function (tree) OVERRIDE {}
 };
 
 /* A subclass of region_model_context for determining if operations fail
diff --git a/gcc/testsuite/gcc.dg/analyzer/callbacks-1.c 
b/gcc/testsuite/gcc.dg/analyzer/callbacks-1.c
new file mode 100644
index 00000000000..52c8fde540a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/callbacks-1.c
@@ -0,0 +1,25 @@
+/* Reproducer for PR analyzer/97258: we should report the double-free
+   inside a static callback if the callback escapes.  */
+
+#include <stdlib.h>
+
+static void callback_1 (void *p)
+{
+  free (p);
+  free (p); /* { dg-warning "double-'free' of 'p'" } */
+}
+
+struct ops {
+  void (*cb) (void *);
+};
+
+static const struct ops ops_1 = {
+  .cb = callback_1
+};
+
+extern void registration (const void *);
+
+void register_1 (void)
+{
+  registration (&ops_1);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/callbacks-2.c 
b/gcc/testsuite/gcc.dg/analyzer/callbacks-2.c
new file mode 100644
index 00000000000..98915ee617b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/callbacks-2.c
@@ -0,0 +1,22 @@
+/* Reproducer for PR analyzer/97258: we should report the double-free
+   inside a static callback if the callback is accessible via a global
+   initializer.  */
+
+#include <stdlib.h>
+
+static void callback_1 (void *p)
+{
+  free (p);
+  free (p); /* { dg-warning "double-'free' of 'p'" } */
+}
+
+struct ops {
+  void (*cb) (void *);
+};
+
+/* Callback struct is not static, and so could be accessed via
+   another TU.  */
+
+const struct ops ops_1 = {
+  .cb = callback_1
+};
diff --git a/gcc/testsuite/gcc.dg/analyzer/callbacks-3.c 
b/gcc/testsuite/gcc.dg/analyzer/callbacks-3.c
new file mode 100644
index 00000000000..5f12c2a28d3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/callbacks-3.c
@@ -0,0 +1,19 @@
+#include "analyzer-decls.h"
+
+typedef __SIZE_TYPE__ size_t;
+typedef int (*__compar_fn_t)(const void *, const void *);
+extern void qsort(void *__base, size_t __nmemb, size_t __size,
+                 __compar_fn_t __compar)
+  __attribute__((__nonnull__(1, 4)));
+
+static int
+test_1_callback (const void *p1, const void *p2)
+{
+  __analyzer_dump_path (); /* { dg-message "here" } */
+  return 0;
+}
+
+void test_1_caller (int *arr, size_t n)
+{
+  qsort (arr, n, sizeof (int), test_1_callback);
+}
-- 
2.26.2

Reply via email to