Die Werkzeugkiste #4: Qt-Code durchleuchten mit clazy
In der Serie "Die Werkzeugkiste" stellen Entwickler nützliche Tools vor: clazy hat sich auf die Fehlersuche für Qt-Anwendungen spezialisiert. Dabei arbeitet es ähnlich wie Lint.
- Tam Hanna
Das Cross-Plattform-Framework Qt ist unter anderem so leistungsfähig, weil der geschriebene Sourcecode im ersten Schritt den Meta-Object Compiler (moc) durchläuft. Er transpiliert den Sourcecode zu reinem C++-Code (s. Abb. 1).
Ein Nachteil dieser Vorgehensweise ist, dass der C-Compiler den Originalcode nicht zu sehen bekommt und seine Verbesserungsalgorithmen somit zumindest teilweise ins Leere laufen. An der Stelle kommt clazy ins Spiel, das speziell auf das Untersuchen von Qt-Anwendungen ausgelegt ist. Für die im Artikel behandelte Beispielanwendung und deren Untersuchung mit clazy sind Grundkenntnisse in Qt notwendig.
Nützliche Helfer
clazy setzt eine Gruppe von Hilfswerkzeugen voraus, die in einer aktuellen Version vorliegen müssen:
- g++,
- clang,
- llvm-dev
- git-core
- libclang-3.8-dev
- qtbase5-dev
- cmake
Da der Kompiliervorgang durchaus fehleranfällig ist, findet sich am Ende des Artikels ein Shell-Skript, das sich um die Bereitstellung kümmert.
Die Werkzeugkiste
In der heise-Developer-Serie "Die Werkzeugkiste" stellen Entwickler in regelmäßigen Abständen ihre nützlichsten Werkzeuge, Tools, und Hilfsmittelchen vor. Wie bei der Werkzeugkiste von Handwerkern gilt auch hier: Die Kisten sind meist ziemlich voll – die Auswahl des bevorzugten Werkzeugs für eine Arbeit immer subjektiv. Wenn Sie ihr Lieblings-Tool vermissen oder selbst gerne in einem Artikel vorstellen wollen, schreiben Sie doch einfach eine E-Mail an heise Developer.
- #1: Helm – Kubernetes-Deployments richtig gemacht
- #2: Container und Serverless: Was kann Knative?
- #3: Mit RapidClipse ohne Konfigurationsaufwand entwickeln
- #4: Qt-Code durchleuten mit clazy
Die Installation von clazy erfordert derzeit das manuelle Kompilieren. Vorgefertigte Pakete gab es beim Schreiben des Artikels noch nicht. Die korrekte Einrichtung lässt sich durch das Ausführen ohne Parameter ausprobieren. Wenn alles funktioniert, beklagt sich das Programm über das Fehlen von Eingabeinformationen:
Ein erster Test besteht darin, eine leere C++-Datei zur Überprüfung an clazy zu übergeben. clazy beschwert sich darüber, dass es keine main()-Funktion finden konnte und das Kompilieren dementsprechend scheiterte:
~/clazyspace$ clazy main.cpp:
In function '_start':
undefined reference to 'main'
clang-7: error: linker command failed with \
exit code 1 (use -v to see invocation)
Blick auf die Architektur
clazy basiert auf dem LLVM-Konzept. Der Compiler errichtet beim Parsen einen abstrakten Syntaxbaum (AST, Abstract Syntax Tree), der in Folge einen oder mehrere Analysatoren durchläuft. Zu deren Text hat der Autor eine ältere von ihm geschriebene Anwendung verwendet: einen einst für Symbian entwickelten Tipptrainer. Als zweites Testobjekt dient ein Serverprogramm namens Pirna, eine Berechnungs-Engine, die ohne Benutzer-Interface auskommt und einige Fragen aufwirft.
Der einfachste Weg zum Kompilieren des Projekts ist, den Make-Generator von qmake auszutricksen. Durch einen Parameter lässt sich der Compiler der Workstation durch clazy ersetzen. Im nächsten Schritt folgt der gewohnte Aufruf von make:
qmake -spec linux-clang QMAKE_CXX="clazy"
make clazy -c -pipe -O2 -Wall -W -D_REENTRANT -fPIE -DQT_NO_DEBUG
Vor allem im Zusammenspiel mit älteren Qt-Versionen gibt das Programm hunderte Fehler pro Datei aus. Die Umleitung von stderr lässt sich auf der Bash-Shell mit dem &>-Operator einstellen:
make &> listerror.txt
Wer eine neuere Version von Qt mit make verwenden möchte, findet auf StackExchange eine Übersicht der Anpassungs- und Einstellungsmöglichkeiten für qmake.
Beim Blick auf die Ausgabe ist zu beachten, dass am Ende jeder Warnung in eckigen Klammern Informationen darüber zu finden ist, welche Regel für die Auslösung verantwortlich war:
...
In file included from \
/usr/include/qt5/QtWidgets/qmainwindow.h:45:
/usr/include/qt5/QtWidgets/qwidget.h:131:5: \
warning: Q_PROPERTY should have either NOTIFY \
or CONSTANT [-Wclazy-qproperty-without-notify]
Q_PROPERTY(bool modal READ isModal)
^
Eine Frage der Einstellung
clazy bringt etwa 50 Prüfungen mit, die häufige Antipatterns erkennen. Zwecks einfacherer Handhabung unterteilen die Entwickler die Tests in insgesamt fünf Stufen:
- level0: Tests, die in mehr als 99,99 Prozent der Fälle keine False Positives produzieren,
- level1: etwas stabilere Tests, die clazy von Haus aus mit ausführt,
- level2: algorithmisch stabile Prüfungen, die allerdings Faktoren prüfen, die aus Sicht der Community teilweise keine schlechten Praktiken sind,
- level3: Prüfungen, die sehr wahrscheinlich False-Positives erzeugen und
- manual: Tests, die aus nicht näher erklärten Gründen außerhalb der Levels stehen.
Folgende Befehlssequenz führt lediglich level0-Tests aus:
export CLAZY_CHECKS="level0"
make clean
make &>level0errors.txt
Bei der Auswertung der Ausgabe mag stören, dass der in clang implementierte Compiler ebenfalls Fehlermeldungen auswirft. Wer nur von clazy erzeugte Fehler finden möchte, sucht nach dem String "Wclazy".
Die Umgebungsvariable CLAZY_CHECKS beschreibt die anzuwendenden Regeln. Die oben verwendete Parametrierung sorgt dafür, dass nur Prüfungen der Klasse level0 zum Einsatz kommen. Mehrere Ebenen lassen sich mit Kommas kombinieren. Folgender Befehl aktiviert alle derzeit in clazy implementierten Checks:
export CLAZY_CHECKS="level0,level1,level2,level3,manual"
Einzelne Module eines Levels lassen sich mit dem Präfix "no-" deaktivieren: Zum Aktivieren eines ganzen Levels mit Ausnahme einzelner Module dient das Präfix "no":
export CLAZY_CHECKS="level0,no-qenums"
Die Angabe eines Tests überführt ihn in die Liste der zu erledigenden Jobs. Die folgende Konfiguration ordnet beispielsweise alle Tests von level0 und das Prüfen von detaching-temporary an:
export CLAZY_CHECKS="level0,detaching-temporary"
Es ist zudem empfehlenswert, nach jeder Änderung von CLAZY_CHECKS einen Durchlauf von make clean zu befehlen. Clang cacht die Ergebnisse vorhergegangener Kompilierprozesse, was zu unvollständigen Analyseergebnissen führen kann.