From 2a482b6ca542dd136827475ebc1dcbefd925ecf2 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 26 Jun 2020 17:01:12 -0400
Subject: [PATCH 264/293] FIXME: add compound_svalue2

---
 gcc/analyzer/analyzer.h                       |   8 +
 gcc/analyzer/region-model2-reachability.cc    |  11 +
 gcc/analyzer/region-model2.cc                 |  57 +++-
 gcc/analyzer/region-model2.h                  | 117 +++++++-
 gcc/analyzer/store2.cc                        | 271 ++++++++++++++----
 gcc/analyzer/store2.h                         |  58 +++-
 .../gcc.dg/analyzer/compound-assignment-3.c   |   2 +-
 .../gcc.dg/analyzer/compound-assignment-4.c   |  28 ++
 .../gcc.dg/analyzer/compound-assignment-5.c   | 151 ++++++++++
 9 files changed, 636 insertions(+), 67 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/compound-assignment-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/compound-assignment-5.c

diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 8abead5f251..0fcc52f7174 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -67,6 +67,7 @@ class svalue2;
   class binop_svalue2;
   class unmergeable_svalue2;
   class widening_svalue2;
+  class compound_svalue2;
 class region2;
   class frame_region2;
   class function_region2;
@@ -195,6 +196,13 @@ public:
     return m_offset;
   }
 
+  bool operator== (const region_offset &other)
+  {
+    return (m_base_region == other.m_base_region
+	    && m_offset == other.m_offset
+	    && m_is_symbolic == other.m_is_symbolic);
+  }
+
 private:
   region_offset (const region2 *base_region, bit_offset_t offset,
 		 bool is_symbolic)
diff --git a/gcc/analyzer/region-model2-reachability.cc b/gcc/analyzer/region-model2-reachability.cc
index 348b5853eee..85d6fffd1e7 100644
--- a/gcc/analyzer/region-model2-reachability.cc
+++ b/gcc/analyzer/region-model2-reachability.cc
@@ -160,6 +160,17 @@ reachable_region2s::handle_sval (const svalue2 *sval)
 	}
       add (pointee, ptr_is_mutable);
     }
+  /* Treat all svalues within a compound_svalue2 as reachable.  */
+  if (const compound_svalue2 *compound_sval
+      = sval->dyn_cast_compound_svalue2 ())
+    {
+      for (compound_svalue2::iterator_t iter = compound_sval->begin ();
+	   iter != compound_sval->end (); ++iter)
+	{
+	  const svalue2 *iter_sval = (*iter).second;
+	  handle_sval (iter_sval);
+	}
+    }
   if (const svalue2 *cast = sval->maybe_undo_cast ())
     handle_sval (cast);
 }
diff --git a/gcc/analyzer/region-model2.cc b/gcc/analyzer/region-model2.cc
index 18e75e04662..c5d8de45537 100644
--- a/gcc/analyzer/region-model2.cc
+++ b/gcc/analyzer/region-model2.cc
@@ -979,6 +979,29 @@ unmergeable_svalue2::dump_to_pp (pretty_printer *pp, bool simple) const
     }
 }
 
+/* class compound_svalue2 : public svalue2.  */
+
+void
+compound_svalue2::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  if (simple)
+    {
+      pp_string (pp, "COMPOUND(");
+      m_map.dump_to_pp (pp, simple, false);
+      pp_character (pp, ')');
+    }
+  else
+    {
+      pp_string (pp, "compound_svalue2 (");
+      // TODO: type?
+      pp_string (pp, ", ");
+      pp_character (pp, '{');
+      m_map.dump_to_pp (pp, simple, false);
+      pp_string (pp, "}, ");
+      pp_character (pp, ')');
+    }
+}
+
 /* class region2 and its various subclasses.  */
 
 /* Get a string for KIND for use in debug dumps.  */
@@ -1554,6 +1577,14 @@ region2::calc_offset () const
 	  break;
 #endif
 
+	case RK_CAST:
+	  {
+	    const cast_region2 *cast_reg
+	      = as_a <const cast_region2 *> (iter_region);
+	    iter_region = cast_reg->get_original_region ();
+	  }
+	  continue;
+
 	default:
 	  return region_offset::make_concrete (iter_region, accum_bit_offset);
 	}
@@ -3055,9 +3086,9 @@ heap_allocated_region2::dump_to_pp (pretty_printer *pp, bool simple) const
 {
   // TODO:
   if (simple)
-    pp_string (pp, "HEAP_ALLOCATED_REGION");
+    pp_printf (pp, "HEAP_ALLOCATED_REGION(%i)", get_id ());
   else
-    pp_string (pp, "heap_allocated_region2()");
+    pp_printf (pp, "heap_allocated_region2(%i)", get_id ());
 }
 
 /* class alloca_region2 : public region2.  */
@@ -3153,6 +3184,9 @@ region_model2_manager::~region_model2_manager ()
   for (widening_values_map_t::iterator iter = m_widening_values_map.begin ();
        iter != m_widening_values_map.end (); ++iter)
     delete (*iter).second;
+  for (compound_values_map_t::iterator iter = m_compound_values_map.begin ();
+       iter != m_compound_values_map.end (); ++iter)
+    delete (*iter).second;
 
   /* Delete consolidated regions.  */
   for (fndecls_map_t::iterator iter = m_fndecls_map.begin ();
@@ -3612,6 +3646,24 @@ region_model2_manager::get_or_create_widening_svalue2 (tree type,
   return widening_sval;
 }
 
+/* FIXME.  */
+
+const svalue2 *
+region_model2_manager::
+get_or_create_compound_svalue2 (tree type,
+				const binding_map &map)
+{
+  compound_svalue2::key_t tmp_key (type, &map);
+  if (compound_svalue2 **slot = m_compound_values_map.get (tmp_key))
+    return *slot;
+  compound_svalue2 *compound_sval
+    = new compound_svalue2 (type, map);
+  /* Use make_key rather than reusing the key, so that we use a
+     ptr to compound_sval's binding_map, rather than the MAP param.  */
+  m_compound_values_map.put (compound_sval->make_key (), compound_sval);
+  return compound_sval;
+}
+
 /* region2 consolidation.  */
 
 /* FIXME.  */
@@ -3896,6 +3948,7 @@ region_model2_manager::log_stats (logger *logger, bool show_objs) const
   log_uniq_map (logger, show_objs, "unmergeable_svalue2",
 		m_unmergeable_values_map);
   log_uniq_map (logger, show_objs, "widening_svalue2", m_widening_values_map);
+  log_uniq_map (logger, show_objs, "compound_svalue2", m_compound_values_map);
   logger->log ("region2 consolidation");
   logger->log ("  next region id: %i", m_next_region_id);
   log_uniq_map (logger, show_objs, "function_region2", m_fndecls_map);
diff --git a/gcc/analyzer/region-model2.h b/gcc/analyzer/region-model2.h
index fc620f76756..0aa78545d68 100644
--- a/gcc/analyzer/region-model2.h
+++ b/gcc/analyzer/region-model2.h
@@ -141,7 +141,8 @@ struct purge_stats2
        from a control-flow perspective that it can inhibit state-merging
      placeholder_svalue2 (SK_PLACEHOLDER): for use in selftests.
      widening_svalue2 (SK_WIDENING): a merger of two svalues (possibly
-                                     in an iteration).  */
+       in an iteration).
+     compound_svalue2 (SK_COMPOUND): a mapping of bit-ranges to svalues.  */
 
 /* An abstract base class representing a value held by a region of memory.  */
 
@@ -163,7 +164,8 @@ public:
    SK_SUB,
    SK_UNMERGEABLE,
    SK_PLACEHOLDER,
-   SK_WIDENING
+   SK_WIDENING,
+   SK_COMPOUND
   };
 
   virtual ~svalue2 () {}
@@ -207,6 +209,8 @@ public:
   dyn_cast_unmergeable_svalue2 () const { return NULL; }
   virtual const widening_svalue2 *
   dyn_cast_widening_svalue2 () const { return NULL; }
+  virtual const compound_svalue2 *
+  dyn_cast_compound_svalue2 () const { return NULL; }
 
   tree maybe_get_constant () const;
   const svalue2 *maybe_undo_cast () const;
@@ -1055,6 +1059,97 @@ template <> struct default_hash_traits<widening_svalue2::key_t>
 
 namespace ana {
 
+/* Concrete subclass of svalue2 representing a mapping of bit-ranges
+   to svalues, analogous to a cluster within the store.
+
+   This is for use in places where we want to represent a store-like
+   mapping, but are required to use an svalue2, such as when handling
+   compound assignments and compound return values.
+
+   Instances of this class shouldn't be bound as-is into the store;
+   instead they should be unpacked.  Similarly, they should not be
+   nested.  */
+
+class compound_svalue2 : public svalue2
+{
+public:
+  typedef binding_map::iterator_t iterator_t;
+
+  /* A support class for uniquifying instances of compound_svalue2.
+     Note that to avoid copies, keys store pointers to binding_maps,
+     rather than the maps themselves.  */
+  struct key_t
+  {
+    key_t (tree type, const binding_map *map_ptr)
+    : m_type (type), m_map_ptr (map_ptr)
+    {}
+
+    hashval_t hash () const
+    {
+      return 42; // FIXME
+    }
+
+    bool operator== (const key_t &other) const
+    {
+      return (m_type == other.m_type
+	      && *m_map_ptr == *other.m_map_ptr);
+    }
+
+    void mark_deleted () { m_type = reinterpret_cast<tree> (1); }
+    void mark_empty () { m_type = NULL_TREE; }
+    bool is_deleted () const { return m_type == reinterpret_cast<tree> (1); }
+    bool is_empty () const { return m_type == NULL_TREE; }
+
+    tree m_type;
+    const binding_map *m_map_ptr;
+  };
+
+  compound_svalue2 (tree type, const binding_map &map)
+  : svalue2 (type), m_map (map)
+  {
+  }
+
+  enum kind get_kind () const FINAL OVERRIDE { return SK_COMPOUND; }
+  const compound_svalue2 *dyn_cast_compound_svalue2 () const { return this; }
+
+  iterator_t begin () const { return m_map.begin (); }
+  iterator_t end () const { return m_map.end (); }
+
+#if 0
+  void dump_dot_to_pp (const region_model2 &model,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+#endif
+
+  void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
+
+  struct key_t make_key () const
+  {
+    return key_t (get_type (), &m_map);
+  }
+
+ private:
+  binding_map m_map;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <compound_svalue2 *>::test (svalue2 *sval)
+{
+  return sval->get_kind () == svalue2::SK_COMPOUND;
+}
+
+template <> struct default_hash_traits<compound_svalue2::key_t>
+: public member_function_hash_traits<compound_svalue2::key_t>
+{
+  static const bool empty_zero_p = true;
+};
+
+namespace ana {
+
 /* Region and its subclasses.
 
    The class hierarchy looks like this (using indentation to show
@@ -1820,6 +1915,18 @@ private:
   const region2 *m_original_region;
 };
 
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const cast_region2 *>::test (const region2 *reg)
+{
+  return reg->get_kind () == region2::RK_CAST;
+}
+
+namespace ana {
+
 /* An untyped region dynamically allocated on the heap via "malloc"
    or similar.  */
 // FIXME: ...but could be NULL
@@ -1963,6 +2070,8 @@ public:
 						 const program_point &point,
 						 const svalue2 *base_svalue,
 						 const svalue2 *iter_svalue);
+  const svalue2 *get_or_create_compound_svalue2 (tree type,
+						 const binding_map &map);
 
   /* region2 consolidation.  */
   const stack_region2 * get_stack_region () const { return &m_stack_region; }
@@ -2062,6 +2171,10 @@ private:
     widening_values_map_t;
   widening_values_map_t m_widening_values_map;
 
+  typedef hash_map<compound_svalue2::key_t,
+		   compound_svalue2 *> compound_values_map_t;
+  compound_values_map_t m_compound_values_map;
+
   /* region2 consolidation.  */
 
   code_region2 m_code_region;
diff --git a/gcc/analyzer/store2.cc b/gcc/analyzer/store2.cc
index aca5003f363..6d916664b01 100644
--- a/gcc/analyzer/store2.cc
+++ b/gcc/analyzer/store2.cc
@@ -233,31 +233,20 @@ simplify_for_binding (const svalue2 *sval)
   return sval;
 }
 
-/* class binding_cluster2.  */
+/* class binding_map.  */
 
 /* FIXME.  */
 
-binding_cluster2::binding_cluster2 (const binding_cluster2 &other)
-: m_base_region (other.m_base_region), m_map (other.m_map),
-  m_escaped (other.m_escaped), m_touched (other.m_touched)
+binding_map::binding_map (const binding_map &other)
+: m_map (other.m_map)
 {
-#if 0
-  for (map_t::iterator iter = other.m_map.begin (); iter != other.m_map.end ();
-       ++iter)
-    {
-      const binding_key2 *key = (*iter).first;
-      const svalue2 *sval = (*iter).second;
-      m_map.put (key, sval);
-    }
-#endif
 }
 
 /* FIXME.  */
 
-binding_cluster2&
-binding_cluster2::operator= (const binding_cluster2 &other)
+binding_map&
+binding_map::operator=(const binding_map &other)
 {
-  gcc_assert (m_base_region == other.m_base_region);
   /* For now, assume we only ever copy to an empty cluster.  */
   gcc_assert (m_map.elements () == 0);
   for (map_t::iterator iter = other.m_map.begin (); iter != other.m_map.end ();
@@ -267,15 +256,13 @@ binding_cluster2::operator= (const binding_cluster2 &other)
       const svalue2 *sval = (*iter).second;
       m_map.put (key, sval);
     }
-  m_escaped = other.m_escaped;
-  m_touched = other.m_touched;
   return *this;
 }
 
 /* FIXME.  */
 
 bool
-binding_cluster2::operator== (const binding_cluster2 &other) const
+binding_map::operator== (const binding_map &other) const
 {
   if (m_map.elements () != m_map.elements ())
     return false;
@@ -296,17 +283,9 @@ binding_cluster2::operator== (const binding_cluster2 &other) const
 
 /* FIXME.  */
 
-bool
-binding_cluster2::symbolic_p () const
-{
-  return m_base_region->get_kind () == region2::RK_SYMBOLIC;
-}
-
-/* FIXME.  */
-
 void
-binding_cluster2::dump_to_pp (pretty_printer *pp, bool simple,
-			      bool multiline) const
+binding_map::dump_to_pp (pretty_printer *pp, bool simple,
+			 bool multiline) const
 {
   auto_vec <const binding_key2 *> binding_key2s;
   for (map_t::iterator iter = m_map.begin ();
@@ -315,30 +294,8 @@ binding_cluster2::dump_to_pp (pretty_printer *pp, bool simple,
       const binding_key2 *key = (*iter).first;
       binding_key2s.safe_push (key);
     }
-
   binding_key2s.qsort (binding_key2::cmp_ptrs);
 
-  if (m_escaped)
-    {
-      if (multiline)
-	{
-	  pp_string (pp, "    ESCAPED");
-	  pp_newline (pp);
-	}
-      else
-	pp_string (pp, "(ESCAPED)");
-    }
-  if (m_touched)
-    {
-      if (multiline)
-	{
-	  pp_string (pp, "    TOUCHED");
-	  pp_newline (pp);
-	}
-      else
-	pp_string (pp, "(TOUCHED)");
-    }
-
   const binding_key2 *key;
   unsigned i;
   FOR_EACH_VEC_ELT (binding_key2s, i, key)
@@ -371,6 +328,93 @@ binding_cluster2::dump_to_pp (pretty_printer *pp, bool simple,
     }
 }
 
+/* Dump a multiline representation of this binding_map to stderr.  */
+
+DEBUG_FUNCTION void
+binding_map::dump (bool simple) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp, simple, true);
+  pp_newline (&pp);
+  pp_flush (&pp);
+}
+
+/* class binding_cluster2.  */
+
+/* FIXME.  */
+
+binding_cluster2::binding_cluster2 (const binding_cluster2 &other)
+: m_base_region (other.m_base_region), m_map (other.m_map),
+  m_escaped (other.m_escaped), m_touched (other.m_touched)
+{
+}
+
+/* FIXME.  */
+
+binding_cluster2&
+binding_cluster2::operator= (const binding_cluster2 &other)
+{
+  gcc_assert (m_base_region == other.m_base_region);
+  m_map = other.m_map;
+  m_escaped = other.m_escaped;
+  m_touched = other.m_touched;
+  return *this;
+}
+
+/* FIXME.  */
+
+bool
+binding_cluster2::operator== (const binding_cluster2 &other) const
+{
+  if (m_map != other.m_map)
+    return false;
+
+  // FIXME: m_base_region, m_escaped, m_touched
+
+  return true;
+}
+
+/* FIXME.  */
+
+bool
+binding_cluster2::symbolic_p () const
+{
+  return m_base_region->get_kind () == region2::RK_SYMBOLIC;
+}
+
+/* FIXME.  */
+
+void
+binding_cluster2::dump_to_pp (pretty_printer *pp, bool simple,
+			      bool multiline) const
+{
+  if (m_escaped)
+    {
+      if (multiline)
+	{
+	  pp_string (pp, "    ESCAPED");
+	  pp_newline (pp);
+	}
+      else
+	pp_string (pp, "(ESCAPED)");
+    }
+  if (m_touched)
+    {
+      if (multiline)
+	{
+	  pp_string (pp, "    TOUCHED");
+	  pp_newline (pp);
+	}
+      else
+	pp_string (pp, "(TOUCHED)");
+    }
+
+  m_map.dump_to_pp (pp, simple, multiline);
+}
+
 /* Dump a multiline representation of this binding_cluster2 to stderr.  */
 
 DEBUG_FUNCTION void
@@ -391,11 +435,54 @@ binding_cluster2::dump (bool simple) const
 
 void
 binding_cluster2::bind (store2_manager *mgr,
-			const region2 *reg, const svalue2 *val,
+			const region2 *reg, const svalue2 *sval,
 			binding_kind kind)
 {
+  /* Unpack compound_svals when binding them, so that we bind them
+     element-wise.  */
+  if (const compound_svalue2 *compound_sval
+      = sval->dyn_cast_compound_svalue2 ())
+    {
+      region_offset reg_offset = reg->get_offset ();
+      if (reg_offset.symbolic_p ())
+	{
+	  /* TODO: offset the bound values to be relative to this
+	     region within the cluster.
+	     For now, merely mark it as symbolically touched, and clear
+	     all bindings.  */
+	  m_touched = true;
+	  clobber_region (mgr, reg);
+	  return;
+	}
+
+      for (map_t::iterator iter = compound_sval->begin ();
+	   iter != compound_sval->end (); ++iter)
+	{
+	  const binding_key2 *iter_key = (*iter).first;
+	  const svalue2 *iter_sval = (*iter).second;
+
+	  if (const concrete_binding2 *concrete_key
+	      = iter_key->dyn_cast_concrete_binding2 ())
+	    {
+	      bit_offset_t effective_start
+		= (concrete_key->get_start_bit_offset ()
+		   + reg_offset.get_bit_offset ());
+	      const concrete_binding2 *effective_concrete_key
+		= mgr->get_concrete_binding (effective_start,
+					     concrete_key->get_size_in_bits (),
+					     iter_key->get_kind ());
+	      m_map.put (effective_concrete_key, iter_sval);
+	    }
+	  else
+	    gcc_unreachable (); // TODO
+	  if (iter_key->symbolic_p ())
+	    m_touched = true;
+	}
+      return;
+    }
+
   const binding_key2 *binding = binding_key2::make (mgr, reg, kind);
-  m_map.put (binding, val);
+  m_map.put (binding, sval);
   if (binding->symbolic_p ())
     m_touched = true;
 }
@@ -463,10 +550,7 @@ binding_cluster2::get_binding (store2_manager *mgr,
 			       binding_kind kind) const
 {
   const binding_key2 *binding = binding_key2::make (mgr, reg, kind);
-  const svalue2 **slot = const_cast<map_t &> (m_map).get (binding);
-  if (slot)
-    return *slot;
-  return NULL;
+  return m_map.get (binding);
 }
 
 /* FIXME.  */
@@ -524,10 +608,79 @@ binding_cluster2::get_any_binding (store2_manager *mgr,
       return rmm_mgr->get_or_create_unknown_svalue2 (reg->get_type ());
     }
 
+  /* TODO: WIP.  */
+  if (const svalue2 *compound_sval = maybe_get_compound_binding (mgr, reg))
+    return compound_sval;
+
   /* Otherwise, the initial value, or uninitialized.  */
   return NULL;
 }
 
+/* Attempt to get a compound_svalue2 for the bindings within the cluster
+   affecting REG (which could be the base region itself).
+
+   Create a compound_svalue2 with the subset of bindings the affect REG,
+   offsetting them so that the offsets are relative to the start of REG
+   within the cluster.
+
+   For example, REG could be one element within an array of structs.
+
+   Return the resulting compound_svalue2, or NULL if there's a problem.  */
+
+const svalue2 *
+binding_cluster2::maybe_get_compound_binding (store2_manager *mgr,
+					      const region2 *reg) const
+{
+  binding_map map;
+
+  region_offset cluster_offset = m_base_region->get_offset ();
+  if (cluster_offset.symbolic_p ())
+    return NULL; // TODO
+  region_offset reg_offset = reg->get_offset ();
+  if (reg_offset.symbolic_p ())
+    return NULL; // TODO
+
+  for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
+    {
+      const binding_key2 *key = (*iter).first;
+      const svalue2 *sval = (*iter).second;
+
+      if (const concrete_binding2 *concrete_key
+	  = key->dyn_cast_concrete_binding2 ())
+	{
+	  /* Skip bindings that are outside the bit range of REG.  */
+	  if (concrete_key->get_start_bit_offset ()
+	      < reg_offset.get_bit_offset ())
+	    continue;
+	  bit_size_t reg_bit_size;
+	  if (reg->get_bit_size (&reg_bit_size))
+	    if (concrete_key->get_start_bit_offset ()
+	      >= reg_offset.get_bit_offset () + reg_bit_size)
+	    continue;
+
+	  /* Get offset of KEY relative to REG, rather than to
+	     the cluster.  */
+	  bit_offset_t relative_start
+	    = (concrete_key->get_start_bit_offset ()
+	       - reg_offset.get_bit_offset ());
+	  const concrete_binding2 *offset_concrete_key
+	    = mgr->get_concrete_binding (relative_start,
+					 concrete_key->get_size_in_bits (),
+					 key->get_kind ());
+	  map.put (offset_concrete_key, sval);
+	}
+      else
+	return NULL; // TODO
+    }
+
+  if (map.elements () == 0)
+    return NULL;
+
+  region_model2_manager *sval_mgr = mgr->get_svalue2_manager ();
+  return sval_mgr->get_or_create_compound_svalue2 (reg->get_type (), map);
+}
+
+
 /* Populate OUT with all bindings within this cluster that overlap REG.  */
 
 void
@@ -786,9 +939,7 @@ binding_cluster2::get_representative_path_vars (const region_model2 *model,
 const svalue2 *
 binding_cluster2::get_any_value (const binding_key2 *key) const
 {
-  if (const svalue2 **slot = const_cast <map_t &> (m_map).get (key))
-    return *slot;
-  return NULL;
+  return m_map.get (key);
 }
 
 /* If this cluster has a single direct binding for the whole of the region,
diff --git a/gcc/analyzer/store2.h b/gcc/analyzer/store2.h
index 48e71c7696e..ce1490b78bb 100644
--- a/gcc/analyzer/store2.h
+++ b/gcc/analyzer/store2.h
@@ -267,6 +267,53 @@ private:
   const region2 *m_region;
 };
 
+/* FIXME.
+   For use by binding_cluster2 and compound_svalue2.  */
+
+class binding_map
+{
+public:
+  typedef hash_map <const binding_key2 *, const svalue2 *> map_t;
+  typedef map_t::iterator iterator_t;
+
+  binding_map () : m_map () {}
+  binding_map (const binding_map &other);
+  binding_map& operator=(const binding_map &other);
+
+  bool operator== (const binding_map &other) const;
+  bool operator!= (const binding_map &other) const
+  {
+    return !(*this == other);
+  }
+
+  const svalue2 *get (const binding_key2 *key) const
+  {
+    const svalue2 **slot = const_cast<map_t &> (m_map).get (key);
+    if (slot)
+      return *slot;
+    else
+      return NULL;
+  }
+  bool put (const binding_key2 *k, const svalue2 *v)
+  {
+    gcc_assert (v);
+    return m_map.put (k, v);
+  }
+
+  void remove (const binding_key2 *k) { m_map.remove (k); }
+  void empty () { m_map.empty (); }
+
+  iterator_t begin () const { return m_map.begin (); }
+  iterator_t end () const { return m_map.end (); }
+  size_t elements () const { return m_map.elements (); }
+
+  void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const;
+  void dump (bool simple) const;
+
+private:
+  map_t m_map;
+};
+
 /* Concept: BindingVisitor, for use by binding_cluster2::for_each_binding
    and store2::for_each_binding.
 
@@ -280,6 +327,9 @@ private:
 class binding_cluster2
 {
 public:
+  typedef hash_map <const binding_key2 *, const svalue2 *> map_t;
+  typedef map_t::iterator iterator_t;
+
   binding_cluster2 (const region2 *base_region)
   : m_base_region (base_region), m_map (),
     m_escaped (false), m_touched (false) {}
@@ -312,6 +362,8 @@ public:
 					enum binding_kind kind) const;
   const svalue2 *get_any_binding (store2_manager *mgr,
 				  const region2 *reg) const;
+  const svalue2 *maybe_get_compound_binding (store2_manager *mgr,
+					     const region2 *reg) const;
 
   void remove_overlapping_bindings (store2_manager *mgr, const region2 *reg);
 
@@ -359,6 +411,9 @@ public:
       }
   }
 
+  iterator_t begin () const { return m_map.begin (); }
+  iterator_t end () const { return m_map.end (); }
+
 private:
   const svalue2 *get_any_value (const binding_key2 *key) const;
   void get_overlapping_bindings (store2_manager *mgr, const region2 *reg,
@@ -366,8 +421,7 @@ private:
 
   const region2 *m_base_region;
 
-  typedef hash_map <const binding_key2 *, const svalue2 *> map_t;
-  map_t m_map;
+  binding_map m_map;
 
   /* Has a pointer to this cluster "escaped" into a part of the program
      we don't know about (via a call to a function with an unknown body).
diff --git a/gcc/testsuite/gcc.dg/analyzer/compound-assignment-3.c b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-3.c
index 5083faa9ce2..49259262c4a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/compound-assignment-3.c
+++ b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-3.c
@@ -22,4 +22,4 @@ test_2 (void)
 {
   struct union_wrapper uw2;
   uw2.u.ptr = malloc (1024);
-} /* { dg-warning "leak of '\\(void \\*\\)uw2.u'" } */
+} /* { dg-warning "leak of 'uw2.u.ptr'" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/compound-assignment-4.c b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-4.c
new file mode 100644
index 00000000000..5c0a5f9d47c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-4.c
@@ -0,0 +1,28 @@
+#include "analyzer-decls.h"
+
+struct coord
+{
+  int x;
+  int y;
+};
+
+void test_1 (void)
+{
+  struct coord arr[16];
+
+  arr[2].y = 4;
+  arr[3].x = 5;
+  arr[3].y = 6;
+  arr[4].x = 7;
+  arr[6].y = 8;
+  arr[8].x = 9;
+
+  arr[7] = arr[3];
+
+  __analyzer_eval (arr[7].x == 5); /* { dg-warning "TRUE" } */
+  __analyzer_eval (arr[7].y == 6); /* { dg-warning "TRUE" } */
+
+  /* Make sure we don't touch the neighbors.  */
+  __analyzer_eval (arr[6].y == 8); /* { dg-warning "TRUE" } */
+  __analyzer_eval (arr[8].x == 9); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/compound-assignment-5.c b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-5.c
new file mode 100644
index 00000000000..ba711404c2c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/compound-assignment-5.c
@@ -0,0 +1,151 @@
+#include "analyzer-decls.h"
+
+struct coord
+{
+  int x;
+  int y;
+};
+
+/* Awkward cases.
+   WIP.
+
+ */
+
+/* TODO: what about e.g. zero fill? */
+
+/* Copying from one on-stack array to another.  */
+
+void test_1 (void)
+{
+  struct coord arr_a[16];
+  struct coord arr_b[16];
+  arr_a[3].x = 5;
+  arr_a[3].y = 6;
+
+  arr_b[7] = arr_a[3];
+
+  __analyzer_eval (arr_b[7].x == 5); /* { dg-warning "TRUE" } */
+  __analyzer_eval (arr_b[7].y == 6); /* { dg-warning "TRUE" } */
+}
+
+/* Copying from an on-stack array to a global array.  */
+
+struct coord glob_arr[16];
+
+void test_2 (void)
+{
+  struct coord arr[16];
+  arr[3].x = 5;
+  arr[3].y = 6;
+
+  glob_arr[7] = arr[3];
+
+  __analyzer_eval (glob_arr[7].x == 5); /* { dg-warning "TRUE" } */
+  __analyzer_eval (glob_arr[7].y == 6); /* { dg-warning "TRUE" } */
+}
+
+/* Copying from a partially initialized on-stack array to a global array.  */
+
+struct coord glob_arr[16];
+
+void test_3 (void)
+{
+  struct coord arr[16];
+  arr[3].y = 6;
+
+  glob_arr[7] = arr[3]; // or should the uninit warning be here?
+
+  __analyzer_eval (glob_arr[7].x); /* { dg-warning "uninitialized" "uninit" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
+  __analyzer_eval (glob_arr[7].y == 6); /* { dg-warning "TRUE" } */
+}
+
+/* Symbolic bindings: copying from one array to another.  */
+
+struct coord glob_arr[16];
+
+void test_4 (int i)
+{
+  struct coord arr_a[16];
+  struct coord arr_b[16];
+  arr_a[i].x = 5;
+  arr_a[i].y = 6;
+  __analyzer_eval (arr_a[i].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
+  __analyzer_eval (arr_a[i].y == 6); /* { dg-warning "TRUE" } */
+
+  arr_b[i] = arr_a[i];
+
+  __analyzer_eval (arr_b[i].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
+  __analyzer_eval (arr_b[i].y == 6); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
+}
+
+/* Symbolic bindings: copying within an array: symbolic src and dest  */
+
+struct coord glob_arr[16];
+
+void test_5a (int i, int j)
+{
+  struct coord arr[16];
+  arr[i].x = 5;
+  arr[i].y = 6;
+
+  arr[j] = arr[i];
+
+  __analyzer_eval (arr[j].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
+  __analyzer_eval (arr[j].y == 6); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
+}
+
+/* Symbolic bindings: copying within an array: symbolic src, concrete dest.  */
+
+struct coord glob_arr[16];
+
+void test_5b (int i)
+{
+  struct coord arr[16];
+  arr[i].x = 5;
+  arr[i].y = 6;
+
+  arr[3] = arr[i];
+
+  __analyzer_eval (arr[3].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
+  __analyzer_eval (arr[3].y == 6); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
+}
+
+/* Symbolic bindings: copying within an array: concrete src, symbolic dest.  */
+
+struct coord glob_arr[16];
+
+void test_5c (int i)
+{
+  struct coord arr[16];
+  arr[3].x = 5;
+  arr[3].y = 6;
+
+  arr[i] = arr[3];
+
+  __analyzer_eval (arr[i].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
+  __analyzer_eval (arr[i].y == 6); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
+}
+
+/* No info on the subregion being copied, and hence
+   binding_cluster2::maybe_get_compound_binding should return NULL.  */
+
+void test_6 (void)
+{
+  struct coord arr[16];
+  arr[7] = glob_arr[3];
+
+  __analyzer_eval (arr[7].x == 5); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (arr[7].y == 6); /* { dg-warning "UNKNOWN" } */
+}
+
+// etc
-- 
2.26.2

