]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/analyzer/sm-malloc.cc
Update copyright years.
[thirdparty/gcc.git] / gcc / analyzer / sm-malloc.cc
index e1fea02486fa7cc47a43ce96c8589d4d01bc4382..8da2e7c17edd4a65345a2511456babba6f1a17ea 100644 (file)
@@ -1,5 +1,5 @@
 /* A state machine for detecting misuses of the malloc/free API.
-   Copyright (C) 2019-2021 Free Software Foundation, Inc.
+   Copyright (C) 2019-2024 Free Software Foundation, Inc.
    Contributed by David Malcolm <dmalcolm@redhat.com>.
 
 This file is part of GCC.
@@ -19,8 +19,10 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
+#define INCLUDE_MEMORY
 #include "system.h"
 #include "coretypes.h"
+#include "make-unique.h"
 #include "tree.h"
 #include "function.h"
 #include "basic-block.h"
@@ -28,20 +30,22 @@ along with GCC; see the file COPYING3.  If not see
 #include "options.h"
 #include "bitmap.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
-#include "function.h"
-#include "json.h"
 #include "analyzer/analyzer.h"
 #include "diagnostic-event-id.h"
 #include "analyzer/analyzer-logging.h"
 #include "analyzer/sm.h"
 #include "analyzer/pending-diagnostic.h"
-#include "tristate.h"
-#include "selftest.h"
 #include "analyzer/call-string.h"
 #include "analyzer/program-point.h"
 #include "analyzer/store.h"
 #include "analyzer/region-model.h"
+#include "analyzer/call-details.h"
+#include "stringpool.h"
+#include "attribs.h"
+#include "analyzer/function-set.h"
+#include "analyzer/program-state.h"
+#include "analyzer/checker-event.h"
+#include "analyzer/exploded-graph.h"
 
 #if ENABLE_ANALYZER
 
@@ -49,18 +53,45 @@ namespace ana {
 
 namespace {
 
-class api;
+/* This state machine and its various support classes track allocations
+   and deallocations.
+
+   It has a few standard allocation/deallocation pairs (e.g. new/delete),
+   and also supports user-defined ones via
+   __attribute__ ((malloc(DEALLOCATOR))).
+
+   There can be more than one valid deallocator for a given allocator,
+   for example:
+     __attribute__ ((malloc (fclose)))
+     __attribute__ ((malloc (freopen, 3)))
+     FILE* fopen (const char*, const char*);
+   A deallocator_set represents a particular set of valid deallocators.
+
+   We track the expected deallocator_set for a value, but not the allocation
+   function - there could be more than one allocator per deallocator_set.
+   For example, there could be dozens of allocators for "free" beyond just
+   malloc e.g. calloc, xstrdup, etc.  We don't want to explode the number
+   of states by tracking individual allocators in the exploded graph;
+   we merely want to track "this value expects to have 'free' called on it".
+   Perhaps we can reconstruct which allocator was used later, when emitting
+   the path, if it's necessary for precision of wording of diagnostics.  */
+
+class deallocator;
+class deallocator_set;
 class malloc_state_machine;
 
 /* An enum for discriminating between different kinds of allocation_state.  */
 
 enum resource_state
 {
-  /* States that are independent of api.  */
+  /* States that are independent of allocator/deallocator.  */
 
   /* The start state.  */
   RS_START,
 
+  /* State for a pointer that's been unconditionally dereferenced.  */
+  RS_ASSUMED_NON_NULL,
+
   /* State for a pointer that's known to be NULL.  */
   RS_NULL,
 
@@ -71,36 +102,61 @@ enum resource_state
   /* Stop state, for pointers we don't want to track any more.  */
   RS_STOP,
 
-  /* States that relate to a specific api.  */
+  /* States that relate to a specific deallocator_set.  */
 
-  /* State for a pointer returned from the api's allocator that hasn't
+  /* State for a pointer returned from an allocator that hasn't
      been checked for NULL.
      It could be a pointer to heap-allocated memory, or could be NULL.  */
   RS_UNCHECKED,
 
-  /* State for a pointer returned from the api's allocator,
+  /* State for a pointer returned from an allocator,
      known to be non-NULL.  */
   RS_NONNULL,
 
-  /* State for a pointer passed to the api's deallocator.  */
+  /* State for a pointer passed to a deallocator.  */
   RS_FREED
 };
 
-/* Custom state subclass, which can optionally refer to an an api.  */
+/* Custom state subclass, which can optionally refer to an a
+   deallocator_set.  */
 
 struct allocation_state : public state_machine::state
 {
   allocation_state (const char *name, unsigned id,
-                   enum resource_state rs, const api *a)
-  : state (name, id), m_rs (rs), m_api (a)
+                   enum resource_state rs,
+                   const deallocator_set *deallocators,
+                   const deallocator *deallocator)
+  : state (name, id), m_rs (rs),
+    m_deallocators (deallocators),
+    m_deallocator (deallocator)
   {}
 
-  void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
+  void dump_to_pp (pretty_printer *pp) const override;
 
   const allocation_state *get_nonnull () const;
 
   enum resource_state m_rs;
-  const api *m_api;
+  const deallocator_set *m_deallocators;
+  const deallocator *m_deallocator;
+};
+
+/* Custom state subclass, for the "assumed-non-null" state
+   where the assumption happens in a particular frame.  */
+
+struct assumed_non_null_state : public allocation_state
+{
+  assumed_non_null_state (const char *name, unsigned id,
+                         const frame_region *frame)
+  : allocation_state (name, id, RS_ASSUMED_NON_NULL,
+                     NULL, NULL),
+    m_frame (frame)
+  {
+    gcc_assert (m_frame);
+  }
+
+  void dump_to_pp (pretty_printer *pp) const final override;
+
+  const frame_region *m_frame;
 };
 
 /* An enum for choosing which wording to use in various diagnostics
@@ -109,52 +165,187 @@ struct allocation_state : public state_machine::state
 enum wording
 {
   WORDING_FREED,
-  WORDING_DELETED
+  WORDING_DELETED,
+  WORDING_DEALLOCATED,
+  WORDING_REALLOCATED
 };
 
-/* Represents a particular family of API calls for allocating/deallocating
-   heap memory that should be matched e.g.
-   - malloc/free
-   - scalar new/delete
-   - vector new[]/delete[]
-   etc.
+/* Base class representing a deallocation function,
+   either a built-in one we know about, or one exposed via
+   __attribute__((malloc(DEALLOCATOR))).  */
 
-   We track the expected deallocation function, but not the allocation
-   function - there could be more than one allocator per deallocator.  For
-   example, there could be dozens of allocators for "free" beyond just
-   malloc e.g. calloc, xstrdup, etc.  We don't want to explode the number
-   of states by tracking individual allocators in the exploded graph;
-   we merely want to track "this value expects to have 'free' called on it".
-   Perhaps we can reconstruct which allocator was used later, when emitting
-   the path, if it's necessary for precision of wording of diagnostics.  */
-
-struct api
+struct deallocator
 {
-  api (malloc_state_machine *sm,
-       const char *name,
-       const char *dealloc_funcname,
-       enum wording wording);
+  hashval_t hash () const;
+  void dump_to_pp (pretty_printer *pp) const;
+  static int cmp (const deallocator *a, const deallocator *b);
+  static int cmp_ptr_ptr (const void *, const void *);
 
-  /* An internal name for identifying this API in dumps.  */
+  /* Name to use in diagnostics.  */
   const char *m_name;
 
-  /* The name of the deallocation function, for use in diagnostics.  */
-  const char *m_dealloc_funcname;
+  /* Which wording to use in diagnostics.  */
+  enum wording m_wording;
+
+  /* State for a value passed to one of the deallocators.  */
+  state_machine::state_t m_freed;
+
+protected:
+  deallocator (malloc_state_machine *sm,
+              const char *name,
+              enum wording wording);
+};
+
+/* Subclass representing a predefined deallocator.
+   e.g. "delete []", without needing a specific FUNCTION_DECL
+   ahead of time.  */
+
+struct standard_deallocator : public deallocator
+{
+  standard_deallocator (malloc_state_machine *sm,
+                       const char *name,
+                       enum wording wording);
+};
+
+/* Subclass representing a user-defined deallocator
+   via __attribute__((malloc(DEALLOCATOR))) given
+   a specific FUNCTION_DECL.  */
+
+struct custom_deallocator : public deallocator
+{
+  custom_deallocator (malloc_state_machine *sm,
+                     tree deallocator_fndecl,
+                     enum wording wording)
+  : deallocator (sm, IDENTIFIER_POINTER (DECL_NAME (deallocator_fndecl)),
+                wording)
+  {
+  }
+};
+
+/* Base class representing a set of possible deallocators.
+   Often this will be just a single deallocator, but some
+   allocators have multiple valid deallocators (e.g. the result of
+   "fopen" can be closed by either "fclose" or "freopen").  */
+
+struct deallocator_set
+{
+  deallocator_set (malloc_state_machine *sm,
+                  enum wording wording);
+  virtual ~deallocator_set () {}
+
+  virtual bool contains_p (const deallocator *d) const = 0;
+  virtual const deallocator *maybe_get_single () const = 0;
+  virtual void dump_to_pp (pretty_printer *pp) const = 0;
+  void dump () const;
 
   /* Which wording to use in diagnostics.  */
   enum wording m_wording;
 
-  /* Pointers to api-specific states.
+  /* Pointers to states.
      These states are owned by the state_machine base class.  */
 
-  /* State for an unchecked result from this api's allocator.  */
+  /* State for an unchecked result from an allocator using this set.  */
   state_machine::state_t m_unchecked;
 
-  /* State for a known non-NULL result from this apis's allocator.  */
+  /* State for a known non-NULL result from such an allocator.  */
   state_machine::state_t m_nonnull;
+};
 
-  /* State for a value passed to this api's deallocator.  */
-  state_machine::state_t m_freed;
+/* Subclass of deallocator_set representing a set of deallocators
+   defined by one or more __attribute__((malloc(DEALLOCATOR))).  */
+
+struct custom_deallocator_set : public deallocator_set
+{
+  typedef const auto_vec <const deallocator *> *key_t;
+
+  custom_deallocator_set (malloc_state_machine *sm,
+                         const auto_vec <const deallocator *> *vec,
+                         //const char *name,
+                         //const char *dealloc_funcname,
+                         //unsigned arg_idx,
+                         enum wording wording);
+
+  bool contains_p (const deallocator *d) const final override;
+  const deallocator *maybe_get_single () const final override;
+  void dump_to_pp (pretty_printer *pp) const final override;
+
+  auto_vec <const deallocator *> m_deallocator_vec;
+};
+
+/* Subclass of deallocator_set representing a set of deallocators
+   with a single standard_deallocator, e.g. "delete []".  */
+
+struct standard_deallocator_set : public deallocator_set
+{
+  standard_deallocator_set (malloc_state_machine *sm,
+                           const char *name,
+                           enum wording wording);
+
+  bool contains_p (const deallocator *d) const final override;
+  const deallocator *maybe_get_single () const final override;
+  void dump_to_pp (pretty_printer *pp) const final override;
+
+  standard_deallocator m_deallocator;
+};
+
+/* Traits class for ensuring uniqueness of deallocator_sets within
+   malloc_state_machine.  */
+
+struct deallocator_set_map_traits
+{
+  typedef custom_deallocator_set::key_t key_type;
+  typedef custom_deallocator_set *value_type;
+  typedef custom_deallocator_set *compare_type;
+
+  static inline hashval_t hash (const key_type &k)
+  {
+    gcc_assert (k != NULL);
+    gcc_assert (k != reinterpret_cast<key_type> (1));
+
+    hashval_t result = 0;
+    unsigned i;
+    const deallocator *d;
+    FOR_EACH_VEC_ELT (*k, i, d)
+      result ^= d->hash ();
+    return result;
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    if (k1->length () != k2->length ())
+      return false;
+
+    for (unsigned i = 0; i < k1->length (); i++)
+      if ((*k1)[i] != (*k2)[i])
+       return false;
+
+    return true;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    /* empty; the nodes are handled elsewhere.  */
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+  static const bool empty_zero_p = false;
 };
 
 /* A state machine for detecting misuses of the malloc/free API.
@@ -167,14 +358,17 @@ public:
   typedef allocation_state custom_data_t;
 
   malloc_state_machine (logger *logger);
+  ~malloc_state_machine ();
 
   state_t
-  add_state (const char *name, enum resource_state rs, const api *a);
+  add_state (const char *name, enum resource_state rs,
+            const deallocator_set *deallocators,
+            const deallocator *deallocator);
 
-  bool inherited_state_p () const FINAL OVERRIDE { return false; }
+  bool inherited_state_p () const final override { return false; }
 
   state_machine::state_t
-  get_default_state (const svalue *sval) const FINAL OVERRIDE
+  get_default_state (const svalue *sval) const final override
   {
     if (tree cst = sval->maybe_get_constant ())
       {
@@ -184,42 +378,78 @@ public:
     if (const region_svalue *ptr = sval->dyn_cast_region_svalue ())
       {
        const region *reg = ptr->get_pointee ();
-       const region *base_reg = reg->get_base_region ();
-       if (base_reg->get_kind () == RK_DECL
-           || base_reg->get_kind () == RK_STRING)
-         return m_non_heap;
+       switch (reg->get_memory_space ())
+         {
+         default:
+           break;
+         case MEMSPACE_CODE:
+         case MEMSPACE_GLOBALS:
+         case MEMSPACE_STACK:
+         case MEMSPACE_READONLY_DATA:
+           return m_non_heap;
+         }
       }
     return m_start;
   }
 
   bool on_stmt (sm_context *sm_ctxt,
                const supernode *node,
-               const gimple *stmt) const FINAL OVERRIDE;
+               const gimple *stmt) const final override;
 
   void on_phi (sm_context *sm_ctxt,
               const supernode *node,
               const gphi *phi,
-              tree rhs) const FINAL OVERRIDE;
+              tree rhs) const final override;
 
   void on_condition (sm_context *sm_ctxt,
                     const supernode *node,
                     const gimple *stmt,
-                    tree lhs,
+                    const svalue *lhs,
                     enum tree_code op,
-                    tree rhs) const FINAL OVERRIDE;
+                    const svalue *rhs) const final override;
 
-  bool can_purge_p (state_t s) const FINAL OVERRIDE;
-  pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
+  void on_pop_frame (sm_state_map *smap,
+                    const frame_region *) const final override;
+
+  bool can_purge_p (state_t s) const final override;
+  std::unique_ptr<pending_diagnostic> on_leak (tree var) const final override;
 
   bool reset_when_passed_to_unknown_fn_p (state_t s,
-                                         bool is_mutable) const FINAL OVERRIDE;
+                                         bool is_mutable) const final override;
+
+  state_t
+  maybe_get_merged_states_nonequal (state_t state_a,
+                                   state_t state_b) const final override;
+
+  static bool unaffected_by_call_p (tree fndecl);
+
+  void maybe_assume_non_null (sm_context *sm_ctxt,
+                             tree ptr,
+                             const gimple *stmt) const;
+
+  void on_realloc_with_move (region_model *model,
+                            sm_state_map *smap,
+                            const svalue *old_ptr_sval,
+                            const svalue *new_ptr_sval,
+                            const extrinsic_state &ext_state) const;
+
+  void transition_ptr_sval_non_null (region_model *model,
+      sm_state_map *smap,
+      const svalue *new_ptr_sval,
+      const extrinsic_state &ext_state) const;
 
-  api m_malloc;
-  api m_scalar_new;
-  api m_vector_new;
+  standard_deallocator_set m_free;
+  standard_deallocator_set m_scalar_delete;
+  standard_deallocator_set m_vector_delete;
+
+  standard_deallocator m_realloc;
 
   /* States that are independent of api.  */
 
+  /* States for a pointer that's been unconditionally dereferenced
+     in a particular stack frame.  */
+  hash_map<const frame_region *, state_t> m_assumed_non_null;
+
   /* State for a pointer that's known to be NULL.  */
   state_t m_null;
 
@@ -232,31 +462,210 @@ public:
   state_t m_stop;
 
 private:
+  const custom_deallocator_set *
+  get_or_create_custom_deallocator_set (tree allocator_fndecl);
+  custom_deallocator_set *
+  maybe_create_custom_deallocator_set (tree allocator_fndecl);
+  const deallocator *
+  get_or_create_deallocator (tree deallocator_fndecl);
+
+  state_t
+  get_or_create_assumed_non_null_state_for_frame (const frame_region *frame);
+
+  void
+  maybe_complain_about_deref_before_check (sm_context *sm_ctxt,
+                                          const supernode *node,
+                                          const gimple *stmt,
+                                          const assumed_non_null_state *,
+                                          tree ptr) const;
+
   void on_allocator_call (sm_context *sm_ctxt,
                          const gcall *call,
-                         const api &ap) const;
+                         const deallocator_set *deallocators,
+                         bool returns_nonnull = false) const;
+  void handle_free_of_non_heap (sm_context *sm_ctxt,
+                               const supernode *node,
+                               const gcall *call,
+                               tree arg,
+                               const deallocator *d) const;
   void on_deallocator_call (sm_context *sm_ctxt,
                            const supernode *node,
                            const gcall *call,
-                           const api &ap) const;
+                           const deallocator *d,
+                           unsigned argno) const;
+  void on_realloc_call (sm_context *sm_ctxt,
+                       const supernode *node,
+                       const gcall *call) const;
   void on_zero_assignment (sm_context *sm_ctxt,
                           const gimple *stmt,
                           tree lhs) const;
+
+  /* A map for consolidating deallocators so that they are
+     unique per deallocator FUNCTION_DECL.  */
+  typedef hash_map<tree, deallocator *> deallocator_map_t;
+  deallocator_map_t m_deallocator_map;
+
+  /* Memoized lookups from FUNCTION_DECL to custom_deallocator_set *.  */
+  typedef hash_map<tree, custom_deallocator_set *> deallocator_set_cache_t;
+  deallocator_set_cache_t m_custom_deallocator_set_cache;
+
+  /* A map for consolidating custom_deallocator_set instances.  */
+  typedef hash_map<custom_deallocator_set::key_t,
+                  custom_deallocator_set *,
+                  deallocator_set_map_traits> custom_deallocator_set_map_t;
+  custom_deallocator_set_map_t m_custom_deallocator_set_map;
+
+  /* Record of dynamically-allocated objects,  for cleanup.  */
+  auto_vec <custom_deallocator_set *> m_dynamic_sets;
+  auto_vec <custom_deallocator *> m_dynamic_deallocators;
 };
 
-/* struct api.  */
+/* struct deallocator.  */
 
-api::api (malloc_state_machine *sm,
-         const char *name,
-         const char *dealloc_funcname,
-         enum wording wording)
+deallocator::deallocator (malloc_state_machine *sm,
+                         const char *name,
+                         enum wording wording)
 : m_name (name),
-  m_dealloc_funcname (dealloc_funcname),
   m_wording (wording),
-  m_unchecked (sm->add_state ("unchecked", RS_UNCHECKED, this)),
-  m_nonnull (sm->add_state ("nonnull", RS_NONNULL, this)),
-  m_freed (sm->add_state ("freed", RS_FREED, this))
+  m_freed (sm->add_state ("freed", RS_FREED, NULL, this))
+{
+}
+
+hashval_t
+deallocator::hash () const
+{
+  return (hashval_t)m_freed->get_id ();
+}
+
+void
+deallocator::dump_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "%qs", m_name);
+}
+
+int
+deallocator::cmp (const deallocator *a, const deallocator *b)
+{
+  return (int)a->m_freed->get_id () - (int)b->m_freed->get_id ();
+}
+
+int
+deallocator::cmp_ptr_ptr (const void *a, const void *b)
+{
+  return cmp (*(const deallocator * const *)a,
+             *(const deallocator * const *)b);
+}
+
+
+/* struct standard_deallocator : public deallocator.  */
+
+standard_deallocator::standard_deallocator (malloc_state_machine *sm,
+                                           const char *name,
+                                           enum wording wording)
+: deallocator (sm, name, wording)
+{
+}
+
+/* struct deallocator_set.  */
+
+deallocator_set::deallocator_set (malloc_state_machine *sm,
+                                 enum wording wording)
+: m_wording (wording),
+  m_unchecked (sm->add_state ("unchecked", RS_UNCHECKED, this, NULL)),
+  m_nonnull (sm->add_state ("nonnull", RS_NONNULL, this, NULL))
+{
+}
+
+/* Dump a description of this deallocator_set to stderr.  */
+
+DEBUG_FUNCTION void
+deallocator_set::dump () const
+{
+  pretty_printer pp;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp);
+  pp_newline (&pp);
+  pp_flush (&pp);
+}
+
+/* struct custom_deallocator_set : public deallocator_set.  */
+
+custom_deallocator_set::
+custom_deallocator_set (malloc_state_machine *sm,
+                       const auto_vec <const deallocator *> *vec,
+                       enum wording wording)
+: deallocator_set (sm, wording),
+  m_deallocator_vec (vec->length ())
 {
+  unsigned i;
+  const deallocator *d;
+  FOR_EACH_VEC_ELT (*vec, i, d)
+    m_deallocator_vec.safe_push (d);
+}
+
+bool
+custom_deallocator_set::contains_p (const deallocator *d) const
+{
+  unsigned i;
+  const deallocator *cd;
+  FOR_EACH_VEC_ELT (m_deallocator_vec, i, cd)
+    if (cd == d)
+      return true;
+  return false;
+}
+
+const deallocator *
+custom_deallocator_set::maybe_get_single () const
+{
+  if (m_deallocator_vec.length () == 1)
+    return m_deallocator_vec[0];
+  return NULL;
+}
+
+void
+custom_deallocator_set::dump_to_pp (pretty_printer *pp) const
+{
+  pp_character (pp, '{');
+  unsigned i;
+  const deallocator *d;
+  FOR_EACH_VEC_ELT (m_deallocator_vec, i, d)
+    {
+      if (i > 0)
+       pp_string (pp, ", ");
+      d->dump_to_pp (pp);
+    }
+  pp_character (pp, '}');
+}
+
+/* struct standard_deallocator_set : public deallocator_set.  */
+
+standard_deallocator_set::standard_deallocator_set (malloc_state_machine *sm,
+                                                   const char *name,
+                                                   enum wording wording)
+: deallocator_set (sm, wording),
+  m_deallocator (sm, name, wording)
+{
+}
+
+bool
+standard_deallocator_set::contains_p (const deallocator *d) const
+{
+  return d == &m_deallocator;
+}
+
+const deallocator *
+standard_deallocator_set::maybe_get_single () const
+{
+  return &m_deallocator;
+}
+
+void
+standard_deallocator_set::dump_to_pp (pretty_printer *pp) const
+{
+  pp_character (pp, '{');
+  pp_string (pp, m_deallocator.m_name);
+  pp_character (pp, '}');
 }
 
 /* Return STATE cast to the custom state subclass, or NULL for the start state.
@@ -291,6 +700,14 @@ get_rs (state_machine::state_t state)
     return RS_START;
 }
 
+/* Return true if STATE is the start state.  */
+
+static bool
+start_p (state_machine::state_t state)
+{
+  return get_rs (state) == RS_START;
+}
+
 /* Return true if STATE is an unchecked result from an allocator.  */
 
 static bool
@@ -315,6 +732,14 @@ freed_p (state_machine::state_t state)
   return get_rs (state) == RS_FREED;
 }
 
+/* Return true if STATE is a value that has been assumed to be non-NULL.  */
+
+static bool
+assumed_non_null_p (state_machine::state_t state)
+{
+  return get_rs (state) == RS_ASSUMED_NON_NULL;
+}
+
 /* Class for diagnostics relating to malloc_state_machine.  */
 
 class malloc_diagnostic : public pending_diagnostic
@@ -324,16 +749,16 @@ public:
   : m_sm (sm), m_arg (arg)
   {}
 
-  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+  bool subclass_equal_p (const pending_diagnostic &base_other) const override
   {
     return same_tree_p (m_arg, ((const malloc_diagnostic &)base_other).m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
-    OVERRIDE
+    override
   {
     if (change.m_old_state == m_sm.get_start_state ()
-       && unchecked_p (change.m_new_state))
+       && (unchecked_p (change.m_new_state) || nonnull_p (change.m_new_state)))
       // TODO: verify that it's the allocation stmt, not a copy
       return label_text::borrow ("allocated here");
     if (unchecked_p (change.m_old_state)
@@ -371,6 +796,20 @@ public:
     return label_text ();
   }
 
+  diagnostic_event::meaning
+  get_meaning_for_state_change (const evdesc::state_change &change)
+    const final override
+  {
+    if (change.m_old_state == m_sm.get_start_state ()
+       && unchecked_p (change.m_new_state))
+      return diagnostic_event::meaning (diagnostic_event::VERB_acquire,
+                                       diagnostic_event::NOUN_memory);
+    if (freed_p (change.m_new_state))
+      return diagnostic_event::meaning (diagnostic_event::VERB_release,
+                                       diagnostic_event::NOUN_memory);
+    return diagnostic_event::meaning ();
+  }
+
 protected:
   const malloc_state_machine &m_sm;
   tree m_arg;
@@ -383,59 +822,81 @@ class mismatching_deallocation : public malloc_diagnostic
 {
 public:
   mismatching_deallocation (const malloc_state_machine &sm, tree arg,
-                           const api *expected_dealloc,
-                           const api *actual_dealloc)
+                           const deallocator_set *expected_deallocators,
+                           const deallocator *actual_dealloc)
   : malloc_diagnostic (sm, arg),
-    m_expected_dealloc (expected_dealloc),
+    m_expected_deallocators (expected_deallocators),
     m_actual_dealloc (actual_dealloc)
   {}
 
-  const char *get_kind () const FINAL OVERRIDE
+  const char *get_kind () const final override
   {
     return "mismatching_deallocation";
   }
 
-  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_mismatching_deallocation;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (762); /* CWE-762: Mismatched Memory Management Routines.  */
-    return warning_meta (rich_loc, m, OPT_Wanalyzer_mismatching_deallocation,
-                        "%qE should have been deallocated with %qs"
-                        " but was deallocated with %qs",
-                        m_arg, m_expected_dealloc->m_dealloc_funcname,
-                        m_actual_dealloc->m_dealloc_funcname);
+    ctxt.add_cwe (762); /* CWE-762: Mismatched Memory Management Routines.  */
+    if (const deallocator *expected_dealloc
+         = m_expected_deallocators->maybe_get_single ())
+      return ctxt.warn ("%qE should have been deallocated with %qs"
+                       " but was deallocated with %qs",
+                       m_arg, expected_dealloc->m_name,
+                       m_actual_dealloc->m_name);
+    else
+      return ctxt.warn ("%qs called on %qE returned from a mismatched"
+                       " allocation function",
+                       m_actual_dealloc->m_name, m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
-    FINAL OVERRIDE
+    final override
   {
     if (unchecked_p (change.m_new_state))
       {
        m_alloc_event = change.m_event_id;
-       return change.formatted_print ("allocated here"
-                                      " (expects deallocation with %qs)",
-                                      m_expected_dealloc->m_dealloc_funcname);
+       if (const deallocator *expected_dealloc
+           = m_expected_deallocators->maybe_get_single ())
+         return change.formatted_print ("allocated here"
+                                        " (expects deallocation with %qs)",
+                                        expected_dealloc->m_name);
+       else
+         return change.formatted_print ("allocated here");
       }
     return malloc_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  label_text describe_final_event (const evdesc::final_event &ev) final override
   {
     if (m_alloc_event.known_p ())
-      return ev.formatted_print
-       ("deallocated with %qs here;"
-        " allocation at %@ expects deallocation with %qs",
-        m_actual_dealloc->m_dealloc_funcname, &m_alloc_event,
-        m_expected_dealloc->m_dealloc_funcname);
+      {
+       if (const deallocator *expected_dealloc
+           = m_expected_deallocators->maybe_get_single ())
+         return ev.formatted_print
+           ("deallocated with %qs here;"
+            " allocation at %@ expects deallocation with %qs",
+            m_actual_dealloc->m_name, &m_alloc_event,
+            expected_dealloc->m_name);
+       else
+         return ev.formatted_print
+           ("deallocated with %qs here;"
+            " allocated at %@",
+            m_actual_dealloc->m_name, &m_alloc_event);
+      }
     return ev.formatted_print ("deallocated with %qs here",
-                              m_actual_dealloc->m_dealloc_funcname);
+                              m_actual_dealloc->m_name);
   }
 
 private:
   diagnostic_event_id_t m_alloc_event;
-  const api *m_expected_dealloc;
-  const api *m_actual_dealloc;
+  const deallocator_set *m_expected_deallocators;
+  const deallocator *m_actual_dealloc;
 };
 
 /* Concrete subclass for reporting double-free diagnostics.  */
@@ -447,19 +908,22 @@ public:
   : malloc_diagnostic (sm, arg), m_funcname (funcname)
   {}
 
-  const char *get_kind () const FINAL OVERRIDE { return "double_free"; }
+  const char *get_kind () const final override { return "double_free"; }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_double_free;
+  }
 
-  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (415); /* CWE-415: Double Free.  */
-    return warning_meta (rich_loc, m, OPT_Wanalyzer_double_free,
-                        "double-%<%s%> of %qE", m_funcname, m_arg);
+    ctxt.add_cwe (415); /* CWE-415: Double Free.  */
+    return ctxt.warn ("double-%qs of %qE", m_funcname, m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
-    FINAL OVERRIDE
+    final override
   {
     if (freed_p (change.m_new_state))
       {
@@ -470,7 +934,7 @@ public:
   }
 
   label_text describe_call_with_state (const evdesc::call_with_state &info)
-    FINAL OVERRIDE
+    final override
   {
     if (freed_p (info.m_state))
       return info.formatted_print
@@ -479,7 +943,7 @@ public:
     return label_text ();
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  label_text describe_final_event (const evdesc::final_event &ev) final override
   {
     if (m_first_free_event.known_p ())
       return ev.formatted_print ("second %qs here; first %qs was at %@",
@@ -504,7 +968,7 @@ public:
   {}
 
   label_text describe_state_change (const evdesc::state_change &change)
-    FINAL OVERRIDE
+    final override
   {
     if (change.m_old_state == m_sm.get_start_state ()
        && unchecked_p (change.m_new_state))
@@ -516,7 +980,7 @@ public:
   }
 
   label_text describe_return_of_state (const evdesc::return_of_state &info)
-    FINAL OVERRIDE
+    final override
   {
     if (unchecked_p (info.m_state))
       return info.formatted_print ("possible return of NULL to %qE from %qE",
@@ -538,19 +1002,21 @@ public:
   : possible_null (sm, arg)
   {}
 
-  const char *get_kind () const FINAL OVERRIDE { return "possible_null_deref"; }
+  const char *get_kind () const final override { return "possible_null_deref"; }
 
-  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_possible_null_dereference;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
-    diagnostic_metadata m;
-    m.add_cwe (690);
-    return warning_meta (rich_loc, m,
-                        OPT_Wanalyzer_possible_null_dereference,
-                        "dereference of possibly-NULL %qE", m_arg);
+    ctxt.add_cwe (690);
+    return ctxt.warn ("dereference of possibly-NULL %qE", m_arg);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  label_text describe_final_event (const evdesc::final_event &ev) final override
   {
     if (m_origin_of_unchecked_event.known_p ())
       return ev.formatted_print ("%qE could be NULL: unchecked value from %@",
@@ -571,7 +1037,7 @@ method_p (tree fndecl)
 }
 
 /* Return a 1-based description of ARG_IDX (0-based) of FNDECL.
-   Compare with %P in the C++ FE  (implemented in cp/error.c: parm_to_string
+   Compare with %P in the C++ FE  (implemented in cp/error.cc: parm_to_string
    as called from cp_printer).  */
 
 static label_text
@@ -594,8 +1060,7 @@ inform_nonnull_attribute (tree fndecl, int arg_idx)
   label_text arg_desc = describe_argument_index (fndecl, arg_idx);
   inform (DECL_SOURCE_LOCATION (fndecl),
          "argument %s of %qD must be non-null",
-         arg_desc.m_buffer, fndecl);
-  arg_desc.maybe_free ();
+         arg_desc.get (), fndecl);
   /* Ideally we would use the location of the parm and underline the
      attribute also - but we don't have the location_t values at this point
      in the middle-end.
@@ -614,9 +1079,10 @@ public:
     m_fndecl (fndecl), m_arg_idx (arg_idx)
   {}
 
-  const char *get_kind () const FINAL OVERRIDE { return "possible_null_arg"; }
+  const char *get_kind () const final override { return "possible_null_arg"; }
 
-  bool subclass_equal_p (const pending_diagnostic &base_other) const
+  bool subclass_equal_p (const pending_diagnostic &base_other)
+    const final override
   {
     const possible_null_arg &sub_other
       = (const possible_null_arg &)base_other;
@@ -625,36 +1091,37 @@ public:
            && m_arg_idx == sub_other.m_arg_idx);
   }
 
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_possible_null_argument;
+  }
 
-  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (690);
+    ctxt.add_cwe (690);
     bool warned
-      = warning_meta (rich_loc, m, OPT_Wanalyzer_possible_null_argument,
-                     "use of possibly-NULL %qE where non-null expected",
-                     m_arg);
+      = ctxt.warn ("use of possibly-NULL %qE where non-null expected",
+                  m_arg);
     if (warned)
       inform_nonnull_attribute (m_fndecl, m_arg_idx);
     return warned;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  label_text describe_final_event (const evdesc::final_event &ev) final override
   {
     label_text arg_desc = describe_argument_index (m_fndecl, m_arg_idx);
     label_text result;
     if (m_origin_of_unchecked_event.known_p ())
       result = ev.formatted_print ("argument %s (%qE) from %@ could be NULL"
                                   " where non-null expected",
-                                  arg_desc.m_buffer, ev.m_expr,
+                                  arg_desc.get (), ev.m_expr,
                                   &m_origin_of_unchecked_event);
     else
       result = ev.formatted_print ("argument %s (%qE) could be NULL"
                                   " where non-null expected",
-                                  arg_desc.m_buffer, ev.m_expr);
-    arg_desc.maybe_free ();
+                                  arg_desc.get (), ev.m_expr);
     return result;
   }
 
@@ -671,20 +1138,24 @@ public:
   null_deref (const malloc_state_machine &sm, tree arg)
   : malloc_diagnostic (sm, arg) {}
 
-  const char *get_kind () const FINAL OVERRIDE { return "null_deref"; }
+  const char *get_kind () const final override { return "null_deref"; }
 
-  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_null_dereference;
+  }
+
+  bool terminate_path_p () const final override { return true; }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-476: NULL Pointer Dereference.  */
-    diagnostic_metadata m;
-    m.add_cwe (476);
-    return warning_meta (rich_loc, m,
-                        OPT_Wanalyzer_null_dereference,
-                        "dereference of NULL %qE", m_arg);
+    ctxt.add_cwe (476);
+    return ctxt.warn ("dereference of NULL %qE", m_arg);
   }
 
   label_text describe_return_of_state (const evdesc::return_of_state &info)
-    FINAL OVERRIDE
+    final override
   {
     if (info.m_state == m_sm.m_null)
       return info.formatted_print ("return of NULL to %qE from %qE",
@@ -692,10 +1163,25 @@ public:
     return label_text ();
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  label_text describe_final_event (const evdesc::final_event &ev) final override
   {
     return ev.formatted_print ("dereference of NULL %qE", ev.m_expr);
   }
+
+  /* Implementation of pending_diagnostic::supercedes_p for
+     null-deref.
+
+     We want null-deref to supercede use-of-unitialized-value,
+     so that if we have these at the same stmt, we don't emit
+     a use-of-uninitialized, just the null-deref.  */
+
+  bool supercedes_p (const pending_diagnostic &other) const final override
+  {
+    if (other.use_of_uninit_p ())
+      return true;
+
+    return false;
+  }
 };
 
 /* Concrete subclass for describing passing a NULL value to a
@@ -710,9 +1196,10 @@ public:
     m_fndecl (fndecl), m_arg_idx (arg_idx)
   {}
 
-  const char *get_kind () const FINAL OVERRIDE { return "null_arg"; }
+  const char *get_kind () const final override { return "null_arg"; }
 
-  bool subclass_equal_p (const pending_diagnostic &base_other) const
+  bool subclass_equal_p (const pending_diagnostic &base_other)
+    const final override
   {
     const null_arg &sub_other
       = (const null_arg &)base_other;
@@ -721,38 +1208,41 @@ public:
            && m_arg_idx == sub_other.m_arg_idx);
   }
 
-  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_null_argument;
+  }
+
+  bool terminate_path_p () const final override { return true; }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-476: NULL Pointer Dereference.  */
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (476);
+    ctxt.add_cwe (476);
 
     bool warned;
     if (zerop (m_arg))
-      warned = warning_meta (rich_loc, m, OPT_Wanalyzer_null_argument,
-                            "use of NULL where non-null expected");
+      warned = ctxt.warn ("use of NULL where non-null expected");
     else
-      warned = warning_meta (rich_loc, m, OPT_Wanalyzer_null_argument,
-                            "use of NULL %qE where non-null expected",
-                            m_arg);
+      warned = ctxt.warn ("use of NULL %qE where non-null expected",
+                         m_arg);
     if (warned)
       inform_nonnull_attribute (m_fndecl, m_arg_idx);
     return warned;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  label_text describe_final_event (const evdesc::final_event &ev) final override
   {
     label_text arg_desc = describe_argument_index (m_fndecl, m_arg_idx);
     label_text result;
     if (zerop (ev.m_expr))
       result = ev.formatted_print ("argument %s NULL where non-null expected",
-                                  arg_desc.m_buffer);
+                                  arg_desc.get ());
     else
       result = ev.formatted_print ("argument %s (%qE) NULL"
                                   " where non-null expected",
-                                  arg_desc.m_buffer, ev.m_expr);
-    arg_desc.maybe_free ();
+                                  arg_desc.get (), ev.m_expr);
     return result;
   }
 
@@ -765,48 +1255,58 @@ class use_after_free : public malloc_diagnostic
 {
 public:
   use_after_free (const malloc_state_machine &sm, tree arg,
-                 const api *a)
-  : malloc_diagnostic (sm, arg), m_api (a)
-  {}
+                 const deallocator *deallocator)
+  : malloc_diagnostic (sm, arg),
+    m_deallocator (deallocator)
+  {
+    gcc_assert (deallocator);
+  }
 
-  const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }
+  const char *get_kind () const final override { return "use_after_free"; }
 
-  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_use_after_free;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-416: Use After Free.  */
-    diagnostic_metadata m;
-    m.add_cwe (416);
-    return warning_meta (rich_loc, m, OPT_Wanalyzer_use_after_free,
-                        "use after %<%s%> of %qE",
-                        m_api->m_dealloc_funcname, m_arg);
+    ctxt.add_cwe (416);
+    return ctxt.warn ("use after %<%s%> of %qE",
+                     m_deallocator->m_name, m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
-    FINAL OVERRIDE
+    final override
   {
     if (freed_p (change.m_new_state))
       {
        m_free_event = change.m_event_id;
-       switch (m_api->m_wording)
+       switch (m_deallocator->m_wording)
          {
          default:
+         case WORDING_REALLOCATED:
            gcc_unreachable ();
          case WORDING_FREED:
            return label_text::borrow ("freed here");
          case WORDING_DELETED:
            return label_text::borrow ("deleted here");
+         case WORDING_DEALLOCATED:
+           return label_text::borrow ("deallocated here");
          }
       }
     return malloc_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  label_text describe_final_event (const evdesc::final_event &ev) final override
   {
-    const char *funcname = m_api->m_dealloc_funcname;
+    const char *funcname = m_deallocator->m_name;
     if (m_free_event.known_p ())
-      switch (m_api->m_wording)
+      switch (m_deallocator->m_wording)
        {
        default:
+       case WORDING_REALLOCATED:
          gcc_unreachable ();
        case WORDING_FREED:
          return ev.formatted_print ("use after %<%s%> of %qE; freed at %@",
@@ -814,15 +1314,38 @@ public:
        case WORDING_DELETED:
          return ev.formatted_print ("use after %<%s%> of %qE; deleted at %@",
                                     funcname, ev.m_expr, &m_free_event);
+       case WORDING_DEALLOCATED:
+         return ev.formatted_print ("use after %<%s%> of %qE;"
+                                    " deallocated at %@",
+                                    funcname, ev.m_expr, &m_free_event);
        }
     else
       return ev.formatted_print ("use after %<%s%> of %qE",
                                 funcname, ev.m_expr);
   }
 
+  /* Implementation of pending_diagnostic::supercedes_p for
+     use_after_free.
+
+     We want use-after-free to supercede use-of-unitialized-value,
+     so that if we have these at the same stmt, we don't emit
+     a use-of-uninitialized, just the use-after-free.
+     (this is because we fully purge information about freed
+     buffers when we free them to avoid state explosions, so
+     that if they are accessed after the free, it looks like
+     they are uninitialized).  */
+
+  bool supercedes_p (const pending_diagnostic &other) const final override
+  {
+    if (other.use_of_uninit_p ())
+      return true;
+
+    return false;
+  }
+
 private:
   diagnostic_event_id_t m_free_event;
-  const api *m_api;
+  const deallocator *m_deallocator;
 };
 
 class malloc_leak : public malloc_diagnostic
@@ -831,24 +1354,28 @@ public:
   malloc_leak (const malloc_state_machine &sm, tree arg)
   : malloc_diagnostic (sm, arg) {}
 
-  const char *get_kind () const FINAL OVERRIDE { return "malloc_leak"; }
+  const char *get_kind () const final override { return "malloc_leak"; }
 
-  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  int get_controlling_option () const final override
   {
-    diagnostic_metadata m;
-    m.add_cwe (401);
+    return OPT_Wanalyzer_malloc_leak;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
+  {
+    /* "CWE-401: Missing Release of Memory after Effective Lifetime".  */
+    ctxt.add_cwe (401);
     if (m_arg)
-      return warning_meta (rich_loc, m, OPT_Wanalyzer_malloc_leak,
-                          "leak of %qE", m_arg);
+      return ctxt.warn ("leak of %qE", m_arg);
     else
-      return warning_meta (rich_loc, m, OPT_Wanalyzer_malloc_leak,
-                          "leak of %qs", "<unknown>");
+      return ctxt.warn ("leak of %qs", "<unknown>");
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
-    FINAL OVERRIDE
+    final override
   {
-    if (unchecked_p (change.m_new_state))
+    if (unchecked_p (change.m_new_state)
+       || (start_p (change.m_old_state) && nonnull_p (change.m_new_state)))
       {
        m_alloc_event = change.m_event_id;
        return label_text::borrow ("allocated here");
@@ -856,7 +1383,7 @@ public:
     return malloc_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  label_text describe_final_event (const evdesc::final_event &ev) final override
   {
     if (ev.m_expr)
       {
@@ -884,79 +1411,228 @@ class free_of_non_heap : public malloc_diagnostic
 {
 public:
   free_of_non_heap (const malloc_state_machine &sm, tree arg,
+                   const region *freed_reg,
                    const char *funcname)
-  : malloc_diagnostic (sm, arg), m_funcname (funcname), m_kind (KIND_UNKNOWN)
+  : malloc_diagnostic (sm, arg), m_freed_reg (freed_reg), m_funcname (funcname)
   {
   }
 
-  const char *get_kind () const FINAL OVERRIDE { return "free_of_non_heap"; }
+  const char *get_kind () const final override { return "free_of_non_heap"; }
 
   bool subclass_equal_p (const pending_diagnostic &base_other) const
-    FINAL OVERRIDE
+    final override
   {
     const free_of_non_heap &other = (const free_of_non_heap &)base_other;
-    return (same_tree_p (m_arg, other.m_arg) && m_kind == other.m_kind);
+    return (same_tree_p (m_arg, other.m_arg)
+           && m_freed_reg == other.m_freed_reg);
   }
 
-  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_free_of_non_heap;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
-    switch (m_kind)
+    ctxt.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
+    switch (get_memory_space ())
       {
       default:
+      case MEMSPACE_HEAP:
        gcc_unreachable ();
-      case KIND_UNKNOWN:
-       return warning_meta (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
-                            "%<%s%> of %qE which points to memory"
-                            " not on the heap",
-                            m_funcname, m_arg);
+      case MEMSPACE_UNKNOWN:
+      case MEMSPACE_CODE:
+      case MEMSPACE_GLOBALS:
+      case MEMSPACE_READONLY_DATA:
+       return ctxt.warn ("%<%s%> of %qE which points to memory"
+                         " not on the heap",
+                         m_funcname, m_arg);
        break;
-      case KIND_ALLOCA:
-       return warning_meta (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
-                            "%<%s%> of memory allocated on the stack by"
-                            " %qs (%qE) will corrupt the heap",
-                            m_funcname, "alloca", m_arg);
+      case MEMSPACE_STACK:
+       return ctxt.warn ("%<%s%> of %qE which points to memory"
+                         " on the stack",
+                         m_funcname, m_arg);
        break;
       }
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
-    FINAL OVERRIDE
+  label_text describe_state_change (const evdesc::state_change &)
+    final override
   {
-    /* Attempt to reconstruct what kind of pointer it is.
-       (It seems neater for this to be a part of the state, though).  */
-    if (TREE_CODE (change.m_expr) == SSA_NAME)
-      {
-       gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr);
-       if (gcall *call = dyn_cast <gcall *> (def_stmt))
-         {
-           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
-                 ("memory is allocated on the stack here");
-             }
-         }
-      }
     return label_text::borrow ("pointer is from here");
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  label_text describe_final_event (const evdesc::final_event &ev) final override
   {
     return ev.formatted_print ("call to %qs here", m_funcname);
   }
 
+  void mark_interesting_stuff (interesting_t *interest) final override
+  {
+    if (m_freed_reg)
+      interest->add_region_creation (m_freed_reg);
+  }
+
 private:
-  enum kind
+  enum memory_space get_memory_space () const
   {
-    KIND_UNKNOWN,
-    KIND_ALLOCA
-  };
+    if (m_freed_reg)
+      return m_freed_reg->get_memory_space ();
+    else
+      return MEMSPACE_UNKNOWN;
+  }
+
+  const region *m_freed_reg;
   const char *m_funcname;
-  enum kind m_kind;
+};
+
+/* Concrete pending_diagnostic subclass for -Wanalyzer-deref-before-check.  */
+
+class deref_before_check : public malloc_diagnostic
+{
+public:
+  deref_before_check (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg),
+    m_deref_enode (NULL),
+    m_deref_expr (NULL),
+    m_check_enode (NULL)
+  {
+    gcc_assert (arg);
+  }
+
+  const char *get_kind () const final override { return "deref_before_check"; }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_deref_before_check;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
+  {
+    /* Don't emit the warning if we can't show where the deref
+       and the check occur.  */
+    if (!m_deref_enode)
+      return false;
+    if (!m_check_enode)
+      return false;
+    /* Only emit the warning for intraprocedural cases.  */
+    const program_point &deref_point = m_deref_enode->get_point ();
+    const program_point &check_point = m_check_enode->get_point ();
+
+    if (!program_point::effectively_intraprocedural_p (deref_point,
+                                                      check_point))
+      return false;
+
+    /* Reject the warning if the check occurs within a macro defintion.
+       This avoids false positives for such code as:
+
+       #define throw_error \
+          do {             \
+            if (p)         \
+              cleanup (p); \
+            return;        \
+          } while (0)
+
+       if (p->idx >= n)
+         throw_error ();
+
+       where the usage of "throw_error" implicitly adds a check
+       on 'p'.
+
+       We do warn when the check is in a macro expansion if we can get
+       at the location of the condition and it is't part of the
+       definition, so that we warn for checks such as:
+          if (words[0][0] == '@')
+            return;
+          g_assert(words[0] != NULL); <--- here
+       Unfortunately we don't have locations for individual gimple
+       arguments, so in:
+          g_assert (ptr);
+       we merely have a gimple_cond
+          if (p_2(D) == 0B)
+       with no way of getting at the location of the condition separately
+       from that of the gimple_cond (where the "if" is within the macro
+       definition).  We reject the warning for such cases.
+
+       We do warn when the *deref* occurs in a macro, since this can be
+       a source of real bugs; see e.g. PR 77425.  */
+    location_t check_loc = m_check_enode->get_point ().get_location ();
+    if (linemap_location_from_macro_definition_p (line_table, check_loc))
+      return false;
+
+    /* Reject if m_deref_expr is sufficiently different from m_arg
+       for cases where the dereference is spelled differently from
+       the check, which is probably two different ways to get the
+       same svalue, and thus not worth reporting.  */
+    if (!m_deref_expr)
+      return false;
+    if (!sufficiently_similar_p (m_deref_expr, m_arg))
+      return false;
+
+    /* Reject the warning if the deref's BB doesn't dominate that
+       of the check, so that we don't warn e.g. for shared cleanup
+       code that checks a pointer for NULL, when that code is sometimes
+       used before a deref and sometimes after.
+       Using the dominance code requires setting cfun.  */
+    auto_cfun sentinel (m_deref_enode->get_function ());
+    calculate_dominance_info (CDI_DOMINATORS);
+    if (!dominated_by_p (CDI_DOMINATORS,
+                        m_check_enode->get_supernode ()->m_bb,
+                        m_deref_enode->get_supernode ()->m_bb))
+      return false;
+
+    return ctxt.warn ("check of %qE for NULL after already"
+                     " dereferencing it",
+                     m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    final override
+  {
+    if (change.m_old_state == m_sm.get_start_state ()
+       && assumed_non_null_p (change.m_new_state))
+      {
+       m_first_deref_event = change.m_event_id;
+       m_deref_enode = change.m_event.get_exploded_node ();
+       m_deref_expr = change.m_expr;
+       return change.formatted_print ("pointer %qE is dereferenced here",
+                                      m_arg);
+      }
+    return malloc_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) final override
+  {
+    m_check_enode = ev.m_event.get_exploded_node ();
+    if (m_first_deref_event.known_p ())
+      return ev.formatted_print ("pointer %qE is checked for NULL here but"
+                                " it was already dereferenced at %@",
+                                m_arg, &m_first_deref_event);
+    else
+      return ev.formatted_print ("pointer %qE is checked for NULL here but"
+                                " it was already dereferenced",
+                                m_arg);
+  }
+
+private:
+  static bool sufficiently_similar_p (tree expr_a, tree expr_b)
+  {
+    pretty_printer *pp_a = global_dc->printer->clone ();
+    pretty_printer *pp_b = global_dc->printer->clone ();
+    pp_printf (pp_a, "%qE", expr_a);
+    pp_printf (pp_b, "%qE", expr_b);
+    bool result = (strcmp (pp_formatted_text (pp_a), pp_formatted_text (pp_b))
+                  == 0);
+    delete pp_a;
+    delete pp_b;
+    return result;
+  }
+
+  diagnostic_event_id_t m_first_deref_event;
+  const exploded_node *m_deref_enode;
+  tree m_deref_expr;
+  const exploded_node *m_check_enode;
 };
 
 /* struct allocation_state : public state_machine::state.  */
@@ -969,40 +1645,249 @@ void
 allocation_state::dump_to_pp (pretty_printer *pp) const
 {
   state_machine::state::dump_to_pp (pp);
-  if (m_api)
-    pp_printf (pp, " (%s)", m_api->m_name);
+  if (m_deallocators)
+    {
+      pp_string (pp, " (");
+      m_deallocators->dump_to_pp (pp);
+      pp_character (pp, ')');
+    }
 }
 
-/* Given a allocation_state for an api, get the "nonnull" state
-   for the corresponding allocator.  */
+/* Given a allocation_state for a deallocator_set, get the "nonnull" state
+   for the corresponding allocator(s).  */
 
 const allocation_state *
 allocation_state::get_nonnull () const
 {
-  gcc_assert (m_api);
-  return as_a_allocation_state (m_api->m_nonnull);
+  gcc_assert (m_deallocators);
+  return as_a_allocation_state (m_deallocators->m_nonnull);
+}
+
+/* struct assumed_non_null_state : public allocation_state.  */
+
+void
+assumed_non_null_state::dump_to_pp (pretty_printer *pp) const
+{
+  allocation_state::dump_to_pp (pp);
+  pp_string (pp, " (in ");
+  m_frame->dump_to_pp (pp, true);
+  pp_character (pp, ')');
 }
 
 /* malloc_state_machine's ctor.  */
 
 malloc_state_machine::malloc_state_machine (logger *logger)
 : state_machine ("malloc", logger),
-  m_malloc (this, "malloc", "free", WORDING_FREED),
-  m_scalar_new (this, "new", "delete", WORDING_DELETED),
-  m_vector_new (this, "new[]", "delete[]", WORDING_DELETED)
+  m_free (this, "free", WORDING_FREED),
+  m_scalar_delete (this, "delete", WORDING_DELETED),
+  m_vector_delete (this, "delete[]", WORDING_DELETED),
+  m_realloc (this, "realloc", WORDING_REALLOCATED)
 {
   gcc_assert (m_start->get_id () == 0);
-  m_null = add_state ("null", RS_FREED, NULL);
-  m_non_heap = add_state ("non-heap", RS_NON_HEAP, NULL);
-  m_stop = add_state ("stop", RS_STOP, NULL);
+  m_null = add_state ("null", RS_FREED, NULL, NULL);
+  m_non_heap = add_state ("non-heap", RS_NON_HEAP, NULL, NULL);
+  m_stop = add_state ("stop", RS_STOP, NULL, NULL);
+}
+
+malloc_state_machine::~malloc_state_machine ()
+{
+  unsigned i;
+  custom_deallocator_set *set;
+  FOR_EACH_VEC_ELT (m_dynamic_sets, i, set)
+    delete set;
+  custom_deallocator *d;
+  FOR_EACH_VEC_ELT (m_dynamic_deallocators, i, d)
+    delete d;
 }
 
 state_machine::state_t
 malloc_state_machine::add_state (const char *name, enum resource_state rs,
-                                const api *a)
+                                const deallocator_set *deallocators,
+                                const deallocator *deallocator)
 {
   return add_custom_state (new allocation_state (name, alloc_state_id (),
-                                                rs, a));
+                                                rs, deallocators,
+                                                deallocator));
+}
+
+/* If ALLOCATOR_FNDECL has any "__attribute__((malloc(FOO)))",
+   return a custom_deallocator_set for them, consolidating them
+   to ensure uniqueness of the sets.
+
+   Return NULL if it has no such attributes.  */
+
+const custom_deallocator_set *
+malloc_state_machine::
+get_or_create_custom_deallocator_set (tree allocator_fndecl)
+{
+  /* Early rejection of decls without attributes.  */
+  tree attrs = DECL_ATTRIBUTES (allocator_fndecl);
+  if (!attrs)
+    return NULL;
+
+  /* Otherwise, call maybe_create_custom_deallocator_set,
+     memoizing the result.  */
+  if (custom_deallocator_set **slot
+      = m_custom_deallocator_set_cache.get (allocator_fndecl))
+    return *slot;
+  custom_deallocator_set *set
+    = maybe_create_custom_deallocator_set (allocator_fndecl);
+  m_custom_deallocator_set_cache.put (allocator_fndecl, set);
+  return set;
+}
+
+/* Given ALLOCATOR_FNDECL, a FUNCTION_DECL with attributes,
+   look for any "__attribute__((malloc(FOO)))" and return a
+   custom_deallocator_set for them, consolidating them
+   to ensure uniqueness of the sets.
+
+   Return NULL if it has no such attributes.
+
+   Subroutine of get_or_create_custom_deallocator_set which
+   memoizes the result.  */
+
+custom_deallocator_set *
+malloc_state_machine::
+maybe_create_custom_deallocator_set (tree allocator_fndecl)
+{
+  tree attrs = DECL_ATTRIBUTES (allocator_fndecl);
+  gcc_assert (attrs);
+
+  /* Look for instances of __attribute__((malloc(FOO))).  */
+  auto_vec<const deallocator *> deallocator_vec;
+  for (tree allocs = attrs;
+       (allocs = lookup_attribute ("malloc", allocs));
+       allocs = TREE_CHAIN (allocs))
+    {
+      tree args = TREE_VALUE (allocs);
+      if (!args)
+       continue;
+      if (TREE_VALUE (args))
+       {
+         const deallocator *d
+           = get_or_create_deallocator (TREE_VALUE (args));
+         deallocator_vec.safe_push (d);
+       }
+    }
+
+  /* If there weren't any deallocators, bail.  */
+  if (deallocator_vec.length () == 0)
+    return NULL;
+
+  /* Consolidate, so that we reuse existing deallocator_set
+     instances.  */
+  deallocator_vec.qsort (deallocator::cmp_ptr_ptr);
+  custom_deallocator_set **slot
+    = m_custom_deallocator_set_map.get (&deallocator_vec);
+  if (slot)
+    return *slot;
+  custom_deallocator_set *set
+    = new custom_deallocator_set (this, &deallocator_vec, WORDING_DEALLOCATED);
+  m_custom_deallocator_set_map.put (&set->m_deallocator_vec, set);
+  m_dynamic_sets.safe_push (set);
+  return set;
+}
+
+/* Get the deallocator for DEALLOCATOR_FNDECL, creating it if necessary.  */
+
+const deallocator *
+malloc_state_machine::get_or_create_deallocator (tree deallocator_fndecl)
+{
+  deallocator **slot = m_deallocator_map.get (deallocator_fndecl);
+  if (slot)
+    return *slot;
+
+  /* Reuse "free".  */
+  deallocator *d;
+  if (is_named_call_p (deallocator_fndecl, "free")
+      || is_std_named_call_p (deallocator_fndecl, "free")
+      || is_named_call_p (deallocator_fndecl, "__builtin_free"))
+    d = &m_free.m_deallocator;
+  else
+    {
+      custom_deallocator *cd
+       = new custom_deallocator (this, deallocator_fndecl,
+                                 WORDING_DEALLOCATED);
+      m_dynamic_deallocators.safe_push (cd);
+      d = cd;
+    }
+  m_deallocator_map.put (deallocator_fndecl, d);
+  return d;
+}
+
+/* Get the "assumed-non-null" state for assumptions made within FRAME,
+   creating it if necessary.  */
+
+state_machine::state_t
+malloc_state_machine::
+get_or_create_assumed_non_null_state_for_frame (const frame_region *frame)
+{
+  if (state_t *slot = m_assumed_non_null.get (frame))
+    return *slot;
+  state_machine::state *new_state
+    = new assumed_non_null_state ("assumed-non-null", alloc_state_id (), frame);
+  add_custom_state (new_state);
+  m_assumed_non_null.put (frame, new_state);
+  return new_state;
+}
+
+/* Try to identify the function declaration either by name or as a known malloc
+   builtin.  */
+
+static bool
+known_allocator_p (const_tree fndecl, const gcall *call)
+{
+  /* Either it is a function we know by name and number of arguments... */
+  if (is_named_call_p (fndecl, "malloc", call, 1)
+      || is_named_call_p (fndecl, "calloc", call, 2)
+      || is_std_named_call_p (fndecl, "malloc", call, 1)
+      || is_std_named_call_p (fndecl, "calloc", call, 2)
+      || is_named_call_p (fndecl, "strdup", call, 1)
+      || is_named_call_p (fndecl, "strndup", call, 2))
+    return true;
+
+  /* ... or it is a builtin allocator that allocates objects freed with
+     __builtin_free.  */
+  if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL))
+    switch (DECL_FUNCTION_CODE (fndecl))
+      {
+      case BUILT_IN_MALLOC:
+      case BUILT_IN_CALLOC:
+      case BUILT_IN_STRDUP:
+      case BUILT_IN_STRNDUP:
+       return true;
+      default:
+       break;
+      }
+
+  return false;
+}
+
+/* If PTR's nullness is not known, transition it to the "assumed-non-null"
+   state for the current frame.  */
+
+void
+malloc_state_machine::maybe_assume_non_null (sm_context *sm_ctxt,
+                                            tree ptr,
+                                            const gimple *stmt) const
+{
+  const region_model *old_model = sm_ctxt->get_old_region_model ();
+  if (!old_model)
+    return;
+
+  tree null_ptr_cst = build_int_cst (TREE_TYPE (ptr), 0);
+  tristate known_non_null
+    = old_model->eval_condition (ptr, NE_EXPR, null_ptr_cst, NULL);
+  if (known_non_null.is_unknown ())
+    {
+      /* Cast away const-ness for cache-like operations.  */
+      malloc_state_machine *mut_this
+       = const_cast <malloc_state_machine *> (this);
+      state_t next_state
+       = mut_this->get_or_create_assumed_non_null_state_for_frame
+       (old_model->get_current_frame ());
+      sm_ctxt->set_next_state (stmt, ptr, next_state);
+    }
 }
 
 /* Implementation of state_machine::on_stmt vfunc for malloc_state_machine.  */
@@ -1015,32 +1900,35 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
     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)
-           || is_std_named_call_p (callee_fndecl, "malloc", call, 1)
-           || is_std_named_call_p (callee_fndecl, "calloc", call, 2)
-           || is_named_call_p (callee_fndecl, "__builtin_malloc", call, 1)
-           || is_named_call_p (callee_fndecl, "__builtin_calloc", call, 2)
-           || is_named_call_p (callee_fndecl, "strdup", call, 1)
-           || is_named_call_p (callee_fndecl, "strndup", call, 2))
+       if (known_allocator_p (callee_fndecl, call))
          {
-           on_allocator_call (sm_ctxt, call, m_malloc);
+           on_allocator_call (sm_ctxt, call, &m_free);
            return true;
          }
 
-       if (is_named_call_p (callee_fndecl, "operator new", call, 1))
-         on_allocator_call (sm_ctxt, call, m_scalar_new);
-       else if (is_named_call_p (callee_fndecl, "operator new []", call, 1))
-         on_allocator_call (sm_ctxt, call, m_vector_new);
-       else if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
-                || is_named_call_p (callee_fndecl, "operator delete", call, 2))
+       if (!is_placement_new_p (call))
          {
-           on_deallocator_call (sm_ctxt, node, call, m_scalar_new);
+           bool returns_nonnull = !TREE_NOTHROW (callee_fndecl)
+                                  && flag_exceptions;
+           if (is_named_call_p (callee_fndecl, "operator new"))
+             on_allocator_call (sm_ctxt, call,
+                                &m_scalar_delete, returns_nonnull);
+           else if (is_named_call_p (callee_fndecl, "operator new []"))
+             on_allocator_call (sm_ctxt, call,
+                                &m_vector_delete, returns_nonnull);
+         }
+
+       if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
+           || is_named_call_p (callee_fndecl, "operator delete", call, 2))
+         {
+           on_deallocator_call (sm_ctxt, node, call,
+                                &m_scalar_delete.m_deallocator, 0);
            return true;
          }
        else if (is_named_call_p (callee_fndecl, "operator delete []", call, 1))
          {
-           on_deallocator_call (sm_ctxt, node, call, m_vector_new);
+           on_deallocator_call (sm_ctxt, node, call,
+                                &m_vector_delete.m_deallocator, 0);
            return true;
          }
 
@@ -1057,78 +1945,143 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
            || is_std_named_call_p (callee_fndecl, "free", call, 1)
            || is_named_call_p (callee_fndecl, "__builtin_free", call, 1))
          {
-           on_deallocator_call (sm_ctxt, node, call, m_malloc);
+           on_deallocator_call (sm_ctxt, node, call,
+                                &m_free.m_deallocator, 0);
+           return true;
+         }
+
+       if (is_named_call_p (callee_fndecl, "realloc", call, 2)
+           || is_named_call_p (callee_fndecl, "__builtin_realloc", call, 2))
+         {
+           on_realloc_call (sm_ctxt, node, call);
            return true;
          }
 
-       /* Handle "__attribute__((nonnull))".   */
+       if (unaffected_by_call_p (callee_fndecl))
+         return true;
+
+       /* Cast away const-ness for cache-like operations.  */
+       malloc_state_machine *mutable_this
+         = const_cast <malloc_state_machine *> (this);
+
+       /* Handle interesting attributes of the callee_fndecl,
+          or prioritize those of the builtin that callee_fndecl is expected
+          to be.
+          Might want this to be controlled by a flag.  */
        {
-         tree fntype = TREE_TYPE (callee_fndecl);
-         bitmap nonnull_args = get_nonnull_args (fntype);
-         if (nonnull_args)
+         tree fndecl = callee_fndecl;
+         /* If call is recognized as a builtin known_function, use that
+            builtin's function_decl.  */
+         if (const region_model *old_model = sm_ctxt->get_old_region_model ())
+           if (const builtin_known_function *builtin_kf
+               = old_model->get_builtin_kf (call))
+             fndecl = builtin_kf->builtin_decl ();
+
+         /* Handle "__attribute__((malloc(FOO)))".   */
+         if (const deallocator_set *deallocators
+             = mutable_this->get_or_create_custom_deallocator_set
+                 (fndecl))
            {
-             for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
-               {
-                 tree arg = gimple_call_arg (stmt, i);
-                 if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
-                   continue;
-                 /* If we have a nonnull-args, and either all pointers, or just
-                    the specified pointers.  */
-                 if (bitmap_empty_p (nonnull_args)
-                     || bitmap_bit_p (nonnull_args, i))
-                   {
-                     tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
-                     state_t state = sm_ctxt->get_state (stmt, arg);
-                     /* Can't use a switch as the states are non-const.  */
-                     if (unchecked_p (state))
-                       {
-                         sm_ctxt->warn (node, stmt, arg,
-                                        new possible_null_arg (*this, diag_arg,
-                                                               callee_fndecl,
-                                                               i));
-                         const allocation_state *astate
-                           = as_a_allocation_state (state);
-                         sm_ctxt->set_next_state (stmt, arg,
-                                                  astate->get_nonnull ());
-                       }
-                     else if (state == m_null)
-                       {
-                         sm_ctxt->warn (node, stmt, arg,
-                                        new null_arg (*this, diag_arg,
-                                                      callee_fndecl, i));
-                         sm_ctxt->set_next_state (stmt, arg, m_stop);
-                       }
-                   }
-               }
-             BITMAP_FREE (nonnull_args);
+             tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (fndecl));
+             bool returns_nonnull
+               = lookup_attribute ("returns_nonnull", attrs);
+             on_allocator_call (sm_ctxt, call, deallocators, returns_nonnull);
+           }
+
+         {
+           /* Handle "__attribute__((nonnull))".   */
+           tree fntype = TREE_TYPE (fndecl);
+           bitmap nonnull_args = get_nonnull_args (fntype);
+           if (nonnull_args)
+             {
+               for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
+                 {
+                   tree arg = gimple_call_arg (stmt, i);
+                   if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
+                     continue;
+                   /* If we have a nonnull-args, and either all pointers, or
+                      just the specified pointers.  */
+                   if (bitmap_empty_p (nonnull_args)
+                       || bitmap_bit_p (nonnull_args, i))
+                     {
+                       state_t state = sm_ctxt->get_state (stmt, arg);
+                       /* Can't use a switch as the states are non-const.  */
+                       /* Do use the fndecl that caused the warning so that the
+                          misused attributes are printed and the user not
+                          confused.  */
+                       if (unchecked_p (state))
+                         {
+                           tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+                           sm_ctxt->warn (node, stmt, arg,
+                                         make_unique<possible_null_arg>
+                                           (*this, diag_arg, fndecl, i));
+                           const allocation_state *astate
+                             = as_a_allocation_state (state);
+                           sm_ctxt->set_next_state (stmt, arg,
+                                                   astate->get_nonnull ());
+                         }
+                       else if (state == m_null)
+                         {
+                           tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+                           sm_ctxt->warn (node, stmt, arg,
+                                         make_unique<null_arg>
+                                           (*this, diag_arg, fndecl, i));
+                           sm_ctxt->set_next_state (stmt, arg, m_stop);
+                         }
+                       else if (state == m_start)
+                         maybe_assume_non_null (sm_ctxt, arg, stmt);
+                     }
+                 }
+               BITMAP_FREE (nonnull_args);
+             }
+         }
+
+         /* Check for this after nonnull, so that if we have both
+            then we transition to "freed", rather than "checked".  */
+         unsigned dealloc_argno = fndecl_dealloc_argno (fndecl);
+         if (dealloc_argno != UINT_MAX)
+           {
+             const deallocator *d
+               = mutable_this->get_or_create_deallocator (fndecl);
+             on_deallocator_call (sm_ctxt, node, call, d, dealloc_argno);
            }
        }
       }
 
-  if (tree lhs = sm_ctxt->is_zero_assignment (stmt))
-    if (any_pointer_p (lhs))
-      on_zero_assignment (sm_ctxt, stmt,lhs);
-
-  /* If we have "LHS = &EXPR;" and EXPR is something other than a MEM_REF,
-     transition LHS from start to non_heap.
-     Doing it for ADDR_EXPR(MEM_REF()) is likely wrong, and can lead to
-     unbounded chains of unmergeable sm-state on pointer arithmetic in loops
-     when optimization is enabled.  */
-  if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
+  /* Look for pointers explicitly being compared against zero
+     that are in state assumed_non_null i.e. we already defererenced
+     them.
+     We have to do this check here, rather than in on_condition
+     because we add a constraint that the pointer is non-null when
+     dereferencing it, and this makes the apply_constraints_for_gcond
+     find known-true and known-false conditions; on_condition is only
+     called when adding new constraints.  */
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (stmt))
     {
-      enum tree_code op = gimple_assign_rhs_code (assign_stmt);
-      if (op == ADDR_EXPR)
+      enum tree_code op = gimple_cond_code (cond_stmt);
+      if (op == EQ_EXPR || op == NE_EXPR)
        {
-         tree lhs = gimple_assign_lhs (assign_stmt);
-         if (lhs)
+         tree lhs = gimple_cond_lhs (cond_stmt);
+         tree rhs = gimple_cond_rhs (cond_stmt);
+         if (any_pointer_p (lhs)
+             && any_pointer_p (rhs)
+             && zerop (rhs))
            {
-             tree addr_expr = gimple_assign_rhs1 (assign_stmt);
-             if (TREE_CODE (TREE_OPERAND (addr_expr, 0)) != MEM_REF)
-               sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+             state_t state = sm_ctxt->get_state (stmt, lhs);
+             if (assumed_non_null_p (state))
+               maybe_complain_about_deref_before_check
+                 (sm_ctxt, node,
+                  stmt,
+                  (const assumed_non_null_state *)state,
+                  lhs);
            }
        }
     }
 
+  if (tree lhs = sm_ctxt->is_zero_assignment (stmt))
+    if (any_pointer_p (lhs))
+      on_zero_assignment (sm_ctxt, stmt,lhs);
+
   /* Handle dereferences.  */
   for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
     {
@@ -1141,28 +2094,33 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
       if (TREE_CODE (op) == MEM_REF)
        {
          tree arg = TREE_OPERAND (op, 0);
-         tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
 
          state_t state = sm_ctxt->get_state (stmt, arg);
-         if (unchecked_p (state))
+         if (state == m_start)
+           maybe_assume_non_null (sm_ctxt, arg, stmt);
+         else if (unchecked_p (state))
            {
+             tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
              sm_ctxt->warn (node, stmt, arg,
-                            new possible_null_deref (*this, diag_arg));
+                            make_unique<possible_null_deref> (*this,
+                                                              diag_arg));
              const allocation_state *astate = as_a_allocation_state (state);
              sm_ctxt->set_next_state (stmt, arg, astate->get_nonnull ());
            }
          else if (state == m_null)
            {
+             tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
              sm_ctxt->warn (node, stmt, arg,
-                            new null_deref (*this, diag_arg));
+                            make_unique<null_deref> (*this, diag_arg));
              sm_ctxt->set_next_state (stmt, arg, m_stop);
            }
          else if (freed_p (state))
            {
+             tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
              const allocation_state *astate = as_a_allocation_state (state);
              sm_ctxt->warn (node, stmt, arg,
-                            new use_after_free (*this, diag_arg,
-                                                astate->m_api));
+                            make_unique<use_after_free>
+                              (*this, diag_arg, astate->m_deallocator));
              sm_ctxt->set_next_state (stmt, arg, m_stop);
            }
        }
@@ -1170,18 +2128,72 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
   return false;
 }
 
-/* Handle a call to an allocator.  */
+/* Given a check against null of PTR in assumed-non-null state STATE,
+   potentially add a deref_before_check warning to SM_CTXT.  */
+
+void
+malloc_state_machine::
+maybe_complain_about_deref_before_check (sm_context *sm_ctxt,
+                                        const supernode *node,
+                                        const gimple *stmt,
+                                        const assumed_non_null_state *state,
+                                        tree ptr) const
+{
+  const region_model *model = sm_ctxt->get_old_region_model ();
+  if (!model)
+    return;
+
+  /* Don't complain if the current frame (where the check is occurring) is
+     deeper than the frame in which the "not null" assumption was made.
+     This suppress false positives for cases like:
+
+       void foo (struct s *p)
+       {
+         int val = s->some_field; // deref here
+         shared_helper (p);
+       }
+
+     where "shared_helper" has:
+
+       void shared_helper (struct s *p)
+       {
+         if (!p) // check here
+           return;
+         // etc
+       }
+
+     since the check in "shared_helper" is OK.  */
+  const frame_region *checked_in_frame = model->get_current_frame ();
+  const frame_region *assumed_nonnull_in_frame = state->m_frame;
+  if (checked_in_frame->get_index () > assumed_nonnull_in_frame->get_index ())
+    return;
+
+  tree diag_ptr = sm_ctxt->get_diagnostic_tree (ptr);
+  if (diag_ptr)
+    sm_ctxt->warn
+      (node, stmt, ptr,
+       make_unique<deref_before_check> (*this, diag_ptr));
+  sm_ctxt->set_next_state (stmt, ptr, m_stop);
+}
+
+/* Handle a call to an allocator.
+   RETURNS_NONNULL is true if CALL is to a fndecl known to have
+   __attribute__((returns_nonnull)).  */
 
 void
 malloc_state_machine::on_allocator_call (sm_context *sm_ctxt,
                                         const gcall *call,
-                                        const api &ap) const
+                                        const deallocator_set *deallocators,
+                                        bool returns_nonnull) const
 {
   tree lhs = gimple_call_lhs (call);
   if (lhs)
     {
       if (sm_ctxt->get_state (call, lhs) == m_start)
-       sm_ctxt->set_next_state (call, lhs, ap.m_unchecked);
+       sm_ctxt->set_next_state (call, lhs,
+                                (returns_nonnull
+                                 ? deallocators->m_nonnull
+                                 : deallocators->m_unchecked));
     }
   else
     {
@@ -1189,53 +2201,133 @@ malloc_state_machine::on_allocator_call (sm_context *sm_ctxt,
     }
 }
 
+/* Handle deallocations of non-heap pointers.
+   non-heap -> stop, with warning.  */
+
+void
+malloc_state_machine::handle_free_of_non_heap (sm_context *sm_ctxt,
+                                              const supernode *node,
+                                              const gcall *call,
+                                              tree arg,
+                                              const deallocator *d) const
+{
+  tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+  const region *freed_reg = NULL;
+  if (const program_state *old_state = sm_ctxt->get_old_program_state ())
+    {
+      const region_model *old_model = old_state->m_region_model;
+      const svalue *ptr_sval = old_model->get_rvalue (arg, NULL);
+      freed_reg = old_model->deref_rvalue (ptr_sval, arg, NULL);
+    }
+  sm_ctxt->warn (node, call, arg,
+                make_unique<free_of_non_heap>
+                  (*this, diag_arg, freed_reg, d->m_name));
+  sm_ctxt->set_next_state (call, arg, m_stop);
+}
+
 void
 malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt,
                                           const supernode *node,
                                           const gcall *call,
-                                          const api &ap) const
+                                          const deallocator *d,
+                                          unsigned argno) const
 {
-  tree arg = gimple_call_arg (call, 0);
-  tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+  if (argno >= gimple_call_num_args (call))
+    return;
+  tree arg = gimple_call_arg (call, argno);
 
   state_t state = sm_ctxt->get_state (call, arg);
 
-  /* start/unchecked/nonnull -> freed.  */
-  if (state == m_start)
-    sm_ctxt->set_next_state (call, arg, ap.m_freed);
+  /* start/assumed_non_null/unchecked/nonnull -> freed.  */
+  if (state == m_start || assumed_non_null_p (state))
+    sm_ctxt->set_next_state (call, arg, d->m_freed);
   else if (unchecked_p (state) || nonnull_p (state))
     {
       const allocation_state *astate = as_a_allocation_state (state);
-
-      if (astate->m_api != &ap)
+      gcc_assert (astate->m_deallocators);
+      if (!astate->m_deallocators->contains_p (d))
        {
          /* Wrong allocator.  */
-         pending_diagnostic *d
-           = new mismatching_deallocation (*this, diag_arg,
-                                           astate->m_api, &ap);
-         sm_ctxt->warn (node, call, arg, d);
+         tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+         sm_ctxt->warn (node, call, arg,
+                        make_unique<mismatching_deallocation>
+                          (*this, diag_arg,
+                           astate->m_deallocators,
+                           d));
        }
-      sm_ctxt->set_next_state (call, arg, ap.m_freed);
+      sm_ctxt->set_next_state (call, arg, d->m_freed);
     }
 
   /* Keep state "null" as-is, rather than transitioning to "freed";
      we don't want to complain about double-free of NULL.  */
-
-  else if (state == ap.m_freed)
+  else if (state == d->m_freed)
     {
       /* freed -> stop, with warning.  */
+      tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
       sm_ctxt->warn (node, call, arg,
-                    new double_free (*this, diag_arg,
-                                     ap.m_dealloc_funcname));
+                    make_unique<double_free> (*this, diag_arg, d->m_name));
       sm_ctxt->set_next_state (call, arg, m_stop);
     }
   else if (state == m_non_heap)
     {
       /* non-heap -> stop, with warning.  */
+      handle_free_of_non_heap (sm_ctxt, node, call, arg, d);
+    }
+}
+
+/* Handle a call to "realloc".
+   Check for free of non-heap or mismatching allocators,
+   transitioning to the "stop" state for such cases.
+
+   Otherwise, kf_realloc::impl_call_post will later
+   get called (which will handle other sm-state transitions
+   when the state is bifurcated).  */
+
+void
+malloc_state_machine::on_realloc_call (sm_context *sm_ctxt,
+                                      const supernode *node,
+                                      const gcall *call) const
+{
+  const unsigned argno = 0;
+  const deallocator *d = &m_realloc;
+
+  tree arg = gimple_call_arg (call, argno);
+
+  state_t state = sm_ctxt->get_state (call, arg);
+
+  if (unchecked_p (state) || nonnull_p (state))
+    {
+      const allocation_state *astate = as_a_allocation_state (state);
+      gcc_assert (astate->m_deallocators);
+      if (!astate->m_deallocators->contains_p (&m_free.m_deallocator))
+       {
+         /* Wrong allocator.  */
+         tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+         sm_ctxt->warn (node, call, arg,
+                        make_unique<mismatching_deallocation>
+                          (*this, diag_arg,
+                           astate->m_deallocators, d));
+         sm_ctxt->set_next_state (call, arg, m_stop);
+         if (path_context *path_ctxt = sm_ctxt->get_path_context ())
+           path_ctxt->terminate_path ();
+       }
+    }
+  else if (state == m_free.m_deallocator.m_freed)
+    {
+      /* freed -> stop, with warning.  */
+      tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
       sm_ctxt->warn (node, call, arg,
-                    new free_of_non_heap (*this, diag_arg,
-                                          ap.m_dealloc_funcname));
+                    make_unique<double_free> (*this, diag_arg, "free"));
       sm_ctxt->set_next_state (call, arg, m_stop);
+      if (path_context *path_ctxt = sm_ctxt->get_path_context ())
+       path_ctxt->terminate_path ();
+    }
+  else if (state == m_non_heap)
+    {
+      /* non-heap -> stop, with warning.  */
+      handle_free_of_non_heap (sm_ctxt, node, call, arg, d);
+      if (path_context *path_ctxt = sm_ctxt->get_path_context ())
+       path_ctxt->terminate_path ();
     }
 }
 
@@ -1261,11 +2353,11 @@ void
 malloc_state_machine::on_condition (sm_context *sm_ctxt,
                                    const supernode *node ATTRIBUTE_UNUSED,
                                    const gimple *stmt,
-                                   tree lhs,
+                                   const svalue *lhs,
                                    enum tree_code op,
-                                   tree rhs) const
+                                   const svalue *rhs) const
 {
-  if (!zerop (rhs))
+  if (!rhs->all_zeroes_p ())
     return;
 
   if (!any_pointer_p (lhs))
@@ -1292,6 +2384,31 @@ malloc_state_machine::on_condition (sm_context *sm_ctxt,
     }
 }
 
+/* Implementation of state_machine::on_pop_frame vfunc for malloc_state_machine.
+   Clear any "assumed-non-null" state where the assumption happened in
+   FRAME_REG.  */
+
+void
+malloc_state_machine::on_pop_frame (sm_state_map *smap,
+                                   const frame_region *frame_reg) const
+{
+  hash_set<const svalue *> svals_to_clear;
+  for (auto kv : *smap)
+    {
+      const svalue *sval = kv.first;
+      state_t state = kv.second.m_state;
+      if (assumed_non_null_p (state))
+       {
+         const assumed_non_null_state *assumed_state
+           = (const assumed_non_null_state *)state;
+         if (frame_reg == assumed_state->m_frame)
+           svals_to_clear.add (sval);
+       }
+    }
+  for (auto sval : svals_to_clear)
+    smap->clear_any_state (sval);
+}
+
 /* Implementation of state_machine::can_purge_p vfunc for malloc_state_machine.
    Don't allow purging of pointers in state 'unchecked' or 'nonnull'
    (to avoid false leak reports).  */
@@ -1307,10 +2424,10 @@ malloc_state_machine::can_purge_p (state_t s) const
    (for complaining about leaks of pointers in state 'unchecked' and
    'nonnull').  */
 
-pending_diagnostic *
+std::unique_ptr<pending_diagnostic>
 malloc_state_machine::on_leak (tree var) const
 {
-  return new malloc_leak (*this, var);
+  return make_unique<malloc_leak> (*this, var);
 }
 
 /* Implementation of state_machine::reset_when_passed_to_unknown_fn_p vfunc
@@ -1329,6 +2446,44 @@ malloc_state_machine::reset_when_passed_to_unknown_fn_p (state_t s,
   return is_mutable;
 }
 
+/* Implementation of state_machine::maybe_get_merged_states_nonequal vfunc
+   for malloc_state_machine.
+
+   Support discarding "assumed-non-null" states when merging with
+   start state.  */
+
+state_machine::state_t
+malloc_state_machine::maybe_get_merged_states_nonequal (state_t state_a,
+                                                       state_t state_b) const
+{
+  if (assumed_non_null_p (state_a) && state_b == m_start)
+    return m_start;
+  if (state_a == m_start && assumed_non_null_p (state_b))
+    return m_start;
+  return NULL;
+}
+
+/* Return true if calls to FNDECL are known to not affect this sm-state.  */
+
+bool
+malloc_state_machine::unaffected_by_call_p (tree fndecl)
+{
+  /* A set of functions that are known to not affect allocation
+     status, even if we haven't fully modelled the rest of their
+     behavior yet.  */
+  static const char * const funcnames[] = {
+    /* This array must be kept sorted.  */
+    "strsep",
+  };
+  const size_t count = ARRAY_SIZE (funcnames);
+  function_set fs (funcnames, count);
+
+  if (fs.contains_decl_p (fndecl))
+    return true;
+
+  return false;
+}
+
 /* Shared logic for handling GIMPLE_ASSIGNs and GIMPLE_PHIs that
    assign zero to LHS.  */
 
@@ -1346,6 +2501,41 @@ malloc_state_machine::on_zero_assignment (sm_context *sm_ctxt,
     sm_ctxt->set_next_state (stmt, lhs, m_null);
 }
 
+/* Special-case hook for handling realloc, for the "success with move to
+   a new buffer" case, marking OLD_PTR_SVAL as freed and NEW_PTR_SVAL as
+   non-null.
+
+   This is similar to on_deallocator_call and on_allocator_call,
+   but the checks happen in on_realloc_call, and by splitting the states.  */
+
+void
+malloc_state_machine::
+on_realloc_with_move (region_model *model,
+                     sm_state_map *smap,
+                     const svalue *old_ptr_sval,
+                     const svalue *new_ptr_sval,
+                     const extrinsic_state &ext_state) const
+{
+  smap->set_state (model, old_ptr_sval,
+                  m_free.m_deallocator.m_freed,
+                  NULL, ext_state);
+
+  smap->set_state (model, new_ptr_sval,
+                  m_free.m_nonnull,
+                  NULL, ext_state);
+}
+
+/*  Hook for get_or_create_region_for_heap_alloc for the case when we want
+   ptr_sval to mark a newly created region as assumed non null on malloc SM.  */
+void
+malloc_state_machine::transition_ptr_sval_non_null (region_model *model,
+    sm_state_map *smap,
+    const svalue *new_ptr_sval,
+    const extrinsic_state &ext_state) const
+{
+  smap->set_state (model, new_ptr_sval, m_free.m_nonnull, NULL, ext_state);
+}
+
 } // anonymous namespace
 
 /* Internal interface to this file. */
@@ -1356,6 +2546,66 @@ make_malloc_state_machine (logger *logger)
   return new malloc_state_machine (logger);
 }
 
+/* Specialcase hook for handling realloc, for use by
+   kf_realloc::impl_call_post::success_with_move::update_model.  */
+
+void
+region_model::on_realloc_with_move (const call_details &cd,
+                                   const svalue *old_ptr_sval,
+                                   const svalue *new_ptr_sval)
+{
+  region_model_context *ctxt = cd.get_ctxt ();
+  if (!ctxt)
+    return;
+  const extrinsic_state *ext_state = ctxt->get_ext_state ();
+  if (!ext_state)
+    return;
+
+  sm_state_map *smap;
+  const state_machine *sm;
+  unsigned sm_idx;
+  if (!ctxt->get_malloc_map (&smap, &sm, &sm_idx))
+    return;
+
+  gcc_assert (smap);
+  gcc_assert (sm);
+
+  const malloc_state_machine &malloc_sm
+    = (const malloc_state_machine &)*sm;
+
+  malloc_sm.on_realloc_with_move (this,
+                                 smap,
+                                 old_ptr_sval,
+                                 new_ptr_sval,
+                                 *ext_state);
+}
+
+/* Moves ptr_sval from start to assumed non-null, for use by
+   region_model::get_or_create_region_for_heap_alloc.  */
+void
+region_model::transition_ptr_sval_non_null (region_model_context *ctxt,
+const svalue *ptr_sval)
+{
+  if (!ctxt)
+    return;
+  const extrinsic_state *ext_state = ctxt->get_ext_state ();
+  if (!ext_state)
+    return;
+
+  sm_state_map *smap;
+  const state_machine *sm;
+  unsigned sm_idx;
+  if (!ctxt->get_malloc_map (&smap, &sm, &sm_idx))
+    return;
+
+  gcc_assert (smap);
+  gcc_assert (sm);
+
+  const malloc_state_machine &malloc_sm = (const malloc_state_machine &)*sm;
+
+  malloc_sm.transition_ptr_sval_non_null (this, smap, ptr_sval, *ext_state);
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */