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.

Werkzeuge  –  0 Kommentare
Qt-Code durchleuten mit clazy

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.

Das moc-Werkzeug beseitigt QT-spezifische Teile des Programms (Abb. 1).


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.

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:

Ohne Parameter beschwert das Programm sich zu Recht, dass etwas fehlt (Abb. 2).

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.