PR middle-end/90923 - hash_map destroys elements without constructing them
authorMartin Sebor <msebor@redhat.com>
Mon, 1 Jul 2019 18:33:36 +0000 (18:33 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Mon, 1 Jul 2019 18:33:36 +0000 (12:33 -0600)
gcc/ChangeLog:

PR middle-end/90923
* hash-map.h (hash_map::put): On insertion invoke element ctor.
(hash_map::get_or_insert): Same.  Reformat comment.
* hash-set.h (hash_set::add): On insertion invoke element ctor.
* hash-map-tests.c (test_map_of_type_with_ctor_and_dtor): New.
  * hash-set-tests.c (test_map_of_type_with_ctor_and_dtor): New.
* hash-table.h (hash_table::operator=): Prevent copy assignment.
 (hash_table::hash_table (const hash_table&)): Use copy ctor
 instead of assignment to copy elements.

From-SVN: r272893

gcc/ChangeLog
gcc/hash-map-tests.c
gcc/hash-map.h
gcc/hash-set-tests.c
gcc/hash-set.h
gcc/hash-table.h

index caee23e7f785ad504c47ce7a90783c3835b1883c..a3bad8c42119bf579105c50306bdd1dd27ac4ce6 100644 (file)
@@ -1,3 +1,15 @@
+2019-07-01  Martin Sebor  <msebor@redhat.com>
+
+       PR middle-end/90923
+       * hash-map.h (hash_map::put): On insertion invoke element ctor.
+       (hash_map::get_or_insert): Same.  Reformat comment.
+       * hash-set.h (hash_set::add): On insertion invoke element ctor.
+       * hash-map-tests.c (test_map_of_type_with_ctor_and_dtor): New.
+       * hash-set-tests.c (test_map_of_type_with_ctor_and_dtor): New.
+       * hash-table.h (hash_table::operator=): Prevent copy assignment.
+        (hash_table::hash_table (const hash_table&)): Use copy ctor
+        instead of assignment to copy elements.
+
 2019-07-01  Wilco Dijkstra  <wdijkstr@arm.com>
            John David Anglin  <danglin@gcc.gnu.org>
 
index b79c7821684497994836fc793a8e310b5bcc47e4..5888f259b2077ce3e0c47db40c25ee6b53671edf 100644 (file)
@@ -103,12 +103,146 @@ test_map_of_strings_to_int ()
   ASSERT_EQ (1, string_map.elements ());
 }
 
+typedef struct hash_map_test_val_t
+{
+  static int ndefault;
+  static int ncopy;
+  static int nassign;
+  static int ndtor;
+
+  hash_map_test_val_t ()
+    : ptr (&ptr)
+  {
+    ++ndefault;
+  }
+
+  hash_map_test_val_t (const hash_map_test_val_t &)
+    : ptr (&ptr)
+  {
+    ++ncopy;
+  }
+
+  hash_map_test_val_t& operator= (const hash_map_test_val_t &)
+    {
+     ++nassign;
+     return *this;
+    }
+
+  ~hash_map_test_val_t ()
+    {
+     gcc_assert (ptr == &ptr);
+     ++ndtor;
+    }
+
+  void *ptr;
+} val_t;
+
+int val_t::ndefault;
+int val_t::ncopy;
+int val_t::nassign;
+int val_t::ndtor;
+
+static void
+test_map_of_type_with_ctor_and_dtor ()
+{
+  typedef hash_map <void *, val_t> Map;
+
+  {
+    /* Test default ctor.  */
+    Map m;
+    (void)&m;
+  }
+
+  ASSERT_TRUE (val_t::ndefault == 0);
+  ASSERT_TRUE (val_t::ncopy == 0);
+  ASSERT_TRUE (val_t::nassign == 0);
+  ASSERT_TRUE (val_t::ndtor == 0);
+
+  {
+    /* Test single insertion.  */
+    Map m;
+    void *p = &p;
+    m.get_or_insert (p);
+  }
+
+  ASSERT_TRUE (val_t::ndefault + val_t::ncopy == val_t::ndtor);
+
+  {
+    /* Test copy ctor.  */
+    Map m1;
+    void *p = &p;
+    val_t &rv1 = m1.get_or_insert (p);
+
+    int ncopy = val_t::ncopy;
+    int nassign = val_t::nassign;
+
+    Map m2 (m1);
+    val_t *pv2 = m2.get (p);
+
+    ASSERT_TRUE (ncopy + 1 == val_t::ncopy);
+    ASSERT_TRUE (nassign == val_t::nassign);
+
+    ASSERT_TRUE (&rv1 != pv2);
+    ASSERT_TRUE (pv2->ptr == &pv2->ptr);
+  }
+
+  ASSERT_TRUE (val_t::ndefault + val_t::ncopy == val_t::ndtor);
+
+#if 0   /* Avoid testing until bug 90959 is fixed.  */
+  {
+    /* Test copy assignment into an empty map.  */
+    Map m1;
+    void *p = &p;
+    val_t &rv1 = m1.get_or_insert (p);
+
+    int ncopy = val_t::ncopy;
+    int nassign = val_t::nassign;
+
+    Map m2;
+    m2 = m1;
+    val_t *pv2 = m2.get (p);
+
+    ASSERT_TRUE (ncopy == val_t::ncopy);
+    ASSERT_TRUE (nassign + 1 == val_t::nassign);
+
+    ASSERT_TRUE (&rv1 != pv2);
+    ASSERT_TRUE (pv2->ptr == &pv2->ptr);
+  }
+
+  ASSERT_TRUE (val_t::ndefault + val_t::ncopy == val_t::ndtor);
+
+#endif
+
+  {
+    Map m;
+    void *p = &p, *q = &q;
+    val_t &v1 = m.get_or_insert (p);
+    val_t &v2 = m.get_or_insert (q);
+
+    ASSERT_TRUE (v1.ptr == &v1.ptr && &v2.ptr == v2.ptr);
+  }
+
+  ASSERT_TRUE (val_t::ndefault + val_t::ncopy == val_t::ndtor);
+
+  {
+    Map m;
+    void *p = &p, *q = &q;
+    m.get_or_insert (p);
+    m.remove (p);
+    m.get_or_insert (q);
+    m.remove (q);
+
+    ASSERT_TRUE (val_t::ndefault + val_t::ncopy == val_t::ndtor);
+  }
+}
+
 /* Run all of the selftests within this file.  */
 
 void
 hash_map_tests_c_tests ()
 {
   test_map_of_strings_to_int ();
+  test_map_of_type_with_ctor_and_dtor ();
 }
 
 } // namespace selftest
index 588dfda04fac5d1f6df1dea59fc316eeeb6255ba..f3f1f9a89ff697e0526e9b00d1864ecd33016843 100644 (file)
@@ -21,8 +21,20 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef hash_map_h
 #define hash_map_h
 
+/* Class hash_map is a hash-value based container mapping objects of
+   KeyId type to those of the Value type.
+   Both KeyId and Value may be non-trivial (non-POD) types provided
+   a suitabe Traits class.  A few default Traits specializations are
+   provided for basic types such as integers, pointers, and std::pair.
+   Inserted elements are value-initialized either to zero for POD types
+   or by invoking their default ctor.  Removed elements are destroyed
+   by invoking their dtor.   On hash_map destruction all elements are
+   removed.  Objects of hash_map type are copy-constructible but not
+   assignable.  */
+
 template<typename KeyId, typename Value,
-        typename Traits>
+        typename Traits /* = simple_hashmap_traits<default_hash_traits<Key>,
+                                                   Value> */>
 class GTY((user)) hash_map
 {
   typedef typename Traits::key_type Key;
@@ -151,12 +163,16 @@ public:
     {
       hash_entry *e = m_table.find_slot_with_hash (k, Traits::hash (k),
                                                   INSERT);
-      bool existed = !hash_entry::is_empty (*e);
-      if (!existed)
-       e->m_key = k;
+      bool ins = hash_entry::is_empty (*e);
+      if (ins)
+       {
+         e->m_key = k;
+         new ((void *) &e->m_value) Value (v);
+       }
+      else
+       e->m_value = v;
 
-      e->m_value = v;
-      return existed;
+      return !ins;
     }
 
   /* if the passed in key is in the map return its value otherwise NULL.  */
@@ -168,8 +184,8 @@ public:
     }
 
   /* Return a reference to the value for the passed in key, creating the entry
-     if it doesn't already exist.  If existed is not NULL then it is set to false
-     if the key was not previously in the map, and true otherwise.  */
+     if it doesn't already exist.  If existed is not NULL then it is set to
+     false if the key was not previously in the map, and true otherwise.  */
 
   Value &get_or_insert (const Key &k, bool *existed = NULL)
     {
@@ -177,7 +193,10 @@ public:
                                                   INSERT);
       bool ins = Traits::is_empty (*e);
       if (ins)
-       e->m_key = k;
+       {
+         e->m_key = k;
+         new ((void *)&e->m_value) Value ();
+       }
 
       if (existed != NULL)
        *existed = !ins;
index e0d1d47805bc6de31dc3e5dab04f582c0350a1f0..c96fe538d9ff7f301625a9346f3b3c71cd6d7508 100644 (file)
@@ -134,12 +134,166 @@ test_set_of_strings ()
   ASSERT_EQ (2, t.elements ());
 }
 
+typedef struct hash_set_test_value_t
+{
+  static int ndefault;
+  static int ncopy;
+  static int nassign;
+  static int ndtor;
+
+  hash_set_test_value_t (int v = 1): pval (&val), val (v)
+  {
+    ++ndefault;
+  }
+
+  hash_set_test_value_t (const hash_set_test_value_t &rhs)
+    : pval (&val), val (rhs.val)
+  {
+    ++ncopy;
+  }
+
+  hash_set_test_value_t& operator= (const hash_set_test_value_t &rhs)
+    {
+     ++nassign;
+     val = rhs.val;
+     return *this;
+    }
+
+  ~hash_set_test_value_t ()
+    {
+     /* Verify that the value hasn't been corrupted.  */
+     gcc_assert (*pval > 0);
+     gcc_assert (pval == &val);
+     *pval = -3;
+     ++ndtor;
+    }
+
+  int *pval;
+  int val;
+} val_t;
+
+int val_t::ndefault;
+int val_t::ncopy;
+int val_t::nassign;
+int val_t::ndtor;
+
+struct value_hash_traits: int_hash<int, -1, -2>
+{
+  typedef int_hash<int, -1, -2> base_type;
+  typedef val_t                 value_type;
+  typedef value_type            compare_type;
+
+  static hashval_t hash (const value_type &v)
+  {
+    return base_type::hash (v.val);
+  }
+
+  static bool equal (const value_type &a, const compare_type &b)
+  {
+    return base_type::equal (a.val, b.val);
+  }
+
+  static void mark_deleted (value_type &v)
+  {
+    base_type::mark_deleted (v.val);
+  }
+
+  static void mark_empty (value_type &v)
+  {
+    base_type::mark_empty (v.val);
+  }
+
+  static bool is_deleted (const value_type &v)
+  {
+    return base_type::is_deleted (v.val);
+  }
+
+  static bool is_empty (const value_type &v)
+  {
+    return base_type::is_empty (v.val);
+  }
+
+  static void remove (value_type &v)
+  {
+    v.~value_type ();
+  }
+};
+
+static void
+test_set_of_type_with_ctor_and_dtor ()
+{
+  typedef hash_set <val_t, false, value_hash_traits> Set;
+
+  {
+    Set s;
+    (void)&s;
+  }
+
+  ASSERT_TRUE (val_t::ndefault == 0);
+  ASSERT_TRUE (val_t::ncopy == 0);
+  ASSERT_TRUE (val_t::nassign == 0);
+  ASSERT_TRUE (val_t::ndtor == 0);
+
+  {
+    Set s;
+    ASSERT_EQ (false, s.add (val_t ()));
+    ASSERT_EQ (true, 1 == s.elements ());
+  }
+
+  ASSERT_TRUE (val_t::ndefault + val_t::ncopy == val_t::ndtor);
+
+  {
+    Set s;
+    ASSERT_EQ (false, s.add (val_t ()));
+    ASSERT_EQ (true, s.add (val_t ()));
+    ASSERT_EQ (true, 1 == s.elements ());
+  }
+
+  ASSERT_TRUE (val_t::ndefault + val_t::ncopy == val_t::ndtor);
+
+  {
+    Set s;
+    val_t v1 (1), v2 (2), v3 (3);
+    int ndefault = val_t::ndefault;
+    int nassign = val_t::nassign;
+
+    ASSERT_EQ (false, s.add (v1));
+    ASSERT_EQ (true, s.contains (v1));
+    ASSERT_EQ (true, 1 == s.elements ());
+
+    ASSERT_EQ (false, s.add (v2));
+    ASSERT_EQ (true, s.contains (v2));
+    ASSERT_EQ (true, 2 == s.elements ());
+
+    ASSERT_EQ (false, s.add (v3));
+    ASSERT_EQ (true, s.contains (v3));
+    ASSERT_EQ (true, 3 == s.elements ());
+
+    ASSERT_EQ (true, s.add (v2));
+    ASSERT_EQ (true, s.contains (v2));
+    ASSERT_EQ (true, 3 == s.elements ());
+
+    s.remove (v2);
+    ASSERT_EQ (true, 2 == s.elements ());
+    s.remove (v3);
+    ASSERT_EQ (true, 1 == s.elements ());
+
+    /* Verify that no default ctors or assignment operators have
+       been called.  */
+    ASSERT_EQ (true, ndefault == val_t::ndefault);
+    ASSERT_EQ (true, nassign == val_t::nassign);
+  }
+
+  ASSERT_TRUE (val_t::ndefault + val_t::ncopy == val_t::ndtor);
+}
+
 /* Run all of the selftests within this file.  */
 
 void
 hash_set_tests_c_tests ()
 {
   test_set_of_strings ();
+  test_set_of_type_with_ctor_and_dtor ();
 }
 
 } // namespace selftest
index d891ed782975d3de00340e8d1c70047b344ddb58..a79a88d1ab91b1583ebbb617bdb0c7899bb63b68 100644 (file)
@@ -21,6 +21,16 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef hash_set_h
 #define hash_set_h
 
+/* Class hash_set is a hash-value based container for objects of
+   KeyId type.
+   KeyId may be a non-trivial (non-POD) type provided a suitabe Traits
+   class.  Default Traits specializations are provided for basic types
+   such as integers, pointers, and std::pair.  Inserted elements are
+   value-initialized either to zero for POD types or by invoking their
+   default ctor.  Removed elements are destroyed by invoking their dtor.
+   On hash_set destruction all elements are removed.  Objects of
+   hash_set type are copy-constructible but not assignable.  */
+
 template<typename KeyId, bool Lazy = false,
         typename Traits = default_hash_traits<KeyId> >
 class hash_set
@@ -48,7 +58,7 @@ public:
       Key *e = m_table.find_slot_with_hash (k, Traits::hash (k), INSERT);
       bool existed = !Traits::is_empty (*e);
       if (!existed)
-       *e = k;
+       new (e) Key (k);
 
       return existed;
     }
index a39fb942158f18a2e4d367f1fbd85b426bfbd6d0..0e95f5b4042f2462bed1cc926f0bf8eef54f7658 100644 (file)
@@ -35,14 +35,17 @@ along with GCC; see the file COPYING3.  If not see
       several things.
 
          - A typedef named 'value_type' to the value type (from above).
+        Provided a suitable Descriptor class it may be a user-defined,
+        non-POD type.
 
          - A static member function named 'hash' that takes a value_type
          (or 'const value_type &') and returns a hashval_t value.
 
          - A typedef named 'compare_type' that is used to test when a value
-         is found.  This type is the comparison type.  Usually, it will be the
-         same as value_type.  If it is not the same type, you must generally
-         explicitly compute hash values and pass them to the hash table.
+        is found.  This type is the comparison type.  Usually, it will be
+        the same as value_type and may be a user-defined, non-POD type.
+        If it is not the same type, you must generally explicitly compute
+        hash values and pass them to the hash table.
 
          - A static member function named 'equal' that takes a value_type
          and a compare_type, and returns a bool.  Both arguments can be
@@ -505,6 +508,9 @@ public:
     }
 
 private:
+  /* FIXME: Make the class assignable.  See pr90959.  */
+  void operator= (hash_table&);
+
   template<typename T> friend void gt_ggc_mx (hash_table<T> *);
   template<typename T> friend void gt_pch_nx (hash_table<T> *);
   template<typename T> friend void
@@ -657,7 +663,7 @@ hash_table<Descriptor, Lazy, Allocator>::hash_table (const hash_table &h,
          if (is_deleted (entry))
            mark_deleted (nentries[i]);
          else if (!is_empty (entry))
-           nentries[i] = entry;
+           new ((void*) (nentries + i)) value_type (entry);
        }
       m_entries = nentries;
     }