Home > Blogs > It's obvious when you think about it

It's obvious when you think about it

By  May 21, 2008

Topics: Programming, Windows Programming

So many bugs that we encounter in our code are obvious in retrospect.  That makes it even worse when we discover them, especially if it takes us a long time to track them down.

I recently wrote a class that inherits from System.IO.Stream so that it can write information to a memory buffer for later manipulation. Since I had to gather information while I was writing, a simple MemoryStream wouldn't suffice.

The Stream-derived component has a very large (almost two gigabytes) byte array called outputBuffer, and the current position in the buffer (where data will be written) is stored in the integer buffIndex:

private byte[] outputBuffer;
private int buffIndex;

The overloaded Write method looks like this:

public override void Write(byte[] buffer, int offset, int count)
    // Check for buffer overrun
    if ((buffIndex + count) > outputBuffer.Length)
        // process the buffer and adjust output pointer
    Buffer.BlockCopy(buffer, offset, outputBuffer, buffIndex, count);

That all looks reasonable, and it even worked in testing. But when I put the program into production it would fail inexplicably on some data sets. Why it didn't throw an exception is beyond me, but there you have it.

The problem is in that the buffer overrun check sometimes overflowed the range of a 32-bit integer. So (buffIndex + count) would exceed 2 gigabytes and the result (due to the magic of integer overflow) would be a negative number. So the condition is not satisfied, the buffer is never emptied, and the BlockCopy operation tries to write beyond the end of the array.

The direct cause of this error was my copying some working buffer management code from another application. Except that application worked with a much smaller buffer--one that couldn't possibly have overflowed an integer. When I pasted that code into this application, I never even considered the possibility of buffer overrun.

In retrospect, of course, I should have thought of it. I went to a lot of trouble to have a buffer that is as large as possible, and it's not like I haven't encountered buffer overruns before. I just got complacent and assumed that working code would work even when I change the environment in which it executes.

Add programming to the list of things that require eternal vigilance.