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) |
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.
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
FindFirstFile
/FindNextFile
interface
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.
uncaught_exception
.