gimple_call_fndecl (and gimple_call_addr_fndecl) fail to identify functions
for non-trivial uses of function pointers.

This patch eliminates most uses of these functions from the analyzer in
favor of resolving the rvalue for the fn ptr in the region_model, so that
with the patch it can e.g. detect the double free here:

  #include <stdlib.h>
  typedef void (*deallocator_t) (void *);
  void test_1 (void *ptr)
  {
    deallocator_t dealloc_fn = free;
    dealloc_fn (ptr);
    dealloc_fn (ptr);
  }

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

Pushed to branch "dmalcolm/analyzer" on the GCC git mirror.

gcc/ChangeLog:
        * analyzer/analyzer.cc (is_named_call_p): Reorganize arguments,
        adding a fndecl.  Drop second overload, and rename first overload
        to...
        (is_special_named_call_p): ...this.
        (is_setjmp_call_p): Update for rename of is_named_call_p to
        is_special_named_call_p.
        (is_longjmp_call_p): Likewise.
        * analyzer/analyzer.h (is_named_call_p): Reorganize arguments,
        adding a fndecl.  Drop second overload, and rename first overload
        to...
        (is_special_named_call_p): ...this.
        * analyzer/engine.cc (impl_sm_context::get_fndecl_for_call): New
        vfunc implementation.
        (exploded_node::on_stmt): Update for rename of is_named_call_p to
        is_special_named_call_p.
        (stmt_requires_new_enode_p): Likewise.
        (exploded_graph::dump_exploded_nodes): Likewise.
        * analyzer/region-model.cc (region_model::on_call_pre): Likewise.
        Replace most calls to is_named_call_p with the new overload taking
        a fndecl, calling get_fndecl_for_call to resolve function pointers.
        (region_model::on_call_post): Likewise.
        (region_model::get_fndecl_for_call): New member function.
        * analyzer/region-model.h (region_model::get_fndecl_for_call): New
        decl.
        * analyzer/sm-file.cc (fileptr_state_machine::on_stmt): Call
        get_fndecl_for_call to resolve function pointers, and update all
        uses of is_named_call_p to use the result.
        * analyzer/sm-malloc.cc (free_of_non_heap::describe_state_change):
        Update for rename of is_named_call_p to is_special_named_call_p.
        (malloc_state_machine::on_stmt): Call get_fndecl_for_call to
        resolve function pointers, and update all uses of is_named_call_p
        to use the result.
        * analyzer/sm-sensitive.cc (sensitive_state_machine::on_stmt):
        Likewise.
        * analyzer/sm-taint.cc (taint_state_machine::on_stmt): Likewise.
        * analyzer/sm.h (sm_context::get_fndecl_for_call): New vfunc.

gcc/testsuite/ChangeLog:
        * gcc.dg/analyzer/attribute-nonnull.c: Add coverage for detecting
        passing NULL to a __attribute__((nonnull)) function called via a
        function pointer.
        * gcc.dg/analyzer/malloc-callbacks.c: New test.
---
 gcc/analyzer/analyzer.cc                      |  38 +++-
 gcc/analyzer/analyzer.h                       |   8 +-
 gcc/analyzer/engine.cc                        |  24 ++-
 gcc/analyzer/region-model.cc                  | 187 ++++++++++--------
 gcc/analyzer/region-model.h                   |   3 +
 gcc/analyzer/sm-file.cc                       |  78 ++++----
 gcc/analyzer/sm-malloc.cc                     | 104 +++++-----
 gcc/analyzer/sm-sensitive.cc                  |  55 +++---
 gcc/analyzer/sm-taint.cc                      |  33 ++--
 gcc/analyzer/sm.h                             |   6 +
 .../gcc.dg/analyzer/attribute-nonnull.c       |  24 +++
 .../gcc.dg/analyzer/malloc-callbacks.c        |  84 ++++++++
 12 files changed, 415 insertions(+), 229 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-callbacks.c

diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
index 399925c96fc9..5405facf5e86 100644
--- a/gcc/analyzer/analyzer.cc
+++ b/gcc/analyzer/analyzer.cc
@@ -28,10 +28,18 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "analyzer/analyzer.h"
 
-/* Helper function for checkers.  Is the CALL to the given function name?  */
+/* Helper function for checkers.  Is the CALL to the given function name,
+   and with the given number of arguments?
+
+   This doesn't resolve function pointers via the region model;
+   is_named_call_p should be used instead, using a fndecl from
+   get_fndecl_for_call; this function should only be used for special cases
+   where it's not practical to get at the region model, or for special
+   analyzer functions such as __analyzer_dump.  */
 
 bool
-is_named_call_p (const gcall *call, const char *funcname)
+is_special_named_call_p (const gcall *call, const char *funcname,
+                        unsigned int num_args)
 {
   gcc_assert (funcname);
 
@@ -39,19 +47,31 @@ is_named_call_p (const gcall *call, const char *funcname)
   if (!fndecl)
     return false;
 
+  return is_named_call_p (fndecl, funcname, call, num_args);
+}
+
+/* Helper function for checkers.  Does FNDECL have the given FUNCNAME?  */
+
+bool
+is_named_call_p (tree fndecl, const char *funcname)
+{
+  gcc_assert (fndecl);
+  gcc_assert (funcname);
+
   return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname);
 }
 
-/* Helper function for checkers.  Is the CALL to the given function name,
-   and with the given number of arguments?  */
+/* Helper function for checkers.  Does FNDECL have the given FUNCNAME, and
+   does CALL have the given number of arguments?  */
 
 bool
-is_named_call_p (const gcall *call, const char *funcname,
-                unsigned int num_args)
+is_named_call_p (tree fndecl, const char *funcname,
+                const gcall *call, unsigned int num_args)
 {
+  gcc_assert (fndecl);
   gcc_assert (funcname);
 
-  if (!is_named_call_p (call, funcname))
+  if (!is_named_call_p (fndecl, funcname))
     return false;
 
   if (gimple_call_num_args (call) != num_args)
@@ -67,7 +87,7 @@ is_setjmp_call_p (const gimple *stmt)
 {
   /* TODO: is there a less hacky way to check for "setjmp"?  */
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    if (is_named_call_p (call, "_setjmp", 1))
+    if (is_special_named_call_p (call, "_setjmp", 1))
       return true;
 
   return false;
@@ -79,7 +99,7 @@ bool
 is_longjmp_call_p (const gcall *call)
 {
   /* TODO: is there a less hacky way to check for "longjmp"?  */
-  if (is_named_call_p (call, "longjmp", 2))
+  if (is_special_named_call_p (call, "longjmp", 2))
     return true;
 
   return false;
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index ace8924662f2..90da44b1a00a 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -68,9 +68,11 @@ class state_change;
 
 ////////////////////////////////////////////////////////////////////////////
 
-extern bool is_named_call_p (const gcall *call, const char *funcname);
-extern bool is_named_call_p (const gcall *call, const char *funcname,
-                            unsigned int num_args);
+extern bool is_special_named_call_p (const gcall *call, const char *funcname,
+                                    unsigned int num_args);
+extern bool is_named_call_p (tree fndecl, const char *funcname);
+extern bool is_named_call_p (tree fndecl, const char *funcname,
+                            const gcall *call, unsigned int num_args);
 extern bool is_setjmp_call_p (const gimple *stmt);
 extern bool is_longjmp_call_p (const gcall *call);
 
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index b1624777a221..eed2be091c93 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -177,6 +177,15 @@ public:
   {
   }
 
+  tree get_fndecl_for_call (const gcall *call) FINAL OVERRIDE
+  {
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
+       m_change, call);
+    region_model *model = m_new_state->m_region_model;
+    return model->get_fndecl_for_call (call, &old_ctxt);
+  }
+
   void on_transition (const supernode *node  ATTRIBUTE_UNUSED,
                      const gimple *stmt  ATTRIBUTE_UNUSED,
                      tree var,
@@ -881,25 +890,25 @@ exploded_node::on_stmt (exploded_graph &eg,
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
     {
       /* Debugging/test support.  */
-      if (is_named_call_p (call, "__analyzer_dump", 0))
+      if (is_special_named_call_p (call, "__analyzer_dump", 0))
        {
          /* Handle the builtin "__analyzer_dump" by dumping state
             to stderr.  */
          dump (eg.get_ext_state ());
        }
-      else if (is_named_call_p (call, "__analyzer_dump_path", 0))
+      else if (is_special_named_call_p (call, "__analyzer_dump_path", 0))
        {
          /* Handle the builtin "__analyzer_dump_path" by queuing a
             diagnostic at this exploded_node.  */
          ctxt.warn (new dump_path_diagnostic ());
        }
-      else if (is_named_call_p (call, "__analyzer_dump_region_model", 0))
+      else if (is_special_named_call_p (call, "__analyzer_dump_region_model", 
0))
        {
          /* Handle the builtin "__analyzer_dump_region_model" by dumping
             the region model's state to stderr.  */
          state->m_region_model->dump (false);
        }
-      else if (is_named_call_p (call, "__analyzer_eval", 1))
+      else if (is_special_named_call_p (call, "__analyzer_eval", 1))
        {
          /* Handle the builtin "__analyzer_eval" by evaluating the input
             and dumping as a dummy warning, so that test cases can use
@@ -913,7 +922,7 @@ exploded_node::on_stmt (exploded_graph &eg,
                                                     &ctxt);
          warning_at (call->location, 0, "%s", t.as_string ());
        }
-      else if (is_named_call_p (call, "__analyzer_break", 0))
+      else if (is_special_named_call_p (call, "__analyzer_break", 0))
        {
          /* Handle the builtin "__analyzer_break" by triggering a
             breakpoint.  */
@@ -2151,7 +2160,7 @@ stmt_requires_new_enode_p (const gimple *stmt,
      "__analyzer_dump_exploded_nodes", so they always appear at the
      start of an exploded_node.  */
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    if (is_named_call_p (call, "__analyzer_dump_exploded_nodes",
+    if (is_special_named_call_p (call, "__analyzer_dump_exploded_nodes",
                         1))
       return true;
 
@@ -3065,7 +3074,8 @@ exploded_graph::dump_exploded_nodes () const
 
       if (const gimple *stmt = enode->get_stmt ())
        if (const gcall *call = dyn_cast <const gcall *> (stmt))
-         if (is_named_call_p (call, "__analyzer_dump_exploded_nodes", 1))
+         if (is_special_named_call_p (call, "__analyzer_dump_exploded_nodes",
+                                      1))
            {
              if (seen.contains (stmt))
                continue;
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 11804dc116b4..dccd4eec37c7 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -4125,69 +4125,75 @@ region_model::on_call_pre (const gcall *call, 
region_model_context *ctxt)
   /* Check for uses of poisoned values.
      For now, special-case "free", to avoid warning about "use-after-free"
      when "double free" would be more precise.  */
-  if (!is_named_call_p (call, "free", 1))
+  if (!is_special_named_call_p (call, "free", 1))
     for (unsigned i = 0; i < gimple_call_num_args (call); i++)
       check_for_poison (gimple_call_arg (call, i), ctxt);
 
-  if (is_named_call_p (call, "malloc", 1))
+  if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
     {
-      // TODO: capture size as a svalue?
-      region_id new_rid = add_new_malloc_region ();
-      if (!lhs_rid.null_p ())
+      if (is_named_call_p (callee_fndecl, "malloc", call, 1))
        {
-         svalue_id ptr_sid
-           = get_or_create_ptr_svalue (lhs_type, new_rid);
-         set_value (lhs_rid, ptr_sid, ctxt);
+         // TODO: capture size as a svalue?
+         region_id new_rid = add_new_malloc_region ();
+         if (!lhs_rid.null_p ())
+           {
+             svalue_id ptr_sid
+               = get_or_create_ptr_svalue (lhs_type, new_rid);
+             set_value (lhs_rid, ptr_sid, ctxt);
+           }
+         return;
        }
-      return;
-    }
-  else if (is_named_call_p (call, "__builtin_alloca", 1))
-    {
-      region_id frame_rid = get_current_frame_id ();
-      region_id new_rid = add_region (new symbolic_region (frame_rid, false));
-      if (!lhs_rid.null_p ())
+      else if (is_named_call_p (callee_fndecl, "__builtin_alloca", call, 1))
        {
-         svalue_id ptr_sid
-           = get_or_create_ptr_svalue (lhs_type, new_rid);
-         set_value (lhs_rid, ptr_sid, ctxt);
+         region_id frame_rid = get_current_frame_id ();
+         region_id new_rid
+           = add_region (new symbolic_region (frame_rid, false));
+         if (!lhs_rid.null_p ())
+           {
+             svalue_id ptr_sid
+               = get_or_create_ptr_svalue (lhs_type, new_rid);
+             set_value (lhs_rid, ptr_sid, ctxt);
+           }
+         return;
        }
-      return;
-    }
-  else if (is_named_call_p (call, "strlen", 1))
-    {
-      region_id buf_rid = deref_rvalue (gimple_call_arg (call, 0), ctxt);
-      svalue_id buf_sid = get_region (buf_rid)->get_value (*this, true, ctxt);
-      if (tree cst_expr = maybe_get_constant (buf_sid))
+      else if (is_named_call_p (callee_fndecl, "strlen", call, 1))
        {
-         if (TREE_CODE (cst_expr) == STRING_CST
-             && !lhs_rid.null_p ())
+         region_id buf_rid = deref_rvalue (gimple_call_arg (call, 0), ctxt);
+         svalue_id buf_sid
+           = get_region (buf_rid)->get_value (*this, true, ctxt);
+         if (tree cst_expr = maybe_get_constant (buf_sid))
            {
-             /* TREE_STRING_LENGTH is sizeof, not strlen.  */
-             int sizeof_cst = TREE_STRING_LENGTH (cst_expr);
-             int strlen_cst = sizeof_cst - 1;
-             tree t_cst = build_int_cst (lhs_type, strlen_cst);
-             svalue_id result_sid
-               = get_or_create_constant_svalue (t_cst);
-             set_value (lhs_rid, result_sid, ctxt);
-             return;
+             if (TREE_CODE (cst_expr) == STRING_CST
+                 && !lhs_rid.null_p ())
+               {
+                 /* TREE_STRING_LENGTH is sizeof, not strlen.  */
+                 int sizeof_cst = TREE_STRING_LENGTH (cst_expr);
+                 int strlen_cst = sizeof_cst - 1;
+                 tree t_cst = build_int_cst (lhs_type, strlen_cst);
+                 svalue_id result_sid
+                   = get_or_create_constant_svalue (t_cst);
+                 set_value (lhs_rid, result_sid, ctxt);
+                 return;
+               }
            }
+         /* Otherwise an unknown value.  */
+       }
+      else if (is_named_call_p (callee_fndecl,
+                               "__analyzer_dump_num_heap_regions", call, 0))
+       {
+         /* Handle the builtin "__analyzer_dump_num_heap_regions" by emitting
+            a warning (for use in DejaGnu tests).  */
+         int num_heap_regions = 0;
+         region_id heap_rid = get_root_region ()->ensure_heap_region (this);
+         unsigned i;
+         region *region;
+         FOR_EACH_VEC_ELT (m_regions, i, region)
+           if (region->get_parent () == heap_rid)
+             num_heap_regions++;
+         /* Use quotes to ensure the output isn't truncated.  */
+         warning_at (call->location, 0,
+                     "num heap regions: %qi", num_heap_regions);
        }
-      /* Otherwise an unknown value.  */
-    }
-  else if (is_named_call_p (call, "__analyzer_dump_num_heap_regions", 0))
-    {
-      /* Handle the builtin "__analyzer_dump_num_heap_regions" by emitting
-        a warning (for use in DejaGnu tests).  */
-      int num_heap_regions = 0;
-      region_id heap_rid = get_root_region ()->ensure_heap_region (this);
-      unsigned i;
-      region *region;
-      FOR_EACH_VEC_ELT (m_regions, i, region)
-       if (region->get_parent () == heap_rid)
-         num_heap_regions++;
-      /* Use quotes to ensure the output isn't truncated.  */
-      warning_at (call->location, 0,
-                 "num heap regions: %qi", num_heap_regions);
     }
 
   /* Unrecognized call.  */
@@ -4225,32 +4231,33 @@ region_model::on_call_post (const gcall *call, 
region_model_context *ctxt)
      (in region_model::eval_condition_without_cm), and thus transition
      all pointers to the region to the "freed" state together, regardless
      of casts.  */
-  if (is_named_call_p (call, "free", 1))
-    {
-      tree ptr = gimple_call_arg (call, 0);
-      svalue_id ptr_sid = get_rvalue (ptr, ctxt);
-      svalue *ptr_sval = get_svalue (ptr_sid);
-      if (region_svalue *ptr_to_region_sval
-         = ptr_sval->dyn_cast_region_svalue ())
-       {
-         /* If the ptr points to an underlying heap region, delete it,
-            poisoning pointers.  */
-         region_id pointee_rid = ptr_to_region_sval->get_pointee ();
-         region_id heap_rid = get_root_region ()->ensure_heap_region (this);
-         if (!pointee_rid.null_p ()
-             && get_region (pointee_rid)->get_parent () == heap_rid)
-           {
-             purge_stats stats;
-             delete_region_and_descendents (pointee_rid,
-                                            POISON_KIND_FREED,
-                                            &stats, ctxt->get_logger ());
-             purge_unused_svalues (&stats, ctxt);
-             validate ();
-             // TODO: do anything with stats?
-           }
-       }
-      return;
-    }
+  if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
+    if (is_named_call_p (callee_fndecl, "free", call, 1))
+      {
+       tree ptr = gimple_call_arg (call, 0);
+       svalue_id ptr_sid = get_rvalue (ptr, ctxt);
+       svalue *ptr_sval = get_svalue (ptr_sid);
+       if (region_svalue *ptr_to_region_sval
+           = ptr_sval->dyn_cast_region_svalue ())
+         {
+           /* If the ptr points to an underlying heap region, delete it,
+              poisoning pointers.  */
+           region_id pointee_rid = ptr_to_region_sval->get_pointee ();
+           region_id heap_rid = get_root_region ()->ensure_heap_region (this);
+           if (!pointee_rid.null_p ()
+               && get_region (pointee_rid)->get_parent () == heap_rid)
+             {
+               purge_stats stats;
+               delete_region_and_descendents (pointee_rid,
+                                              POISON_KIND_FREED,
+                                              &stats, ctxt->get_logger ());
+               purge_unused_svalues (&stats, ctxt);
+               validate ();
+               // TODO: do anything with stats?
+             }
+         }
+       return;
+      }
 }
 
 /* Update this model for the RETURN_STMT, using CTXT to report any
@@ -6356,6 +6363,32 @@ region_model::get_or_create_view (region_id raw_rid, 
tree type)
   return raw_rid;
 }
 
+/* Attempt to get the fndecl used at CALL, if known, or NULL_TREE
+   otherwise.  */
+
+tree
+region_model::get_fndecl_for_call (const gcall *call,
+                                  region_model_context *ctxt)
+{
+  tree fn_ptr = gimple_call_fn (call);
+  if (fn_ptr == NULL_TREE)
+    return NULL_TREE;
+  svalue_id fn_ptr_sid = get_rvalue (fn_ptr, ctxt);
+  svalue *fn_ptr_sval = get_svalue (fn_ptr_sid);
+  if (region_svalue *fn_ptr_ptr = fn_ptr_sval->dyn_cast_region_svalue ())
+    {
+      region_id fn_rid = fn_ptr_ptr->get_pointee ();
+      code_region *code = get_root_region ()->get_code_region (this);
+      if (code)
+       {
+         tree fn_decl = code->get_tree_for_child_region (fn_rid);
+         return fn_decl;
+       }
+    }
+
+  return NULL_TREE;
+}
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* struct model_merger.  */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 0b8d366a2e51..5cb3a9a15ab3 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -1749,6 +1749,9 @@ class region_model
                                             region_model_context *ctxt);
   region_id get_or_create_view (region_id raw_rid, tree type);
 
+  tree get_fndecl_for_call (const gcall *call,
+                           region_model_context *ctxt);
+
  private:
   region_id get_lvalue_1 (path_var pv, region_model_context *ctxt);
   svalue_id get_rvalue_1 (path_var pv, region_model_context *ctxt);
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index f2e8030a49b1..a96971464af7 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -217,44 +217,46 @@ fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
                                const gimple *stmt) const
 {
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-      if (is_named_call_p (call, "fopen", 2))
-       {
-         tree lhs = gimple_call_lhs (call);
-         if (lhs)
-           {
-             lhs = sm_ctxt->get_readable_tree (lhs);
-             sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
-           }
-         else
-           {
-             /* TODO: report leak.  */
-           }
-         return true;
-       }
-
-      if (is_named_call_p (call, "fclose", 1))
-       {
-         tree arg = gimple_call_arg (call, 0);
-         arg = sm_ctxt->get_readable_tree (arg);
-
-         sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
-
-         // TODO: is it safe to call fclose (NULL) ?
-         sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
-         sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
-
-         sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
-
-         sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
-                                  new double_fclose (*this, arg));
-         sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
-         return true;
-       }
-
-      // TODO: operations on closed file
-      // etc
-    }
+    if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
+      {
+       if (is_named_call_p (callee_fndecl, "fopen", call, 2))
+         {
+           tree lhs = gimple_call_lhs (call);
+           if (lhs)
+             {
+               lhs = sm_ctxt->get_readable_tree (lhs);
+               sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+             }
+           else
+             {
+               /* TODO: report leak.  */
+             }
+           return true;
+         }
+
+       if (is_named_call_p (callee_fndecl, "fclose", call, 1))
+         {
+           tree arg = gimple_call_arg (call, 0);
+           arg = sm_ctxt->get_readable_tree (arg);
+
+           sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
+
+           // TODO: is it safe to call fclose (NULL) ?
+           sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
+           sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
+
+           sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
+
+           sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
+                                    new double_fclose (*this, arg));
+           sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
+           return true;
+         }
+
+       // TODO: operations on closed file
+       // etc
+      }
+
   return false;
 }
 
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index d3d91474276c..4253d2b42535 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -532,8 +532,8 @@ public:
        gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr);
        if (gcall *call = dyn_cast <gcall *> (def_stmt))
          {
-           if (is_named_call_p (call, "alloca", 1)
-               || is_named_call_p (call, "__builtin_alloca", 1))
+           if (is_special_named_call_p (call, "alloca", 1)
+               || is_special_named_call_p (call, "__builtin_alloca", 1))
              {
                m_kind = KIND_ALLOCA;
                return label_text::borrow
@@ -582,63 +582,63 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
                               const gimple *stmt) const
 {
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-      if (is_named_call_p (call, "malloc", 1)
-         || is_named_call_p (call, "calloc", 2))
-       {
-         tree lhs = gimple_call_lhs (call);
-         if (lhs)
-           {
-             lhs = sm_ctxt->get_readable_tree (lhs);
-             sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
-           }
-         else
-           {
-             /* TODO: report leak.  */
-           }
-         return true;
-       }
+    if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
+      {
+       if (is_named_call_p (callee_fndecl, "malloc", call, 1)
+           || is_named_call_p (callee_fndecl, "calloc", call, 2))
+         {
+           tree lhs = gimple_call_lhs (call);
+           if (lhs)
+             {
+               lhs = sm_ctxt->get_readable_tree (lhs);
+               sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+             }
+           else
+             {
+               /* TODO: report leak.  */
+             }
+           return true;
+         }
 
-      if (is_named_call_p (call, "alloca", 1)
-         || is_named_call_p (call, "__builtin_alloca", 1))
-       {
-         tree lhs = gimple_call_lhs (call);
-         if (lhs)
-           {
-             lhs = sm_ctxt->get_readable_tree (lhs);
-             sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
-           }
-         return true;
-       }
+       if (is_named_call_p (callee_fndecl, "alloca", call, 1)
+           || is_named_call_p (callee_fndecl, "__builtin_alloca", call, 1))
+         {
+           tree lhs = gimple_call_lhs (call);
+           if (lhs)
+             {
+               lhs = sm_ctxt->get_readable_tree (lhs);
+               sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+             }
+           return true;
+         }
 
-      if (is_named_call_p (call, "free", 1))
-       {
-         tree arg = gimple_call_arg (call, 0);
+       if (is_named_call_p (callee_fndecl, "free", call, 1))
+         {
+           tree arg = gimple_call_arg (call, 0);
 
-         arg = sm_ctxt->get_readable_tree (arg);
+           arg = sm_ctxt->get_readable_tree (arg);
 
-         /* start/unchecked/nonnull -> freed.  */
-         sm_ctxt->on_transition (node, stmt, arg, m_start, m_freed);
-         sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_freed);
-         sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_freed);
+           /* start/unchecked/nonnull -> freed.  */
+           sm_ctxt->on_transition (node, stmt, arg, m_start, m_freed);
+           sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_freed);
+           sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_freed);
 
-         /* Keep state "null" as-is, rather than transitioning to "free";
-            we don't want want to complain about double-free of NULL.  */
+           /* Keep state "null" as-is, rather than transitioning to "free";
+              we don't want want to complain about double-free of NULL.  */
 
-         /* freed -> stop, with warning.  */
-         sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
-                                  new double_free (*this, arg));
-         sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
+           /* freed -> stop, with warning.  */
+           sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
+                                    new double_free (*this, arg));
+           sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
 
-         /* non-heap -> stop, with warning.  */
-         sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
-                                  new free_of_non_heap (*this, arg));
-         sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
-         return true;
-       }
+           /* non-heap -> stop, with warning.  */
+           sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
+                                    new free_of_non_heap (*this, arg));
+           sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
+           return true;
+         }
 
-      /* Handle "__attribute__((nonnull))".   */
-      if (tree callee_fndecl = gimple_call_fndecl (stmt))
+       /* Handle "__attribute__((nonnull))".   */
        {
          tree fntype = TREE_TYPE (callee_fndecl);
          bitmap nonnull_args = get_nonnull_args (fntype);
@@ -669,7 +669,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
              BITMAP_FREE (nonnull_args);
            }
        }
-    }
+      }
 
   if (tree lhs = is_zero_assignment (stmt))
     {
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
index 414dd56948d5..08b1b32b7933 100644
--- a/gcc/analyzer/sm-sensitive.cc
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -135,33 +135,34 @@ sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
                                  const gimple *stmt) const
 {
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-      if (is_named_call_p (call, "getpass", 1))
-       {
-         tree lhs = gimple_call_lhs (call);
-         if (lhs)
-           sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
-         return true;
-       }
-      else if (is_named_call_p (call, "fprintf")
-              || is_named_call_p (call, "printf"))
-       {
-         /* Handle a match at any position in varargs.  */
-         for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
-           {
-             tree arg = gimple_call_arg (call, idx);
-             warn_for_any_exposure (sm_ctxt, node, stmt, arg);
-           }
-         return true;
-       }
-      else if (is_named_call_p (call, "fwrite", 4))
-       {
-         tree arg = gimple_call_arg (call, 0);
-         warn_for_any_exposure (sm_ctxt, node, stmt, arg);
-         return true;
-       }
-      // TODO: ...etc.  This is just a proof-of-concept at this point.
-    }
+    if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
+      {
+       if (is_named_call_p (callee_fndecl, "getpass", call, 1))
+         {
+           tree lhs = gimple_call_lhs (call);
+           if (lhs)
+             sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
+           return true;
+         }
+       else if (is_named_call_p (callee_fndecl, "fprintf")
+                || is_named_call_p (callee_fndecl, "printf"))
+         {
+           /* Handle a match at any position in varargs.  */
+           for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
+             {
+               tree arg = gimple_call_arg (call, idx);
+               warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+             }
+           return true;
+         }
+       else if (is_named_call_p (callee_fndecl, "fwrite", call, 4))
+         {
+           tree arg = gimple_call_arg (call, 0);
+           warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+           return true;
+         }
+       // TODO: ...etc.  This is just a proof-of-concept at this point.
+      }
   return false;
 }
 
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index 59b9171a58e1..1c110119b2d6 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -198,22 +198,23 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
                               const gimple *stmt) const
 {
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-      if (is_named_call_p (call, "fread", 4))
-       {
-         tree arg = gimple_call_arg (call, 0);
-         arg = sm_ctxt->get_readable_tree (arg);
-
-         sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted);
-
-         /* Dereference an ADDR_EXPR.  */
-         // TODO: should the engine do this?
-         if (TREE_CODE (arg) == ADDR_EXPR)
-           sm_ctxt->on_transition (node, stmt, TREE_OPERAND (arg, 0),
-                                   m_start, m_tainted);
-         return true;
-       }
-    }
+    if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
+      {
+       if (is_named_call_p (callee_fndecl, "fread", call, 4))
+         {
+           tree arg = gimple_call_arg (call, 0);
+           arg = sm_ctxt->get_readable_tree (arg);
+
+           sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted);
+
+           /* Dereference an ADDR_EXPR.  */
+           // TODO: should the engine do this?
+           if (TREE_CODE (arg) == ADDR_EXPR)
+             sm_ctxt->on_transition (node, stmt, TREE_OPERAND (arg, 0),
+                                     m_start, m_tainted);
+           return true;
+         }
+      }
   // TODO: ...etc; many other sources of untrusted data
 
   if (const gassign *assign = dyn_cast <const gassign *> (stmt))
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index b35d8c535c2a..fe67c6b4b979 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -112,6 +112,12 @@ class sm_context
 public:
   virtual ~sm_context () {}
 
+  /* Get the fndecl used at call, or NULL_TREE.
+     Use in preference to gimple_call_fndecl (and gimple_call_addr_fndecl),
+     since it can look through function pointer assignments and
+     other callback handling.  */
+  virtual tree get_fndecl_for_call (const gcall *call) = 0;
+
   /* Called by state_machine in response to pattern matches:
      if VAR is in state FROM, transition it to state TO, potentially
      recording the "origin" of the state as ORIGIN.
diff --git a/gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c 
b/gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
index b3673fb7e756..8c27b3ae7a45 100644
--- a/gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
+++ b/gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
@@ -55,3 +55,27 @@ void test_4 (void *q, void *r)
 
   free(p);
 }
+
+/* Verify that we detect passing NULL to a __attribute__((nonnull)) function
+   when it's called via a function pointer.  */
+
+typedef void (*bar_t)(void *ptrA, void *ptrB, void *ptrC);
+
+static bar_t __attribute__((noinline))
+get_bar (void)
+{
+  return bar;
+}
+
+void test_5 (void *q, void *r)
+{
+  void *p = malloc(1024); /* { dg-message "\\(1\\) this call could return 
NULL" } */
+  bar_t cb = get_bar ();
+  cb(p, q, r); /* { dg-warning "use of possibly-NULL 'p' where non-null 
expected" } */
+  /* { dg-message "argument 1 \\('p'\\) from \\(1\\) could be NULL where 
non-null expected" "" { target *-*-* } .-1 } */
+  /* TODO: do we want an event showing where cb is assigned "bar"?  */
+
+  cb(p, q, r);
+
+  free(p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-callbacks.c 
b/gcc/testsuite/gcc.dg/analyzer/malloc-callbacks.c
new file mode 100644
index 000000000000..eb5545e5da00
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-callbacks.c
@@ -0,0 +1,84 @@
+#include <stdlib.h>
+
+typedef void *(*allocator_t) (size_t);
+typedef void (*deallocator_t) (void *);
+
+static allocator_t __attribute__((noinline))
+get_malloc (void)
+{
+  return malloc;
+}
+
+static allocator_t __attribute__((noinline))
+get_alloca (void)
+{
+  return alloca;
+}
+
+static deallocator_t __attribute__((noinline))
+get_free (void)
+{
+  return free;
+}
+
+void test_1 (void *ptr)
+{
+  deallocator_t dealloc_fn = free;
+  dealloc_fn (ptr); /* { dg-message "first 'free' here" } */
+  dealloc_fn (ptr); /* { dg-warning "double-'free'" } */
+}
+
+void test_2 (void *ptr)
+{
+  deallocator_t dealloc_fn = get_free ();
+  dealloc_fn (ptr); /* { dg-message "first 'free' here" } */
+  dealloc_fn (ptr); /* { dg-warning "double-'free'" } */
+}
+
+static void __attribute__((noinline))
+called_by_test_3 (void *ptr, deallocator_t dealloc_fn)
+{
+  dealloc_fn (ptr); /* { dg-warning "double-'free'" } */
+}
+
+void test_3 (void *ptr)
+{
+  called_by_test_3 (ptr, free);
+  called_by_test_3 (ptr, free);
+}
+
+int *test_4 (void)
+{
+  allocator_t alloc_fn = get_malloc ();
+  int *ptr = alloc_fn (sizeof (int)); /* { dg-message "this call could return 
NULL" } */
+  *ptr = 42; /* { dg-warning "dereference of possibly-NULL 'ptr'" } */
+  return ptr;
+}
+
+int *test_5 (void)
+{
+  allocator_t alloc_fn = get_alloca ();
+  deallocator_t dealloc_fn = get_free ();
+  int *ptr = alloc_fn (sizeof (int)); /* { dg-message "pointer is from here" } 
*/
+  /* TODO: message should read "memory is allocated on the stack here".  */
+  dealloc_fn (ptr); /* { dg-warning "'free' of 'ptr' which points to memory 
not on the heap" } */
+}
+
+static void __attribute__((noinline))
+called_by_test_6a (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-'free'" "" { xfail *-*-* } } */
+}
+
+static deallocator_t __attribute__((noinline))
+called_by_test_6b (void)
+{
+  return called_by_test_6a;
+}
+
+void test_6 (void *ptr)
+{
+  deallocator_t dealloc_fn = called_by_test_6b ();
+  dealloc_fn (ptr);
+  dealloc_fn (ptr);
+}
-- 
2.21.0

Reply via email to