Commit 7b8795a1 by Martin Sebor Committed by Martin Sebor

PR middle-end/90923 - hash_map destroys elements without constructing them

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
parent 25cd9afb
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>
......
......@@ -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
......
......@@ -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;
......
......@@ -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
......
......@@ -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;
}
......
......@@ -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;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment