Die Geschichte des Treffens des statischen Analysators PVS-Studio mit dem Haiku-Betriebssystemcode geht auf das Jahr 2015 zurück. Es war ein interessantes Experiment und eine nützliche Erfahrung für die Teams beider Projekte. Warum ein Experiment? Damals gab es keinen Analysator für Linux und es wird keine weiteren anderthalb Jahre geben. Die Arbeit der Enthusiasten unseres Teams wurde jedoch belohnt: Wir haben uns mit den Haiku-Entwicklern getroffen und die Qualität des Codes verbessert, die Datenbank mit seltenen Programmierfehlern aufgefüllt und den Analysator verfeinert. Das Überprüfen des Haiku-Codes auf Fehler ist jetzt schnell und einfach.
Einführung
Die Helden unserer Geschichte sind das Open-Source-Betriebssystem
Haiku und der statische Analysator
PVS-Studio für C, C ++, C # und Java. Als wir vor 4,5 Jahren mit der Analyse des Projekts begannen, mussten wir nur mit der kompilierten ausführbaren Datei des Analysators arbeiten. Die gesamte Infrastruktur zum Parsen von Kompilierungsparametern, Starten eines Präprozessors, Parallelisieren der Analyse usw. wurde aus dem Dienstprogramm
Compiler Monitoring UI in C # übernommen, das teilweise auf die Mono-Plattform portiert wurde, um unter Linux ausgeführt zu werden. Das Haiku-Projekt selbst wird mit einem Cross-Compiler unter verschiedenen Betriebssystemen außer Windows erstellt. Ich möchte noch einmal auf die Bequemlichkeit und Vollständigkeit der Haiku-Baugruppendokumentation hinweisen und mich auch bei den Haiku-Entwicklern für ihre Hilfe beim Aufbau des Projekts bedanken.
Jetzt ist die Analyse viel einfacher. Die Liste aller Befehle zum Erstellen und Analysieren des Projekts sieht folgendermaßen aus:
cd /opt git clone https:
Die Analyse des Projekts wurde übrigens im Docker-Container durchgeführt. Kürzlich haben wir eine neue Dokumentation zu diesem Thema vorbereitet:
Ausführen von PVS-Studio in Docker . Dies kann die Verwendung statischer Projektanalysetechniken für einige Unternehmen erheblich vereinfachen.
Nicht initialisierte Variablen
V614 Nicht initialisierte Variable 'rval' verwendet. fetch.c 1727
int auto_fetch(int argc, char *argv[]) { volatile int argpos; int rval;
Die Variable
rval wurde beim Deklarieren nicht initialisiert. Wenn Sie sie also mit einem Nullwert vergleichen, erhalten Sie ein undefiniertes Ergebnis. Wenn die Umstände fehlschlagen, wird der undefinierte Wert der Variablen
rval möglicherweise zum Rückgabewert der Funktion
auto_fetch .
V614 Nicht initialisierter Zeiger 'res' verwendet. Befehle.c 2873
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; char *ai_canonname; struct sockaddr *ai_addr; struct addrinfo *ai_next; }; static int sourceroute(struct addrinfo *ai, char *arg, char **cpp, int *lenp, int *protop, int *optp) { static char buf[1024 + ALIGNBYTES]; char *cp, *cp2, *lsrp, *ep; struct sockaddr_in *_sin; #ifdef INET6 struct sockaddr_in6 *sin6; struct ip6_rthdr *rth; #endif struct addrinfo hints, *res;
Ein ähnlicher Fall der Verwendung einer nicht initialisierten Variablen, nur hier ist der nicht initialisierte Zeiger
res .
V506 Der Zeiger auf die lokale Variable 'normalisiert' wird außerhalb des Bereichs dieser Variablen gespeichert. Ein solcher Zeiger wird ungültig. TextView.cpp 5596
void BTextView::_ApplyStyleRange(...., const BFont* font, ....) { if (font != NULL) { BFont normalized = *font; _NormalizeFont(&normalized); font = &normalized; } .... fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode, font, color); }
Wahrscheinlich musste der Programmierer das Objekt durch eine Zwischenvariable normalisieren. Jetzt wurde im
Schriftzeiger der Zeiger auf das
normalisierte temporäre Objekt
gespeichert , der zerstört wird, nachdem der Bereich verlassen wurde, in dem dieses temporäre Objekt erstellt wurde.
V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 27
int8 BUnicodeChar::Type(uint32 c) { BUnicodeChar(); return u_charType(c); }
Ein sehr häufiger Fehler unter C ++ - Programmierern besteht darin, den Konstruktoraufruf zu verwenden, um angeblich die Klassenfelder zu initialisieren / auf Null zu setzen. In diesem Fall werden die Felder der Klasse nicht geändert, es wird jedoch ein neues unbenanntes Objekt dieser Klasse erstellt, das sofort zerstört wird. Leider gibt es viele solcher Orte:
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 37
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 49
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 58
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 67
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 77
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 89
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 103
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 115
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 126
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 142
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 152
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 163
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 186
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 196
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 206
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 214
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 222
- V603 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Wenn Sie den Konstruktor aufrufen möchten, sollte 'this-> BUnicodeChar :: BUnicodeChar (....)' verwendet werden. UnicodeChar.cpp 230
V670 Das nicht initialisierte Klassenmitglied 'fPatternHandler' wird verwendet, um das 'fInternal'-Mitglied zu initialisieren. Denken Sie daran, dass Mitglieder in der Reihenfolge ihrer Deklarationen innerhalb einer Klasse initialisiert werden. Painter.cpp 184
Painter::Painter() : fInternal(fPatternHandler), .... fPatternHandler(), .... { .... }; class Painter { .... private: mutable PainterAggInterface fInternal;
Ein weiteres Beispiel für eine falsche Initialisierung. Klassenfelder werden in der Reihenfolge initialisiert, in der sie in der Klasse selbst deklariert sind. In diesem Beispiel wird das
interne Feld zuerst mit dem nicht initialisierten Wert
fPatternHandler initialisiert.
Verdächtig #define
V523 Die Anweisung 'then' entspricht der Anweisung 'else'. subr_gtaskqueue.c 191
#define TQ_LOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_lock_spin(&(tq)->tq_mutex); \ else \ mtx_lock(&(tq)->tq_mutex); \ } while (0) #define TQ_ASSERT_LOCKED(tq) mtx_assert(&(tq)->tq_mutex, MA_OWNED) #define TQ_UNLOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_unlock_spin(&(tq)->tq_mutex); \ else \ mtx_unlock(&(tq)->tq_mutex); \ } while (0) void grouptask_block(struct grouptask *grouptask) { .... TQ_LOCK(queue); gtask->ta_flags |= TASK_NOENQUEUE; gtaskqueue_drain_locked(queue, gtask); TQ_UNLOCK(queue); }
Das Code-Snippet sieht erst dann verdächtig aus, wenn Sie sich das Ergebnis des Präprozessors ansehen:
void grouptask_block(struct grouptask *grouptask) { .... do { if ((queue)->tq_spin) mtx_lock(&(queue)->tq_mutex); else mtx_lock(&(queue)->tq_mutex); } while (0); gtask->ta_flags |= 0x4; gtaskqueue_drain_locked(queue, gtask); do { if ((queue)->tq_spin) mtx_unlock(&(queue)->tq_mutex); else mtx_unlock(&(queue)->tq_mutex); } while (0); }
Der Analysator hat wirklich Recht - die
if- und
else- Zweige sind identisch. Aber wohin gingen die Funktionen
mtx_lock_spin und
mtx_unlock_spin ? Die Makros
TQ_LOCK ,
TQ_UNLOCK und die Funktion
grouptask_block werden in derselben Datei deklariert und fast nebeneinander, es gab jedoch irgendwo eine Ersetzung.
Eine Suche nach dem Inhalt der Dateien ergab nur
mutex.h mit folgendem Inhalt:
#define mtx_lock_spin(x) mtx_lock(x) #define mtx_unlock_spin(x) mtx_unlock(x)
Ob eine solche Ersetzung korrekt ist oder nicht, sollte für die Projektentwickler überprüft werden. Ich habe das Projekt unter Linux überprüft, und eine solche Ersetzung schien mir verdächtig.
Fehler mit der freien Funktion
V575 Der Nullzeiger wird an die Funktion 'frei' übergeben. Überprüfen Sie das erste Argument. setmime.cpp 727
void MimeType::_PurgeProperties() { fShort.Truncate(0); fLong.Truncate(0); fPrefApp.Truncate(0); fPrefAppSig.Truncate(0); fSniffRule.Truncate(0); delete fSmallIcon; fSmallIcon = NULL; delete fBigIcon; fBigIcon = NULL; fVectorIcon = NULL;
Sie können einen Nullzeiger an die
freie Funktion übergeben, aber ein solcher Eintrag ist eindeutig verdächtig. Der Analysator fand also die verwirrten Codezeilen. Zuerst mussten Sie den Speicher mit dem Zeiger
fVectorIcon freigeben und erst dann auf
NULL setzen .
V575 Der Nullzeiger wird an die Funktion 'frei' übergeben. Überprüfen Sie das erste Argument. driver_settings.cpp 461
static settings_handle * load_driver_settings_from_file(int file, const char *driverName) { .... handle = new_settings(text, driverName); if (handle != NULL) {
Ein weiteres Beispiel für die explizite Übergabe eines Nullzeigers an die
freie Funktion. Diese Zeile kann gelöscht werden, weil Nach erfolgreichem Empfang des Zeigers wird die Funktion beendet.
V575 Der Nullzeiger wird an die Funktion 'frei' übergeben. Überprüfen Sie das erste Argument. PackageFileHeapWriter.cpp 166
void* _GetBuffer() { .... void* buffer = malloc(fBufferSize); if (buffer == NULL && !fBuffers.AddItem(buffer)) { free(buffer); throw std::bad_alloc(); } return buffer; }
Hier ist ein Fehler. Anstelle des Operators && muss der Operator || verwendet werden. Nur in diesem Fall wird eine Ausnahme
std :: bad_alloc () ausgelöst, wenn es nicht möglich war, Speicher mit der Funktion
malloc zuzuweisen.
Fehler mit Löschoperator
V611 Der Speicher wurde mit dem Operator 'new T []' zugewiesen, aber mit dem Operator 'delete' freigegeben. Überprüfen Sie diesen Code. Es ist wahrscheinlich besser, 'delete [] fMsg;' zu verwenden. Err.cpp 65
class Err { public: .... private: char *fMsg; ssize_t fPos; }; void Err::Unset() { delete fMsg;
Der
fMsg- Zeiger
wird verwendet, um Speicher zum Speichern eines Array von Zeichen zuzuweisen, und der
Löschoperator wird verwendet, um Speicher
freizugeben , anstatt
delete [] .
V611 Der Speicher wurde mit dem Operator 'new' zugewiesen, aber mit der Funktion 'free' freigegeben. Überprüfen Sie die Operationslogik hinter der Variablen 'wrapperPool'. vm_page.cpp 3080
status_t vm_page_write_modified_page_range(....) { .... PageWriteWrapper* wrapperPool = new(malloc_flags(allocationFlags)) PageWriteWrapper[maxPages + 1]; PageWriteWrapper** wrappers = new(malloc_flags(allocationFlags)) PageWriteWrapper*[maxPages]; if (wrapperPool == NULL || wrappers == NULL) { free(wrapperPool);
Hier ist
malloc_flags die Funktion, die
malloc macht. Und dann konstruiert
Placement-New das Objekt hier. Und da die
PageWriteWrapper- Klasse wie folgt implementiert ist:
class PageWriteWrapper { public: PageWriteWrapper(); ~PageWriteWrapper(); void SetTo(vm_page* page); bool Done(status_t result); private: vm_page* fPage; struct VMCache* fCache; bool fIsActive; }; PageWriteWrapper::PageWriteWrapper() : fIsActive(false) { } PageWriteWrapper::~PageWriteWrapper() { if (fIsActive) panic("page write wrapper going out of scope but isn't completed"); }
dann werden Objektdestruktoren dieser Klasse aufgrund der Verwendung der
freien Funktion zum Freigeben von Speicher nicht aufgerufen.
V611 Der Speicher wurde mit dem Operator 'new T []' zugewiesen, aber mit dem Operator 'delete' freigegeben. Überprüfen Sie diesen Code. Es ist wahrscheinlich besser, 'delete [] fOutBuffer;' zu verwenden. Überprüfen Sie die Zeilen: 26, 45. PCL6Rasterizer.h 26
class PCL6Rasterizer : public Rasterizer { public: .... ~PCL6Rasterizer() { delete fOutBuffer; fOutBuffer = NULL; } .... virtual void InitializeBuffer() { fOutBuffer = new uchar[fOutBufferSize]; } private: uchar* fOutBuffer; int fOutBufferSize; };
Die Verwendung des
Löschoperators anstelle von
delete [] ist ein sehr häufiger Fehler. Der einfachste Weg, beim Schreiben einer Klasse einen Fehler zu machen, ist Der Destruktorcode befindet sich häufig weit entfernt von den Speicherzuordnungsorten. Hier gibt der Programmierer durch den Zeiger
fOutBuffer fälschlicherweise Speicher im Destruktor
frei .
V772 Das Aufrufen eines Löschoperators für einen ungültigen Zeiger führt zu undefiniertem Verhalten. Hashtable.cpp 207
void Hashtable::MakeEmpty(int8 keyMode,int8 valueMode) { .... for (entry = fTable[index]; entry; entry = next) { switch (keyMode) { case HASH_EMPTY_DELETE:
Zusätzlich zur falschen Wahl zwischen
delete /
delete [] und
free können Sie dem Programm auf andere Weise undefiniertes Verhalten hinzufügen - versuchen Sie, den Speicher durch einen Zeiger auf einen undefinierten Typ
(void *) zu löschen.
Funktionen ohne Rückgabewert
V591 Non-void-Funktion sollte einen Wert zurückgeben. Referenceable.h 228
BReference& operator=(const BReference<const Type>& other) { fReference = other.fReference; }
Der überschriebene Zuweisungsoperator hat nicht genügend Rückgabewert. In diesem Fall gibt der Bediener einen zufälligen Wert zurück, was zu seltsamen Fehlern führen kann.
Ähnliche Probleme in anderen Codefragmenten dieser Klasse:
- V591 Non-void-Funktion sollte einen Wert zurückgeben. Referenceable.h 233
- V591 Non-void-Funktion sollte einen Wert zurückgeben. Referenceable.h 239
V591 Non-void-Funktion sollte einen Wert zurückgeben. main.c 1010
void errx(int, const char *, ...) ; char * getoptionvalue(const char *name) { struct option *c; if (name == NULL) errx(1, "getoptionvalue() invoked with NULL name"); c = getoption(name); if (c != NULL) return (c->value); errx(1, "getoptionvalue() invoked with unknown option '%s'", name); }
Ein NOTREACHED-Benutzerkommentar bedeutet hier nichts. Um Code für solche Szenarien korrekt zu schreiben, müssen Sie Funktionen wie noreturn markieren. Hierfür gibt es noreturn-Attribute: standard- und compilerspezifisch. Zuallererst werden solche Attribute von Compilern für die korrekte Codegenerierung oder Benachrichtigung über einige Arten von Fehlern mithilfe von Warings berücksichtigt. Verschiedene statische Analysewerkzeuge berücksichtigen auch Attribute, um die Analysequalität zu verbessern.
Ausnahmebehandlung
V596 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Das Schlüsselwort 'throw' könnte fehlen: throw ParseException (FOO); Response.cpp 659
size_t Response::ExtractNumber(BDataIO& stream) { BString string = ExtractString(stream); const char* end; size_t number = strtoul(string.String(), (char**)&end, 10); if (end == NULL || end[0] != '\0') ParseException("Invalid number!"); return number; }
Hier wird das Schlüsselwort
throw versehentlich vergessen. Daher wird eine
ParseException nicht
ausgelöst , und ein Objekt dieser Klasse wird einfach zerstört, wenn es den Bereich verlässt. Danach setzt die Funktion ihre Arbeit fort, als wäre nichts passiert, als wäre die richtige Nummer eingegeben worden.
V1022 Eine Ausnahme wurde vom Zeiger ausgelöst. Ziehen Sie es stattdessen nach Wert. gensyscallinfos.cpp 316
int main(int argc, char** argv) { try { return Main().Run(argc, argv); } catch (Exception& exception) {
Der Analysator hat eine vom Zeiger ausgelöste
IOException erkannt. Wenn Sie einen Zeiger werfen, wird die Ausnahme nicht abgefangen, sodass die Ausnahme schließlich als Referenz abgefangen wird. Die Verwendung eines Zeigers zwingt den Interceptor außerdem, den
Löschoperator aufzurufen, um das erstellte Objekt zu zerstören, was ebenfalls nicht erfolgt.
Zwei weitere Problembereiche des Codes:
- V1022 Eine Ausnahme wurde vom Zeiger ausgelöst. Ziehen Sie es stattdessen nach Wert. gensyscallinfos.cpp 347
- V1022 Eine Ausnahme wurde vom Zeiger ausgelöst. Ziehen Sie es stattdessen nach Wert. gensyscallinfos.cpp 413
Formale Sicherheit
V597 Der Compiler könnte den Funktionsaufruf 'memset' löschen, mit dem das Objekt 'f_key' geleert wird. Die Funktion memset_s () sollte verwendet werden, um die privaten Daten zu löschen. dst_api.c 1018
#ifndef SAFE_FREE #define SAFE_FREE(a) \ do{if(a != NULL){memset(a,0, sizeof(*a)); free(a); a=NULL;}} while (0) .... #endif DST_KEY * dst_free_key(DST_KEY *f_key) { if (f_key == NULL) return (f_key); if (f_key->dk_func && f_key->dk_func->destroy) f_key->dk_KEY_struct = f_key->dk_func->destroy(f_key->dk_KEY_struct); else { EREPORT(("dst_free_key(): Unknown key alg %d\n", f_key->dk_alg)); } if (f_key->dk_KEY_struct) { free(f_key->dk_KEY_struct); f_key->dk_KEY_struct = NULL; } if (f_key->dk_key_name) SAFE_FREE(f_key->dk_key_name); SAFE_FREE(f_key); return (NULL); }
Der Analysator hat verdächtigen Code erkannt, mit dem private Daten sicher bereinigt werden können. Leider macht das
SAFE_FREE- Makro, das zu
Memset ,
kostenlosen Aufrufen und
NULL- Zuweisung erweitert wurde, den Code nicht sicherer, weil All dies wird vom Compiler während der
O2- Optimierung entfernt.
Dies ist übrigens nichts anderes als
CWE-14 : Compiler-Entfernung von Code zum Löschen von Puffern.
Die gesamte Liste der Stellen, an denen Puffer nicht gelöscht werden:
- V597 Der Compiler könnte den Funktionsaufruf 'memset' löschen, mit dem der Puffer 'encoded_block' geleert wird. Die Funktion memset_s () sollte verwendet werden, um die privaten Daten zu löschen. dst_api.c 446
- V597 Der Compiler könnte den Funktionsaufruf 'memset' löschen, mit dem das Objekt 'key_st' geleert wird. Die Funktion memset_s () sollte verwendet werden, um die privaten Daten zu löschen. dst_api.c 685
- V597 Der Compiler könnte den Funktionsaufruf 'memset' löschen, mit dem der Puffer 'in_buff' geleert wird. Die Funktion memset_s () sollte verwendet werden, um die privaten Daten zu löschen. dst_api.c 916
- V597 Der Compiler könnte den Funktionsaufruf 'memset' löschen, mit dem das Objekt 'ce' gelöscht wird. Die Funktion memset_s () sollte verwendet werden, um die privaten Daten zu löschen. fs_cache.c 1078
Vorzeichenlose Vergleiche
V547 Der Ausdruck 'verbleibend <0' ist immer falsch. Der vorzeichenlose Typwert ist niemals <0. DwarfFile.cpp 1947
status_t DwarfFile::_UnwindCallFrame(....) { .... uint64 remaining = lengthOffset + length - dataReader.Offset(); if (remaining < 0) return B_BAD_DATA; .... }
Der Analysator fand einen expliziten Vergleich einer vorzeichenlosen Variablen mit negativen Werten. Vielleicht sollten Sie die
verbleibende Variable
mit nur Null vergleichen oder eine Überlaufprüfung durchführen.
V547 Der Ausdruck 'sleep ((unsigned) secs) <0' ist immer falsch. Der vorzeichenlose Typwert ist niemals <0. Misc.cpp 56
status_t snooze(bigtime_t amount) { if (amount <= 0) return B_OK; int64 secs = amount / 1000000LL; int64 usecs = amount % 1000000LL; if (secs > 0) { if (sleep((unsigned)secs) < 0)
Um zu verstehen, was der Fehler ist, wenden wir uns den Signaturen der
Schlaf- und
Schlaffunktionen zu :
extern unsigned int sleep (unsigned int __seconds); extern int usleep (__useconds_t __useconds);
Wie wir sehen können, gibt die
Sleep- Funktion einen Wert eines vorzeichenlosen Typs zurück und seine Verwendung im Code ist falsch.
Gefährliche Hinweise
V774 Der Zeiger 'Gerät' wurde verwendet, nachdem der Speicher freigegeben wurde. xhci.cpp 1572
void XHCI::FreeDevice(Device *device) { uint8 slot = fPortSlots[device->HubPort()]; TRACE("FreeDevice() port %d slot %d\n", device->HubPort(), slot);
Ein
Geräteobjekt wird vom
Löschoperator gelöscht . Dies ist eine logische Aktion für eine Funktion namens
FreeDevice . Um jedoch aus irgendeinem Grund andere Ressourcen im Code freizugeben, wird erneut ein bereits gelöschtes Objekt angesprochen.
Ein solcher Code ist äußerst gefährlich und kommt an mehreren anderen Stellen vor:
- V774 Der 'Selbst'-Zeiger wurde verwendet, nachdem der Speicher freigegeben wurde. TranslatorRoster.cpp 884
- V774 Der 'String'-Zeiger wurde verwendet, nachdem der Speicher freigegeben wurde. RemoteView.cpp 1269
- V774 Der Zeiger 'bs' wurde verwendet, nachdem der Speicher freigegeben wurde. mkntfs.c 4291
- V774 Der Zeiger 'bs' wurde verwendet, nachdem der Speicher freigegeben wurde. mkntfs.c 4308
- V774 Der 'al'-Zeiger wurde verwendet, nachdem der Speicher neu zugewiesen wurde. inode.c 1155
V522 Eine Dereferenzierung des Nullzeigers 'Daten' kann stattfinden. Der Nullzeiger wird an die Funktion 'malo_hal_send_helper' übergeben. Untersuchen Sie das dritte Argument. Überprüfen Sie die Zeilen: 350, 394. if_malohal.c 350
static int malo_hal_fwload_helper(struct malo_hal *mh, char *helper) { .... error = malo_hal_send_helper(mh, 0, NULL, 0, MALO_NOWAIT);
Die interprozedurale Analyse ergab eine Situation, in der
NULL an die Funktion übergeben wird und der Datenzeiger mit diesem Wert anschließend in der
memcpy- Funktion dereferenziert wird.
V773 Die Funktion wurde beendet, ohne den Zeiger 'inputFileFile' freizugeben. Ein Speicherverlust ist möglich. command_recompress.cpp 119
int command_recompress(int argc, const char* const* argv) { .... BFile* inputFileFile = new BFile; error = inputFileFile->SetTo(inputPackageFileName, O_RDONLY); if (error != B_OK) { fprintf(stderr, "Error: Failed to open input file \"%s\": %s\n", inputPackageFileName, strerror(error)); return 1; } inputFile = inputFileFile; .... }
PVS-Studio kann Speicherlecks erkennen . Hier wird im Falle eines Fehlers kein Speicher für
inputFileFile freigegeben. Jemand glaubt, dass Sie sich im Fehlerfall nicht darum kümmern können, Speicher freizugeben - das Programm endet trotzdem. Dies ist jedoch nicht immer der Fall. Behandeln Sie Fehler richtig und arbeiten Sie weiter - eine Voraussetzung für so viele Programme.
V595 Der Zeiger 'fReply' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 49, 52. ReplyBuilder.cpp 49
RPC::CallbackReply* ReplyBuilder::Reply() { fReply->Stream().InsertUInt(fStatusPosition, _HaikuErrorToNFS4(fStatus)); fReply->Stream().InsertUInt(fOpCountPosition, fOpCount); if (fReply == NULL || fReply->Stream().Error() == B_OK) return fReply; else return NULL; }
Wie oft dereferenzieren Entwickler Zeiger, bevor sie sie überprüfen. Diagnostics
V595 ist fast immer führend in der Anzahl der Warnungen in einem Projekt. Die gefährliche Verwendung des
fReply- Zeigers in diesem Code.
V595 Der Zeiger 'mq' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 782, 786. oce_queue.c 782
static void oce_mq_free(struct oce_mq *mq) { POCE_SOFTC sc = (POCE_SOFTC) mq->parent; struct oce_mbx mbx; struct mbx_destroy_common_mq *fwcmd; if (!mq) return; .... }
Ein ähnliches Beispiel. Der
mg- Zeiger wird einige Zeilen früher dereferenziert, als er auf einen Nullwert überprüft wird. Es gibt viele ähnliche Stellen im Projekt. An einigen Stellen ist die Verwendung und Überprüfung von Zeigern weit voneinander entfernt, sodass der Artikel zwei Beispiele enthält. Der Rest kann die Entwickler im vollständigen Analysebericht sehen.
Verschiedene Fehler
V645 Der Funktionsaufruf 'strncat' kann zum Pufferüberlauf 'output' führen. Die Grenzen sollten nicht die Größe des Puffers enthalten, sondern eine Anzahl von Zeichen, die er enthalten kann. NamespaceDump.cpp 101
static void dump_acpi_namespace(acpi_ns_device_info *device, char *root, int indenting) { char output[320]; char tabs[255] = ""; .... strlcat(tabs, "|--- ", sizeof(tabs)); .... while (....) { uint32 type = device->acpi->get_object_type(result); snprintf(output, sizeof(output), "%s%s", tabs, result + depth); switch(type) { case ACPI_TYPE_INTEGER: strncat(output, " INTEGER", sizeof(output)); break; case ACPI_TYPE_STRING: strncat(output, " STRING", sizeof(output)); break; .... } .... } .... }
Der Unterschied zwischen den
Funktionen strlcat und
strncat ist für eine Person, die mit der Beschreibung dieser Funktionen noch nicht
vertraut ist, nicht ganz offensichtlich. Die Funktion
strlcat verwendet die
Größe des gesamten Puffers als drittes Argument, und die Funktion
strncat verwendet
die Größe des freien Speicherplatzes im Puffer , für den vor dem Aufruf der Funktion der gewünschte Wert berechnet werden muss. Aber Entwickler vergessen es oft oder wissen es nicht. Das Übergeben der
strncat- Funktion
an die Größe des gesamten Puffers kann zu einem Pufferüberlauf führen, weil Die Funktion betrachtet diesen Wert als die Anzahl der Zeichen, die zum Kopieren zugelassen sind. Die
strlcat- Funktion
hat dieses Problem nicht, aber damit es richtig funktioniert, müssen Zeichenfolgen übergeben werden, die mit Terminal Null enden.
Die ganze Liste gefährlicher Orte mit Linien:
- V645 Der Funktionsaufruf 'strncat' kann zum Pufferüberlauf 'output' führen. Die Grenzen sollten nicht die Größe des Puffers enthalten, sondern eine Anzahl von Zeichen, die er enthalten kann. NamespaceDump.cpp 104
- V645 Der Funktionsaufruf 'strncat' kann zum Pufferüberlauf 'output' führen. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 107
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 110
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 113
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 118
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 119
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 120
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 123
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 126
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 129
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 132
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 135
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 138
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 141
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 144
- V645 The 'strncat' function call could lead to the 'features_string' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. VirtioDevice.cpp 283
- V645 The 'strncat' function call could lead to the 'features_string' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. VirtioDevice.cpp 284
- V645 Der Funktionsaufruf 'strncat' kann zum Pufferüberlauf 'features_string' führen. Die Grenzen sollten nicht die Größe des Puffers enthalten, sondern eine Anzahl von Zeichen, die er enthalten kann. VirtioDevice.cpp 285
V792 Die Funktion 'SetDecoratorSettings' befindet sich rechts vom Operator '|' wird unabhängig vom Wert des linken Operanden aufgerufen. Vielleicht ist es besser, '||' zu verwenden. DesktopListener.cpp 324 class DesktopListener : public DoublyLinkedListLinkImpl<DesktopListener> { public: .... virtual bool SetDecoratorSettings(Window* window, const BMessage& settings) = 0; .... }; bool DesktopObservable::SetDecoratorSettings(Window* window, const BMessage& settings) { if (fWeAreInvoking) return false; InvokeGuard invokeGuard(fWeAreInvoking); bool changed = false; for (DesktopListener* listener = fDesktopListenerList.First(); listener != NULL; listener = fDesktopListenerList.GetNext(listener)) changed = changed | listener->SetDecoratorSettings(window, settings); return changed; }
Höchstwahrscheinlich sind die Operatoren '|' und '||'. Ein solcher Fehler führt zu unnötigen Aufrufen der SetDecoratorSettings- Funktion .V627 Überprüfen Sie den Ausdruck. Das Argument von sizeof () ist das Makro, das zu einer Zahl erweitert wird. device.c 72 #define PCI_line_size 0x0c static status_t wb840_open(const char* name, uint32 flags, void** cookie) { .... data->wb_cachesize = gPci->read_pci_config(data->pciInfo->bus, data->pciInfo->device, data->pciInfo->function, PCI_line_size, sizeof(PCI_line_size)) & 0xff; .... }
Transfer des Betreibers sizeof Wert 0x0c sieht verdächtig. Vielleicht sollten Sie die Größe eines Objekts berechnen, z. B. Daten .V562 Es ist seltsam, einen Wert vom Typ Bool mit einem Wert von 18 zu vergleichen: 0x12 == IsProfessionalSpdif (). CEchoGals_mixer.cpp 533 typedef bool BOOL; virtual BOOL IsProfessionalSpdif() { ... } #define ECHOSTATUS_DSP_DEAD 0x12 ECHOSTATUS CEchoGals::ProcessMixerFunction(....) { .... if ( ECHOSTATUS_DSP_DEAD == IsProfessionalSpdif() )
Die IsProfessionalSpdif- Funktion gibt einen Wert vom Typ bool zurück , während unter der Bedingung das Ergebnis der Funktion mit der Zahl 0x12 verglichen wird .Fazit
Wir haben die Veröffentlichung der ersten Haiku Beta im letzten Herbst verpasst waren damit beschäftigt, PVS-Studio für die Java-Sprache freizugeben. Die Art der Programmiererfehler ist jedoch so, dass sie nur dann auftreten, wenn Sie nach ihnen suchen und die Qualität des Codes überhaupt nicht beachten. Die Entwickler haben versucht, Coverity Scan zu verwenden , aber der letzte Durchlauf der Analyse war vor fast zwei Jahren. Dies sollte Haiku-Benutzer verärgern. Obwohl die Analyse mit Coverity im Jahr 2014 konfiguriert wurde, hat uns dies nicht davon abgehalten, 2015 zwei große Artikel mit Fehlerprüfungen zu schreiben ( Teil 1 , Teil 2 ).Diejenigen, die bis zum Ende gelesen haben, warten auf eine weitere Überprüfung des Haiku-Codes von nicht weniger Volumen mit neuen Arten von Fehlern. Vor dem Veröffentlichen von Codeüberprüfungen wird ein vollständiger Analysebericht an die Entwickler gesendet, sodass einige Fehler behoben werden können. Um die Zeit zwischen den Veröffentlichungen zu vertreiben, empfehle ich, PVS-Studio für Ihr Projekt herunterzuladen und auszuprobieren .Möchten Sie Haiku ausprobieren und haben Sie Fragen? Haiku-Entwickler laden Sie zum
Telegrammkanal ein .

Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Svyatoslav Razmyslov.
Wie man sich in C und C ++ in den Fuß schießt. Haiku OS Kochbuch