diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 50652b2cfa6b8376366fd8db369698a488bdab9e..df04b0ba5d6343025d056b4cf24204b1df03d107 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -1185,6 +1185,8 @@ exploded_node::on_stmt (exploded_graph &eg,
 	       to stderr.  */
 	    state->dump (eg.get_ext_state (), true);
 	  }
+	else if (is_special_named_call_p (call, "__analyzer_dump_capacity", 1))
+	  state->m_region_model->impl_call_analyzer_dump_capacity (call, &ctxt);
 	else if (is_special_named_call_p (call, "__analyzer_dump_path", 0))
 	  {
 	    /* Handle the builtin "__analyzer_dump_path" by queuing a
@@ -1237,7 +1239,6 @@ exploded_node::on_stmt (exploded_graph &eg,
   if (terminate_path)
     return on_stmt_flags::terminate_path ();
 
-  bool any_sm_changes = false;
   int sm_idx;
   sm_state_map *smap;
   FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
@@ -1276,14 +1277,12 @@ exploded_node::on_stmt (exploded_graph &eg,
       /* Allow the state_machine to handle the stmt.  */
       if (sm.on_stmt (&sm_ctxt, snode, stmt))
 	unknown_side_effects = false;
-      if (*old_smap != *new_smap)
-	any_sm_changes = true;
     }
 
   if (const gcall *call = dyn_cast <const gcall *> (stmt))
     state->m_region_model->on_call_post (call, unknown_side_effects, &ctxt);
 
-  return on_stmt_flags (any_sm_changes);
+  return on_stmt_flags ();
 }
 
 /* Consider the effect of following superedge SUCC from this node.
@@ -2925,6 +2924,36 @@ stmt_requires_new_enode_p (const gimple *stmt,
   return false;
 }
 
+/* Return true if OLD_STATE and NEW_STATE are sufficiently different that
+   we should split enodes and create an exploded_edge separating them
+   (which makes it easier to identify state changes of intereset when
+   constructing checker_paths).  */
+
+static bool
+state_change_requires_new_enode_p (const program_state &old_state,
+				   const program_state &new_state)
+{
+  /* Changes in dynamic extents signify creations of heap/alloca regions
+     and resizings of heap regions; likely to be of interest in
+     diagnostic paths.  */
+  if (old_state.m_region_model->get_dynamic_extents ()
+      != new_state.m_region_model->get_dynamic_extents ())
+    return true;
+
+  /* Changes in sm-state are of interest.  */
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+    {
+      const sm_state_map *old_smap = old_state.m_checker_states[sm_idx];
+      const sm_state_map *new_smap = new_state.m_checker_states[sm_idx];
+      if (*old_smap != *new_smap)
+	return true;
+    }
+
+  return false;
+}
+
 /* The core of exploded_graph::process_worklist (the main analysis loop),
    handling one node in the worklist.
 
@@ -3067,7 +3096,8 @@ exploded_graph::process_node (exploded_node *node)
 	    next_state = next_state.prune_for_point (*this, next_point, node,
 						     &uncertainty);
 
-	    if (flags.m_sm_changes || flag_analyzer_fine_grained)
+	    if (flag_analyzer_fine_grained
+		|| state_change_requires_new_enode_p (old_state, next_state))
 	      {
 		program_point split_point
 		  = program_point::before_stmt (point.get_supernode (),
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index c67f7b70605a47394f7310cff0602899b85edd31..eb1baefad692972b011a32cc8cba34fbb653a7df 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -198,33 +198,21 @@ class exploded_node : public dnode<eg_traits>
   /* The result of on_stmt.  */
   struct on_stmt_flags
   {
-    on_stmt_flags (bool sm_changes)
-    : m_sm_changes (sm_changes),
-      m_terminate_path (false)
+    on_stmt_flags () : m_terminate_path (false)
     {}
 
     static on_stmt_flags terminate_path ()
     {
-      return on_stmt_flags (true, true);
+      return on_stmt_flags (true);
     }
 
-    static on_stmt_flags state_change (bool any_sm_changes)
-    {
-      return on_stmt_flags (any_sm_changes, false);
-    }
-
-    /* Did any sm-changes occur handling the stmt.  */
-    bool m_sm_changes : 1;
-
     /* Should we stop analyzing this path (on_stmt may have already
        added nodes/edges, e.g. when handling longjmp).  */
     bool m_terminate_path : 1;
 
   private:
-    on_stmt_flags (bool sm_changes,
-		   bool terminate_path)
-    : m_sm_changes (sm_changes),
-      m_terminate_path (terminate_path)
+    on_stmt_flags (bool terminate_path)
+    : m_terminate_path (terminate_path)
     {}
   };
 
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 76959c135db30785ab8df4fd7f2bfbbcef510c3f..67dd785297ef1d48930a56e096c66513cd7e47f6 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -1270,6 +1270,15 @@ program_state::detect_leaks (const program_state &src_state,
   /* Purge dead svals from constraints.  */
   dest_state.m_region_model->get_constraints ()->on_liveness_change
     (maybe_dest_svalues, dest_state.m_region_model);
+
+  /* Purge dead heap-allocated regions from dynamic extents.  */
+  for (const svalue *sval : dead_svals)
+    if (const region_svalue *region_sval = sval->dyn_cast_region_svalue ())
+      {
+	const region *reg = region_sval->get_pointee ();
+	if (reg->get_kind () == RK_HEAP_ALLOCATED)
+	  dest_state.m_region_model->unset_dynamic_extents (reg);
+      }
 }
 
 #if CHECKING_P
@@ -1426,7 +1435,7 @@ test_program_state_1 ()
   program_state s (ext_state);
   region_model *model = s.m_region_model;
   const svalue *size_in_bytes
-    = mgr->get_or_create_unknown_svalue (integer_type_node);
+    = mgr->get_or_create_unknown_svalue (size_type_node);
   const region *new_reg = model->create_region_for_heap_alloc (size_in_bytes);
   const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg);
   model->set_value (model->get_lvalue (p, NULL),
@@ -1482,7 +1491,7 @@ test_program_state_merging ()
 
   region_model *model0 = s0.m_region_model;
   const svalue *size_in_bytes
-    = mgr->get_or_create_unknown_svalue (integer_type_node);
+    = mgr->get_or_create_unknown_svalue (size_type_node);
   const region *new_reg = model0->create_region_for_heap_alloc (size_in_bytes);
   const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg);
   model0->set_value (model0->get_lvalue (p, &ctxt),
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 4052bb393f82dcb68f740429ced42369d1095a6b..099520a95b0da7c6ae6d9a16a89ef124aa0d768e 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -206,6 +206,25 @@ region_model::impl_call_analyzer_describe (const gcall *call,
   warning_at (call->location, 0, "svalue: %qs", desc.m_buffer);
 }
 
+/* Handle a call to "__analyzer_dump_capacity".
+
+   Emit a warning describing the capacity of the base region of
+   the region pointed to by the 1st argument.
+   This is for use when debugging, and may be of use in DejaGnu tests.  */
+
+void
+region_model::impl_call_analyzer_dump_capacity (const gcall *call,
+						region_model_context *ctxt)
+{
+  tree t_ptr = gimple_call_arg (call, 0);
+  const svalue *sval_ptr = get_rvalue (t_ptr, ctxt);
+  const region *reg = deref_rvalue (sval_ptr, t_ptr, ctxt);
+  const region *base_reg = reg->get_base_region ();
+  const svalue *capacity = get_capacity (base_reg);
+  label_text desc = capacity->get_desc (true);
+  warning_at (call->location, 0, "capacity: %qs", desc.m_buffer);
+}
+
 /* Handle a call to "__analyzer_eval" by evaluating the input
    and dumping as a dummy warning, so that test cases can use
    dg-warning to validate the result (and so unexpected warnings will
@@ -312,6 +331,7 @@ region_model::impl_call_free (const call_details &cd)
 	 poisoning pointers.  */
       const region *freed_reg = ptr_to_region_sval->get_pointee ();
       unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
+      m_dynamic_extents.remove (freed_reg);
     }
 }
 
diff --git a/gcc/analyzer/region-model-reachability.h b/gcc/analyzer/region-model-reachability.h
index c6a21e98e61ecb1dfe5cb798c29e7aed67a4349c..57daf7255fbd8579cd923bc47f0c60b099b3d84d 100644
--- a/gcc/analyzer/region-model-reachability.h
+++ b/gcc/analyzer/region-model-reachability.h
@@ -89,6 +89,14 @@ public:
   {
     return m_mutable_svals.end ();
   }
+  hash_set<const region *>::iterator begin_mutable_base_regs ()
+  {
+    return m_mutable_base_regs.begin ();
+  }
+  hash_set<const region *>::iterator end_mutable_base_regs ()
+  {
+    return m_mutable_base_regs.end ();
+  }
 
   void dump_to_pp (pretty_printer *pp) const;
 
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 43f991a2a29c81291ea06790980e9237a8f6351a..e02a89765f0c79104e8aadb6f6332c9a8df336a2 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -66,6 +66,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/analyzer-selftests.h"
 #include "stor-layout.h"
 #include "attribs.h"
+#include "tree-object-size.h"
 
 #if ENABLE_ANALYZER
 
@@ -225,7 +226,8 @@ region_to_value_map::can_merge_with_p (const region_to_value_map &other,
 /* Ctor for region_model: construct an "empty" model.  */
 
 region_model::region_model (region_model_manager *mgr)
-: m_mgr (mgr), m_store (), m_current_frame (NULL)
+: m_mgr (mgr), m_store (), m_current_frame (NULL),
+  m_dynamic_extents ()
 {
   m_constraints = new constraint_manager (mgr);
 }
@@ -235,7 +237,8 @@ region_model::region_model (region_model_manager *mgr)
 region_model::region_model (const region_model &other)
 : m_mgr (other.m_mgr), m_store (other.m_store),
   m_constraints (new constraint_manager (*other.m_constraints)),
-  m_current_frame (other.m_current_frame)
+  m_current_frame (other.m_current_frame),
+  m_dynamic_extents (other.m_dynamic_extents)
 {
 }
 
@@ -261,6 +264,8 @@ region_model::operator= (const region_model &other)
 
   m_current_frame = other.m_current_frame;
 
+  m_dynamic_extents = other.m_dynamic_extents;
+
   return *this;
 }
 
@@ -285,6 +290,9 @@ region_model::operator== (const region_model &other) const
   if (m_current_frame != other.m_current_frame)
     return false;
 
+  if (m_dynamic_extents != other.m_dynamic_extents)
+    return false;
+
   gcc_checking_assert (hash () == other.hash ());
 
   return true;
@@ -346,6 +354,13 @@ region_model::dump_to_pp (pretty_printer *pp, bool simple,
   m_constraints->dump_to_pp (pp, multiline);
   if (!multiline)
     pp_string (pp, "}");
+
+  /* Dump sizes of dynamic regions, if any are known.  */
+  if (!m_dynamic_extents.is_empty ())
+    {
+      pp_string (pp, "dynamic_extents:");
+      m_dynamic_extents.dump_to_pp (pp, simple, multiline);
+    }
 }
 
 /* Dump a representation of this model to FILE.  */
@@ -1140,6 +1155,17 @@ region_model::handle_unrecognized_call (const gcall *call,
   /* Update bindings for all clusters that have escaped, whether above,
      or previously.  */
   m_store.on_unknown_fncall (call, m_mgr->get_store_manager ());
+
+  /* Purge dynamic extents from any regions that have escaped mutably:
+     realloc could have been called on them.  */
+  for (hash_set<const region *>::iterator
+	 iter = reachable_regs.begin_mutable_base_regs ();
+       iter != reachable_regs.end_mutable_base_regs ();
+       ++iter)
+    {
+      const region *base_reg = (*iter);
+      unset_dynamic_extents (base_reg);
+    }
 }
 
 /* Traverse the regions in this model, determining what regions are
@@ -1972,6 +1998,41 @@ region_model::check_for_writable_region (const region* dest_reg,
     }
 }
 
+/* Get the capacity of REG in bytes.  */
+
+const svalue *
+region_model::get_capacity (const region *reg) const
+{
+  switch (reg->get_kind ())
+    {
+    default:
+      break;
+    case RK_DECL:
+      {
+	const decl_region *decl_reg = as_a <const decl_region *> (reg);
+	tree decl = decl_reg->get_decl ();
+	if (TREE_CODE (decl) == SSA_NAME)
+	  {
+	    tree type = TREE_TYPE (decl);
+	    tree size = TYPE_SIZE (type);
+	    return get_rvalue (size, NULL);
+	  }
+	else
+	  {
+	    tree size = decl_init_size (decl, false);
+	    if (size)
+	      return get_rvalue (size, NULL);
+	  }
+      }
+      break;
+    }
+
+  if (const svalue *recorded = get_dynamic_extents (reg))
+    return recorded;
+
+  return m_mgr->get_or_create_unknown_svalue (sizetype);
+}
+
 /* Set the value of the region given by LHS_REG to the value given
    by RHS_SVAL.  */
 
@@ -2241,6 +2302,12 @@ region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
   if (ctxt)
     ctxt->on_condition (lhs, op, rhs);
 
+  /* If we have &REGION == NULL, then drop dynamic extents for REGION (for
+     the case where REGION is heap-allocated and thus could be NULL).  */
+  if (op == EQ_EXPR && zerop (rhs))
+    if (const region_svalue *region_sval = lhs_sval->dyn_cast_region_svalue ())
+      unset_dynamic_extents (region_sval->get_pointee ());
+
   return true;
 }
 
@@ -3146,7 +3213,8 @@ region_model::get_frame_at_index (int index) const
 
 /* Unbind svalues for any regions in REG and below.
    Find any pointers to such regions; convert them to
-   poisoned values of kind PKIND.  */
+   poisoned values of kind PKIND.
+   Also purge any dynamic extents.  */
 
 void
 region_model::unbind_region_and_descendents (const region *reg,
@@ -3167,6 +3235,15 @@ region_model::unbind_region_and_descendents (const region *reg,
 
   /* Find any pointers to REG or its descendents; convert to poisoned.  */
   poison_any_pointers_to_descendents (reg, pkind);
+
+  /* Purge dynamic extents of any base regions in REG and below
+     (e.g. VLAs and alloca stack regions).  */
+  for (auto iter : m_dynamic_extents)
+    {
+      const region *iter_reg = iter.first;
+      if (iter_reg->descendent_of_p (reg))
+	unset_dynamic_extents (iter_reg);
+    }
 }
 
 /* Implementation of BindingVisitor.
@@ -3241,6 +3318,10 @@ region_model::can_merge_with_p (const region_model &other_model,
 			   &m))
     return false;
 
+  if (!m_dynamic_extents.can_merge_with_p (other_model.m_dynamic_extents,
+					   &out_model->m_dynamic_extents))
+    return false;
+
   /* Merge constraints.  */
   constraint_manager::merge (*m_constraints,
 			      *other_model.m_constraints,
@@ -3322,7 +3403,8 @@ const region *
 region_model::create_region_for_heap_alloc (const svalue *size_in_bytes)
 {
   const region *reg = m_mgr->create_region_for_heap_alloc ();
-  record_dynamic_extents (reg, size_in_bytes);
+  assert_compat_types (size_in_bytes->get_type (), size_type_node);
+  set_dynamic_extents (reg, size_in_bytes);
   return reg;
 }
 
@@ -3333,18 +3415,38 @@ const region *
 region_model::create_region_for_alloca (const svalue *size_in_bytes)
 {
   const region *reg = m_mgr->create_region_for_alloca (m_current_frame);
-  record_dynamic_extents (reg, size_in_bytes);
+  assert_compat_types (size_in_bytes->get_type (), size_type_node);
+  set_dynamic_extents (reg, size_in_bytes);
   return reg;
 }
 
-/* Placeholder hook for recording that the size of REG is SIZE_IN_BYTES.
-   Currently does nothing.  */
+/* Record that the size of REG is SIZE_IN_BYTES.  */
 
 void
-region_model::
-record_dynamic_extents (const region *reg ATTRIBUTE_UNUSED,
-			const svalue *size_in_bytes ATTRIBUTE_UNUSED)
+region_model::set_dynamic_extents (const region *reg,
+				   const svalue *size_in_bytes)
+{
+  assert_compat_types (size_in_bytes->get_type (), size_type_node);
+  m_dynamic_extents.put (reg, size_in_bytes);
+}
+
+/* Get the recording of REG in bytes, or NULL if no dynamic size was
+   recorded.  */
+
+const svalue *
+region_model::get_dynamic_extents (const region *reg) const
 {
+  if (const svalue * const *slot = m_dynamic_extents.get (reg))
+    return *slot;
+  return NULL;
+}
+
+/* Unset any recorded dynamic size of REG.  */
+
+void
+region_model::unset_dynamic_extents (const region *reg)
+{
+  m_dynamic_extents.remove (reg);
 }
 
 /* struct model_merger.  */
@@ -4644,7 +4746,7 @@ test_state_merging ()
   {
     test_region_model_context ctxt;
     region_model model0 (&mgr);
-    tree size = build_int_cst (integer_type_node, 1024);
+    tree size = build_int_cst (size_type_node, 1024);
     const svalue *size_sval = mgr.get_or_create_constant_svalue (size);
     const region *new_reg = model0.create_region_for_heap_alloc (size_sval);
     const svalue *ptr_sval = mgr.get_ptr_svalue (ptr_type_node, new_reg);
@@ -5034,7 +5136,7 @@ test_malloc_constraints ()
   tree null_ptr = build_int_cst (ptr_type_node, 0);
 
   const svalue *size_in_bytes
-    = mgr.get_or_create_unknown_svalue (integer_type_node);
+    = mgr.get_or_create_unknown_svalue (size_type_node);
   const region *reg = model.create_region_for_heap_alloc (size_in_bytes);
   const svalue *sval = mgr.get_ptr_svalue (ptr_type_node, reg);
   model.set_value (model.get_lvalue (p, NULL), sval, NULL);
@@ -5259,7 +5361,7 @@ test_malloc ()
   const region *reg = model.create_region_for_heap_alloc (size_sval);
   const svalue *ptr = mgr.get_ptr_svalue (int_star, reg);
   model.set_value (model.get_lvalue (p, &ctxt), ptr, &ctxt);
-  // TODO: verify dynamic extents
+  ASSERT_EQ (model.get_capacity (reg), size_sval);
 }
 
 /* Verify that alloca works.  */
@@ -5294,7 +5396,7 @@ test_alloca ()
   ASSERT_EQ (reg->get_parent_region (), frame_reg);
   const svalue *ptr = mgr.get_ptr_svalue (int_star, reg);
   model.set_value (model.get_lvalue (p, &ctxt), ptr, &ctxt);
-  // TODO: verify dynamic extents
+  ASSERT_EQ (model.get_capacity (reg), size_sval);
 
   /* Verify that the pointers to the alloca region are replaced by
      poisoned values when the frame is popped.  */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 0afcb8635a9dd531db4dbedb3ee9b14d0ad298f0..8b669df00beffbba6848ef7a8961c991b5398908 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -163,6 +163,8 @@ public:
     m_hash_map.remove (reg);
   }
 
+  bool is_empty () const { return m_hash_map.is_empty (); }
+
   void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const;
   void dump (bool simple) const;
 
@@ -450,12 +452,16 @@ private:
    a tree of regions, along with their associated values.
    The representation is graph-like because values can be pointers to
    regions.
-   It also stores a constraint_manager, capturing relationships between
-   the values.  */
+   It also stores:
+   - a constraint_manager, capturing relationships between the values, and
+   - dynamic extents, mapping dynamically-allocated regions to svalues (their
+   capacities).  */
 
 class region_model
 {
  public:
+  typedef region_to_value_map dynamic_extents_t;
+
   region_model (region_model_manager *mgr);
   region_model (const region_model &other);
   ~region_model ();
@@ -495,6 +501,8 @@ class region_model
   bool impl_call_alloca (const call_details &cd);
   void impl_call_analyzer_describe (const gcall *call,
 				    region_model_context *ctxt);
+  void impl_call_analyzer_dump_capacity (const gcall *call,
+					 region_model_context *ctxt);
   void impl_call_analyzer_eval (const gcall *call,
 				region_model_context *ctxt);
   bool impl_call_builtin_expect (const call_details &cd);
@@ -606,6 +614,16 @@ class region_model
   store *get_store () { return &m_store; }
   const store *get_store () const { return &m_store; }
 
+  const dynamic_extents_t &
+  get_dynamic_extents () const
+  {
+    return m_dynamic_extents;
+  }
+  const svalue *get_dynamic_extents (const region *reg) const;
+  void set_dynamic_extents (const region *reg,
+			    const svalue *size_in_bytes);
+  void unset_dynamic_extents (const region *reg);
+
   region_model_manager *get_manager () const { return m_mgr; }
 
   void unbind_region_and_descendents (const region *reg,
@@ -629,6 +647,8 @@ class region_model
 
   void loop_replay_fixup (const region_model *dst_state);
 
+  const svalue *get_capacity (const region *reg) const;
+
  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;
@@ -676,9 +696,6 @@ class region_model
 
   void on_top_level_param (tree param, region_model_context *ctxt);
 
-  void record_dynamic_extents (const region *reg,
-			       const svalue *size_in_bytes);
-
   bool called_from_main_p () const;
   const svalue *get_initial_value_for_global (const region *reg) const;
 
@@ -693,6 +710,12 @@ class region_model
   constraint_manager *m_constraints; // TODO: embed, rather than dynalloc?
 
   const frame_region *m_current_frame;
+
+  /* Map from base region to size in bytes, for tracking the sizes of
+     dynamically-allocated regions.
+     This is part of the region_model rather than the region to allow for
+     memory regions to be resized (e.g. by realloc).  */
+  dynamic_extents_t m_dynamic_extents;
 };
 
 /* Some region_model activity could lead to warnings (e.g. attempts to use an
diff --git a/gcc/doc/analyzer.texi b/gcc/doc/analyzer.texi
index 26808ff5d22b57a281d32fbcb24d00130d7c8c7c..2ca4bf61352fd03a399e042ae7f19c8eef7658cb 100644
--- a/gcc/doc/analyzer.texi
+++ b/gcc/doc/analyzer.texi
@@ -479,6 +479,13 @@ __analyzer_dump ();
 will dump the copious information about the analyzer's state each time it
 reaches the call in its traversal of the source.
 
+@smallexample
+extern void __analyzer_dump_capacity (const void *ptr);
+@end smallexample
+
+will emit a warning describing the capacity of the base region of
+the region pointed to by the 1st argument.
+
 @smallexample
 __analyzer_dump_path ();
 @end smallexample
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
index d96b3f2f29a3fae192369f1c6203efdb3c30293f..24466939882a5a2e89262fc32287f22c02bc9908 100644
--- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
@@ -15,6 +15,9 @@ extern void __analyzer_describe (int verbosity, ...);
 /* Dump copious information about the analyzer’s state when reached.  */
 extern void __analyzer_dump (void);
 
+/* Emit a warning describing the size of the base region of (*ptr).  */
+extern void __analyzer_dump_capacity (const void *ptr);
+
 /* Dump information after analysis on all of the exploded nodes at this
    program point.
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/capacity-1.c b/gcc/testsuite/gcc.dg/analyzer/capacity-1.c
new file mode 100644
index 0000000000000000000000000000000000000000..9ea41f72e1d286f0bb46bd8cff869e0ede92e094
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/capacity-1.c
@@ -0,0 +1,106 @@
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+typedef unsigned __INT32_TYPE__ u32;
+
+void
+test_1 (void)
+{
+  char buf[16];
+  __analyzer_dump_capacity (buf); /* { dg-warning "capacity: '\\(sizetype\\)16'" } */
+}
+
+void
+test_2 (void)
+{
+  char ch;
+  __analyzer_dump_capacity (&ch); /* { dg-warning "capacity: '\\(sizetype\\)1'" } */
+}
+
+struct s3 { char buf[100]; };
+
+void
+test_3 (void)
+{
+  struct s3 s;
+  __analyzer_dump_capacity (&s); /* { dg-warning "capacity: '\\(sizetype\\)100'" } */
+}
+
+/* Capacity refers to the base region, not any offset within it.  */
+
+void
+test_4 (void)
+{
+  char buf[1024];
+  __analyzer_dump_capacity (buf + 100); /* { dg-warning "capacity: '\\(sizetype\\)1024'" } */
+}
+
+void
+test_5 (void *p)
+{
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+}
+
+void
+test_malloc (void)
+{
+  void *p = malloc (1024);
+
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)1024'" } */
+  free (p);
+}
+
+void
+test_alloca (size_t sz)
+{
+  void *p = alloca (sz);
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+}
+
+void
+test_vla (size_t sz)
+{
+  char buf[sz];
+  __analyzer_dump_capacity (buf);  /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+}
+
+static void * __attribute__((noinline))
+called_by_test_interproc_malloc (size_t a)
+{
+  return malloc (a);
+}
+
+void *
+test_interproc_malloc (size_t sz)
+{
+  void *p = called_by_test_interproc_malloc (sz);
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+  return p;
+}
+
+struct s
+{
+  u32 f1;
+  char arr[];
+};
+
+static struct s * __attribute__((noinline))
+alloc_s (size_t num)
+{
+  struct s *p = malloc (sizeof(struct s) + num);
+  return p;
+}
+
+struct s *
+test_trailing_array (void)
+{
+  struct s *p = alloc_s (5);
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(\[^\n\r\]*\\)9'" } */
+  return p;
+}
+
+void
+test_unknown_arr (int p[])
+{
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/capacity-2.c b/gcc/testsuite/gcc.dg/analyzer/capacity-2.c
new file mode 100644
index 0000000000000000000000000000000000000000..9f92bcfc0a4cafe58e1206e6b010338e7febdada
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/capacity-2.c
@@ -0,0 +1,53 @@
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+extern void might_realloc (void *);
+extern void cant_realloc (const void *);
+
+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\\)'" } */
+  return q;
+}
+
+void *
+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;  
+}
+
+void *
+test_might_realloc (void)
+{
+  void *p = malloc (1024);
+
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)1024'" } */
+
+  might_realloc (p);
+
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+
+  return p;
+}
+
+void *
+test_cant_realloc (void)
+{
+  void *p = malloc (1024);
+
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)1024'" } */
+
+  cant_realloc (p);
+
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)1024'" } */
+
+  return p;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/capacity-3.c b/gcc/testsuite/gcc.dg/analyzer/capacity-3.c
new file mode 100644
index 0000000000000000000000000000000000000000..41e282cee92deb35b99d450e04c63c3f451800f2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/capacity-3.c
@@ -0,0 +1,82 @@
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+static void __attribute__((noinline))
+__analyzer_callee_1 (size_t inner_sz)
+{
+  void *p = alloca (inner_sz);
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(outer_sz_\[^\n\r\]*\\)'" } */
+}
+
+void
+test_1 (int flag, size_t outer_sz)
+{
+  if (flag)
+    __analyzer_callee_1 (outer_sz);
+
+  /* Verify that we merge state; in particular, the dynamic size of "p"
+     in the called frame should have been purged.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
+
+void
+test_2 (int flag, size_t sz)
+{
+  if (flag)
+    {
+      void *p = malloc (sz);
+      __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+      free (p);
+      /* The dynamic size of "p" should have been purged.  */
+      __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+    }
+
+  /* Verify that we merge state.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
+/* Verify that we purge state on the NULL branch when a malloc result is
+   tested against NULL.  */
+
+void
+test_3 (size_t sz)
+{
+  void *p = malloc (sz);
+
+  if (p)
+    {
+      __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+    }
+  else
+    {
+      /* The dynamic size of "p" should have been purged
+	 due to "p" being equal to NULL.  */
+      __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+    }
+
+  free (p);
+  
+  /* The dynamic size of "p" should have been purged.  */
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+
+    /* Verify that we merge state.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
+
+/* Verify that we purge dynamic extent of a pointer when it leaks.  */
+
+static void __attribute__((noinline))
+__analyzer_callee_4 (size_t inner_sz)
+{
+  void *p = malloc (inner_sz);
+  __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(outer_sz_\[^\n\r\]*\\)'" } */
+} /* { dg-warning "leak of 'p'" } */
+
+void
+test_4 (int flag, size_t outer_sz)
+{
+  if (flag)
+    __analyzer_callee_4 (outer_sz);
+
+  /* Verify that we merge state.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}