[Gc] Example for premature reclaim caused by compiler optimization

Martin Wartens martin.wartens at mymail.ch
Thu Sep 8 07:08:03 PDT 2005


Hi,
I have recently encountered an error with a premature reclaim of an object, 
which was caused by a compiler optimization. The pointer to the newly created 
object gets optimized away by the compiler so that it is not held in any 
register nor on the stack. When there is no other reference to the object in 
memory, the gc thinks the object is dead and removes it. This may happen even 
if there are still live pointers to other objects reachable only by first 
object.
Given below is an example program to demonstrate the problem. There are two 
examples which are basically identical. The first example creates a set and 
tries to iterate over it. During the iteration the set is collected away, which 
makes the iterators point to garbage. The second example is a stripped down 
version of the first and uses a mockup list instead of a set.
For compiling the example program, use "g++ -O2 -I./include -I./gc6.5/include -
o mygctest mygctest.cpp   ./lib/libgc.a  -lpthread -ldl", which was tested on 
linux with gcc3.3, or VC7.1 with /O2 switch. 
It is quite hard to find such a bug, since it only shows in optimized code. 
Trying to output the wrongly destroyed pointer can keep it alive and make the 
error go away. The first step to finding such a bug is to clutter the code with 
multiple forced collector calls: "GC_gcollect(); GC_gcollect(); GC_gcollect(); 
GC_gcollect();". This hopefully makes the bug appear at a deterministic point 
early in the program.
Removing the bug is not so hard: Either create the offending object on the 
stack (not with new), or call delete for the pointer to that object. With both 
variants, the pointer to the object seems to survive somewhere in memory so 
that the gc can find it.

=========================================================================
#include <iostream>
#include <set>
using namespace std;

#include "gc_cpp.h"
#include "gc_allocator.h"


/****************first example********************/
struct SetHolder : public gc_cleanup
{
        typedef std::set<unsigned, std::less<unsigned>,
                         gc_allocator<unsigned> > SetT;
        SetT set;

        SetHolder()
        {
                //create set and insert some data
                set.insert(147);
        }
        ~SetHolder()
        {
                cout<<"Destroy SetHolder"<<endl;
                //destroy set
        }
};

void testgcset(){ 
        SetHolder* setholder = new SetHolder();
        SetHolder::SetT::const_iterator lineIt = setholder->set.begin();
        SetHolder::SetT::const_iterator lineEnd = setholder->set.end();

        //iterate over set contents
        for(; lineIt != lineEnd; lineIt++){
                GC_gcollect(); GC_gcollect();
        }
        cout<<"end of loop"<<endl;
}


/****************second example********************/
//single linked list node
struct Node: public gc
{
        //pointer to next node in list
        Node* next_node;
        Node* getNext()
        {
                if(next_node==0)
                   cout<<"Iterator increment: invalid ptr"<<endl;
                return next_node;
        }
};

//mockup of a single linked list
struct FakeList : public gc_cleanup
{
        //first node in list
        Node* first_node;
        FakeList()
        {
                //create first node and another node,
                //let first point to another_node
                first_node = new Node();
                Node* another_node = new Node();
                first_node->next_node = another_node;
        }
        //get first node in list
        Node* begin(){ return first_node; }
        //get last node in list
        Node* end(){ return first_node->next_node; }
        ~FakeList()
        {
                first_node->next_node = 0;
                first_node = 0;
                cout<<"fake list destroyed"<<endl;
        }
};

void testgclist(){ 
        //create new list. this pointer will be optimized away
        FakeList* fakelist = new FakeList();

        Node* iter = fakelist->begin();
        Node* lastptr = fakelist->end();

        //iterate over list contents
        while(iter!=lastptr)
        {
                //this will trigger collection of fakelist,
                //while we are still iterating over it.
                GC_gcollect(); GC_gcollect();
                //this fails, iter is dead
                iter=iter->getNext();
        }
        cout<<"end of loop, everything fine"<<endl;
}       

int main()
{
    testgcset();
    testgclist();    
    GC_gcollect();
}




More information about the Gc mailing list