[PATCH] fix singular iterator dereference in libstdc++ testcase 23_containers/list_modifiers.cc

Doug Gregor dgregor@apple.com
Tue Jun 10 16:18:00 GMT 2003


On Tuesday, June 10, 2003, at 08:15 AM, Stephen M. Webb wrote:

> On June 9, 2003 06:21 pm, Doug Gregor wrote:
>> In the testcase 23_containers/list_modifiers.cc, the function test01()
>> contains this block of code:
>>
>>    list0101.pop_back();          // list should be [2 1]
>>    VERIFY(list0101.size() == 2);
>>    VERIFY(T::dtorCount() == 1);
>>    VERIFY(i->id() == 1);
>>    VERIFY(j->id() == 1); // INVALID
>>    VERIFY(k->id() == 1);
> The standard pretty explicitly states (in [23.2.2.3 (3)]) that 
> pop_back()
> invalidates only iterators and references to the erased elements, and 
> j is an
> iterator into list0101 that is not an iterator to the erased element. 
> It
> should not be invalidated, and I would expect it to continue to work
> correctly in a conforming implementation, since there's nothing in the
> standard that leads me to believe otherwise.

The comments that pertain to iterator invalidation by container 
operations only consider the "iterator" and "const_iterator" types for 
a container, and not the "reverse_iterator" or "const_reverse_iterator" 
types. Under this interpretation, the testcase is wrong, because it 
fails when the latter two types are std::reverse_iterator<iterator> and 
std::reverse_iterator<const_iterator>, respectively. As you say below:

> Note that j.base() will be invalidated by the pop_back() call, since 
> it is an
> iterator to the erased element.

j.base(), which is the same as j.current, is invalidated by the 
pop_back() call. Then when we are using std::reverse_iterator (as noted 
in 23.1.1/9, Table 66), the dereference operator invokes --j.current, 
which has undefined behavior.

> The test case does not pass by coincidence and does not rely on the
> persistence of a deallocated node.  The "current" member of the
> reverse_iterator in the case of a list is merely a collection of 
> pointers,

The "current" member is of type _List_iterator<T, T&, T*>, and its only 
data comes from its base class, _List_iterator_base, which contains 
(only):

   _List_node_base* _M_node;

And here's how we decrement a _List_iterator (includes dereferencing 
the pointer):

     void
     _M_decr()
     { _M_node = _M_node->_M_prev; }

> the important one of which points back to the actual node referenced 
> by the
> reverse_iterator.  Regardless of the fact that the "current" member is
> theoretically an invalid iterator, "--current" will continue to be a 
> valid
> reference into the list, and j will continue to be valid and 
> dereferenceable,
> and it just works.

--current will dereference _M_node, and that is an error. To convince 
yourself of this, clear out the _M_next/_M_prev fields just before 
_M_put node deallocates the node, so it looks like this:

     void
     _M_put_node(_List_node<_Tp>* __p)
     {
       std::cerr << "Deallocate node at " << __p << '\n';
       __p->_M_next = 0;
       __p->_M_prev = 0;
       _Alloc_type::deallocate(__p, 1);
     }

Here's what I get, with a little extra debugging information added in. 
It includes all deference/deallocate/decrement operations on list 
iterators, starting from the push_back:

Decrement node at 0x321ae0
Deallocate node at 0x321b10     // #1
Dereference node at 0x321af0
Decrement node at 0x321b10  // #2
Dereference node at 0 // #3

At #1 is a deallocation, and then we try to decrement that same node at 
#2, that dereferences a pointer already passed to the allocator's 
deallocate() method. And at #3 I get a bus error, because of the 
pointer-zeroing code.

The testcase is incorrect, and with more tracking information we can 
see that the behavior under libstdc++ is also undefined.

	Doug



More information about the Libstdc++ mailing list