[Bug bootstrap/92762] New: hash_table::empty_slow invokes assignment on invalid objects
msebor at gcc dot gnu.org
gcc-bugzilla@gcc.gnu.org
Tue Dec 3 00:50:00 GMT 2019
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92762
Bug ID: 92762
Summary: hash_table::empty_slow invokes assignment on invalid
objects
Product: gcc
Version: 10.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: bootstrap
Assignee: unassigned at gcc dot gnu.org
Reporter: msebor at gcc dot gnu.org
Target Milestone: ---
A similar bug to pr92761 I ran into: The GCC internal hash_table::empty_slow()
member function invokes the assignment operator on invalid/empty elements. The
following test case inserted into the C++ parser (where I ran into the problem)
reproduces the bug:
struct S
{
S (): p (&p) { }
S (const S &s): p (&p) { gcc_assert (s.p == &s.p); }
S& operator= (const S &s) {
gcc_assert (p == &p); <<< this fails because p is null
gcc_assert (s.p == &s.p);
return *this; }
~S () { gcc_assert (p == &p); }
void *p;
};
hash_map<tree, S> x;
static void test_hash_table ()
{
for (int i = 1; i != 8; ++i)
x.put ((tree)i, S ());
x.empty ();
}
/* Parse one entire translation unit. */
void
c_parse_file (void)
{
test_hash_table ();
...
}
internal compiler error: in operator=, at cp/parser.c:43419
0xaefdf1 S::operator=(S const&)
/src/gcc/61339/gcc/cp/parser.c:43419
0xaf4c6e hash_map<tree_node*, S,
simple_hashmap_traits<default_hash_traits<tree_node*>, S>
>::hash_entry::operator=(hash_map<tree_node*, S,
simple_hashmap_traits<default_hash_traits<tree_node*>, S> >::hash_entry const&)
/src/gcc/61339/gcc/hash-map.h:42
0xaf51b6 hash_table<hash_map<tree_node*, S,
simple_hashmap_traits<default_hash_traits<tree_node*>, S> >::hash_entry, false,
xcallocator>::empty_slow()
/src/gcc/61339/gcc/hash-table.h:878
0xaf3559 hash_table<hash_map<tree_node*, S,
simple_hashmap_traits<default_hash_traits<tree_node*>, S> >::hash_entry, false,
xcallocator>::empty()
/src/gcc/61339/gcc/hash-table.h:412
0xaf1475 hash_map<tree_node*, S,
simple_hashmap_traits<default_hash_traits<tree_node*>, S> >::empty()
/src/gcc/61339/gcc/hash-map.h:237
0xaeb3ff test_hash_table
/src/gcc/61339/gcc/cp/parser.c:43475
0xaeb40b c_parse_file()
/src/gcc/61339/gcc/cp/parser.c:43483
0xcd471d c_common_parse_file()
/src/gcc/61339/gcc/c-family/c-opts.c:1185
The problem is hash_table::empty_slow () calling Descriptor::remove() on the
existing entries first, invoking their destructor, but then assigning to them
in the second loop, which invokes the copy assignment operator.
I'm not quite sure what the purpose of the assignment or the memset call below
it is supposed to be. It was introduced in r249234 to avoid the new
-Wclass-memaccess warning but that was just a stab in the dark on my part made
with the assumption that the specializations of the template on non-trivial
types with a copy ctor and copy assignment were properly tested. Clearly,
that's not the case.
template<typename Descriptor, bool Lazy,
template<typename Type> class Allocator>
void
hash_table<Descriptor, Lazy, Allocator>::empty_slow ()
{
size_t size = m_size;
size_t nsize = size;
value_type *entries = m_entries;
for (size_t i = size - 1; i < size; i--)
if (!is_empty (entries[i]) && !is_deleted (entries[i]))
Descriptor::remove (entries[i]); << destroys the element
/* Instead of clearing megabyte, downsize the table. */
if (size > 1024*1024 / sizeof (value_type))
nsize = 1024 / sizeof (value_type);
else if (too_empty_p (m_n_elements))
nsize = m_n_elements * 2;
if (nsize != size)
{
unsigned int nindex = hash_table_higher_prime_index (nsize);
nsize = prime_tab[nindex].prime;
if (!m_ggc)
Allocator <value_type> ::data_free (m_entries);
else
ggc_free (m_entries);
m_entries = alloc_entries (nsize);
m_size = nsize;
m_size_prime_index = nindex;
}
else
{
#ifndef BROKEN_VALUE_INITIALIZATION
for ( ; size; ++entries, --size)
*entries = value_type (); <<< assigns to a destroyed element
#else
memset (entries, 0, size * sizeof (value_type));
#endif
}
m_n_deleted = 0;
m_n_elements = 0;
}
More information about the Gcc-bugs
mailing list