The iterators over hash_map behave in a manner inconsistent with the other
std::map, std::list, etc... data structures. This can cause segfaults which are
hard to root-cause. Sample Code Included with additional information:

Specifically, the iterators over hash_map require the 'first' datamember to
be valid until the iterator has advanced past that point. (see code for clarity)
The iterator operator++ should not depend on being able to call the
hash functor on that data member to advance. Because, as in the example, 
you can't call the functor when the pointer has been deleted.

This behavior happens on other hosts of g++ (tested on 3.4.3, linux). (I just
have cygwin on here...)

The offending code is placed in #if 0 and #endif directives.

While this isn't a bug per se, it is a problem with the behavior of 
hash_map that should at least be documented in LARGE letters 
in the hash_map, if not fixed to work like the rest of the stl.

// $Id$
// Program: hashbroke
// Purpose: To demonstrate how __gnu_cxx::hash_map is broken. (sorta)
// Author: Jeffrey Zampieron
// License: GPL.

#include <iostream>
#include <ext/hash_map>
#include <map>

using namespace std;
using namespace __gnu_cxx;

typedef struct {
  size_t operator()( const string* str ) const {
    return __gnu_cxx::__stl_hash_string( str->c_str() );
  }
} strptrhash;

typedef struct {
  bool operator()( const string* lhs, const string* rhs ) const {
    return *lhs == *rhs;
  }
} strptrequal;

typedef struct {
  bool operator()( const string* lhs, const string* rhs ) const {
    return *lhs < *rhs;
  }
} strptrless;

typedef hash_map< string*, string*, strptrhash, strptrequal > StringPtrHash;
typedef std::map< string*, string*, strptrless > StringPtrMap;

int main() {
  StringPtrHash testhash;
  // test keys
  string* k1 = new string( "key1" );
  string* k2 = new string( "key2" );
  string* k3 = new string( "key3" );
  string* k4 = new string( "key4" );
  string* k5 = new string( "key5" );
  string* k6 = new string( "key6" );

  // test vals
  string* v1 = new string( "val1" );
  string* v2 = new string( "val2" );
  string* v3 = new string( "val3" );
  string* v4 = new string( "val4" );
  string* v5 = new string( "val5" );
  string* v6 = new string( "val6" );

  testhash[ k1 ] = v1;
  testhash[ k2 ] = v2;
  testhash[ k3 ] = v3;
  testhash[ k4 ] = v4;
  testhash[ k5 ] = v5;
  testhash[ k6 ] = v6;
  
  cout << "Hash:\n";
  for( StringPtrHash::iterator iter = testhash.begin(); 
       iter != testhash.end();
       iter++ ) {
    cout << "Key: " << *( iter->first ) << endl;
    cout << "Val: " << *( iter->second ) << endl;
  }

  // this SHOULD be valid.
  // and is for std::map
  // but causes a segfault here.
#if 0
  for( StringPtrHash::iterator iter = testhash.begin(); 
       iter != testhash.end();
       iter++ ) {
    delete( iter->first );
    delete( iter->second );
  }
#endif 

  // Work around:
  string* prev = __null;
  for( StringPtrHash::iterator iter = testhash.begin(); 
       iter != testhash.end();
       iter++ ) {
    delete( prev );
    delete( iter->second );
    prev = iter->first;
  }
  delete( prev );

  // That this works with map:
  StringPtrMap testmap;
  k1 = new string( "key1" );
  k2 = new string( "key2" );
  k3 = new string( "key3" );
  k4 = new string( "key4" );
  k5 = new string( "key5" );
  k6 = new string( "key6" );
  
  // test vals
  v1 = new string( "val1" );
  v2 = new string( "val2" );
  v3 = new string( "val3" );
  v4 = new string( "val4" );
  v5 = new string( "val5" );
  v6 = new string( "val6" );
  

  testmap[ k1 ] = v1;
  testmap[ k2 ] = v2;
  testmap[ k3 ] = v3;
  testmap[ k4 ] = v4;
  testmap[ k5 ] = v5;
  testmap[ k6 ] = v6;

  cout << "\nMap:\n";
  for( StringPtrMap::iterator iter = testmap.begin(); 
       iter != testmap.end();
       iter++ ) {
    cout << "Key: " << *( iter->first ) << endl;
    cout << "Val: " << *( iter->second ) << endl;
  }

  // works like map should.
  for( StringPtrMap::iterator iter = testmap.begin(); 
       iter != testmap.end();
       iter++ ) {
    delete( iter->first );
    delete( iter->second );
  }
 
  // The problem seems to be that the 
  // operator++ in the hash_map iterator
  // needs to call the function provided 
  // by strptrhash in order to advance...
  // but in my example that pointer has been deleted...
  // hence the segfault.
  // This is apparent if you run valgrind on the exec.
  // (I don't have it on this machine, or I'd include the output)

  // Why is this the case? Iterating over a map
  // should not require computation of a hash key.
  // While not really a bug, this is a caveat that should
  // be documented somewhere, or ideally fixed
  // Notice that map does not call strptrcmp to advance the iterator

}

-- 
           Summary: __gnu_cxx::hash_map inconsistent iterator behavior
           Product: gcc
           Version: 3.4.4
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: libstdc++
        AssignedTo: unassigned at gcc dot gnu dot org
        ReportedBy: jzampier at gmail dot com
                CC: gcc-bugs at gcc dot gnu dot org
 GCC build triplet: i686-pc-cygwin
  GCC host triplet: i686-pc-cygwin
GCC target triplet: i686-pc-cygwin


http://gcc.gnu.org/bugzilla/show_bug.cgi?id=23244

Reply via email to