public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [committed] analyzer: support "bifurcation"; reimplement realloc [PR99260]
@ 2021-08-30 22:45 David Malcolm
  2021-09-03 18:00 ` Gerald Pfeifer
  0 siblings, 1 reply; 3+ messages in thread
From: David Malcolm @ 2021-08-30 22:45 UTC (permalink / raw)
  To: gcc-patches

Most of the state-management code in the analyzer involves
modifying state objects in-place, which implies a single outcome.
(I originally implemented in-place modification because I wanted
to avoid having to create copies of state objects, and it's now
very difficult to change this aspect of the analyzer's design)

However, there are various special-cases such as "realloc" for which
it's best to split the state into multiple outcomes.

This patch adds a mechanism for "bifurcating" the analysis in places
where there isn't a split in the CFG, and uses it to implement realloc,
in this case treating it as having 3 possible outcomes:
- failure, returning NULL
- success, growing the buffer in-place without moving it
- success, allocating a new buffer, copying the content of the old
  buffer to it, and freeing the old buffer.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r12-3237-geafa9d969237fd8f712c4b25a8c58932c01f44b4.

gcc/ChangeLog:
	PR analyzer/99260
	* Makefile.in (ANALYZER_OBJS): Add analyzer/call-info.o.

gcc/analyzer/ChangeLog:
	PR analyzer/99260
	* analyzer.h (class custom_edge_info): New class, adapted from
	exploded_edge::custom_info_t.  Make member functions const.
	Make update_model return bool, converting edge param from
	reference to a pointer, and adding a ctxt param.
	(class path_context): New class.
	* call-info.cc: New file.
	* call-info.h: New file.
	* engine.cc: Include "analyzer/call-info.h" and <memory>.
	(impl_region_model_context::impl_region_model_context): Update for
	new m_path_ctxt field.
	(impl_region_model_context::bifurcate): New.
	(impl_region_model_context::terminate_path): New.
	(impl_region_model_context::get_malloc_map): New.
	(impl_sm_context::impl_sm_context): Update for new m_path_ctxt
	field.
	(impl_sm_context::get_fndecl_for_call): Likewise.
	(impl_sm_context::set_next_state): Likewise.
	(impl_sm_context::warn): Likewise.
	(impl_sm_context::is_zero_assignment): Likewise.
	(impl_sm_context::get_path_context): New.
	(impl_sm_context::m_path_ctxt): New.
	(impl_region_model_context::on_condition): Update for new
	path_ctxt param.  Handle m_enode_for_diag being NULL.
	(impl_region_model_context::on_phi): Update for new path_ctxt
	param.
	(exploded_node::on_stmt): Add path_ctxt param, updating ctor calls
	to use it as necessary.  Use it to bail out after sm-handling,
	if needed.
	(exploded_node::detect_leaks): Update for new path_ctxt param.
	(dynamic_call_info_t::update_model): Update for conversion of
	exploded_edge::custom_info_t to custom_edge_info.
	(dynamic_call_info_t::add_events_to_path): Likewise.
	(rewind_info_t::update_model): Likewise.
	(rewind_info_t::add_events_to_path): Likewise.
	(exploded_edge::exploded_edge): Likewise.
	(exploded_graph::add_edge): Likewise.
	(exploded_graph::maybe_process_run_of_before_supernode_enodes):
	Update for new path_ctxt param.
	(class impl_path_context): New.
	(exploded_graph::process_node): Update for new path_ctxt param.
	Create an impl_path_context and pass it to exploded_node::on_stmt.
	Use it to terminate iterating stmts if terminate_path is called
	on it.  After processing a run of stmts, query path_ctxt to
	potentially terminate the analysis path, and/or to "bifurcate" the
	analysis into multiple additional paths.
	(feasibility_state::maybe_update_for_edge): Update for new
	update_model ctxt param.
	* exploded-graph.h
	(impl_region_model_context::impl_region_model_context): Add
	path_ctxt param.
	(impl_region_model_context::bifurcate): New.
	(impl_region_model_context::terminate_path): New
	(impl_region_model_context::get_ext_state): New.
	(impl_region_model_context::get_malloc_map): New.
	(impl_region_model_context::m_path_ctxt): New field.
	(exploded_node::on_stmt): Add path_ctxt param.
	(class exploded_edge::custom_info_t): Move to analyzer.h, renaming
	to custom_edge_info, and making the changes as noted in analyzer.h
	above.
	(exploded_edge::exploded_edge): Update for these changes to
	exploded_edge::custom_info_t.
	(exploded_edge::m_custom_info): Likewise.
	(class dynamic_call_info_t): Likewise.
	(class rewind_info_t): Likewise.
	(exploded_graph::add_edge): Likewise.
	* program-state.cc (program_state::on_edge): Update for new
	path_ctxt param.
	(program_state::push_call): Likewise.
	(program_state::returning_call): Likewise.
	(program_state::prune_for_point): Likewise.
	* region-model-impl-calls.cc: Include "analyzer/call-info.h".
	(call_details::get_fndecl_for_call): New.
	(region_model::impl_call_realloc): Reimplement.
	* region-model.cc (region_model::on_call_pre): Move call to
	impl_call_realloc to...
	(region_model::on_call_post): ...here.  Consolidate creation
	of call_details instance.
	(noop_region_model_context::bifurcate): New.
	(noop_region_model_context::terminate_path): New.
	* region-model.h (call_details::get_call_stmt): New.
	(call_details::get_fndecl_for_call): New.
	(region_model::on_realloc_with_move): New.
	(region_model_context::bifurcate): New.
	(region_model_context::terminate_path): New.
	(region_model_context::get_ext_state): New.
	(region_model_context::get_malloc_map): New.
	(noop_region_model_context::bifurcate): New.
	(noop_region_model_context::terminate_path): New.
	(noop_region_model_context::get_ext_state): New.
	(noop_region_model_context::get_malloc_map): New.
	* sm-malloc.cc: Include "analyzer/program-state.h".
	(malloc_state_machine::on_realloc_call): Reimplement.
	(malloc_state_machine::on_realloc_with_move): New.
	(region_model::on_realloc_with_move): New.
	* sm-signal.cc (class signal_delivery_edge_info_t): Update for
	conversion from exploded_edge::custom_info_t to custom_edge_info.
	* sm.h (sm_context::get_path_context): New.
	* svalue.cc (svalue::maybe_get_constant): Call
	unwrap_any_unmergeable.

gcc/testsuite/ChangeLog:
	PR analyzer/99260
	* gcc.dg/analyzer/capacity-2.c: Update for changes to realloc
	analysis.
	* gcc.dg/analyzer/pr99193-1.c: Likewise.
	* gcc.dg/analyzer/pr99193-3.c: Likewise.
	* gcc.dg/analyzer/realloc-1.c: Likewise.  Add test coverage for
	realloc of non-heap pointer, realloc from mismatching allocator,
	and realloc on a freed pointer.
	* gcc.dg/analyzer/realloc-2.c: New test.
---
 gcc/Makefile.in                            |   1 +
 gcc/analyzer/analyzer.h                    |  51 ++++
 gcc/analyzer/call-info.cc                  | 162 ++++++++++++
 gcc/analyzer/call-info.h                   |  83 +++++++
 gcc/analyzer/engine.cc                     | 271 +++++++++++++++++----
 gcc/analyzer/exploded-graph.h              |  62 +++--
 gcc/analyzer/program-state.cc              |   6 +-
 gcc/analyzer/region-model-impl-calls.cc    | 176 ++++++++++++-
 gcc/analyzer/region-model.cc               |  28 ++-
 gcc/analyzer/region-model.h                |  36 +++
 gcc/analyzer/sm-malloc.cc                  | 136 ++++++++---
 gcc/analyzer/sm-signal.cc                  |  15 +-
 gcc/analyzer/sm.h                          |   5 +
 gcc/analyzer/svalue.cc                     |   3 +-
 gcc/testsuite/gcc.dg/analyzer/capacity-2.c |   8 +-
 gcc/testsuite/gcc.dg/analyzer/pr99193-1.c  |   2 +
 gcc/testsuite/gcc.dg/analyzer/pr99193-3.c  |   2 +
 gcc/testsuite/gcc.dg/analyzer/realloc-1.c  |  47 +++-
 gcc/testsuite/gcc.dg/analyzer/realloc-2.c  |  80 ++++++
 19 files changed, 1042 insertions(+), 132 deletions(-)
 create mode 100644 gcc/analyzer/call-info.cc
 create mode 100644 gcc/analyzer/call-info.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/realloc-2.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 9714fcaac37..f0c560fe45b 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1249,6 +1249,7 @@ ANALYZER_OBJS = \
 	analyzer/analyzer-pass.o \
 	analyzer/analyzer-selftests.o \
 	analyzer/bar-chart.o \
+	analyzer/call-info.o \
 	analyzer/call-string.o \
 	analyzer/checker-path.o \
 	analyzer/complexity.o \
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 05d47512bd7..7ad1081ca6c 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -220,6 +220,57 @@ enum access_direction
   DIR_WRITE
 };
 
+/* Abstract base class for associating custom data with an
+   exploded_edge, for handling non-standard edges such as
+   rewinding from a longjmp, signal handlers, etc.
+   Also used when "bifurcating" state: splitting the execution
+   path in non-standard ways (e.g. for simulating the various
+   outcomes of "realloc").  */
+
+class custom_edge_info
+{
+public:
+  virtual ~custom_edge_info () {}
+
+  /* Hook for making .dot label more readable.  */
+  virtual void print (pretty_printer *pp) const = 0;
+
+  /* Hook for updating MODEL within exploded_path::feasible_p
+     and when handling bifurcation.  */
+  virtual bool update_model (region_model *model,
+			     const exploded_edge *eedge,
+			     region_model_context *ctxt) const = 0;
+
+  virtual void add_events_to_path (checker_path *emission_path,
+				   const exploded_edge &eedge) const = 0;
+};
+
+/* Abstract base class for splitting state.
+
+   Most of the state-management code in the analyzer involves
+   modifying state objects in-place, which assumes a single outcome.
+
+   This class provides an escape hatch to allow for multiple outcomes
+   for such updates e.g. for modelling multiple outcomes from function
+   calls, such as the various outcomes of "realloc".  */
+
+class path_context
+{
+public:
+  virtual ~path_context () {}
+
+  /* Hook for clients to split state with a non-standard path.
+     Take ownership of INFO.  */
+  virtual void bifurcate (custom_edge_info *info) = 0;
+
+  /* Hook for clients to terminate the standard path.  */
+  virtual void terminate_path () = 0;
+
+  /* Hook for clients to determine if the standard path has been
+     terminated.  */
+  virtual bool terminate_path_p () const = 0;
+};
+
 } // namespace ana
 
 extern bool is_special_named_call_p (const gcall *call, const char *funcname,
diff --git a/gcc/analyzer/call-info.cc b/gcc/analyzer/call-info.cc
new file mode 100644
index 00000000000..1d44cb88221
--- /dev/null
+++ b/gcc/analyzer/call-info.cc
@@ -0,0 +1,162 @@
+/* Subclasses of custom_edge_info for describing outcomes of function calls.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "diagnostic-core.h"
+#include "options.h"
+#include "cgraph.h"
+#include "tree-pretty-print.h"
+#include "tristate.h"
+#include "bitmap.h"
+#include "selftest.h"
+#include "function.h"
+#include "json.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "ordered-hash-map.h"
+#include "cfg.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "sbitmap.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/region-model-reachability.h"
+#include "analyzer/analyzer-selftests.h"
+#include "analyzer/program-state.h"
+#include "diagnostic-path.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/diagnostic-manager.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/call-info.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* class call_info : public custom_eedge_info_t.  */
+
+/* Implementation of custom_edge_info::print vfunc for call_info:
+   use get_desc to get a label_text, and print it to PP.  */
+
+void
+call_info::print (pretty_printer *pp) const
+{
+  label_text desc (get_desc (pp_show_color (pp)));
+  pp_string (pp, desc.m_buffer);
+  desc.maybe_free ();
+}
+
+/* Implementation of custom_edge_info::add_events_to_path vfunc for
+   call_info: add a custom_event using call_info::get_desc as its
+   description.  */
+
+void
+call_info::add_events_to_path (checker_path *emission_path,
+			       const exploded_edge &eedge) const
+{
+  class call_event : public custom_event
+  {
+  public:
+    call_event (location_t loc, tree fndecl, int depth,
+		const call_info *call_info)
+      : custom_event (loc, fndecl, depth),
+	m_call_info (call_info)
+    {}
+
+    label_text get_desc (bool can_colorize) const
+    {
+      return m_call_info->get_desc (can_colorize);
+    }
+
+  private:
+    const call_info *m_call_info;
+  };
+
+  const exploded_node *src_node = eedge.m_src;
+  const program_point &src_point = src_node->get_point ();
+  tree caller_fndecl = src_point.get_fndecl ();
+  const int stack_depth = src_point.get_stack_depth ();
+
+  emission_path->add_event (new call_event (get_call_stmt ()->location,
+					    caller_fndecl,
+					    stack_depth,
+					    this));
+}
+
+/* Recreate a call_details instance from this call_info.  */
+
+call_details
+call_info::get_call_details (region_model *model,
+			     region_model_context *ctxt) const
+{
+  return call_details (m_call_stmt, model, ctxt);
+}
+
+/* call_info's ctor.
+
+   The call_info instance will outlive the call_details instance;
+   call_details instances are typically created on the stack.  */
+
+call_info::call_info (const call_details &cd)
+: m_call_stmt (cd.get_call_stmt ()),
+  m_fndecl (cd.get_fndecl_for_call ())
+{
+  gcc_assert (m_fndecl);
+}
+
+/* class success_call_info : public call_info.  */
+
+/* Implementation of call_info::get_desc vfunc for success_call_info.  */
+
+label_text
+success_call_info::get_desc (bool can_colorize) const
+{
+  return make_label_text (can_colorize, "when %qE succeeds", get_fndecl ());
+}
+
+/* class failed_call_info : public call_info.  */
+
+/* Implementation of call_info::get_desc vfunc for failed_call_info.  */
+
+label_text
+failed_call_info::get_desc (bool can_colorize) const
+{
+  return make_label_text (can_colorize, "when %qE fails", get_fndecl ());
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/call-info.h b/gcc/analyzer/call-info.h
new file mode 100644
index 00000000000..369d217a22f
--- /dev/null
+++ b/gcc/analyzer/call-info.h
@@ -0,0 +1,83 @@
+/* Subclasses of custom_edge_info for describing outcomes of function calls.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_CALL_INFO_H
+#define GCC_ANALYZER_CALL_INFO_H
+
+namespace ana {
+
+/* Subclass of custom_edge_info for an outcome of a call.
+   This is still abstract; the update_model and get_desc vfuncs must be
+   implemented.  */
+
+class call_info : public custom_edge_info
+{
+public:
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+  void add_events_to_path (checker_path *emission_path,
+			   const exploded_edge &eedge) const FINAL OVERRIDE;
+
+  const gcall *get_call_stmt () const { return m_call_stmt; }
+  tree get_fndecl () const { return m_fndecl; }
+
+  virtual label_text get_desc (bool can_colorize) const = 0;
+
+  call_details get_call_details (region_model *model,
+				 region_model_context *ctxt) const;
+
+protected:
+  call_info (const call_details &cd);
+
+private:
+  const gcall *m_call_stmt;
+  tree m_fndecl;
+};
+
+/* Subclass of call_info for a "success" outcome of a call,
+   adding a "when `FNDECL' succeeds" message.
+   This is still abstract: the custom_edge_info::update_model vfunc
+   must be implemented.  */
+
+class success_call_info : public call_info
+{
+public:
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+protected:
+  success_call_info (const call_details &cd) : call_info (cd) {}
+};
+
+/* Subclass of call_info for a "failure" outcome of a call,
+   adding a "when `FNDECL' fails" message.
+   This is still abstract: the custom_edge_info::update_model vfunc
+   must be implemented.  */
+
+class failed_call_info : public call_info
+{
+public:
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+protected:
+  failed_call_info (const call_details &cd) : call_info (cd) {}
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_CALL_INFO_H */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 9c604d1eb8c..24f0931197d 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -62,9 +62,11 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/checker-path.h"
 #include "analyzer/state-purge.h"
 #include "analyzer/bar-chart.h"
+#include "analyzer/call-info.h"
 #include <zlib.h>
 #include "plugin.h"
 #include "target.h"
+#include <memory>
 
 /* For an overview, see gcc/doc/analyzer.texi.  */
 
@@ -80,6 +82,7 @@ impl_region_model_context (exploded_graph &eg,
 			   const program_state *old_state,
 			   program_state *new_state,
 			   uncertainty_t *uncertainty,
+			   path_context *path_ctxt,
 			   const gimple *stmt,
 			   stmt_finder *stmt_finder)
 : m_eg (&eg), m_logger (eg.get_logger ()),
@@ -89,7 +92,8 @@ impl_region_model_context (exploded_graph &eg,
   m_stmt (stmt),
   m_stmt_finder (stmt_finder),
   m_ext_state (eg.get_ext_state ()),
-  m_uncertainty (uncertainty)
+  m_uncertainty (uncertainty),
+  m_path_ctxt (path_ctxt)
 {
 }
 
@@ -104,7 +108,8 @@ impl_region_model_context (program_state *state,
   m_stmt (NULL),
   m_stmt_finder (NULL),
   m_ext_state (ext_state),
-  m_uncertainty (uncertainty)
+  m_uncertainty (uncertainty),
+  m_path_ctxt (NULL)
 {
 }
 
@@ -183,6 +188,37 @@ impl_region_model_context::purge_state_involving (const svalue *sval)
     smap->purge_state_involving (sval, m_ext_state);
 }
 
+void
+impl_region_model_context::bifurcate (custom_edge_info *info)
+{
+  if (m_path_ctxt)
+    m_path_ctxt->bifurcate (info);
+  else
+    delete info;
+}
+
+void
+impl_region_model_context::terminate_path ()
+{
+  if (m_path_ctxt)
+    return m_path_ctxt->terminate_path ();
+}
+
+bool
+impl_region_model_context::get_malloc_map (sm_state_map **out_smap,
+					   const state_machine **out_sm,
+					   unsigned *out_sm_idx)
+{
+  unsigned malloc_sm_idx;
+  if (!m_ext_state.get_sm_idx_by_name ("malloc", &malloc_sm_idx))
+    return false;
+
+  *out_smap = m_new_state->m_checker_states[malloc_sm_idx];
+  *out_sm = &m_ext_state.get_sm (malloc_sm_idx);
+  *out_sm_idx = malloc_sm_idx;
+  return true;
+}
+
 /* struct setjmp_record.  */
 
 int
@@ -237,12 +273,14 @@ public:
 		   program_state *new_state,
 		   const sm_state_map *old_smap,
 		   sm_state_map *new_smap,
+		   path_context *path_ctxt,
 		   stmt_finder *stmt_finder = NULL)
   : sm_context (sm_idx, sm),
     m_logger (eg.get_logger ()),
     m_eg (eg), m_enode_for_diag (enode_for_diag),
     m_old_state (old_state), m_new_state (new_state),
     m_old_smap (old_smap), m_new_smap (new_smap),
+    m_path_ctxt (path_ctxt),
     m_stmt_finder (stmt_finder)
   {
   }
@@ -252,7 +290,7 @@ public:
   tree get_fndecl_for_call (const gcall *call) FINAL OVERRIDE
   {
     impl_region_model_context old_ctxt
-      (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
+      (m_eg, m_enode_for_diag, NULL, NULL, NULL/*m_enode->get_state ()*/,
        NULL, call);
     region_model *model = m_new_state->m_region_model;
     return model->get_fndecl_for_call (call, &old_ctxt);
@@ -292,7 +330,7 @@ public:
     LOG_FUNC (logger);
     impl_region_model_context new_ctxt (m_eg, m_enode_for_diag,
 					m_old_state, m_new_state,
-					NULL,
+					NULL, NULL,
 					stmt);
     const svalue *var_new_sval
       = m_new_state->m_region_model->get_rvalue (var, &new_ctxt);
@@ -320,12 +358,12 @@ public:
     logger * const logger = get_logger ();
     LOG_FUNC (logger);
     impl_region_model_context old_ctxt
-      (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
+      (m_eg, m_enode_for_diag, NULL, NULL, NULL/*m_enode->get_state ()*/,
        NULL, stmt);
 
     impl_region_model_context new_ctxt (m_eg, m_enode_for_diag,
 					m_old_state, m_new_state,
-					NULL,
+					NULL, NULL,
 					stmt);
     const svalue *origin_new_sval
       = m_new_state->m_region_model->get_rvalue (origin, &new_ctxt);
@@ -353,7 +391,7 @@ public:
     LOG_FUNC (get_logger ());
     gcc_assert (d); // take ownership
     impl_region_model_context old_ctxt
-      (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL);
+      (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, NULL);
 
     const svalue *var_old_sval
       = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
@@ -418,7 +456,7 @@ public:
     if (!assign_stmt)
      return NULL_TREE;
     impl_region_model_context old_ctxt
-      (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, stmt);
+      (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, stmt);
     if (const svalue *sval
 	= m_new_state->m_region_model->get_gassign_result (assign_stmt,
 							    &old_ctxt))
@@ -428,6 +466,11 @@ public:
     return NULL_TREE;
   }
 
+  path_context *get_path_context () const FINAL OVERRIDE
+  {
+    return m_path_ctxt;
+  }
+
   log_user m_logger;
   exploded_graph &m_eg;
   exploded_node *m_enode_for_diag;
@@ -435,6 +478,7 @@ public:
   program_state *m_new_state;
   const sm_state_map *m_old_smap;
   sm_state_map *m_new_smap;
+  path_context *m_path_ctxt;
   stmt_finder *m_stmt_finder;
 };
 
@@ -751,9 +795,13 @@ impl_region_model_context::on_condition (const svalue *lhs,
       impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag,
 			       m_old_state, m_new_state,
 			       m_old_state->m_checker_states[sm_idx],
-			       m_new_state->m_checker_states[sm_idx]);
+			       m_new_state->m_checker_states[sm_idx],
+			       m_path_ctxt);
       sm.on_condition (&sm_ctxt,
-		       m_enode_for_diag->get_supernode (), m_stmt,
+		       (m_enode_for_diag
+			? m_enode_for_diag->get_supernode ()
+			: NULL),
+		       m_stmt,
 		       lhs, op, rhs);
     }
 }
@@ -773,7 +821,8 @@ impl_region_model_context::on_phi (const gphi *phi, tree rhs)
       impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag,
 			       m_old_state, m_new_state,
 			       m_old_state->m_checker_states[sm_idx],
-			       m_new_state->m_checker_states[sm_idx]);
+			       m_new_state->m_checker_states[sm_idx],
+			       m_path_ctxt);
       sm.on_phi (&sm_ctxt, m_enode_for_diag->get_supernode (), phi, rhs);
     }
 }
@@ -1190,7 +1239,8 @@ exploded_node::on_stmt (exploded_graph &eg,
 			const supernode *snode,
 			const gimple *stmt,
 			program_state *state,
-			uncertainty_t *uncertainty)
+			uncertainty_t *uncertainty,
+			path_context *path_ctxt)
 {
   logger *logger = eg.get_logger ();
   LOG_SCOPE (logger);
@@ -1215,7 +1265,7 @@ exploded_node::on_stmt (exploded_graph &eg,
 
   impl_region_model_context ctxt (eg, this,
 				  &old_state, state, uncertainty,
-				  stmt);
+				  path_ctxt, stmt);
 
   bool unknown_side_effects = false;
   bool terminate_path = false;
@@ -1235,13 +1285,16 @@ exploded_node::on_stmt (exploded_graph &eg,
 	= old_state.m_checker_states[sm_idx];
       sm_state_map *new_smap = state->m_checker_states[sm_idx];
       impl_sm_context sm_ctxt (eg, sm_idx, sm, this, &old_state, state,
-			       old_smap, new_smap);
+			       old_smap, new_smap, path_ctxt);
 
       /* Allow the state_machine to handle the stmt.  */
       if (sm.on_stmt (&sm_ctxt, snode, stmt))
 	unknown_side_effects = false;
     }
 
+  if (path_ctxt->terminate_path_p ())
+    return on_stmt_flags::terminate_path ();
+
   on_stmt_post (stmt, state, unknown_side_effects, &ctxt);
 
   return on_stmt_flags ();
@@ -1592,7 +1645,7 @@ exploded_node::detect_leaks (exploded_graph &eg)
 
   uncertainty_t uncertainty;
   impl_region_model_context ctxt (eg, this,
-				  &old_state, &new_state, &uncertainty,
+				  &old_state, &new_state, &uncertainty, NULL,
 				  get_stmt ());
   const svalue *result = NULL;
   new_state.m_region_model->pop_frame (NULL, &result, &ctxt);
@@ -1627,27 +1680,30 @@ exploded_node::dump_succs_and_preds (FILE *outf) const
   }
 }
 
-/* class dynamic_call_info_t : public exploded_edge::custom_info_t.  */
+/* class dynamic_call_info_t : public custom_edge_info.  */
 
-/* Implementation of exploded_edge::custom_info_t::update_model vfunc
+/* Implementation of custom_edge_info::update_model vfunc
    for dynamic_call_info_t.
 
    Update state for the dynamically discorverd calls */
 
-void
+bool
 dynamic_call_info_t::update_model (region_model *model,
-				   const exploded_edge &eedge)
+				   const exploded_edge *eedge,
+				   region_model_context *) const
 {
-  const program_state &dest_state = eedge.m_dest->get_state ();
+  gcc_assert (eedge);
+  const program_state &dest_state = eedge->m_dest->get_state ();
   *model = *dest_state.m_region_model;
+  return true;
 }
 
-/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc
+/* Implementation of custom_edge_info::add_events_to_path vfunc
    for dynamic_call_info_t.  */
 
 void
 dynamic_call_info_t::add_events_to_path (checker_path *emission_path,
-				   const exploded_edge &eedge)
+				   const exploded_edge &eedge) const
 {
   const exploded_node *src_node = eedge.m_src;
   const program_point &src_point = src_node->get_point ();
@@ -1671,21 +1727,23 @@ dynamic_call_info_t::add_events_to_path (checker_path *emission_path,
 
 }
 
-/* class rewind_info_t : public exploded_edge::custom_info_t.  */
+/* class rewind_info_t : public custom_edge_info.  */
 
-/* Implementation of exploded_edge::custom_info_t::update_model vfunc
+/* Implementation of custom_edge_info::update_model vfunc
    for rewind_info_t.
 
    Update state for the special-case of a rewind of a longjmp
    to a setjmp (which doesn't have a superedge, but does affect
    state).  */
 
-void
+bool
 rewind_info_t::update_model (region_model *model,
-			     const exploded_edge &eedge)
+			     const exploded_edge *eedge,
+			     region_model_context *) const
 {
-  const program_point &longjmp_point = eedge.m_src->get_point ();
-  const program_point &setjmp_point = eedge.m_dest->get_point ();
+  gcc_assert (eedge);
+  const program_point &longjmp_point = eedge->m_src->get_point ();
+  const program_point &setjmp_point = eedge->m_dest->get_point ();
 
   gcc_assert (longjmp_point.get_stack_depth ()
 	      >= setjmp_point.get_stack_depth ());
@@ -1693,14 +1751,15 @@ rewind_info_t::update_model (region_model *model,
   model->on_longjmp (get_longjmp_call (),
 		     get_setjmp_call (),
 		     setjmp_point.get_stack_depth (), NULL);
+  return true;
 }
 
-/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc
+/* Implementation of custom_edge_info::add_events_to_path vfunc
    for rewind_info_t.  */
 
 void
 rewind_info_t::add_events_to_path (checker_path *emission_path,
-				   const exploded_edge &eedge)
+				   const exploded_edge &eedge) const
 {
   const exploded_node *src_node = eedge.m_src;
   const program_point &src_point = src_node->get_point ();
@@ -1727,7 +1786,7 @@ rewind_info_t::add_events_to_path (checker_path *emission_path,
 
 exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
 			      const superedge *sedge,
-			      custom_info_t *custom_info)
+			      custom_edge_info *custom_info)
 : dedge<eg_traits> (src, dest), m_sedge (sedge),
   m_custom_info (custom_info)
 {
@@ -2432,7 +2491,7 @@ exploded_graph::get_or_create_node (const program_point &point,
 exploded_edge *
 exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
 			  const superedge *sedge,
-			  exploded_edge::custom_info_t *custom_info)
+			  custom_edge_info *custom_info)
 {
   if (get_logger ())
     get_logger ()->log ("creating edge EN: %i -> EN: %i",
@@ -2866,7 +2925,7 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode)
 	  uncertainty_t uncertainty;
 	  impl_region_model_context ctxt (*this, iter_enode,
 					  &state, next_state,
-					  &uncertainty, NULL);
+					  &uncertainty, NULL, NULL);
 	  const cfg_superedge *last_cfg_superedge
 	    = iter_sedge->dyn_cast_cfg_superedge ();
 	  if (last_cfg_superedge)
@@ -3095,6 +3154,72 @@ exploded_graph::maybe_create_dynamic_call (const gcall *call,
   return false;
 }
 
+/* Subclass of path_context for use within exploded_graph::process_node,
+   so that we can split states e.g. at "realloc" calls.  */
+
+class impl_path_context : public path_context
+{
+public:
+  impl_path_context (const program_state *cur_state)
+  : m_cur_state (cur_state),
+    m_terminate_path (false)
+  {
+  }
+
+  bool bifurcation_p () const
+  {
+    return m_custom_eedge_infos.length () > 0;
+  }
+
+  const program_state &get_state_at_bifurcation () const
+  {
+    gcc_assert (m_state_at_bifurcation);
+    return *m_state_at_bifurcation;
+  }
+
+  void
+  bifurcate (custom_edge_info *info) FINAL OVERRIDE
+  {
+    if (m_state_at_bifurcation)
+      /* Verify that the state at bifurcation is consistent when we
+	 split into multiple out-edges.  */
+      gcc_assert (*m_state_at_bifurcation == *m_cur_state);
+    else
+      /* Take a copy of the cur_state at the moment when bifurcation
+	 happens.  */
+      m_state_at_bifurcation
+	= std::unique_ptr<program_state> (new program_state (*m_cur_state));
+
+    /* Take ownership of INFO.  */
+    m_custom_eedge_infos.safe_push (info);
+  }
+
+  void terminate_path () FINAL OVERRIDE
+  {
+    m_terminate_path = true;
+  }
+
+  bool terminate_path_p () const FINAL OVERRIDE
+  {
+    return m_terminate_path;
+  }
+
+  const vec<custom_edge_info *> & get_custom_eedge_infos ()
+  {
+    return m_custom_eedge_infos;
+  }
+
+private:
+  const program_state *m_cur_state;
+
+  /* Lazily-created copy of the state before the split.  */
+  std::unique_ptr<program_state> m_state_at_bifurcation;
+
+  auto_vec <custom_edge_info *> m_custom_eedge_infos;
+
+  bool m_terminate_path;
+};
+
 /* The core of exploded_graph::process_worklist (the main analysis loop),
    handling one node in the worklist.
 
@@ -3150,7 +3275,7 @@ exploded_graph::process_node (exploded_node *node)
 	  {
 	    impl_region_model_context ctxt (*this, node,
 					    &state, &next_state,
-					    &uncertainty, NULL);
+					    &uncertainty, NULL, NULL);
 	    const cfg_superedge *last_cfg_superedge
 	      = point.get_from_edge ()->dyn_cast_cfg_superedge ();
 	    if (last_cfg_superedge)
@@ -3188,6 +3313,9 @@ exploded_graph::process_node (exploded_node *node)
 	   the sm-state-change occurs on an edge where the src enode has
 	   exactly one stmt, the one that caused the change. */
 	program_state next_state (state);
+
+	impl_path_context path_ctxt (&next_state);
+
 	uncertainty_t uncertainty;
 	const supernode *snode = point.get_supernode ();
 	unsigned stmt_idx;
@@ -3210,7 +3338,8 @@ exploded_graph::process_node (exploded_node *node)
 
 	    /* Process the stmt.  */
 	    exploded_node::on_stmt_flags flags
-	      = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty);
+	      = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty,
+			       &path_ctxt);
 	    node->m_num_processed_stmts++;
 
 	    /* If flags.m_terminate_path, stop analyzing; any nodes/edges
@@ -3222,7 +3351,7 @@ exploded_graph::process_node (exploded_node *node)
 	      {
 		impl_region_model_context ctxt (*this, node,
 						&old_state, &next_state,
-						&uncertainty, stmt);
+						&uncertainty, NULL, stmt);
 		program_state::detect_leaks (old_state, next_state, NULL,
 					     get_ext_state (), &ctxt);
 	      }
@@ -3238,7 +3367,9 @@ exploded_graph::process_node (exploded_node *node)
 						     &uncertainty);
 
 	    if (flag_analyzer_fine_grained
-		|| state_change_requires_new_enode_p (old_state, next_state))
+		|| state_change_requires_new_enode_p (old_state, next_state)
+		|| path_ctxt.bifurcation_p ()
+		|| path_ctxt.terminate_path_p ())
 	      {
 		program_point split_point
 		  = program_point::before_stmt (point.get_supernode (),
@@ -3282,9 +3413,66 @@ exploded_graph::process_node (exploded_node *node)
 					   point.get_call_string ())
 	     : program_point::after_supernode (point.get_supernode (),
 					       point.get_call_string ()));
-	exploded_node *next = get_or_create_node (next_point, next_state, node);
-	if (next)
-	  add_edge (node, next, NULL);
+	if (path_ctxt.terminate_path_p ())
+	  {
+	    if (logger)
+	      logger->log ("not adding node: terminating path");
+	  }
+	else
+	  {
+	    exploded_node *next
+	      = get_or_create_node (next_point, next_state, node);
+	    if (next)
+	      add_edge (node, next, NULL);
+	  }
+
+	/* If we have custom edge infos, "bifurcate" the state
+	   accordingly, potentially creating a new state/enode/eedge
+	   instances.  For example, to handle a "realloc" call, we
+	   might split into 3 states, for the "failure",
+	   "resizing in place", and "moving to a new buffer" cases.  */
+	for (auto edge_info : path_ctxt.get_custom_eedge_infos ())
+	  {
+	    if (logger)
+	      {
+		logger->start_log_line ();
+		logger->log_partial ("bifurcating for edge: ");
+		edge_info->print (logger->get_printer ());
+		logger->end_log_line ();
+	      }
+	    program_state bifurcated_new_state
+	      (path_ctxt.get_state_at_bifurcation ());
+
+	    /* Apply edge_info to state.  */
+	    impl_region_model_context
+	      bifurcation_ctxt (*this,
+				NULL, // enode_for_diag
+				&path_ctxt.get_state_at_bifurcation (),
+				&bifurcated_new_state,
+				NULL, // uncertainty_t *uncertainty
+				NULL, // path_context *path_ctxt
+				stmt);
+	    if (edge_info->update_model (bifurcated_new_state.m_region_model,
+					 NULL, /* no exploded_edge yet.  */
+					 &bifurcation_ctxt))
+	      {
+		exploded_node *next2
+		  = get_or_create_node (next_point, bifurcated_new_state, node);
+		if (next2)
+		  {
+		    /* Take ownership of edge_info.  */
+		    add_edge (node, next2, NULL, edge_info);
+		  }
+		else
+		  delete edge_info;
+	      }
+	    else
+	      {
+		if (logger)
+		  logger->log ("infeasible state, not adding node");
+		delete edge_info;
+	      }
+	  }
       }
       break;
     case PK_AFTER_SUPERNODE:
@@ -3351,6 +3539,7 @@ exploded_graph::process_node (exploded_node *node)
                                                 &state,
                                                 &next_state,
                                                 &uncertainty,
+						NULL,
                                                 point.get_stmt());
 
                 region_model *model = state.m_region_model;
@@ -3968,7 +4157,7 @@ feasibility_state::maybe_update_for_edge (logger *logger,
 	}
       else if (eedge->m_custom_info)
 	{
-	  eedge->m_custom_info->update_model (&m_model, *eedge);
+	  eedge->m_custom_info->update_model (&m_model, eedge, NULL);
 	}
     }
 
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 6890e84f985..b9c17672aec 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -37,6 +37,7 @@ class impl_region_model_context : public region_model_context
 			     const program_state *old_state,
 			     program_state *new_state,
 			     uncertainty_t *uncertainty,
+			     path_context *path_ctxt,
 
 			     const gimple *stmt,
 			     stmt_finder *stmt_finder = NULL);
@@ -76,6 +77,16 @@ class impl_region_model_context : public region_model_context
 
   void purge_state_involving (const svalue *sval) FINAL OVERRIDE;
 
+  void bifurcate (custom_edge_info *info) FINAL OVERRIDE;
+  void terminate_path () FINAL OVERRIDE;
+  const extrinsic_state *get_ext_state () const FINAL OVERRIDE
+  {
+    return &m_ext_state;
+  }
+  bool get_malloc_map (sm_state_map **out_smap,
+		       const state_machine **out_sm,
+		       unsigned *out_sm_idx) FINAL OVERRIDE;
+
   exploded_graph *m_eg;
   log_user m_logger;
   exploded_node *m_enode_for_diag;
@@ -85,6 +96,7 @@ class impl_region_model_context : public region_model_context
   stmt_finder *m_stmt_finder;
   const extrinsic_state &m_ext_state;
   uncertainty_t *m_uncertainty;
+  path_context *m_path_ctxt;
 };
 
 /* A <program_point, program_state> pair, used internally by
@@ -224,7 +236,8 @@ class exploded_node : public dnode<eg_traits>
 			 const supernode *snode,
 			 const gimple *stmt,
 			 program_state *state,
-			 uncertainty_t *uncertainty);
+			 uncertainty_t *uncertainty,
+			 path_context *path_ctxt);
   void on_stmt_pre (exploded_graph &eg,
 		    const gimple *stmt,
 		    program_state *state,
@@ -319,28 +332,9 @@ public:
 class exploded_edge : public dedge<eg_traits>
 {
  public:
-  /* Abstract base class for associating custom data with an
-     exploded_edge, for handling non-standard edges such as
-     rewinding from a longjmp, signal handlers, etc.  */
-  class custom_info_t
-  {
-  public:
-    virtual ~custom_info_t () {}
-
-    /* Hook for making .dot label more readable .  */
-    virtual void print (pretty_printer *pp) = 0;
-
-    /* Hook for updating MODEL within exploded_path::feasible_p.  */
-    virtual void update_model (region_model *model,
-			       const exploded_edge &eedge) = 0;
-
-    virtual void add_events_to_path (checker_path *emission_path,
-				     const exploded_edge &eedge) = 0;
-  };
-
   exploded_edge (exploded_node *src, exploded_node *dest,
 		 const superedge *sedge,
-		 custom_info_t *custom_info);
+		 custom_edge_info *custom_info);
   ~exploded_edge ();
   void dump_dot (graphviz_out *gv, const dump_args_t &args)
     const FINAL OVERRIDE;
@@ -356,7 +350,7 @@ class exploded_edge : public dedge<eg_traits>
      a signal is delivered to a signal-handler.
 
      Owned by this class.  */
-  custom_info_t *m_custom_info;
+  custom_edge_info *m_custom_info;
 
 private:
   DISABLE_COPY_AND_ASSIGN (exploded_edge);
@@ -365,7 +359,7 @@ private:
 /* Extra data for an exploded_edge that represents dynamic call info ( calls
    that doesn't have an underlying superedge representing the call ).  */
 
-class dynamic_call_info_t : public exploded_edge::custom_info_t
+class dynamic_call_info_t : public custom_edge_info
 {
 public:
   dynamic_call_info_t (const gcall *dynamic_call,
@@ -374,7 +368,7 @@ public:
     m_is_returning_call (is_returning_call)
   {}
 
-  void print (pretty_printer *pp) FINAL OVERRIDE
+  void print (pretty_printer *pp) const FINAL OVERRIDE
   {
     if (m_is_returning_call)
       pp_string (pp, "dynamic_return");
@@ -382,11 +376,12 @@ public:
       pp_string (pp, "dynamic_call");
   }
 
-  void update_model (region_model *model,
-		     const exploded_edge &eedge) FINAL OVERRIDE;
+  bool update_model (region_model *model,
+		     const exploded_edge *eedge,
+		     region_model_context *ctxt) const FINAL OVERRIDE;
 
   void add_events_to_path (checker_path *emission_path,
-			   const exploded_edge &eedge) FINAL OVERRIDE;
+			   const exploded_edge &eedge) const FINAL OVERRIDE;
 private:
   const gcall *m_dynamic_call;
   const bool m_is_returning_call;
@@ -396,7 +391,7 @@ private:
 /* Extra data for an exploded_edge that represents a rewind from a
    longjmp to a setjmp (or from a siglongjmp to a sigsetjmp).  */
 
-class rewind_info_t : public exploded_edge::custom_info_t
+class rewind_info_t : public custom_edge_info
 {
 public:
   rewind_info_t (const setjmp_record &setjmp_record,
@@ -405,16 +400,17 @@ public:
     m_longjmp_call (longjmp_call)
   {}
 
-  void print (pretty_printer *pp) FINAL OVERRIDE
+  void print (pretty_printer *pp) const FINAL OVERRIDE
   {
     pp_string (pp, "rewind");
   }
 
-  void update_model (region_model *model,
-		     const exploded_edge &eedge) FINAL OVERRIDE;
+  bool update_model (region_model *model,
+		     const exploded_edge *eedge,
+		     region_model_context *ctxt) const FINAL OVERRIDE;
 
   void add_events_to_path (checker_path *emission_path,
-			   const exploded_edge &eedge) FINAL OVERRIDE;
+			   const exploded_edge &eedge) const FINAL OVERRIDE;
 
   const program_point &get_setjmp_point () const
   {
@@ -829,7 +825,7 @@ public:
 				     exploded_node *enode_for_diag);
   exploded_edge *add_edge (exploded_node *src, exploded_node *dest,
 			   const superedge *sedge,
-			   exploded_edge::custom_info_t *custom = NULL);
+			   custom_edge_info *custom = NULL);
 
   per_program_point_data *
   get_or_create_per_program_point_data (const program_point &);
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index ea53c61f497..c1ff0d88bb8 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -1013,7 +1013,7 @@ program_state::on_edge (exploded_graph &eg,
   impl_region_model_context ctxt (eg, enode,
 				  &enode->get_state (),
 				  this,
-				  uncertainty,
+				  uncertainty, NULL,
 				  last_stmt);
   if (!m_region_model->maybe_update_for_edge (*succ,
 					      last_stmt,
@@ -1052,6 +1052,7 @@ program_state::push_call (exploded_graph &eg,
                                   &enode->get_state (),
                                   this,
                                   uncertainty,
+				  NULL,
                                   last_stmt);
   m_region_model->update_for_gcall (call_stmt, &ctxt);
 }
@@ -1074,6 +1075,7 @@ program_state::returning_call (exploded_graph &eg,
                                   &enode->get_state (),
                                   this,
                                   uncertainty,
+				  NULL,
                                   last_stmt);
   m_region_model->update_for_return_gcall (call_stmt, &ctxt);
 }
@@ -1152,7 +1154,7 @@ program_state::prune_for_point (exploded_graph &eg,
 	  impl_region_model_context ctxt (eg, enode_for_diag,
 					  this,
 					  &new_state,
-					  uncertainty,
+					  uncertainty, NULL,
 					  point.get_stmt ());
 	  detect_leaks (*this, new_state, NULL, eg.get_ext_state (), &ctxt);
 	}
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index e5a6cb2e154..875719f9989 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -56,6 +56,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/program-point.h"
 #include "analyzer/store.h"
 #include "analyzer/region-model.h"
+#include "analyzer/call-info.h"
 #include "gimple-pretty-print.h"
 
 #if ENABLE_ANALYZER
@@ -158,6 +159,15 @@ call_details::get_arg_string_literal (unsigned idx) const
   return NULL;
 }
 
+/* Attempt to get the fndecl used at this call, if known, or NULL_TREE
+   otherwise.  */
+
+tree
+call_details::get_fndecl_for_call () const
+{
+  return m_model->get_fndecl_for_call (m_call, m_ctxt);
+}
+
 /* Dump a multiline representation of this call to PP.  */
 
 void
@@ -486,15 +496,169 @@ region_model::impl_call_operator_delete (const call_details &cd)
     }
 }
 
-/* Handle the on_call_pre part of "realloc".  */
+/* Handle the on_call_post part of "realloc":
+
+     void *realloc(void *ptr, size_t size);
+
+   realloc(3) is awkward, since it has various different outcomes
+   that are best modelled as separate exploded nodes/edges.
+
+   We first check for sm-state, in
+   malloc_state_machine::on_realloc_call, so that we
+   can complain about issues such as realloc of a non-heap
+   pointer, and terminate the path for such cases (and issue
+   the complaints at the call's exploded node).
+
+   Assuming that these checks pass, we split the path here into
+   three special cases (and terminate the "standard" path):
+   (A) failure, returning NULL
+   (B) success, growing the buffer in-place without moving it
+   (C) success, allocating a new buffer, copying the content
+   of the old buffer to it, and freeing the old buffer.
+
+   Each of these has a custom_edge_info subclass, which updates
+   the region_model and sm-state of the destination state.  */
 
 void
-region_model::impl_call_realloc (const call_details &)
+region_model::impl_call_realloc (const call_details &cd)
 {
-  /* Currently we don't support bifurcating state, so there's no good
-     way to implement realloc(3).
-     For now, malloc_state_machine::on_realloc_call has a minimal
-     implementation to suppress false positives.  */
+  /* Three custom subclasses of custom_edge_info, for handling the various
+     outcomes of "realloc".  */
+
+  /* Concrete custom_edge_info: a realloc call that fails, returning NULL.  */
+  class failure : public failed_call_info
+  {
+  public:
+    failure (const call_details &cd)
+    : failed_call_info (cd)
+    {
+    }
+
+    bool update_model (region_model *model,
+		       const exploded_edge *,
+		       region_model_context *ctxt) const FINAL OVERRIDE
+    {
+      /* Return NULL; everything else is unchanged.  */
+      const call_details cd (get_call_details (model, ctxt));
+      const svalue *zero
+	= model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+      model->set_value (cd.get_lhs_region (),
+			zero,
+			cd.get_ctxt ());
+      return true;
+    }
+  };
+
+  /* Concrete custom_edge_info: a realloc call that succeeds, growing
+     the existing buffer without moving it.  */
+  class success_no_move : public call_info
+  {
+  public:
+    success_no_move (const call_details &cd)
+    : call_info (cd)
+    {
+    }
+
+    label_text get_desc (bool can_colorize) const FINAL OVERRIDE
+    {
+      return make_label_text (can_colorize,
+			      "when %qE succeeds, without moving buffer",
+			      get_fndecl ());
+    }
+
+    bool update_model (region_model *model,
+		       const exploded_edge *,
+		       region_model_context *ctxt) const FINAL OVERRIDE
+    {
+      /* Update size of buffer and return the ptr unchanged.  */
+      const call_details cd (get_call_details (model, ctxt));
+      const svalue *ptr_sval = cd.get_arg_svalue (0);
+      const svalue *size_sval = cd.get_arg_svalue (1);
+      if (const region *buffer_reg = ptr_sval->maybe_get_region ())
+	model->set_dynamic_extents (buffer_reg, size_sval);
+      model->set_value (cd.get_lhs_region (), ptr_sval, cd.get_ctxt ());
+      const svalue *zero
+	= model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+      return model->add_constraint (ptr_sval, NE_EXPR, zero, cd.get_ctxt ());
+    }
+  };
+
+  /* Concrete custom_edge_info: a realloc call that succeeds, freeing
+     the existing buffer and moving the content to a freshly allocated
+     buffer.  */
+  class success_with_move : public call_info
+  {
+  public:
+    success_with_move (const call_details &cd)
+    : call_info (cd)
+    {
+    }
+
+    label_text get_desc (bool can_colorize) const FINAL OVERRIDE
+    {
+      return make_label_text (can_colorize,
+			      "when %qE succeeds, moving buffer",
+			      get_fndecl ());
+    }
+    bool update_model (region_model *model,
+		       const exploded_edge *,
+		       region_model_context *ctxt) const FINAL OVERRIDE
+    {
+      const call_details cd (get_call_details (model, ctxt));
+      const svalue *old_ptr_sval = cd.get_arg_svalue (0);
+      const svalue *new_size_sval = cd.get_arg_svalue (1);
+
+      /* Create the new region.  */
+      const region *new_reg
+	= model->create_region_for_heap_alloc (new_size_sval);
+      const svalue *new_ptr_sval
+	= model->m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+      if (cd.get_lhs_type ())
+	cd.maybe_set_lhs (new_ptr_sval);
+
+      if (const region *freed_reg = old_ptr_sval->maybe_get_region ())
+	{
+	  /* Copy the data.  */
+	  const svalue *old_size_sval = model->get_dynamic_extents (freed_reg);
+	  if (old_size_sval)
+	    {
+	      const region *sized_old_reg
+		= model->m_mgr->get_sized_region (freed_reg, NULL,
+						  old_size_sval);
+	      const svalue *buffer_content_sval
+		= model->get_store_value (sized_old_reg, cd.get_ctxt ());
+	      model->set_value (new_reg, buffer_content_sval, cd.get_ctxt ());
+	    }
+
+	  /* Free the old region, so that pointers to the old buffer become
+	     invalid.  */
+
+	  /* If the ptr points to an underlying heap region, delete it,
+	     poisoning pointers.  */
+	  model->unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
+	  model->m_dynamic_extents.remove (freed_reg);
+	}
+
+      /* Update the sm-state: mark the old_ptr_sval as "freed",
+	 and the new_ptr_sval as "nonnull".  */
+      model->on_realloc_with_move (cd, old_ptr_sval, new_ptr_sval);
+
+      const svalue *zero
+	= model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+      return model->add_constraint (new_ptr_sval, NE_EXPR, zero,
+				    cd.get_ctxt ());
+    }
+  };
+
+  /* Body of region_model::impl_call_realloc.  */
+
+  if (cd.get_ctxt ())
+    {
+      cd.get_ctxt ()->bifurcate (new failure (cd));
+      cd.get_ctxt ()->bifurcate (new success_no_move (cd));
+      cd.get_ctxt ()->bifurcate (new success_with_move (cd));
+      cd.get_ctxt ()->terminate_path ();
+    }
 }
 
 /* Handle the on_call_pre part of "strcpy" and "__builtin_strcpy_chk".  */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 787f2ed33c0..3bfc4ba53ee 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1132,7 +1132,6 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
 	    return false;
 	    break;
 	  case BUILT_IN_REALLOC:
-	    impl_call_realloc (cd);
 	    return false;
 	  case BUILT_IN_STRCPY:
 	  case BUILT_IN_STRCPY_CHK:
@@ -1276,9 +1275,9 @@ region_model::on_call_post (const gcall *call,
 {
   if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
     {
+      call_details cd (call, this, ctxt);
       if (is_named_call_p (callee_fndecl, "free", call, 1))
 	{
-	  call_details cd (call, this, ctxt);
 	  impl_call_free (cd);
 	  return;
 	}
@@ -1286,7 +1285,6 @@ region_model::on_call_post (const gcall *call,
 	  || is_named_call_p (callee_fndecl, "operator delete", call, 2)
 	  || is_named_call_p (callee_fndecl, "operator delete []", call, 1))
 	{
-	  call_details cd (call, this, ctxt);
 	  impl_call_operator_delete (cd);
 	  return;
 	}
@@ -1294,10 +1292,19 @@ region_model::on_call_post (const gcall *call,
 	 __attribute__((malloc(FOO)))?  */
       if (lookup_attribute ("*dealloc", DECL_ATTRIBUTES (callee_fndecl)))
 	{
-	  call_details cd (call, this, ctxt);
 	  impl_deallocation_call (cd);
 	  return;
 	}
+      if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL)
+	  && gimple_builtin_call_types_compatible_p (call, callee_fndecl))
+	switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl))
+	  {
+	  default:
+	    break;
+	  case BUILT_IN_REALLOC:
+	    impl_call_realloc (cd);
+	    return;
+	  }
     }
 
   if (unknown_side_effects)
@@ -3765,6 +3772,19 @@ region_model::unset_dynamic_extents (const region *reg)
   m_dynamic_extents.remove (reg);
 }
 
+/* class noop_region_model_context : public region_model_context.  */
+
+void
+noop_region_model_context::bifurcate (custom_edge_info *info)
+{
+  delete info;
+}
+
+void
+noop_region_model_context::terminate_path ()
+{
+}
+
 /* struct model_merger.  */
 
 /* Dump a multiline representation of this merger to PP.  */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index f2c82b0dd80..5fabf7881e2 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -487,11 +487,15 @@ public:
 
   unsigned num_args () const;
 
+  const gcall *get_call_stmt () const { return m_call; }
+
   tree get_arg_tree (unsigned idx) const;
   tree get_arg_type (unsigned idx) const;
   const svalue *get_arg_svalue (unsigned idx) const;
   const char *get_arg_string_literal (unsigned idx) const;
 
+  tree get_fndecl_for_call () const;
+
   void dump_to_pp (pretty_printer *pp, bool simple) const;
   void dump (bool simple) const;
 
@@ -732,6 +736,11 @@ class region_model
 
   const svalue *get_capacity (const region *reg) const;
 
+  /* Implemented in sm-malloc.cc  */
+  void on_realloc_with_move (const call_details &cd,
+			     const svalue *old_ptr_sval,
+			     const svalue *new_ptr_sval);
+
  private:
   const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
   const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
@@ -867,6 +876,21 @@ class region_model_context
 
   /* Hook for clients to purge state involving SVAL.  */
   virtual void purge_state_involving (const svalue *sval) = 0;
+
+  /* Hook for clients to split state with a non-standard path.
+     Take ownership of INFO.  */
+  virtual void bifurcate (custom_edge_info *info) = 0;
+
+  /* Hook for clients to terminate the standard path.  */
+  virtual void terminate_path () = 0;
+
+  virtual const extrinsic_state *get_ext_state () const = 0;
+
+  /* Hook for clients to access the "malloc" state machine in
+     any underlying program_state.  */
+  virtual bool get_malloc_map (sm_state_map **out_smap,
+			       const state_machine **out_sm,
+			       unsigned *out_sm_idx) = 0;
 };
 
 /* A "do nothing" subclass of region_model_context.  */
@@ -899,6 +923,18 @@ public:
   uncertainty_t *get_uncertainty () OVERRIDE { return NULL; }
 
   void purge_state_involving (const svalue *sval ATTRIBUTE_UNUSED) OVERRIDE {}
+
+  void bifurcate (custom_edge_info *info) OVERRIDE;
+  void terminate_path () OVERRIDE;
+
+  const extrinsic_state *get_ext_state () const OVERRIDE { return NULL; }
+
+  bool get_malloc_map (sm_state_map **,
+		       const state_machine **,
+		       unsigned *) OVERRIDE
+  {
+    return false;
+  }
 };
 
 /* A subclass of region_model_context for determining if operations fail
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 74c6fee2638..bf5e3c365b4 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -45,6 +45,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "stringpool.h"
 #include "attribs.h"
 #include "analyzer/function-set.h"
+#include "analyzer/program-state.h"
 
 #if ENABLE_ANALYZER
 
@@ -387,6 +388,12 @@ public:
 
   static bool unaffected_by_call_p (tree fndecl);
 
+  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;
+
   standard_deallocator_set m_free;
   standard_deallocator_set m_scalar_delete;
   standard_deallocator_set m_vector_delete;
@@ -1836,54 +1843,65 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt,
     }
 }
 
-/* Implementation of realloc(3):
-
-     void *realloc(void *ptr, size_t size);
-
-   realloc(3) is awkward.
+/* Handle a call to "realloc".
+   Check for free of non-heap or mismatching allocators,
+   transitioning to the "stop" state for such cases.
 
-   We currently don't have a way to express multiple possible outcomes
-   from a function call, "bifurcating" the state such as:
-   - success: non-NULL is returned
-   - failure: NULL is returned, existing buffer is not freed.
-   or even an N-way state split e.g.:
-   - buffer grew successfully in-place
-   - buffer was successfully moved to a larger allocation
-   - buffer was successfully contracted
-   - realloc failed, returning NULL, without freeing existing buffer.
-   (PR analyzer/99260 tracks this)
-
-   Given that we can currently only express one outcome, eliminate
-   false positives by dropping state from the buffer.  */
+   Otherwise, region_model::impl_call_realloc 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 ATTRIBUTE_UNUSED,
+				       const supernode *node,
 				       const gcall *call) const
 {
-  tree ptr = gimple_call_arg (call, 0);
+  const unsigned argno = 0;
+  const deallocator *d = &m_realloc;
+
+  tree arg = gimple_call_arg (call, argno);
 
-  state_t state = sm_ctxt->get_state (call, ptr);
+  state_t state = sm_ctxt->get_state (call, arg);
 
-  /* Detect mismatches.  */
   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 != &m_free)
+      if (!astate->m_deallocators->contains_p (&m_free.m_deallocator))
 	{
 	  /* Wrong allocator.  */
-	  tree diag_ptr = sm_ctxt->get_diagnostic_tree (ptr);
+	  tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
 	  pending_diagnostic *pd
-	    = new mismatching_deallocation (*this, diag_ptr,
+	    = new mismatching_deallocation (*this, diag_arg,
 					    astate->m_deallocators,
-					    &m_realloc);
-	  sm_ctxt->warn (node, call, ptr, pd);
+					    d);
+	  sm_ctxt->warn (node, call, arg, pd);
+	  sm_ctxt->set_next_state (call, arg, m_stop);
+	  if (path_context *path_ctxt = sm_ctxt->get_path_context ())
+	    path_ctxt->terminate_path ();
 	}
     }
-
-  /* Transition ptr to "stop" state.  */
-  sm_ctxt->set_next_state (call, ptr, m_stop);
+  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 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.  */
+      tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+      sm_ctxt->warn (node, call, arg,
+		     new free_of_non_heap (*this, diag_arg,
+					   d->m_name));
+      sm_ctxt->set_next_state (call, arg, m_stop);
+      if (path_context *path_ctxt = sm_ctxt->get_path_context ())
+	path_ctxt->terminate_path ();
+    }
 }
 
 /* Implementation of state_machine::on_phi vfunc for malloc_state_machine.  */
@@ -2015,6 +2033,30 @@ 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);
+}
+
 } // anonymous namespace
 
 /* Internal interface to this file. */
@@ -2025,6 +2067,40 @@ make_malloc_state_machine (logger *logger)
   return new malloc_state_machine (logger);
 }
 
+/* Specialcase hook for handling realloc, for use by
+   region_model::impl_call_realloc::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);
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index fcbf322f502..e8cbe2d3f82 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -206,10 +206,10 @@ update_model_for_signal_handler (region_model *model,
 
 /* Custom exploded_edge info: entry into a signal-handler.  */
 
-class signal_delivery_edge_info_t : public exploded_edge::custom_info_t
+class signal_delivery_edge_info_t : public custom_edge_info
 {
 public:
-  void print (pretty_printer *pp) FINAL OVERRIDE
+  void print (pretty_printer *pp) const FINAL OVERRIDE
   {
     pp_string (pp, "signal delivered");
   }
@@ -220,15 +220,18 @@ public:
     return custom_obj;
   }
 
-  void update_model (region_model *model,
-		     const exploded_edge &eedge) FINAL OVERRIDE
+  bool update_model (region_model *model,
+		     const exploded_edge *eedge,
+		     region_model_context *) const FINAL OVERRIDE
   {
-    update_model_for_signal_handler (model, eedge.m_dest->get_function ());
+    gcc_assert (eedge);
+    update_model_for_signal_handler (model, eedge->m_dest->get_function ());
+    return true;
   }
 
   void add_events_to_path (checker_path *emission_path,
 			   const exploded_edge &eedge ATTRIBUTE_UNUSED)
-    FINAL OVERRIDE
+    const FINAL OVERRIDE
   {
     emission_path->add_event
       (new precanned_custom_event
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index 6bb036e343b..02faffbff99 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -257,6 +257,11 @@ public:
      Otherwise return NULL_TREE.  */
   virtual tree is_zero_assignment (const gimple *stmt) = 0;
 
+  virtual path_context *get_path_context () const
+  {
+    return NULL;
+  }
+
 protected:
   sm_context (int sm_idx, const state_machine &sm)
   : m_sm_idx (sm_idx), m_sm (sm) {}
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index 691316179f0..5f2fe4c6449 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -105,7 +105,8 @@ svalue::to_json () const
 tree
 svalue::maybe_get_constant () const
 {
-  if (const constant_svalue *cst_sval = dyn_cast_constant_svalue ())
+  const svalue *sval = unwrap_any_unmergeable ();
+  if (const constant_svalue *cst_sval = sval->dyn_cast_constant_svalue ())
     return cst_sval->get_constant ();
   else
     return NULL_TREE;
diff --git a/gcc/testsuite/gcc.dg/analyzer/capacity-2.c b/gcc/testsuite/gcc.dg/analyzer/capacity-2.c
index 9f92bcfc0a4..2db1b3fa200 100644
--- a/gcc/testsuite/gcc.dg/analyzer/capacity-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/capacity-2.c
@@ -8,7 +8,8 @@ void *
 test_realloc_1 (void *p, size_t new_sz)
 {
   void *q = realloc (p, new_sz);
-  __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+  __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" "failure" } */
+  /* { dg-warning "capacity: 'INIT_VAL\\(new_sz\[^\n\r\]*\\)'" "success" { target *-*-* } .-1 } */
   return q;
 }
 
@@ -18,8 +19,9 @@ test_realloc_2 (size_t sz_a, size_t sz_b)
   void *p = malloc (sz_a);
   __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_a_\[^\n\r\]*\\)'" } */
   void *q = realloc (p, sz_b);
-  __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
-  return p;  
+  __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" "failure" } */
+  /* { dg-warning "capacity: 'INIT_VAL\\(sz_b\[^\n\r\]*\\)'" "success" { target *-*-* } .-1 } */
+  return q; /* { dg-warning "leak of 'p'" } */
 }
 
 void *
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c b/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c
index c6179e98948..459357cf138 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
 /* Verify absence of false positive from -Wanalyzer-mismatching-deallocation
    on realloc(3).
    Based on https://github.com/libguestfs/libguestfs/blob/f19fd566f6387ce7e4d82409528c9dde374d25e0/daemon/command.c#L115
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c b/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c
index 3e7ffd65212..d64b0458e9e 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
 /* Verify absence of false positive from -Wanalyzer-mismatching-deallocation
    on realloc(3).
    Based on https://github.com/libguestfs/libguestfs/blob/f19fd566f6387ce7e4d82409528c9dde374d25e0/daemon/debug.c#L115
diff --git a/gcc/testsuite/gcc.dg/analyzer/realloc-1.c b/gcc/testsuite/gcc.dg/analyzer/realloc-1.c
index a6c6bfc3b22..606a19abe77 100644
--- a/gcc/testsuite/gcc.dg/analyzer/realloc-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/realloc-1.c
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-Wno-free-nonheap-object" } */
+
 typedef __SIZE_TYPE__ size_t;
 
 #define NULL ((void *)0)
@@ -20,11 +22,10 @@ void *test_1 (void *ptr)
 
 void *test_2 (void *ptr)
 {
-  void *p = malloc (1024);
-  p = realloc (p, 4096);
-  /* TODO: should warn about the leak when the above call fails (PR analyzer/99260).  */
+  void *p = malloc (1024); /* { dg-message "allocated here" } */
+  p = realloc (p, 4096); /* { dg-message "when 'realloc' fails" } */
   free (p);
-}
+} /* { dg-warning "leak of 'p'" } */ // ideally this would be on the realloc stmt
 
 void *test_3 (void *ptr)
 {
@@ -44,8 +45,8 @@ void *test_4 (void)
 int *test_5 (int *p)
 {
   *p = 42;
-  int *q = realloc (p, sizeof(int) * 4);
-  *q = 43; /* { dg-warning "possibly-NULL 'q'" "PR analyzer/99260" { xfail *-*-* } } */
+  int *q = realloc (p, sizeof(int) * 4); /* { dg-message "when 'realloc' fails" } */
+  *q = 43; /* { dg-warning "dereference of NULL 'q'" } */
   return q;
 }
 
@@ -53,3 +54,37 @@ void test_6 (size_t sz)
 {
   void *p = realloc (NULL, sz);
 } /* { dg-warning "leak of 'p'" } */
+
+/* The analyzer should complain about realloc of non-heap.  */
+
+void *test_7 (size_t sz)
+{
+  char buf[100];
+  void *p = realloc (&buf, sz); /* { dg-warning "'realloc' of '&buf' which points to memory not on the heap" } */
+  return p;  
+}
+
+/* Mismatched allocator.  */
+
+struct foo
+{
+  int m_int;
+};
+
+extern void foo_release (struct foo *);
+extern struct foo *foo_acquire (void)
+  __attribute__ ((malloc (foo_release)));
+
+void test_8 (void)
+{
+  struct foo *p = foo_acquire ();
+  void *q = realloc (p, 1024); /* { dg-warning "'p' should have been deallocated with 'foo_release' but was deallocated with 'realloc'" } */
+}
+
+/* We should complain about realloc on a freed pointer.  */
+
+void test_9 (void *p)
+{
+  free (p);
+  void *q = realloc (p, 1024); /* { dg-warning "double-'free' of 'p'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/realloc-2.c b/gcc/testsuite/gcc.dg/analyzer/realloc-2.c
new file mode 100644
index 00000000000..a39775354a3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/realloc-2.c
@@ -0,0 +1,80 @@
+#include "analyzer-decls.h"
+
+typedef __SIZE_TYPE__ size_t;
+
+#define NULL ((void *)0)
+
+extern void *malloc (size_t __size)
+  __attribute__ ((__nothrow__ , __leaf__))
+  __attribute__ ((__malloc__))
+  __attribute__ ((__alloc_size__ (1)));
+extern void *realloc (void *__ptr, size_t __size)
+  __attribute__ ((__nothrow__ , __leaf__))
+  __attribute__ ((__warn_unused_result__))
+  __attribute__ ((__alloc_size__ (2)));
+extern void free (void *__ptr)
+  __attribute__ ((__nothrow__ , __leaf__));
+
+char *test_8 (size_t sz)
+{
+  char *p, *q;
+
+  p = malloc (3);
+  if (!p)
+    return NULL;
+
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)3'" } */
+
+  p[0] = 'a';
+  p[1] = 'b';
+  p[2] = 'c';
+
+  __analyzer_eval (p[0] == 'a'); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[1] == 'b'); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[2] == 'c'); /* { dg-warning "TRUE" } */
+
+  q = realloc (p, 6);
+
+  /* We should have 3 nodes, corresponding to "failure",
+     "success without moving", and "success with moving".  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
+  
+  if (q)
+    {
+      __analyzer_dump_capacity (q); /* { dg-warning "capacity: '\\(size_t\\)6'" } */
+      q[3] = 'd';
+      q[4] = 'e';
+      q[5] = 'f';
+      if (q == p)
+	{
+	  /* "realloc" success, growing the buffer in-place.  */
+	  __analyzer_eval (p[0] == 'a'); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (p[1] == 'b'); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (p[2] == 'c'); /* { dg-warning "TRUE" } */
+	  // TODO
+	}
+      else
+	{
+	  /* "realloc" success, moving the buffer (and thus freeing "p").  */
+	  __analyzer_eval (q[0] == 'a'); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (q[1] == 'b'); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (q[2] == 'c'); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (p[0] == 'a'); /* { dg-warning "UNKNOWN" "unknown" } */
+	  /* { dg-warning "use after 'free' of 'p'" "use after free" { target *-*-* } .-1 } */
+	}
+      __analyzer_eval (q[3] == 'd'); /* { dg-warning "TRUE" } */
+      __analyzer_eval (q[4] == 'e'); /* { dg-warning "TRUE" } */
+      __analyzer_eval (q[5] == 'f'); /* { dg-warning "TRUE" } */
+    }
+  else
+    {
+      /* "realloc" failure.  p should be unchanged.  */
+      __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)3'" } */
+      __analyzer_eval (p[0] == 'a'); /* { dg-warning "TRUE" } */
+      __analyzer_eval (p[1] == 'b'); /* { dg-warning "TRUE" } */
+      __analyzer_eval (p[2] == 'c'); /* { dg-warning "TRUE" } */
+      return p;
+    }
+
+  return q;
+}
-- 
2.26.3


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [committed] analyzer: support "bifurcation"; reimplement realloc [PR99260]
  2021-08-30 22:45 [committed] analyzer: support "bifurcation"; reimplement realloc [PR99260] David Malcolm
@ 2021-09-03 18:00 ` Gerald Pfeifer
  2021-09-08 19:02   ` Gerald Pfeifer
  0 siblings, 1 reply; 3+ messages in thread
From: Gerald Pfeifer @ 2021-09-03 18:00 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches

On Mon, 30 Aug 2021, David Malcolm via Gcc-patches wrote:
> gcc/analyzer/ChangeLog:
> 	PR analyzer/99260
> 	* analyzer.h (class custom_edge_info): New class, adapted from
> 	exploded_edge::custom_info_t.  Make member functions const.
> 	Make update_model return bool, converting edge param from
> 	reference to a pointer, and adding a ctxt param.
> 	(class path_context): New class.
> 	* call-info.cc: New file.
> 	* call-info.h: New file.
> 	* engine.cc: Include "analyzer/call-info.h" and <memory>.
> 	(impl_region_model_context::impl_region_model_context): Update for
> 	new m_path_ctxt field.
> 	(impl_region_model_context::bifurcate): New.
> 	(impl_region_model_context::terminate_path): New.

I believe it is this change that is causing bootstrap to fail with

  In file included from /scratch/tmp/gerald/GCC-HEAD/gcc/analyzer/engine.cc:69:
  In file included from /usr/include/c++/v1/memory:653:
  /usr/include/c++/v1/typeinfo:346:5: error: no member named 'fancy_abort' 
    in name space 'std::__1'; did you mean simply 'fancy_abort'?
    _VSTD::abort();
    ^~~~~~~
  /usr/include/c++/v1/__config:782:15: note: expanded from macro '_VSTD'
  #define _VSTD std::_LIBCPP_ABI_NAMESPACE
                ^
  /scratch/tmp/gerald/GCC-HEAD/gcc/system.h:777:13: note: 'fancy_abort' declared here
  extern void fancy_abort (const char *, int, const char *)
              ^

This is on FreeBSD 11 with clang version 10.0.1 as system compiler and
libc++, which I assume is what triggers this?

Gerald

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [committed] analyzer: support "bifurcation"; reimplement realloc [PR99260]
  2021-09-03 18:00 ` Gerald Pfeifer
@ 2021-09-08 19:02   ` Gerald Pfeifer
  0 siblings, 0 replies; 3+ messages in thread
From: Gerald Pfeifer @ 2021-09-08 19:02 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches

On Fri, 3 Sep 2021, Gerald Pfeifer wrote:
> On Mon, 30 Aug 2021, David Malcolm via Gcc-patches wrote:
>> gcc/analyzer/ChangeLog:
>> 	PR analyzer/99260
>> 	* analyzer.h (class custom_edge_info): New class, adapted from
>> 	exploded_edge::custom_info_t.  Make member functions const.
>> 	Make update_model return bool, converting edge param from
>> 	reference to a pointer, and adding a ctxt param.
>> 	(class path_context): New class.
>> 	* call-info.cc: New file.
>> 	* call-info.h: New file.
>> 	* engine.cc: Include "analyzer/call-info.h" and <memory>.
> I believe it is this change that is causing bootstrap to fail with
> 
>   In file included from /scratch/tmp/gerald/GCC-HEAD/gcc/analyzer/engine.cc:69:
>   In file included from /usr/include/c++/v1/memory:653:
>   /usr/include/c++/v1/typeinfo:346:5: error: no member named 'fancy_abort' 
>     in name space 'std::__1'; did you mean simply 'fancy_abort'?
>     _VSTD::abort();
>     ^~~~~~~
>   /usr/include/c++/v1/__config:782:15: note: expanded from macro '_VSTD'
>   #define _VSTD std::_LIBCPP_ABI_NAMESPACE

I'm now 99% certain this patch is the trigger and also filed a bug
report:

   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102242

Good news is that I have prototype patch in testing, so if you have not 
started yet I suggest to wait and I should have an updated by tomorrow 
morning.

Gerald

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2021-09-08 19:02 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-30 22:45 [committed] analyzer: support "bifurcation"; reimplement realloc [PR99260] David Malcolm
2021-09-03 18:00 ` Gerald Pfeifer
2021-09-08 19:02   ` Gerald Pfeifer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).