Restoring original Qt behaviour

I have just committed an interesting change to Qt 4.4 now, fixing an open task reported by David Faure. By itself, the change is hardly worth mentioning:

-#if (defined(Q_OS_UNIX) || defined(Q_CC_MINGW)) && defined(QT_DEBUG)
+#if (defined(Q_OS_UNIX) || defined(Q_CC_MINGW))
abort(); // trap; generates core dump
#else
exit(1); // goodbye cruel world

The interesting part is the story behind the change.

Last night, some KDE friends (Dirk and David to be precise) pointed out that Q_ASSERT and qFatal only call abort(2) if Qt was compiled in debug mode, thereby making it impossible to use a crash handler to display a message to the user, maybe to restart the application as well. You could argue that an application shipped to a user should not have assertions enabled and should not be tripping them anyways, even if you had them enabled. And you'd be correct. The issue was that the application in question was still in development phase, in debug mode, therefore with assertions enabled. But Qt was in release mode, so it simply called exit(3) instead of aborting. The result is that the application being debugged and developed simply disappears, leaving no trace behind of why.

So they asked me: why was that the code like that in the first place?

Inspired by a recent movie I watched in the cinema, that set me off in an archeology expedition, digging through the history of Qt code -- more precisely, the qglobal.cpp file. After a few minutes, I had managed to trace down the actual change, after going through two renames of the file (from src/tools/qglobal.cpp [Qt 1 to 3 forms] to src/core/global/qglobal.cpp [unreleased Qt 4 form] to src/corelib/global/qglobal.cpp [current form]), one wide-reaching whitespace change, removing all Tabs in Qt source code, and several renames of the macros, to this change:

Author: Haavard Nord
Date: Tue Apr 18 15:43:46 1995 +0100

fatal() calls abort() if debug flag defined


-#if defined(UNIX)
+#if defined(UNIX) && defined(DEBUG)
abort(); // trap; generates core dump
#else
exit( 1 ); // goodbye cruel world

That means I have just reverted a 13-year-old commit by one of the Trolltech founders!

If we dig further, to the history of the abort() line itself, we end up in change number 43, whose log message is:

Author: Haavard Nord
Date: Mon Sep 5 05:54:23 1994 +0100

Initial revision

And I can't go beyond that... As with many projects, Qt's first authors decision to use a version control system was an afterthought. That change above added 43 files and 8438 lines of code.

So, here's what the qFatal function looked like in Sep 5, 1994:

void fatal( const char *msg, ... )              // print message and exit
{
char buf[240];
va_list ap;
va_start( ap, msg ); // use variable arg list
if ( handler ) {
vsprintf( buf, msg, ap );
(*handler)( buf );
}
else {
vfprintf( stderr, msg, ap );
fprintf( stderr, "n" ); // add newline
}
va_end( ap );
#if defined(UNIX)
abort(); // trap; generates core dump
#else
exit( 1 ); // goodbye cruel world
#endif
}

And here's what the equivalent code looks like today, June 5th, 2008, in what will be released as Qt 4.4.1 (edited for brevity):

void qFatal(const char *msg, ...)
{
char buf[QT_BUFFER_LENGTH];
buf[QT_BUFFER_LENGTH - 1] = '';
va_list ap;
va_start(ap, msg); // use variable arg list
if (msg)
qvsnprintf(buf, QT_BUFFER_LENGTH - 1, msg, ap);
va_end(ap);

qt_message_output(QtFatalMsg, buf);
}

void qt_message_output(QtMsgType msgType, const char *buf)
{
if (handler) {
(*handler)(msgType, buf);
} else {
[...]
fprintf(stderr, "%sn", buf);
fflush(stderr);
}

if (msgType == QtFatalMsg
|| (msgType == QtWarningMsg
&& (!qgetenv("QT_FATAL_WARNINGS").isNull())) ) {
[...]
#if (defined(Q_OS_UNIX) || defined(Q_CC_MINGW))
abort(); // trap; generates core dump
#else
exit(1); // goodbye cruel world
#endif
}
}


Blog Topics:

Comments