Thursday, June 3, 2010

Verktyg - or how to tame signals and slots

Using the meta-object functionality of Qt gives every C++ developer a mighty tool at hand that makes many daily tasks solvable in an easy and elegant way. You can decouple components from each other via signals and slots, communicate over thread border in a safe way, add custom properties to any QObject based object and invoke methods via QMetaObject::invokeMethod() delayed by the event loop. However all these neat features come at a certain price, we basically lost type checking by the C++ compiler. This friendly companion tells us whenever we use the wrong type for a variable, pass the wrong number of arguments to a method or try to assign variables of incompatible type. When it comes to the QObject::connect() or QObject::disconnect() methods, our beloved C++ compiler reaches its limits, though. The following statement

QObject::connect( timer, SIGNAL( timeout() ), this, SLOT( slotTimeout() ) );

is processed by the preprocessor and converted to

QObject::connect( timer, "timeout()", this, "slotTimeout()" );

So while the meta object compiler (moc) can parse the first representation and extracts all information it needs, the C++ compiler only gets two 'const char*' parameters where it doesn't know how to interpret them further. But who will make sure that the object 'timer' really provides a signal with the signature 'timeout()' and the 'this' object provides a slot with signature 'slotTimeout()'? Currently these information are only checked by QMetaObject on runtime and if an error is detected, a message similar to the following is printed to the standard output:

QObject::connect: No such signal MyClass::foobar()

The problem with runtime checking is, that errors can only be detected if the code is really executed during testing. However today's software is so complex that it is mostly impossible to execute all possible code paths during the test phase, we have to find another way...

So when do broken signal/slot connections occur? A quick search over the KDE code base shows that broken connections exists mostly in outdated or seldom run code (e.g. unit tests !!!) and in code that is under heavy development and refactoring. During refactoring signals and slots are moved around, will be renamed or removed completely from the code base. While all direct calls to the missing/renamed methods will trigger a compile error, the outdated signatures inside the QObject::[dis]connect() calls won't be noticed and lead to difficult to find bugs (yes, I mean the really ugly ones...).

After 8 years of studying computer science (Rome wasn't built in a day either ;)) it is now the time to write my diploma thesis, and since until now there is no application available that solves the problem described above, I asked KDAB Germany whether they would be interested to act as supervisor for a thesis that will address this issue. KDAB does a lot of software porting from Motif, MFC and Qt3 towards Qt4 and faces the problems of broken signal/slot connections in a larger scale, so they agreed and now Till Adam (aka KDE-PIM/KMail dude) is my official supervisor :)

Since the work started already on April 1th (no joke ;)), we have a working application now. Its name is 'Verktyg' and it is based on the KDevPlatform and KDevelop C++ parser. At this point I have to say a big Thank You! to my colleagues at KDAB Milian Wolff and Bertjan Broeksema who helped me to get into the KDevelop code quite fast and motivated me to keep on using this framework :) So what can Verktyg do for you? Verktyg can analyze CMake/Qt based projects statically for broken signal/slot connections, wrong usage of QMetaObject::invokeMethod() and missuses of QObject::setProperty()/QObject::property(). The code is available under GPL and can be cloned from http://gitorious.org/kdevcpptools/verktyg. Everything you need to know to compile and setup Verktyg is described in the README file inside the source directory. I know that deployment is still a bit tricky, you need a current version (aka git master) of KDevPlatform and KDevelop and additionally the kdevcpptools. But once you have Verktyg running, it will reward you with nice error reports like the following from kdepimlibs:

WARN: qgpgme/eventloopinteractor_win.cpp:[35:4] Class QObject has no slot 'slotReadActivity(int)' but its subclass EventLoopInteractor does, is it really used here?
WARN: qgpgme/eventloopinteractor_win.cpp:[41:4] Class QObject has no slot 'slotWriteActivity(int)' but its subclass EventLoopInteractor does, is it really used here?
WARN: akonadi/job.cpp:[280:13] Class KJob has no signal 'aboutToStart(Akonadi::Job*)' but its subclass Job does, is it really used here?
WARN: akonadi/session.cpp:[135:22] Class QIODevice has no signal 'disconnected()' but its subclass QAbstractSocket does, is it really used here?
WARN: akonadi/contact/contactdefaultactions.cpp:[51:13] Class QObject has no signal 'urlClicked(KUrl)' but its subclass ContactViewer does, is it really used here?
WARN: kontactinterface/core.cpp:[92:4] Class QObject has no slot 'slotPartDestroyed(QObject*)' but its subclass Core does, is it really used here?
ERRO: kblog/tests/testlivejournal.cpp:[358:11] Class LiveJournal has no signal 'fetchedUserInfo(QMap)'
...

As you can see there are some warnings, where Verktyg cannot decide whether the signal or slot is valid or not, because an implicit type (QObject) is passed to the connect statement instead of the explicit type, but at least it gives you some hints which makes the manual review a lot easier (Thanks to Volker Krause for the idea!). But there is also an error, which in fact is a real error that should be fixed... (did I mention outdated unit tests already?!? ;)).

So if you like to play around with Verktyg and need some help to set it up or if you have some nice ideas how to improve it, feel free to contact me via mail or ping me on IRC.

Happy analyzing!