Die Windows-Programmierer unter euch werden sicher auch festgestellt haben, dass sich Funktionsdefinitionen in den Header-Dateien vom Platform Windows SDK in den letzten Versionen etwas verändert haben. Hieß es bei Windows XP noch
WINBASEAPI
UINT
WINAPI
GetTempFileNameW(
IN LPCWSTR lpPathName,
IN LPCWSTR lpPrefixString,
IN UINT uUnique,
OUT LPWSTR lpTempFileName
);
so schaut die Definition im Windows SDK für Server 2008 so aus:
WINBASEAPI
UINT
WINAPI
GetTempFileNameW(
__in LPCWSTR lpPathName,
__in LPCWSTR lpPrefixString,
__in UINT uUnique,
__out_ecount(MAX_PATH) LPWSTR lpTempFileName
);
An den Funktionsparametern selbst hat sich natürlich nichts verändert, aber die neuen Präfixe fallen doch ins Auge. Anfangs habe ich mir dabei nicht viel gedacht, aber mittlerweile hat sich mir der tiefere Sinn der Änderungen erschlossen.
Alles hängt mit der Standard Annotation Language (SAL) zusammen. In Verbindung mit einem statischen Codeanalyse-Tool lassen sich so mögliche Programmierfehler schon beim Kompilieren ermitteln. Auch wenn sich ein Projekt unter höchster Warnstufe ohne Warnungen kompilieren lässt, so heißt dies noch lange nicht, dass alles sauber ablaufen muss.
Leider ist das statischen Codeanalyse-Tool den höherpreisigen Visual Studio-Versionen Team System und Team Edition for Developers vorbehalten. Die Team Edition for Developers kostet aber locker 5000 Euro mehr als die Professional Edition, was für den normalen Entwickler ziemlich unerschwinglich sein dürfte.
Abhilfe schafft hier das Windows SDK für Vista sowie das für Server 2008. Beide enthalten die vollständige Compilersammlung von Visual Studio 2005 bzw. Visual Studio 2008 samt Codeanalyse-Tool. Für die Codeanalyse sind die Dateien c1ast.dll, c1xxast.dll, mspft80.dll und mspft80ui.dll zuständig. Die ersten drei Dateien befinden sich unter VC\bin, die letzte enthält die englischsprachigen Ressourcen und ist unter VC\bin\1033 zu Hause.
Nachdem man die vier Dateien an den entsprechenden Ort kopiert hat, muss die Codeanalyse nur noch aktiviert werden. In der Team Edition for Developers gibt es dafür eine Seite in den Projekteigenschaften, in der Professional Edition muss man den Kommandoschalter /analyze unter Configuration Properties – C/C++ – Command Line manuell eintragen.
Was macht die Codeanalyse nun so interessant? Nehmen wir einfach mal die oben angesprochene Funktion GetTempFileNameW. Die Verwendung könnte so aussehen:
29: WCHAR szTempFileName[240];
30: GetTempFileNameW(L”C:\\”, NULL, 1, szTempFileName);
Der Compiler hat hier nichts auszusetzen, selbst bei /W4 gibt es keine Warnungen. Aktiviert man aber die Codeanalyse, dann sieht man plötzlich folgende Warnhinweise:
: warning C6202: Buffer overrun for ’szTempFileName’, which is possibly stack allocated, in call to ‘GetTempFileNameW’: length ‘520′ exceeds buffer size ‘480′
:warning C6309: Argument ‘2′ is null: this does not adhere to function specification of ‘GetTempFileNameW’
: warning C6386: Buffer overrun: accessing ‘argument 4′, the writable size is ‘480′ bytes, but ‘520′ bytes might be written: Lines: 29, 30
: warning C6387: ‘argument 2′ might be ‘0′: this does not adhere to the specification for the function ‘GetTempFileNameW’: Lines: 29, 30
Anhand der Definition von GetTempFileNameW erkennt der Compiler, dass die Funktion als vierten Parameter einen Puffer von mindestens 260 Zeichen erwartet, der Programmierer aber aus unerklärlichen Gründen nur einen Puffer von 240 Zeichen bereitgestellt hat. Ist der gewünschte temporäre Dateiname nun länger als die zur Verfügung stehenden 240 Zeichen, dann führt dies zu einem sicheren Pufferüberlauf.
Der Grund für die zweite Warnung ist der zweite Funktionsparameter. __in legt fest, dass lpPrefixString ein gültiger und initialisierter Zeiger sein muss. Der Compiler erkennt, dass NULL nicht in diese Kategorie passt und weist uns darauf hin. NULL wäre nur gültig, wenn lpPrefixString stattdessen mit __in_opt gekennzeichnet sein würde.
Insgesamt hat die statische Codeanalyse über 130 verschiedene Warnhinweise in petto. Sie erkennt z.B., wenn HRESULT-Werte in booleschen Vergleichen verwendet werden:
CAtlFile file;
if (!file.Create(L”C:\\test.txt”, GENERIC_READ, FILE_SHARE_READ, CREATE_ALWAYS))
{
//
}
oder wenn eine Funktion Variablen enthält, die unverhältnismäßig viel Platz auf dem Stack belegen:
WCHAR szTempFileName[32768];
GetTempFileNameW(L”C:\\”, L”123″, 1, szTempFileName);
Mich selbst haben die Möglichkeiten der statischen Codeanalyse mehr als begeistert. Wenn man sich die möglichen 138 Warnungen so anschaut, dann entdeckt man viele Sachen, die man gerne mal falsch macht und über die man auch schon das eine oder andere Mal gestolpert ist. In der nächsten Zeit werde ich daher meine Projekte Stück für Stück mit /analyze kompilieren und die angezeigten Warnungen abarbeiten. Im zweiten Schritt werden dann die eigenen Funktionen mit den SAL-Parametern versehen.