[Bug libstdc++/62313] New: Data race in debug iterators

dvyukov at google dot com gcc-bugzilla@gcc.gnu.org
Sat Aug 30 16:07:00 GMT 2014


https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62313

            Bug ID: 62313
           Summary: Data race in debug iterators
           Product: gcc
           Version: 5.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: dvyukov at google dot com

$ g++ -v
gcc version 5.0.0 20140830 (experimental) (GCC) 

The program is:

=======
#include <list>
#include <pthread.h>
#include <unistd.h>

std::list<int>::iterator iter, iter2;

void *thread(void *arg)
{
    for (int i = 0; i < 1000000; i++) {
        std::list<int>::iterator it(iter);
        iter2 = it;
    }
    return 0;
}

int main()
{
    std::list<int> li;
    li.push_back(0);
    iter = li.begin();
    pthread_t th;
    pthread_create(&th, 0, thread, 0);
    for (int i = 0; i < 1000000; i++) {
        li.push_back(1);
        std::list<int>::iterator it = li.begin();
        it++;
        li.erase(it);
    }
    pthread_join(th, 0);
}
=====

Build it as:
$ g++ test.cc -Wall -lpthread -fsanitize=thread -pie -fPIE -D_GLIBCXX_DEBUG -g
-O2

Run the program. Output is:

WARNING: ThreadSanitizer: data race (pid=17058)
  Read of size 8 at 0x7faedc637b00 by main thread (mutexes: write M2):
    #0 void __gnu_debug::_Safe_sequence<std::__debug::list<int,
std::allocator<int> >
>::_M_invalidate_if<__gnu_debug::_Equal_to<std::__cxx1998::_List_const_iterator<int>
> >(__gnu_debug::_Equal_to<std::__cxx1998::_List_const_iterator<int> >)
/foo/include/c++/5.0.0/debug/safe_sequence.tcc:48 (a.out+0x000000002a61)
    #1 std::__debug::list<int, std::allocator<int>
>::_M_erase(std::__cxx1998::_List_iterator<int>)
/foo/include/c++/5.0.0/debug/list:464 (a.out+0x000000001e21)
    #2 std::__debug::list<int, std::allocator<int>
>::erase(__gnu_debug::_Safe_iterator<std::__cxx1998::_List_iterator<int>,
std::__debug::list<int, std::allocator<int> > >)
/foo/include/c++/5.0.0/debug/list:477 (a.out+0x000000001e21)
    #3 main test.cc:27 (a.out+0x000000001e21)

  Previous write of size 8 at 0x7faedc637b00 by thread T1:
    #0 __gnu_debug::_Safe_iterator<std::__cxx1998::_List_iterator<int>,
std::__debug::list<int, std::allocator<int> >
>::operator=(__gnu_debug::_Safe_iterator<std::__cxx1998::_List_iterator<int>,
std::__debug::list<int, std::allocator<int> > > const&)
/foo/include/c++/5.0.0/debug/safe_iterator.h:223 (a.out+0x000000002460)
    #1 thread(void*) test.cc:11 (a.out+0x000000002460)

  Location is global 'iter2' of size 40 at 0x7faedc637ae0
(a.out+0x000000203b00)

  Mutex M2 (0x7faedb100698) created at:
    #0 pthread_mutex_lock
../../.././libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:2686
(libtsan.so.0+0x0000000368a6)
    #1 __gthread_mutex_lock
/ssd/src/gcc_trunk/x86_64-unknown-linux-gnu/libstdc++-v3/include/x86_64-unknown-linux-gnu/bits/gthr-default.h:748
(libstdc++.so.6+0x0000000b284b)
    #2 __gnu_cxx::__mutex::lock()
/ssd/src/gcc_trunk/x86_64-unknown-linux-gnu/libstdc++-v3/include/ext/concurrence.h:152
(libstdc++.so.6+0x0000000b284b)
    #3 __gnu_cxx::__scoped_lock::__scoped_lock(__gnu_cxx::__mutex&)
/ssd/src/gcc_trunk/x86_64-unknown-linux-gnu/libstdc++-v3/include/ext/concurrence.h:244
(libstdc++.so.6+0x0000000b284b)
    #4
__gnu_debug::_Safe_sequence_base::_M_attach(__gnu_debug::_Safe_iterator_base*,
bool) ../../../.././libstdc++-v3/src/c++11/debug.cc:268
(libstdc++.so.6+0x0000000b284b)
    #5 __libc_start_main <null>:0 (libc.so.6+0x000000021ec4)

  Thread T1 (tid=17060, running) created by main thread at:
    #0 pthread_create ../../.././libsanitizer/tsan/tsan_interceptors.cc:853
(libtsan.so.0+0x000000026eb4)
    #1 main test.cc:22 (a.out+0x000000001aad)


As far as I see the safe iterator first attaches itself to the sequence in
_Safe_iterator_base ctor:

      _Safe_iterator(const _Safe_iterator& __x)
      : _Safe_iterator_base(__x, _M_constant()), _M_current(__x._M_current)

After that point it's accessible from other threads (in particular that
invalidate other iterators).
Only then it sets _M_current.
But _M_current is already read from another thread that invalidates a different
iterator.

There can be a similar race in dtor. They usually come together.

It can make sense to run some extensive test suite (if one exists) for debug
iterators with -fsanitize=thread -O{0,1,2}.



More information about the Gcc-bugs mailing list