My Favorite Bugs

Bill Gates tells us the reason that people report bugs:
Because it's cool. It's like, "Yeah, been there done that - oh, yeah, I know that bug."

Well, I want to be cool, so I'm going to report some bugs in my favorite Microsoft programs: Microsoft Visual C++, Microsoft Developer's Studio, and Microsoft Windows NT.

A search on BUG in Microsoft Developer Network turns up hundreds of entries, but reporting those would hardly be cool. No, these are bugs that I found myself, and I have the lost time and the ugly workarounds to show for it. Neither are these old bugs that have been fixed in the latest release. As of 1999 Apr 02, these bugs are still out there, waiting to make more people cool. You could be next.


uncaught_exception

std::uncaught_exception() is a standard C++ library routine. It returns true if the stack is unwinding due to an exception. You need this routine, because if you throw a second exception while the stack is unwinding, the program terminates. So you need to protect throw statements in destructors, like this:
Foo::~Foo()
{
    bool error = ...
    if (error && !std::uncaught_exception())
        throw "Foo::~Foo error";
}

In MSVC++, std::uncaught_exception() is implemented like this

bool std::uncaught_exception() { return false; }

I wish I could write code like that. It would be so easy. Consider:
bool IsPrime   (int n)    { return false; }
Usually right
bool IsEOF     (HANDLE h) { return false; }
Only wrong once per file
int  Millennium()         { return 1000; }
Good for a thousand years (1000 CE - 1999 CE)


Watch those slashes

Microsoft Developer's Studio has a
Project Settings -> C++ -> Preprocessor -> Additional Include Directories

text entry box. If you have #include files that aren't on the standard include path, this is where you enter the directories where they are located.

I got into trouble because I was entering include paths like this:

/foo/bar

See the problem? Neither does MSDS. The problem is that I was using forward slashes as the path delimiter, instead of back slashes:

\foo\bar

It's easy enough to make this mistake, because you always use forward slashes in C++ source code:

#include "/foo/bar/baz.h"

The compiler will compile your code correctly either way, so it isn't immediately obvious that you've done anything wrong. However, when you close the project, MSDS may corrupt the .dsp file.

Not catastrophically—that would make the problem too easy to track down. It just loses the include path. Sometime later, you reopen the project, and the compiler complains that it can't find your #include files.

If you aren't used to hacking .dsp files, the whole IDE seems pretty opaque. So if you thought you had all the #include paths, but now one is missing, you just reenter it—with forward slashes—and you're back on the merry-go-round.

I chased my tail for a long time before I figured this one out.


Can you write this loop?

DOS provided the FindFirst/FindNext routines to iterate over the files in a directory. Like the rest of DOS, the interface was crude, but serviceable:
for (char *pszName=FindFirst("*.*");
           pszName;
           pszName=FindNext())
{
    // process the file
}

State was maintained across calls in internal globals, so applications could only loop through one directory at a time.

In WindowsNT, all OS services are provided through handles. This ought to be an improvement, since each handle can maintain its own state. However, instead of providing a proper Open/Read/Close interface to scan directories, Microsoft insisted on stuffing handles into a FindFirst/FindNext interface:

HANDLE FindFirstFile(char  *pszFileName, WIN32_FIND_DATA *pFindFileData);
bool   FindNextFile (HANDLE h          , WIN32_FIND_DATA *pFindFileData);

In this interface, FindFirstFile and FindNextFile don't return the same data type. This makes it extremely difficult to write a correct, compact, uniform loop to scan a directory. Here's my current best attempt:

HANDLE          h;
BOOL            ok;
WIN32_FIND_DATA	fd;

for (h =FindFirstFile("*.*", &fd), ok=1;
     h!=INVALID_HANDLE_VALUE && ok;
     ok=FindNextFile(h, &fd))
{
    // process the file
}

int error = GetLastError();
if (error!=ERROR_NO_MORE_FILES)
    printf("Find*File: error %d\n", error);
	
if (h!=INVALID_HANDLE_VALUE)
{
    BOOL ok = FindClose(h);
    if (!ok) 
        printf("FindClose: error %d", GetLastError());
}

The loop itself isn't all that bad, once you figure out how to write it, but the error handling and cleanup are hard to get right, and 20 lines of code to scan a directory seems excessive.

I decided to see how Microsoft thinks we should code to this interface. In the Microsoft source code samples supplied with MSVC++, we find FillFile.c, which contains the FillFile() routine. FillFile() fills a list box with the names of the files in a directory. This ought to be the canonical example of scanning a directory.

The first thing I discovered is that you can't actually compile FillFile.c, because somewhere between Redmond and the CD foundry, all the tabs were deleted. Not expanded, not converted to spaces—deleted. So lines that used to be

HANDLE	hFindFile;

are now

HANDLEhFindFile;

and won't compile.

But this may be a good thing. Further examination suggests that no one should compile FillFile.c, tabs or no. FillFile() is a 230 line subroutine, the directory scanning loop is over 100 lines, and the FindFirstFile()/FindNextFile()/FindClose() calls are scattered across 170 lines of code. However, with a good text editor and some diligence, we can extract the relevant control structure. It looks like this:

HANDLE hFindFile = FindFirstFile(szFind, &FindFileData);
if (hFindFile==INVALID_HANDLE_VALUE)
    return 0; 

BOOL  fNextFile   = TRUE; 
DWORD dwLastError = ERROR_SUCCESS;  
    
for (int i=0; fNextFile || dwLastError!=ERROR_NO_MORE_FILES; i++) 
{ 
    ...
    fNextFile   = FindNextFile(hFindFile, &FindFileData); 
    dwLastError = GetLastError(); 
} 

FindClose(hFindFile); 

Now look at the loop termination expression:

fNextFile || dwLastError!=ERROR_NO_MORE_FILES

In order for the loop to terminate, FindNextFile() has to return FALSE and GetLastError() has to return ERROR_NO_MORE_FILES. If GetLastError() returns anything else—if, for example, there is an actual error reading the directory—then the code is stuck in an infinite loop. So maybe it's just as well that Microsoft lost the tabs.

In summary

This isn't a bug per se: FindFirstFile and FindNextFile do what they're supposed to do. It's more like a meta-bug: this interface creates the conditions for bugs in all the applications that use it.

And ultimately, a meta-bug is worse. Someday, Microsoft will implement uncaught_exception(), and then all the code that uses it will work. Someday, Microsoft will fix their IDE, and then users will no longer mysteriously lose their include paths. But Microsoft is never going to fix this interface, because they don't think it's broken, and applications that use it are always—more likely than not—going to be buggy.


Notes

hundreds
500, to be precise, but this appears to be an artificial limit of the Search tab.
myself
Other people have found some of these bugs, too. A single bug can make lots of people cool.
implemented
No, I don't have access to the MSVC++ library sources. I traced into the subroutine body in the debugger and decompiled it in my head.
may
It seems to do this if the leading characters of the directory path look like a valid compiler switch.
get right
Of course, it's much easier if it doesn't have to work. See, for example, uncaught_exception.
broken
A less charitable interpretation is that Microsoft deliberately perverts its interfaces to prevent developers from writing platform-independent code. See, for example evil and rude.

Steven W. McDougall / resume / swmcd@theworld.com / 1999 Apr 02