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; }