DeepCode: Seitenansicht

Bild 1

Vor nicht allzu langer Zeit begann DeepCode, ein auf maschinellem Lernen basierender statischer Analysator, die Validierung von C- und C ++ - Projekten zu unterstützen. Und jetzt können wir in der Praxis sehen, wie sich die Ergebnisse der klassischen statischen Analyse und der statischen Analyse auf der Grundlage des maschinellen Lernens unterscheiden.

Wir haben DeepCode bereits in unserem Artikel " Verwenden von maschinellem Lernen bei der statischen Analyse des Quellcodes von Programmen " erwähnt. Und bald veröffentlichten sie den Artikel " DeepCode fügt AI-basierte statische Code-Analyse-Unterstützung für C und C ++ hinzu " mit der Ankündigung der Unterstützung für die Analyse von Projekten, die in C und C ++ geschrieben wurden.

Um die Ergebnisse der DeepCode-Analyse zu betrachten, habe ich mich für das PhysX-Projekt entschieden. Ich habe hier keinen Zweck, eine Analyse der von PVS-Studio oder DeepCode in diesem Projekt gefundenen Fehler zu präsentieren, aber ich wollte nur ein Projekt eines Drittanbieters nehmen und mir dessen Beispiel ansehen, wie die Analysatoren funktionieren.

Das Ausführen der DeepCode-Analyse ist äußerst einfach. Die Analyse der auf GitHub veröffentlichten Projekte startet automatisch. Es gibt zwar ein Problem, da das gesamte Repository hintereinander überprüft wurde, obwohl es verschiedene Projekte enthielt, und Warnungen an sie wurden verwechselt. Die Analysegeschwindigkeit zeigt auch an, dass der Code nicht kompiliert wurde und viele Fehler möglicherweise vor dem Analysator verborgen sind.

Das Gesamtergebnis von DeepCode lautet wie folgt:

Bild 2

Dies spiegelt nicht ganz die Anzahl der Operationen wider, da sie in einer Warnung zusammengefasst sind, wie ich später zeigen werde. Natürlich ist es offensichtlich, dass die statische Analyse von C / C ++ in DeepCode eine Innovation ist, und die Arbeit daran hat gerade erst begonnen, aber ich denke, es ist bereits möglich, einige Schlussfolgerungen zu ziehen.

Als ich anfing, DeepCode-Trigger zu untersuchen, stieß ich auf die folgende Warnung für einen Fehler, auf den der Analysator mindestens 31 Mal gestoßen war:

Bild 3

Diese Antwort fiel mir auf, da PVS-Studio eine ähnliche Diagnose hatte und ich darüber hinaus eine solche Antwort in einem anderen Artikel untersuchte. Als ich ihn zum ersten Mal traf, schien es mir, dass nur sehr wenige Menschen eine mathematische Konstante „von Hand“ schreiben würden.

Und PVS-Studio fand auch eine solche nachlässige Verwendung von Konstanten, aber es gab nicht mehr als 31, sondern 52 Versuche dieser Art. Fast sofort gab es einen Moment, in dem maschinelles Lernen dem klassischen Ansatz unterlegen war. Wenn der Fehlercode zumindest irgendwie von einem sehr typischen Fehler dieser Art abweicht, wird es viel schwieriger, ihn zu finden. Für die klassische statische Analyse muss der Fehler nicht viele, viele Male auftreten - es reicht aus, wenn der Diagnoseentwickler ein allgemeines Prinzip für das Auftreten eines Fehlers entwickelt.

Darüber hinaus funktioniert die PVS-Studio-Diagnose nicht nur mit der Pi-Zahl oder anderen häufig verwendeten Konstanten, sondern auch mit bestimmten Konstanten (z. B. der Landau-Ramanujan-Konstante), deren fehlerhafte Verwendung aufgrund ihrer seltenen Verwendung aufgrund einer großen Trainingsbasis schwierig zu erfassen sein kann .

Da DeepCode ausschließlich an einer Konstante der Form 3.1415 arbeitete, fügte ich aus Interesse der Konstante im Code an einer Stelle 4 Dezimalstellen (3.14159263) hinzu, und die Operation verschwand.

Bild 7

PVS-Studio arbeitet an verschiedenen Optionen zum Runden von Konstanten.

Bild 8

Einschließlich gibt es eine Antwort auf den geänderten Abschnitt:

V624 Es gibt wahrscheinlich einen Druckfehler in der Konstante '3.14159263'. Erwägen Sie die Verwendung der M_PI-Konstante aus <math.h>. Crab.cpp 219

Und der Punkt hier ist nicht, dass dies eine Art schrecklicher Fehler / grobe Rundung / die Möglichkeit eines Tippfehlers ist, sondern dass es bestätigt, dass das Training des Codes auf genau diesen Code beschränkt ist und, falls etwas passiert Verlassen Sie das allgemeine Muster oder treten Sie nur in seltenen Fällen auf. Ein Fehler oder ein Mangel kann unbemerkt bleiben.

Betrachten wir noch eine Operation. DeepCode gab eine Warnung für den folgenden Code aus:

bool Shader::loadShaderCode(const char* fragmentShaderCode, ....)
{
  ....
  if (mFragmentShaderSource != NULL)
    free(mFragmentShaderSource);

  ....

  if (mFragmentShaderSource != NULL)
    free(mFragmentShaderSource);
  ....
}

Bild 10

PVS-Studio hatte weitere Beschwerden über diesen Code:

  1. V809 Verifying that a pointer value is not NULL is not required. The 'if (mFragmentShaderSource != 0)' check can be removed. Shader.cpp 178
  2. V809 Verifying that a pointer value is not NULL is not required. The 'if (mFragmentShaderSource != 0)' check can be removed. Shader.cpp 194
  3. V774 The 'mFragmentShaderSource' pointer was used after the memory was released. Shader.cpp 194
  4. V586 The 'free' function is called twice for deallocation of the same memory space. Inspect the first argument. Check lines: 179, 195. Shader.cpp 195

Sie könnten denken, dass sich die DeepCode-Triggerung einfach als lakonisch herausgestellt hat und PVS-Studio viele unnötige Triggerungen erzeugt hat, aber dies ist nicht der Fall, und jede Triggerung hier hat ihre eigene Bedeutung.

Die ersten beiden Operationen beziehen sich auf eine übermäßige Zeigerprüfung, da eine solche Prüfung nicht erforderlich ist , um die Funktion free () zu verwenden . Die dritte Betätigung zeigt an, dass die Verwendung des Zeigers nach dem Loslassen nicht sicher ist. Auch wenn der Zeiger selbst nicht dereferenziert, sondern nur überprüft wird, ist er dennoch verdächtig und weist häufig auf einen Tippfehler hin. Nun, die letzte Operation weist nur auf dasselbe Problem hin, das DeepCode entdeckt hat.

Bild 11

Im DeepCode-Artikel gab es einen Screenshot über den Beginn der C / C ++ - Unterstützung , der, wie es uns schien, ein falsches Positiv enthält. Die Warnung weist auf einen möglichen Pufferüberlauf hin. Der Speicher für den Puffer wird jedoch unter Berücksichtigung der Länge des Home und der Länge der Zeile zugewiesen , die durch + 1 weiter addiert wird. Somit ist mit Sicherheit genügend Platz im Puffer vorhanden.

Das Problem hierbei kann sein, dass nicht überprüft wurde, ob der Speicher erfolgreich zugewiesen wurde. Vielleicht spricht die DeepCode-Warnung in diesem Fall über etwas anderes: Der erste Satz der Warnung hilft nicht besonders, zu verstehen, was der Fehler ist und worüber der Analysator tatsächlich schwört.

In diesem Fall wird möglicherweise ein weiterer Faktor ausgelöst, über den wir geschrieben haben - die Schwierigkeit, aussagekräftige Warnungen zu erstellen. Entweder werden Warnungen nach der Klassifizierung der Antworten eines geschulten Analysators von Personen geschrieben oder aus Kommentaren zu Commits / Dokumentationen gebildet. Das Generieren einer guten Warnung kann jedoch schwierig sein, wenn das Fehlermuster durch maschinelles Lernen und nicht vom Entwickler abgeleitet wurde.

Ich habe mich gefragt, was in diesem Code geändert werden muss, damit die Warnung verschwindet. Seltsamerweise trat jedoch in einem völlig ähnlichen Codeabschnitt in der neuen Datei keine solche Warnung auf, und eine andere erschien.

Bild 5

Vielleicht liegt der Punkt darin, dass die Umgebung, an der sich der Analysator orientiert hat, verschwunden ist oder ich etwas falsch gemacht habe. Die Instabilität von Warnungen und die Tatsache, dass ihr Erscheinungsbild unvorhersehbar ist, kann meiner Meinung nach nicht nur für Benutzer, sondern auch für die Entwickler des Analysators selbst zu Unannehmlichkeiten führen und den Entwicklungsprozess erschweren.

Obwohl die meisten DeepCode-Antworten noch nicht falsch sind, ist ihre Anzahl im Allgemeinen so gering, dass das Ergebnis seiner Arbeit im Moment eher selten als genau ist. Eine kleine Anzahl von Fehlalarmen geht im Prinzip mit einer kleinen Anzahl von Positiven einher.

Ich gebe Ihnen einige interessante Fehler, die von PVS-Studio gefunden wurden.

Fragment N1

V773Die Funktion wurde beendet, ohne den Zeiger 'line' loszulassen. Ein Speicherverlust ist möglich. PxTkBmpLoader.cpp 166

bool BmpLoader::loadBmp(PxFileHandle f)
{
  ....
  int lineLen = ....;
  unsigned char* line = static_cast<unsigned char*>(malloc(lineLen));
  for(int i = info.Height-1; i >= 0; i--)
  {
    num = fread(line, 1, static_cast<size_t>(lineLen), f);
    if (num != static_cast<size_t>(lineLen)) { fclose(f); return false; }
    ....
  }
  free(line);
  return true;
}

Hier tritt für den Fall, dass das Lesen aus der Datei unterbrochen wurde, ein Speicherverlust auf, da der Speicher vor der Rückkehr von der Funktion nicht freigegeben wird.

Fragment N2

V595 Der Zeiger 'gSphereActor' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 228, 230. SnippetContactReportCCD.cpp 228

void initScene()
{
  ....
  gSphereActor = gPhysics->createRigidDynamic(spherePose);
  gSphereActor->setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD, true);

  if (!gSphereActor)
    return;
  ....
}

Der gSphereActor- Zeiger wird dereferenziert, aber unmittelbar danach wird er auf nullptr überprüft und die Funktion wird beendet. Das heißt, ein Nullzeiger ist hier möglich, aber es kommt immer noch zu einer Dereferenzierung. Im PhysX-Projekt gab es 24 Fehler dieser Art.

DeepCode gab nur Positive für einen bestimmten Falltyp zurück (so wie ich es verstehe), bei dem der Zeiger ursprünglich auf Null initialisiert wurde und es keine anderen Zuweisungen im Sichtfeld gab. PVS-Studio hat mit einem solchen Code nicht funktioniert, da ein solcher Brand meistens falsch ist, da der Zeiger einen Wert in einer anderen Übersetzungseinheit erhalten kann (die meisten Brände betrafen globale Variablen). Dieses Beispiel zeigt, dass weniger falsch positive Ergebnisse in der trainierten statischen Analyse nicht unbedingt wahr sind.

Bild 15

Bild 13

Hier zeigt DeepCode höchstwahrscheinlich aus irgendeinem Grund die falsche Initialisierung an. Stattdessen hat die Initialisierung gCooking die Initialisierung gFoundation hervorgehoben , obwohl der Index nicht weiter beleuchtet ist.

Fragment N3

V517 Die Verwendung des Musters 'if (A) {...} else if (A) {...}' wurde erkannt. Es besteht die Wahrscheinlichkeit, dass ein logischer Fehler vorliegt. Überprüfen Sie die Zeilen: 266, 268. AABox.cpp 266

bool AABox::initRT(int depthSamples, int coverageSamples)
{
  ....
  int query;
  ....
  if (....)  
  {
    ....
    if ( query < coverageSamples)
      ret = false;
    else if ( query > coverageSamples) 
      coverageSamples = query;
    ....
    if ( query < depthSamples)
      ret = false;
    else if ( query > depthSamples)
      depthSamples = query;
  }
  else {
    ....
    if ( query < depthSamples)
      ret = false;
    else if ( query < depthSamples) // <=
      depthSamples = query;
    ....
  }
  ....
}

Hier scheint sich der Fehler beim Kopieren und Einfügen eingeschlichen zu haben. Der Analysator hat den gleichen Zustand in if und else festgestellt . Aus dem vorherigen if-else können Sie ersehen, dass das ">" in if und das "<" in else normalerweise aktiviert sind . Obwohl das Muster extrem einfach aussieht, habe ich unter den DeepCode-Warnungen keine solche Antwort gefunden, obwohl es ein sehr einfach zu erkennendes Muster zu sein scheint.

Fazit

Natürlich hat DeepCode gerade die Unterstützung für C / C ++ angekündigt und es ist Teil seines Produkts, das gerade erst mit der Entwicklung begonnen hat. Sie können es auch bei Ihren Projekten ausprobieren. In deren Service wurde ein praktisches Feedback-Fenster implementiert, falls Sie eine falsche Warnung erhalten haben.

Wir können jedoch jetzt die Mängel sehen, die mit maschinellem Lernen in der statischen Analyse einhergehen. Aus diesem Grund stehen wir der Idee, statischen Analysegeräten maschinelles Lernen hinzuzufügen, skeptisch gegenüber, da der Gewinn zweifelhaft ist: Es ist genau dieselbe Unterstützung für Analysatoren, das Schreiben oder Bearbeiten von Dokumentationen und die Arbeit an der Diagnose selbst erforderlich. Darüber hinaus erfordern Probleme, die während der Entwicklung auftreten, komplexere Lösungen und spezifische Fähigkeiten der Entwickler, wodurch die Anzahl potenzieller Kandidaten bei der Erweiterung des Teams verringert wird. In der Tat sollte der Entwickler in diesem Fall nicht nur ein Spezialist für die analysierte Sprache sein, sondern auch über Kenntnisse auf dem Gebiet des maschinellen Lernens verfügen.


Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Übersetzungslink: Victoria Khanieva. DeepCode: Außerhalb der Perspektive .

All Articles