Compiler und Interpreter beanstanden zwar ungültigen Programmcode, in vielen Programmiersprachen sind aber selbst sehr ungewöhnliche Codezeilen noch gültig. Man kann Code auch absichtlich verwirrend gestalten – es gibt sogar diverse Obfuscated Coding Contests ([1],[2]), also Wettbewerbe, die zum Ziel haben, ein Programm absichtlich möglichst unleserlich zu gestalten. Viel öfter allerdings geschieht das unabsichtlich, insbesondere wenn (wie in vielen Open-Source-Projekten) mehrere Entwickler gemeinsam an einem Programm arbeiten. Nicht immer bemerkt der Compiler die daraus resultierenden Schnitzer.

Glücklicherweise gibt es Tools, die hier ansetzen und solche ungewöhnlichen Codekonstruktionen aufspüren. Damit helfen sie, mögliche Fehlerquellen zu beseitigen, und sorgen so für bessere Codequalität. Im Folgenden stellen wir einige ausgewählte Vertreter dieser Gattung vor, wobei unsere Aufstellung keinen AAnspruchauf Vollständigkeit erhebt: Ähnliche Open- und Closed-Source-Werkzeuge gibt es für praktisch jede Programmiersprache.

Splint – Codeprüfung für C

Das erste Werkzeug zur statischen Quellcodeprüfung, Lint, kam bereits 1979 mit Unix zur Auslieferung. Das Programm wurde schnell so populär, dass es den Programmierer-Jargon um den Ausdruck "linten" für statische Codeprüfungen bereicherte. In der Folge diente es dann als Namensgeber für viele ähnliche Tools. Zu diesen gehört das Open-Source-Programm Splint ("Secure Programming Lint", [3]), das wie sein Vorbild mögliche Problemquellen in C-Code aufspürt.

Als Beispiel sehen wir uns das kurze C-Programm aus Listing 1 (Zeile 2 bis 10) dienen, das melden soll, wenn zehn Argumente auf der Kommandozeile übergeben werden. Dies prüft es, indem es argc auswertet: Diese Variable enthält bekanntlich immer die Anzahl der übergebenen Argumente plus Eins, weil es den Programmnamen als erstes Argument mitzählt.

Listing 1

$ cat main.c
#include <stdio.h>
int main(int argc, char * argv[])
{
  if (argc = 10+1) {
    printf("10 Argumente!\n");
  }
  return 0;
  printf("Programm beendet.\n");
}
$ gcc main.c
$ gcc -Wall main.c
main.c: In function 'main':
main.c:4:2: warning: suggest parentheses around assignment used as truth value [-Wparentheses]

Der Compiler GCC meldet beim Übersetzen des Quellcodes normalerweise gar nichts (Zeile 11). Erst wenn Sie mittels der Option -Wall alle Warnungen einschalten (Zeile 12) wirft er eine kurze kryptische Meldung aus, weil der Code anstatt eines Vergleichs (==) eine Zuweisung (=) verwendet (Zeile 5).

Sehen Sie sich zum Vergleich die Ausgabe von Splint in Listing 2 an: Das Tool findet nicht weniger als vier mögliche Fehler in main.c. Außerdem beschreibt es ausführlich die Art der Probleme und die möglichen Ursachen. Daneben liefert es Hinweise, mit welchen Änderungen am Code oder den Parametern Sie die vermuteten Fehler korrigieren oder die Ausgabe unterdrücken können.

Listing 2

$ splint main.c
Splint 3.1.2 --- 29 Oct 2011
main.c: (in function main)
main.c:4:6: Test expression for if is assignment expression: argc = 10 + 1
  The condition test is an assignment expression. Probably, you mean to use ==
  instead of =. If an assignment is intended, add an extra parentheses nesting
  (e.g., if ((a = b)) ...) to suppress this message. (Use -predassign to
  inhibit warning)
main.c:4:6: Test expression for if not boolean, type int: argc = 10 + 1
  Test expression type is not boolean or int. (Use -predboolint to inhibit
  warning)
main.c:8:2: Unreachable code: printf("Programm...
  This code will never be reached on any possible execution. (Use -unreachable
  to inhibit warning)
main.c:2:27: Parameter argv not used
  A function parameter is not used in the body of the function. If the argument
  is needed for type compatibility or future plans, use /*@unused@*/ in the
  argument declaration. (Use -paramuse to inhibit warning)
Finished checking --- 4 code warnings

Neben der schon von GCC bemerkten fehlerhaften Zuweisung im If-Statement, zu der Splint gleich zwei Anmerkungen liefert, findet der Analyzer auch noch eine unbenutzte Variable (argv) sowie Programmcode, der niemals ausgeführt wird. Beim Kommentar /*@unused@*/, dem Splint in der vierten Warnung vorschlägt, handelt es sich um eine sogenannte Annotation. Ein solcher spezieller Kommentar steuert das Verhalten von Splint, indem er beispielweise gewisse Prüfungen (de-)aktiviert und Splint weitere Hinweise gibt, welche die Prüfungen unterstützen. Der Aufruf splint --help annotations gibt einen Überblick über die Möglichkeiten.

Neben der Hilfe-Funktion, die mittels splint --help einen ersten Überblick über die Themen liefert, kennt Splint noch zahlreiche weitere Kommandozeilenoptionen. Für Einsteiger sind wahrscheinlich die Optionen --weak, --standard, --checks und --strict am interessantesten, die steuern, wie pedantisch sich Splint gibt. Der Aufruf splint --help modes gibt Ihnen einen ersten Überblick über die (de-)aktivierten Optionen in diesen Einstellungen.

Übrigens bemerkt auch GCC durchaus, dass der Befehl printf("Programm beendet\n") nie ausgeführt wird. Er optimiert den Befehl daraufhin schlicht weg, sprich: erzeugt gar nicht erst Code dafür. Das erkennen Sie unschwer, indem Sie sich mit dem Kommando strings die Texte im generierten Executable ansehen. Etwas lesbarer fällt die Variante aus, mit gcc -S main.c die Assembler-Datei main.s zu erzeugen. In jedem Fall unterschlägt der Compiler jedoch hier die Information, dass hier völlig überflüssiger Code vorhanden ist. Klar, er soll ja auch übersetzen und nicht Fehler suchen – zur Verbesserung der Codequalität trägt das aber nicht gerade bei.

Auch andere C-Compiler schlagen sich hier nicht viel besser: Der Open64-Compiler meldet standardmäßig das selbe wie GCC: Nichts. Der Sun/Oracle-Compiler beanstandet zwar das nicht erreichbare Statement, findet aber an der wahrscheinlich fehlerhaften Zuweisung in der If-Abfrage nichts auszusetzen. LLVM/Clang bemerkt zwar die falsche Zuweisung, meldet aber ebenfalls nichts zum nicht ausführbaren Code.

Perl::Critic

Die Programmiersprache Perl tritt explizit mit dem Motto "There is more than one way to do it" an – sie erlaubt also viele Wege, um ein Problem zu lösen. Für möglichst seltsame und unlesbare Wege gibt es sogar einen eigenen Wettbewerb, den Obfuscated Perl Contest [2] . Dazu passend hat der Code-Analyzer Perl::Critic das Motto "Some Ways Are Better Than Others". Perl::Critic hilft wie Splint, mögliche Fehler zu finden und einen einheitlichen Programmierstil durchzusetzen.

Möchten Sie sich Perl::Critic ohne Installation ansehen, können Sie dazu den entsprechenden Webservice [4] nutzen (Abbildung 1). Dort laden Sie Ihren Perl-Code hoch und sehen dann sofort das Ergebnis der Evaluierung. Dabei unterstützt das Analysetool fünf verschiedene "Härtegrade".

Abbildung 1: Der Perl::Critic-Webservice liefert nach dem Hochladen des zu prüfenden Codes sofort Ergebnisse.

Auf der Kommandozeile rufen Sie das einmal installierte Werkzeug mit einem schlichten perlcritic auf. Auch hier kennt Perl::Critic die bereits erwähnten fünf Prüfstufen, die von 1 ("brutal") bis 5 ("gentle") reichen. Im Beispiel aus Listing 3 rufen wir den Analyzer mit dem dritten Level ("harsh") auf, damit er bei dem kurzen Programm (Zeilen 2 bis 5) auch etwas zu reklamieren findet.

TIPP

In OpenSuse 12.1 fehlt (im Gegensatz zu anderen gängigen Distributionen) das Executable-Bit bei der Datei /usr/bin/perlcritic. Sie müssen es entweder nachträglich setzen oder das Analyse-Tool explizit mit perl /usr/bin/perlcritic aufrufen, um es zu nutzen.

Listing 3

$ cat test.pl
#!/usr/bin/perl
print `cat test.pl`
print "Return drücken";
my $filename = <STDIN>;
$ perlcritic --severity 3 test.pl
Code before strictures are enabled at line 2, column 1.  See page 429 of PBP.  (Severity: 5)
Code before warnings are enabled at line 2, column 1.  See page 431 of PBP.  (Severity: 4)
Backtick operator used at line 2, column 7.  Use IPC::Open3 instead.  (Severity: 3)
Use "<>" or "<ARGV>" or a prompting module instead of "<STDIN>" at line 4, column 16.  See pages 216,220,221 of PBP.  (Severity: 4)

Tatsächlich macht Perl::Critic auf gleich vier Verbesserungsmöglichkeiten aufmerksam (Zeilen 7 bis 10). Dabei gibt es jeweils eine Beurteilung des Schweregrads und häufig auch eine Referenz auf das Buch "Perl Best Practices" (PBP) von Damian Conway aus. Ähnlich wie Splint können Sie auch Perl::Critic mit speziellen Kommentaren (## no critic) am Ende einer Zeile anweisen, diese nicht zu untersuchen. Daneben verzichtet das Tool darauf, Code zwischen den Marken ## no critic und ## use critic zu prüfen.

Genau wie Splint bietet Perl::Critic daneben auch noch wesentlich mehr Einstellungsmöglichkeiten. Zum einen dürfen Sie vorgeben, welche Prüf-Policies es anwenden soll, zum anderen können Sie dem vorhandenen Fundus auch eigene Policies hinzuzufügen. Der Befehl perlcritic --help liefert eine erste Übersicht, auch eine umfangreiche Manpage (man perlcritic) steht zur Verfügung.

Als Teil der kommerziellen Activestate-Perl-Distribution [5] gibt es auch eine grafische Oberfläche für Perl::Critic (perlcritic-gui, Abbildung 2), mit der Sie sich einen groben Überblick über die vielfältigen Optionen verschaffen können. Active State Perl lässt sich kostenlos herunterladen und nutzen, ist jedoch keine freie Software.

Abbildung 2: Active State Perl bringt eine grafische Oberfläche für Perl::Critic mit.

PHP Code Sniffer

Auch für die populäre Programmiersprache PHP gibt es ein ähnliches Projekt, den PHP Code Sniffer[6]. Nach der Installation testen Sie mit phpcs eine einzelne PHP-Datei oder gleich ein komplettes Verzeichnis prüfen. Phpcs definiert dazu verschiedene Coding-Standards, die Sie über die Option --standard=Typ auswählen.

Im Beispiel aus Listing 4 beanstandet Phpcs einige eher kosmetische Kleinigkeiten wie beispielsweise falsche Einrückungen oder fehlende Leerzeichen – definitiv keine Fehler, aber unschön. Auch bei Phpcs haben Sie etliche Optionen zur Verfügung (phpcs --help), können eigene Regel-Sets definieren und einzelne Codeteile von der Prüfung ausklammern (Listing 5).

Listing 4

$ phpcs -i
The installed coding standards are PHPCS, Squiz, MySource, Zend and PEAR
$ cat test.php
<?php /* Testdatei für phpcs */
echo "abc";
if (rand(1,10)>5) {
  echo "Zufallszahl >5";
}
?>
$ phpcs test.php
FILE: /home/dauti/Artikel/2012-Code-Analyse/test.php
--------------------------------------------------------------------------------
FOUND 4 ERROR(S) AFFECTING 3 LINE(S)
--------------------------------------------------------------------------------
 1 | ERROR | You must use "/**" style comments for a file comment
 3 | ERROR | No space found after comma in function call
 4 | ERROR | Spaces must be used to indent lines; tabs are not allowed
 4 | ERROR | Line indented incorrectly; expected at least 4 spaces, found 1
--------------------------------------------------------------------------------
$ phpcs --standard=PHPCS test.php
FILE: /home/dauti/Artikel/2012-Code-Analyse/test.php
--------------------------------------------------------------------------------
FOUND 10 ERROR(S) AFFECTING 4 LINE(S)
--------------------------------------------------------------------------------
 1 | ERROR | You must use "/**" style comments for a file comment
 1 | ERROR | Single line block comment not allowed; use inline ("// text")
   |       | comment instead
 2 | ERROR | String "abc" does not require double quotes; use single quotes
   |       | instead
 3 | ERROR | No space found after comma in function call
 3 | ERROR | Expected 1 space before ">"; 0 found
 3 | ERROR | Expected 1 space after ">"; 0 found
 4 | ERROR | Spaces must be used to indent lines; tabs are not allowed
 4 | ERROR | Line indented incorrectly; expected at least 4 spaces, found 1
 4 | ERROR | Line indented incorrectly; expected at least 4 spaces, found 1
 4 | ERROR | String "Zufallszahl >5" does not require double quotes; use single
   |       | quotes instead
--------------------------------------------------------------------------------

Listing 5

// @codingStandardsIgnoreStart
... dieser Code wird nicht geprüft ...
// @codingStandardsIgnoreEnd

Sonstige vergleichbare Projekte

Lint-ähnliche Codeprüfer gibt es nicht nur für C, Perl und PHP, sondern auch für viele weitere Programmiersprachen. Suchen Sie doch einfach mal nach dem Begriff "lint" und Ihrer Lieblingsprogrammiersprache im Netz – Sie werden mit hoher Wahrscheinlichkeit schnell fündig. Doch solche Tools gibt es nicht nur für Programmiersprachen, sondern in vergleichbarer Form auch für das Textsatzsystem LaTeX (ChkTeX, [7]), Pakete der Typen RPM (RPM Lint, [8]) und DEB (Lintian, [9]) sowie Cascading Style Sheets (CSS Lint, [10]).

Fazit

Es empfiehlt sich übrigens nicht, mal eben auf die Schnelle potenzielle "Fehler" in fremden Projekten zu korrigieren. Sonst kann es einem so gehen, wie einigen Entwicklern des Debian-Projekts.

Diese stellten aufgrund einer Prüfung mit Valgrind (einem Programm, das ein Programm in einer Art virtuellen Maschine ausführt und so Fehler sucht) einen "Bug" im OpenSSL-Projekt fest: Das Programm griff auf einen noch nicht initialisierten Speicher zu – flugs kommentierten die Debianer die vermeintlich fehlerhafte Funktion aus.

Allerdings handelte es sich bei dem derart "korrigierten" Code um eine Funktion, die Zufallszahlen erzeugt – der Zugriff auf den nicht initialisierten Speicher sollte unter anderem zufällige Werte auslesen. Durch die "Korrektur" wurden jahrelang nicht zufällige, sondern stattdessen leicht vorhersagbare SSL-Zertifikate generiert – eine kryptographische Katastrophe [11].

Bei der Arbeit am eigenen Code jedoch erleichtern die diversen Lint-Varianten und Codechecker die Fehlersuche in Programmen ganz wesentlich und sorgen zudem dafür, dass grössere Software-Projekte mit mehreren Entwicklern schnell zu einem gemeinsamen Coding-Stil finden. 

Glossar

Statische Quellcodeprüfung

Bei diesem Verfahren wird der Quellcode analysiert, ohne das Programm auszuführen. Es gibt auch Code-Analyzer, die das das Programm in einer Art virtuellen Maschine ausführen und so Fehler suchen (beispielsweise Valgrind).

a.out

Der Standardname des vom C-Compiler erzeugten Programms, wenn Sie nicht über die -o Name etwas Anderes angegeben.

Der Autor

Der Systemadminstrator Wolfgang Dautermann hat neben vielen Linux-Varianten auch schon Unix-Systeme wie Solaris, Irix oder Tru64 gebändigt. Er zählt zu den Organisatoren der Grazer Linuxtage.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 

Related content

Kommentare
Weitere interessante Sourcecode-Analyzer.
Wolfgang Dautermann, Sonntag, 14. Juli 2013 10:08:35
Ein/Ausklappen

* http://www.shellcheck.net/ Shellskript-Analyzer
* http://cppcheck.sourceforge.net/ Ein weiterer interessanter C/C++ Checker
* http://www.jslint.com/ Javascript-Checker


Bewertung: 92 Punkte bei 3 Stimmen.
Den Beitrag bewerten: Gut / Schlecht