How I lost my right foot!

I don't particularly mind dealing with bugs as long as they are interesting to fix and you learn something along the way. I was not always so well disposed towards them though. It is only after reading Debugging Applicationsby John Robbinsthat I realized how much fun fixing bugs can be 8-)! There are bugs that you feel good about when you fix 'em because it required you to use your "amazing ingenuity" and "extensive knowledge" of how stuff works under the hood and then there are bugs that make you feel rotten because the darn thing won't get reproduced on your box!

Those are the nastiest kind of bugs to get stuck with because you know it is there somewhere and there isn't a single thing that you can do about it! I had to deal with a bug like that the other day the root cause for which in the end turned out to be a fairly silly little programming error. "If you can shoot yourself in the foot with 'C', you can blow your whole leg off with C++" reminisced the wise old man while helping himself up the handicap ramp seated on his wheelchair. I know he said this because I was right there behind him when he said it with a big smoking hole in my right foot. Here's what happened.

Ours is an ambitious little web application that seeks to do a whole lot of things all by itself using pretty much every single technology that mankind has managed to come up with till about 5 minutes back. One of the things that it jauntily goes about doing every now and then is to call a little Internet Server API (ISAPI) extension on the web server whenever somebody logs off. When a user happens to crash out of a web session however (as can happen for instance when lightning strikes the user's computer and does not give her a chance to cleanly exit the browser and shut the computer down) that little notification does not ever reach the ISAPI extension and the web server remains tragically unaware of the user's untimely end. After twiddling thumbs for some time though the ISAPI extension runs out of patience and just ends that user's session. Now, here's the important part - as part of the processing where it terminates that non-responsive user's session, it turns on a little boolean member in a little C++ class to register the fact that that session has been aborted (as opposed to cleanly logging off).

All the session information is stored in a Standard Template Library list<> object and this is what the session object looks like:

class CSession
{
public:
    bool    m_bAbnormalLogOff; // this tells me whether
                               // this session ended abnormally
    int     haplessInt;
    float   haplessFloat;

public:
    CSession()
    {
        //
        // initialize everything
        //
        m_bAbnormalLogOff = false;
        haplessInt = 0;
        haplessFloat = 0.0f;
    }

    CSession( const CSession& s1 )
    {
        haplessInt = s1.haplessInt;
        haplessFloat = s1.haplessFloat;
    }
};

Now, can you spot the error in this code? The error is of course that m_bAbnormalLogOff is not initialized in the copy constructor. Why is that a bad thing? Please look at the following code and try predicting what the output will be:

list<CSession> listOfSessions;

//
// create an abnormal session and push
// it onto the list
//
CSession badSession;
badSession.m_bAbnormalLogOff = true;
listOfSessions.push_back( badSession );

//
// now pop it off the list and push
// a normal session object
//
listOfSessions.pop_front();
CSession goodSession;    // now m_bAbnormalLogOff would be "false"
listOfSession.push_back( goodSession );

cout<<goodSession.m_bAbnormalLogOff<<endl;

If you said that it would print 0 (zero), then wouldn't you be surprised if I told you that the actual output on Microsoft's C++ compiler (the one they give free with Visual C++ Express Edition 2005) is 1? Well, fact is, it prints 1 and this is the nasty little bug that troubled us no end! Here's my take on what is most likely happening in this case:

  • When the first CSession object gets pushed on to the list<> it allocates some space for it and keeps it there. The point to note here is that list<> classes maintain their own copies of the objects that they are tracking and routines like list<>::push_back invoke the object's copy constructor for creating the copy. This is the reason why it is important that classes that you plan to store in STL containers implement the copy constructor and the assignment operator.
  • When this object gets popped off the list<> and is replaced by another CSession object, the new instance, instead of occupying fresh memory space just sits nice and snug in the space that the previous CSession object had occupied. As before list<>::push_back dutifully invokes CSession::CSession( const CSession& s1 ) for creating the object copy.
  • Since we forgot to copy the value for m_bAbnormalLogOff from s1 in the copy constructor it automatically assumes whatever value is currently stored in that location.
  • Given that we initialized m_bAbnormalLogOff with the value true for badSession,goodSession continues to use the same value!

The fallout of this little beauty is that every once in a while the system would report sessions where the user had logged off legitimately as having been aborted. Invariably this would always happen for 2 or 3 sessions that immediately followed a session that got aborted!

comments powered by Disqus