Verwendung des maschinellen Lernens bei der statischen Analyse des Programmquellcodes

Verwendung des maschinellen Lernens bei der statischen Analyse des Programmquellcodes

Maschinelles Lernen ist in verschiedenen Bereichen der menschlichen Tätigkeit tief verwurzelt: von der Spracherkennung bis zur medizinischen Diagnostik. Die Popularität dieses Ansatzes ist so groß, dass sie versuchen, ihn zu verwenden, wo immer dies möglich ist. Einige Versuche, die klassischen Ansätze durch neuronale Netze zu ersetzen, sind nicht so erfolgreich. Betrachten wir das maschinelle Lernen unter dem Gesichtspunkt der Erstellung effektiver statischer Code-Analysatoren zum Auffinden von Fehlern und potenziellen Schwachstellen.

Das PVS-Studio-Team wird häufig gefragt, ob wir mit dem maschinellen Lernen beginnen möchten, um Fehler im Quellcode von Programmen zu finden. Kurze Antwort: Ja, aber sehr begrenzt. Wir glauben, dass der Einsatz von maschinellem Lernen bei Code-Analyse-Problemen viele Fallstricke mit sich bringt. Im zweiten Teil des Artikels werden wir darüber sprechen. Beginnen wir mit einer Überprüfung neuer Lösungen und Ideen.

Neue Ansätze


Derzeit gibt es bereits viele Implementierungen von statischen Analysatoren, die auf maschinellem Lernen basieren oder dieses verwenden, einschließlich Deep Learning und NLP zur Fehlererkennung. Nicht nur Enthusiasten, sondern auch große Unternehmen wie Facebook, Amazon oder Mozilla machten auf das Potenzial des maschinellen Lernens bei der Fehlersuche aufmerksam. Einige Projekte sind keine vollwertigen statischen Analysegeräte, sondern stellen nur zwischenzeitlich bestimmte Fehler bei Commits fest.

Interessanterweise sind fast alle als wegweisende Produkte positioniert, die mithilfe künstlicher Intelligenz den Entwicklungsprozess verändern werden.


Betrachten Sie einige bekannte Beispiele:

  1. Deepcode
  2. Infer, Sapienz, SapFix
  3. Embold
  4. Quelle {d}
  5. Clever-Commit, Commit-Assistent
  6. CodeGuru

Deepcode


Deep Code ist ein Schwachstellensuchwerkzeug im Code von Programmen, die in Java, JavaScript, TypeScript und Python geschrieben sind und in denen maschinelles Lernen als Komponente vorhanden ist. Laut Boris Paskalev gelten bereits mehr als 250.000 Regeln. Dieses Tool basiert auf Änderungen, die Entwickler am Quellcode offener Projekte vorgenommen haben (eine Million Repositories). Das Unternehmen selbst sagt, ihr Projekt sei Grammatik für Entwickler.



Im Wesentlichen vergleicht dieser Analyzer Ihre Lösung mit der Projektdatenbank und bietet Ihnen die geschätzte beste Lösung aus der Erfahrung anderer Entwickler.

Im Mai 2018 schrieben Entwickler, dass die Unterstützung für die C ++ - Sprache vorbereitet wird. Diese Sprache wird jedoch immer noch nicht unterstützt. Obwohl auf der Site selbst angegeben ist, dass innerhalb weniger Wochen eine neue Sprache hinzugefügt werden kann, hängt nur ein Schritt von der Sprachanalyse ab.





Auf der Website wird auch eine Gruppe von Veröffentlichungen zu den Methoden veröffentlicht, auf denen der Analysator basiert.

Infer


Facebook ist sehr bemüht, neue Ansätze in seine Produkte einzuführen. Sie haben ihre Aufmerksamkeit und ihr maschinelles Lernen nicht umgangen. 2013 kauften sie ein Startup, das einen maschinenbasierten statischen Analysator entwickelte. Und im Jahr 2015 wurde der Quellcode des Projekts geöffnet .

Infer ist ein statischer Analysator für Projekte, die in Java, C, C ++ und Objective-C geschrieben wurden und von Facebook entwickelt wurden. Laut der Website wird es auch in Amazon Web Services, Oculus, Uber und anderen beliebten Projekten verwendet.

Infer kann derzeit Fehler im Zusammenhang mit der Dereferenzierung eines Nullzeigers und Speicherlecks erkennen. Infer basiert auf Hoars Logik, Trennungslogik und Biabduktion sowie auf der Theorie der abstrakten Interpretation. Mit diesen Ansätzen kann der Analysator das Programm in kleine Blöcke (Chunks) aufteilen und diese unabhängig voneinander analysieren.

Sie können versuchen, Infer für Ihre Projekte zu verwenden. Entwickler warnen jedoch davor, dass bei Facebook-Projekten nützliche Antworten 80% der Ergebnisse ausmachen, bei anderen Projekten jedoch eine geringe Anzahl von Fehlalarmen nicht garantiert ist. Einige der Fehler, die Infer noch nicht finden kann, aber die Entwickler arbeiten daran, solche Trigger einzuführen:

  • Verlassen des Arrays;
  • typisierte Ausnahmen;
  • Verlust nicht verifizierter Daten;
  • Rennen Race Condition.

Sapfix


SapFix ist ein automatisiertes Bearbeitungswerkzeug. Es erhält Informationen von Sapienz, einem Testautomatisierungstool, und dem statischen Infer-Analysator. Basierend auf den neuesten Änderungen und Meldungen wählt Infer eine von mehreren Strategien zur Fehlerbehebung.



In einigen Fällen setzt SapFix die Änderungen ganz oder teilweise zurück. In anderen Fällen versucht er, das Problem zu lösen, indem er einen Patch aus seinen Fixmustern generiert. Dieser Satz wird aus den Bearbeitungsvorlagen gebildet, die die Programmierer selbst aus den bereits einmal vorgenommenen Bearbeitungen zusammengestellt haben. Wenn eine solche Vorlage den Fehler nicht behebt, versucht SapFix, die Vorlage an die Situation anzupassen, und nimmt kleine Änderungen im abstrakten Syntaxbaum vor, bis eine potenzielle Lösung gefunden wird.

Eine mögliche Lösung reicht jedoch nicht aus, daher sammelt SapFix mehrere Lösungen, die anhand von drei Fragen ausgewählt werden: Gibt es Kompilierungsfehler, gibt es einen Absturz, führt die Bearbeitung neue Abstürze ein? Nachdem die Bearbeitungen vollständig getestet wurden, werden die Patches zur Überprüfung an den Programmierer gesendet, der entscheidet, welche der Bearbeitungen das Problem am besten löst.

Embold


Embold ist eine Startplattform zur statischen Analyse des Quellcodes von Programmen, die vor der Umbenennung Gamma hieß. Die statische Analyse wird auf der Grundlage unserer eigenen Diagnose sowie auf der Grundlage von eingebauten Analysegeräten wie Cppheck, SpotBugs, SQL Check und anderen durchgeführt.



Neben der eigentlichen Diagnose liegt der Schwerpunkt auf der visuellen Anzeige von Infografiken anhand der Codebasis und der bequemen Anzeige der gefundenen Fehler sowie der Suche nach Refactoring-Möglichkeiten. Darüber hinaus verfügt dieser Analyzer über eine Reihe von Anti-Patterns, mit denen Sie Probleme in der Codestruktur auf Klassen- und Methodenebene erkennen und verschiedene Metriken zur Berechnung der Systemqualität verwenden können.



Einer der Hauptvorteile ist das intelligente Lösungs- und Änderungsvorschlagsystem, das neben der üblichen Diagnose auch Änderungen anhand von Informationen über frühere Änderungen überprüft.



Mit NLP zerlegt Embold den Code in Teile und sucht nach Zusammenhängen und Abhängigkeiten zwischen Funktionen und Methoden, wodurch Umgestaltungszeit gespart wird.



Embold bietet daher hauptsächlich eine komfortable Visualisierung der Analyseergebnisse Ihres Quellcodes durch verschiedene Analysegeräte sowie eine eigene Diagnose, die teilweise auf maschinellem Lernen basiert.

Quelle {d}


Source {d} ist in Bezug auf die Implementierung der von uns untersuchten Analysegeräte am offensten. Es ist auch eine Open Source-Lösung . Auf ihrer Website können Sie (im Austausch für Ihre E-Mail-Adresse) eine Broschüre mit einer Beschreibung der von ihnen verwendeten Technologien erhalten. Darüber hinaus enthält es einen Link zur Publikationsbasis, die sie im Zusammenhang mit der Verwendung von maschinellem Lernen für die Codeanalyse gesammelt haben, sowie ein Repository mit einem Datensatz für die Codeschulung. Das Produkt selbst ist eine Plattform zur Analyse des Quellcodes und des Softwareprodukts und konzentriert sich nicht auf Entwickler, sondern auf die Verknüpfung der Manager. Zu seinen Fähigkeiten gehört eine Funktion zum Ermitteln des Volumens der technischen Schulden, von Engpässen im Entwicklungsprozess und anderer globaler Statistiken zum Projekt.



Sie stützen ihren Ansatz zur maschinengestützten Code-Analyse auf die Natural-Hypothese, die im Artikel " Über die Natürlichkeit von Software " formuliert ist.

„Programmiersprachen sind theoretisch komplex, flexibel und leistungsfähig, aber die Programme, die echte Menschen tatsächlich schreiben, sind größtenteils einfach und ziemlich repetitiv, und deshalb haben sie nützliche und vorhersagbare statistische Eigenschaften, die sich in Statistiken ausdrücken lassen Sprachmodelle und Verwendung für Softwareentwicklungsaufgaben. “

Basierend auf dieser Hypothese sind die statistischen Eigenschaften umso ausgeprägter und genauer, je größer die Codebasis für das Training des Analysators ist.

Für die Analyse des Codes verwendet source {d} den Babelfish-Dienst, der eine Codedatei in einer der verfügbaren Sprachen analysieren, einen abstrakten Syntaxbaum abrufen und in einen universellen Syntaxbaum konvertieren kann.



Quelle {d} sucht jedoch nicht nach Fehlern im Code. Basierend auf dem Baum zeigt source {d} beim maschinellen Lernen auf der Grundlage des gesamten Projekts, wie der Code formatiert ist, welcher Codierungsstil im Projekt verwendet wird und wann der Code festgeschrieben wird. Wenn der neue Code nicht mit dem Codierungsstil des Projekts übereinstimmt, werden die entsprechenden Änderungen vorgenommen.





Das Training wird von verschiedenen Grundelementen geleitet: Leerzeichen, Tabulatoren, Zeilenumbrüche usw.



Lesen Sie mehr dazu in ihrer Publikation: " STYLE-ANALYZER: Beheben von Inkonsistenzen im Codestil mit interpretierbaren, nicht überwachten Algorithmen ".

Im Allgemeinen ist source {d} eine umfassende Plattform für die Erfassung einer Vielzahl von Statistiken zum Quellcode und zum Projektentwicklungsprozess, von der Berechnung der Wirksamkeit von Entwicklern bis zur Ermittlung von Zeitkosten für Codeüberprüfungen.

Clever begehen


Clever-Commit ist ein Analysator, der von Mozilla in Zusammenarbeit mit Ubisoft erstellt wurde. Es basiert auf der CLEVER - Studie (Combining Levels of Bug Prevention and Resolution Techniques) von Ubisoft und dem darauf basierenden Assistant-Produkt, das verdächtige Commits identifiziert, die wahrscheinlich einen Fehler enthalten. Aufgrund der Tatsache, dass CLEVER auf einem Codevergleich basiert, weist es nicht nur auf einen gefährlichen Code hin, sondern macht auch Vorschläge zu möglichen Korrekturen. Nach der Beschreibung findet Clever-Commit in 60-70% der Fälle Problembereiche und bietet in der gleichen Häufigkeit korrekte Korrekturen an. Im Allgemeinen gibt es nur wenige Informationen zu diesem Projekt und zu den Fehlern, die es finden kann.

CodeGuru


In jüngerer Zeit wurde die Liste der Analysegeräte, die maschinelles Lernen verwenden, mit einem Produkt von Amazon namens CodeGuru ergänzt. Dieser Service basiert auf maschinellem Lernen, mit dem Sie Fehler im Code finden und kostspielige Abschnitte darin identifizieren können. Bisher ist die Analyse nur für Java-Code gedacht, aber sie beschreiben die Unterstützung für andere Sprachen in der Zukunft. Andy Jassi, CEO von AWS (Amazon Web Services), gab bekannt, dass er es bereits seit langer Zeit in Amazon selbst verwendet.

Der Website zufolge wurden Schulungen auf der Codebasis von Amazon selbst sowie für mehr als 10.000 Open Source-Projekte durchgeführt.

Tatsächlich besteht der Dienst aus zwei Teilen: CodeGuru Reviewer, der durch Suchen nach assoziativen Regeln und Suchen nach Fehlern im Code geschult wird, und CodeGuru Profiler, der die Anwendungsleistung überwacht.



Im Allgemeinen wurden nicht viele Informationen zu diesem Projekt veröffentlicht. Um zu erfahren, wie Abweichungen von "Best Practices" festgestellt werden, analysiert Reviewer die Amazon-Codebasen und sucht nach Pull-Requests, die AWS-API-Aufrufe enthalten. Anschließend betrachtet er die vorgenommenen Änderungen und vergleicht sie mit den Daten aus der Dokumentation, die parallel analysiert wird. Das Ergebnis ist ein Modell für "Best Practices".

Es wird auch gesagt, dass sich die Empfehlungen für benutzerdefinierten Code verbessern, nachdem Feedback zu den Empfehlungen eingegangen ist.

Die Liste der Fehler, auf die der Prüfer reagiert, ist ziemlich verschwommen, da keine spezifische Dokumentation für Fehler veröffentlicht wurde:
  • AWS Best Practices
  • Parallelität
  • Ressourcenlecks
  • Leck vertraulicher Informationen
  • Allgemeine "Best Practices" für die Codierung

Unsere Skepsis


Betrachten wir nun das Problem der Fehlersuche mit den Augen unseres Teams, das seit vielen Jahren statische Analysegeräte entwickelt. Wir sehen eine Reihe von Problemen auf hoher Ebene bei der Anwendung von Schulungen, über die wir sprechen möchten. Aber am Anfang teilen wir alle ML-Ansätze grob in zwei Typen ein:

  1. Trainieren Sie einen statischen Analysator manuell, um anhand von Beispielen für synthetischen und realen Code nach verschiedenen Problemen zu suchen.
  2. Trainieren Sie die Algorithmen mit einer großen Anzahl von Open-Source-Code (GitHub) und der Änderungshistorie, wonach der Analysator selbst beginnt, Fehler zu erkennen und sogar Korrekturen vorzuschlagen.

Wir werden über jede Richtung getrennt sprechen, da sie verschiedene Mängel aufweisen. Danach, denke ich, wird es den Lesern klar, warum wir die Möglichkeit des maschinellen Lernens nicht verweigern, aber auch nicht die Begeisterung teilen.

Hinweis Wir betrachten die Entwicklung eines universellen statischen Analysegeräts für allgemeine Zwecke. Wir konzentrieren uns auf die Entwicklung eines Analysators, der sich nicht auf eine bestimmte Codebasis konzentriert, sondern den jedes Team in jedem Projekt verwenden kann.

Manuelles Training des statischen Analysators


Angenommen, wir möchten ML verwenden, damit der Analysator nach Anomalien der folgenden Form im Code sucht:

if (A == A) 

Es ist seltsam, eine Variable mit sich selbst zu vergleichen. Wir können viele Beispiele für korrekten und inkorrekten Code schreiben und den Analysator so trainieren, dass er nach solchen Fehlern sucht. Zusätzlich ist es möglich, den Tests reale Beispiele bereits gefundener Fehler hinzuzufügen. Die Frage ist natürlich, woher diese Beispiele stammen. Wir werden jedoch in Betracht ziehen, dass dies möglich ist. Beispielsweise haben wir eine Reihe von Beispielen für solche Fehler gesammelt : V501 , V3001 , V6001 .

Ist es also möglich, solche Fehler im Code mithilfe von Algorithmen für maschinelles Lernen zu suchen? Sie können. Es ist jedoch nicht klar, warum dies getan werden soll.

Sehen Sie, um den Analysator zu trainieren, müssen wir viel Aufwand betreiben, um Beispiele für das Training vorzubereiten. Oder kennzeichnen Sie den Code echter Anwendungen und geben Sie an, wo Sie schwören sollen und wo nicht. In jedem Fall muss viel Arbeit geleistet werden, da es Tausende von Beispielen für Schulungen geben sollte. Oder Zehntausende.

Schließlich wollen wir nicht nur nach Fällen (A == A) suchen, sondern auch nach:

  • if (X && A == A)
  • if (A + 1 == A + 1)
  • if (A [i] == A [i])
  • if ((A) == (A))
  • usw.



Nun wollen wir sehen, wie eine so einfache Diagnose in PVS-Studio implementiert wird:

 void RulePrototype_V501(VivaWalker &walker, const Ptree *left, const Ptree *right, const Ptree *operation) { if (SafeEq(operation, "==") && SafeEqual(left, right)) { walker.AddError(" , !", left, 501, Level_1, "CWE-571"); } } 

Und alle. Keine Beispielschulungsbasis erforderlich!

Zukünftig sollte die Diagnose so unterrichtet werden, dass sie eine Reihe von Ausnahmen berücksichtigt und Sie verstehen, dass Sie (A [0] == A [1-1]) beschwören müssen. All dies ist jedoch sehr einfach zu programmieren. Aber nur mit der Basis von Beispielen für das Training wird alles schlecht sein.

Beachten Sie, dass in beiden Fällen weiterhin ein Testsystem, das Verfassen von Dokumentationen usw. erforderlich ist. Der Aufwand für die Erstellung einer neuen Diagnose liegt jedoch eindeutig auf der Seite des klassischen Ansatzes, bei dem die Regel einfach im Code fest codiert ist.

Schauen wir uns jetzt eine andere Regel an. Zum Beispiel, dass das Ergebnis einiger Funktionen verwendet werden muss. Es macht keinen Sinn, sie anzurufen, ohne ihr Ergebnis zu verwenden. Hier sind einige dieser Funktionen:
  • Malloc
  • memcmp
  • Zeichenfolge :: leer

In der Regel ist dies bei der in PVS-Studio implementierten Diagnose von V530 der Fall .

Wir möchten also nach Aufrufen für solche Funktionen suchen, bei denen das Ergebnis ihrer Arbeit nicht verwendet wird. Dazu können Sie viele Tests generieren. Und wir denken, dass alles gut funktionieren wird. Aber auch hier ist nicht klar, warum dies notwendig ist.

Die Implementierung der V530-Diagnose mit allen Ausnahmen im PVS-Studio-Analysator umfasst 258 Codezeilen, von denen 64 Kommentarzeilen sind. Außerdem gibt es eine Tabelle mit Anmerkungen zu Funktionen, in der darauf hingewiesen wird, dass deren Ergebnis verwendet werden sollte. Das Auffüllen dieser Tabelle ist viel einfacher als das Erstellen von synthetischen Beispielen.

Bei Diagnosen mit Datenflussanalyse wird die Situation noch schlimmer. Zum Beispiel kann der PVS-Studio-Analysator den Wert von Zeigern verfolgen, was das Auffinden eines solchen Speicherverlusts ermöglicht:

 uint32_t* BnNew() { uint32_t* result = new uint32_t[kBigIntSize]; memset(result, 0, kBigIntSize * sizeof(uint32_t)); return result; } std::string AndroidRSAPublicKey(crypto::RSAPrivateKey* key) { .... uint32_t* n = BnNew(); .... RSAPublicKey pkey; pkey.len = kRSANumWords; pkey.exponent = 65537; // Fixed public exponent pkey.n0inv = 0 - ModInverse(n0, 0x100000000LL); if (pkey.n0inv == 0) return kDummyRSAPublicKey; // <= .... } 

Ein Beispiel stammt aus dem Artikel " Chrom: Speicherlecks ". Wenn die Bedingung (pkey.n0inv == 0) erfüllt ist , wird die Funktion beendet, ohne den Puffer freizugeben , auf den der Zeiger in der Variablen n gespeichert ist.

Aus Sicht von PVS-Studio ist nichts kompliziert. Der Analysator hat die BnNew- Funktion untersucht und sich daran erinnert, dass sie einen Zeiger auf einen zugewiesenen Speicherblock zurückgibt. In einer anderen Funktion bemerkte er, dass eine Situation möglich ist, in der der Puffer nicht freigegeben wird und der Zeiger darauf verloren geht, wenn die Funktion beendet wird.

Ein allgemeiner Werteverfolgungsalgorithmus funktioniert. Es ist egal, wie der Code geschrieben ist. Es spielt keine Rolle, was sich in der Funktion noch befindet, das nicht mit dem Arbeiten mit Zeigern zusammenhängt. Der Algorithmus ist universell und die V773-Diagnose findet in verschiedenen Projekten viele Fehler. Sehen Sie, wie unterschiedlich die Codefragmente sind, in denen die Fehler erkannt werden!

Wir sind keine Experten für maschinelles Lernen, aber es scheint, dass es große Probleme geben wird. Es gibt unglaublich viele Möglichkeiten, wie Sie Code mit Speicherlecks schreiben können. Selbst wenn die Maschine für die Verfolgung des Werts von Variablen geschult ist, muss sie geschult werden, um zu verstehen, dass Funktionsaufrufe vorliegen.

Es besteht der Verdacht, dass für das Training so viele Beispiele erforderlich sind, dass die Aufgabe entmutigend wird. Wir sagen nicht, dass es nicht realisierbar ist. Wir bezweifeln, dass sich die Kosten für die Erstellung eines Analysegeräts auszahlen werden.

Analogie. Eine Analogie kommt einem Taschenrechner in den Sinn, bei dem anstelle der Diagnose arithmetische Operationen programmiert werden müssen. Wir sind sicher, dass Sie einem Taschenrechner, der auf ML basiert, beibringen können, Zahlen gut zu addieren, indem Sie eine Wissensbasis über das Ergebnis der Operationen 1 + 1 = 2, 1 + 2 = 3, 2 + 1 = 3, 100 + 200 = 300 usw. einführen. Wie Sie wissen, ist die Zweckmäßigkeit der Entwicklung eines solchen Taschenrechners eine große Frage (wenn kein Zuschuss dafür gewährt wird :). Ein viel einfacher, schneller, genauer und zuverlässiger Taschenrechner kann unter Verwendung der normalen "+" - Operation im Code geschrieben werden.

Fazit Die Methode wird funktionieren. Aber es zu benutzen, macht unserer Meinung nach keinen praktischen Sinn. Die Entwicklung wird zeitaufwändiger und das Ergebnis ist weniger zuverlässig und genau, insbesondere wenn komplexe Diagnosen auf der Grundlage der Analyse des Datenstroms durchgeführt werden sollen.

Von viel Open Source lernen


Nun, wir haben manuelle Synthesebeispiele herausgefunden, aber es gibt GitHub. Sie können den Verlauf von Commits verfolgen und Muster von Codeänderungen / -korrekturen ableiten. Dann können Sie nicht nur auf Teile des verdächtigen Codes hinweisen, sondern auch eine Möglichkeit vorschlagen, dies zu beheben.

Wenn Sie bei dieser Detailebene stehen bleiben, sieht alles gut aus. Der Teufel steckt wie immer im Detail. Sprechen wir über diese Details.

Die erste Nuance. Datenquelle.

Die Bearbeitungen auf GitHub sind ziemlich chaotisch und abwechslungsreich. Die Leute sind oft zu faul, um atomare Verpflichtungen einzugehen und mehrere Änderungen am Code gleichzeitig vorzunehmen. Sie selbst wissen, wie es passiert: Sie haben den Fehler behoben und gleichzeitig ein wenig überarbeitet („Und hier werde ich gleichzeitig die Bearbeitung eines solchen Falls hinzufügen ...“). Selbst dann kann es für eine Person nicht klar sein, ob diese Änderungen miteinander zusammenhängen oder nicht.

Das Problem ist, wie man die tatsächlichen Fehler vom Hinzufügen neuer Funktionen oder etwas anderem unterscheidet. Sie können natürlich 1.000 Personen manuell anpflanzen, um Commits zu markieren. Die Benutzer müssen angeben, dass sie den Fehler hier korrigiert, hier umgestaltet, hier neue Funktionen implementiert, die Anforderungen hier geändert usw. haben.

Ist dieses Markup möglich? Möglich. Achten Sie jedoch darauf, wie schnell die Änderung erfolgt. Anstatt "den Algorithmus selbst auf der Basis von GitHub zu lernen", diskutieren wir bereits, wie man Hunderte von Menschen für eine lange Zeit rätselt. Die Arbeitskosten und die Kosten für die Erstellung eines Werkzeugs steigen stark an.

Sie können versuchen, automatisch zu identifizieren, wo genau die Fehler behoben wurden. Dazu sollten Sie Kommentare zu Commits analysieren und auf kleine lokale Änderungen achten, bei denen es sich höchstwahrscheinlich genau um die Fehlerrevision handelt. Es ist schwer zu sagen, wie gut Sie automatisch nach Fehlerkorrekturen suchen können. In jedem Fall ist dies eine große Aufgabe, die getrennte Forschung und Programmierung erfordert.

Also, wir haben das Training noch nicht erreicht, aber es gibt schon Nuancen :).

Die zweite Nuance. Verzögerung in der Entwicklung.

Analysatoren, die auf der Basis von Datenbanken wie GitHub trainiert werden, sind immer einem Syndrom wie "geistiger Behinderung" ausgesetzt. Dies liegt daran, dass sich die Programmiersprachen im Laufe der Zeit ändern.

In C # 8.0 wurden nullfähige Referenztypen eingeführt , um den Umgang mit Nullreferenzausnahmen (NREs) zu erleichtern. JDK 12 führt eine neue switch-Anweisung ein ( JEP 325 ). In C ++ 17 wurde es möglich, bedingte Konstruktionen in der Kompilierungsphase auszuführen ( constexpr if ). Usw.

Programmiersprachen entwickeln sich weiter. Darüber hinaus wie C ++ sind sehr schnell und aktiv. Darin erscheinen neue Designs, neue Standardfunktionen werden hinzugefügt und so weiter. Neben neuen Funktionen treten auch neue Fehlermuster auf, die wir auch mithilfe der statischen Codeanalyse identifizieren möchten.

Und hier hat die betrachtete Lehrmethode ein Problem: Das Fehlermuster ist möglicherweise bereits bekannt, es besteht der Wunsch, es zu identifizieren, aber es gibt nichts, woraus man lernen kann.

Schauen wir uns dieses Problem an einem konkreten Beispiel an. Die bereichsbasierte for-Schleife wurde in C ++ 11 angezeigt. Sie können den folgenden Code schreiben und dabei alle Elemente im Container durchlaufen:

 std::vector<int> numbers; .... for (int num : numbers) foo(num); 

Der neue Zyklus brachte ein neues Fehlermuster mit sich. Wenn der Container innerhalb der Schleife geändert wird, führt dies zur Ungültigkeit der „Schatten“ -Iteratoren.

Betrachten Sie den folgenden falschen Code:

 for (int num : numbers) { numbers.push_back(num * 2); } 

Der Compiler wird es in so etwas verwandeln:

 for (auto __begin = begin(numbers), __end = end(numbers); __begin != __end; ++__begin) { int num = *__begin; numbers.push_back(num * 2); } 

Während der Push_back- Operation kann eine Ungültigmachung der Iteratoren __begin und __end auftreten, wenn eine Speicherzuweisung innerhalb des Vektors auftritt. Das Ergebnis ist ein undefiniertes Programmverhalten.

Daher ist das Fehlermuster seit langem bekannt und in der Literatur beschrieben. Der PVS-Studio-Analysator diagnostiziert es mithilfe der V789- Diagnose und hat in offenen Projekten bereits echte Fehler gefunden.

Wann wird es genug neuen Code auf GitHub geben, um dieses Muster zu bemerken? Gute Frage ... Sie müssen verstehen, dass, wenn eine bereichsbezogene for-Schleife angezeigt wird, dies nicht bedeutet, dass alle Programmierer sofort damit begonnen haben, sie massiv zu verwenden. Es kann Jahre dauern, bis mit einer neuen Schleife viel Code angezeigt wird. Darüber hinaus müssen viele Fehler festgeschrieben und anschließend korrigiert werden, damit der Algorithmus das Muster in den Änderungen erkennen kann.

Wie viele Jahre sollten vergehen? Fünf? Zehn?

Zehn ist zu viel, und sind wir Pessimisten? Gar nicht. Acht Jahre sind vergangen, als dieser Artikel geschrieben wurde, da die bereichsbasierte for-Schleife in C ++ 11 erschien. Bisher wurden jedoch nur drei Fälle eines solchen Fehlers in unsere Datenbank geschrieben. Drei Fehler sind nicht viel und nicht wenig. Aus ihrer Anzahl sollte keine Schlussfolgerung gezogen werden. Die Hauptsache ist, dass Sie bestätigen können, dass ein solches Fehlermuster real ist und es sinnvoll ist, es zu erkennen.

Vergleichen Sie nun diesen Betrag beispielsweise mit diesem Fehlermuster: Der Zeiger wird vor der Überprüfung dereferenziert . Insgesamt haben wir bei der Prüfung von Open-Source-Projekten bereits 1716 solcher Fälle identifiziert.

Vielleicht sollten Sie überhaupt nicht nach bereichsbasierten Schleifenfehlern suchen? Nein. Nur Programmierer sind träge, und dieser Operator gewinnt sehr langsam an Popularität. Allmählich wird es eine Menge Code mit seiner Teilnahme geben, und dementsprechend wird es auch mehr Fehler geben.

Dies wird höchstwahrscheinlich erst 10-15 Jahre nach dem Erscheinen von C ++ 11 geschehen. Und jetzt eine philosophische Frage. Da wir das Fehlermuster bereits kennen, werden wir viele Jahre warten, bis sich in offenen Projekten viele Fehler ansammeln.

Wenn die Antwort "Ja" lautet, ist es möglich, die Diagnose "geistige Behinderung" für alle Analysegeräte auf der Grundlage von ML angemessen zu diagnostizieren.

Wenn die Antwort nein ist, was soll ich dann tun? Es gibt keine Beispiele. Um sie manuell zu schreiben? Aber dann kehren wir zum vorherigen Kapitel zurück, in dem wir darüber nachdachten, einer Person viele Beispiele für das Lernen zu schreiben.

Dies kann getan werden, aber auch hier stellt sich die Frage nach der Zweckmäßigkeit. Die Implementierung der V789-Diagnose mit allen Ausnahmen im PVS-Studio-Analysator umfasst nur 118 Codezeilen, von denen 13 Kommentarzeilen sind. Das heißt Dies ist eine sehr einfache Diagnose, die auf klassische Weise leicht durchgeführt und programmiert werden kann.

Eine ähnliche Situation wird es bei allen anderen Innovationen geben, die in einer anderen Sprache erscheinen. Wie sie sagen, gibt es etwas zu überlegen.

Die dritte Nuance. Dokumentation

Eine wichtige Komponente jedes statischen Analysegeräts ist die Dokumentation, in der die einzelnen Diagnosen beschrieben werden. Ohne diesen kann der Analysator nur sehr schwer oder gar nicht verwendet werden. In der Dokumentation zu PVS-Studio finden Sie eine Beschreibung der einzelnen Diagnosen, die ein Beispiel für einen fehlerhaften Code und dessen Behebung enthält. Es gibt auch einen Link zu CWE, wo Sie eine alternative Beschreibung des Problems lesen können. Und trotzdem ist manchmal etwas für Benutzer unverständlich und sie stellen uns klärende Fragen.

Bei statischen Analysatoren, die auf Algorithmen für maschinelles Lernen basieren, ist das Dokumentationsproblem irgendwie vertuscht. Es wird davon ausgegangen, dass der Analysator lediglich einen Ort anzeigt, der ihm verdächtig erscheint, und möglicherweise sogar vorschlägt, wie er diesen Fehler beheben kann. Die Entscheidung, eine Änderung vorzunehmen oder nicht, bleibt bei der Person. Und hier ... ähm ... Es ist nicht einfach, eine Entscheidung zu treffen, auf deren Grundlage der Analysator die eine oder andere Stelle im Code verdächtigt.

Natürlich wird in einigen Fällen alles offensichtlich sein. Angenommen, der Analysator verweist auf diesen Code:

 char *p = (char *)malloc(strlen(src + 1)); strcpy(p, src); 

Und wird anbieten, es zu ersetzen durch:

 char *p = (char *)malloc(strlen(src) + 1); strcpy(p, src); 

Es ist sofort klar, dass der Programmierer versiegelt und 1 an der falschen Stelle hinzugefügt hat. Infolgedessen wird weniger Speicher zugewiesen.

Hier ist ohne Dokumentation alles klar. Dies wird jedoch nicht immer der Fall sein.

Stellen Sie sich vor, der Analysator verweist stillschweigend auf diesen Code:

 char check(const uint8 *hash_stage2) { .... return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE); } 

Und schlägt vor, den Typ des Rückgabewerts von char in int zu ändern:

 int check(const uint8 *hash_stage2) { .... return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE); } 

Es gibt keine Dokumentation für die Warnung. Und anscheinend wird der Text der Warnung selbst, wie wir ihn verstehen, auch nicht sein, wenn wir über einen völlig unabhängigen Analysator sprechen.

Was zu tun ist? Was ist der unterschied Soll ich so einen Ersatz machen?

Im Prinzip können Sie hier eine Chance nutzen und sich darauf einigen, den Code zu reparieren. Obwohl Änderungen akzeptiert werden, ohne sie zu verstehen, ist dies eine einfache Übung ... :) Sie können sich die Beschreibung der memcmp- Funktion ansehen und lesen, dass die Funktion int : 0-Werte zurückgibt , die größer als Null und kleiner als Null sind. Es ist jedoch möglicherweise nicht klar, warum Änderungen vorgenommen werden müssen, wenn der Code bereits erfolgreich funktioniert.

Wenn Sie jetzt nicht wissen, wozu eine solche Bearbeitung gut ist, lesen Sie die Beschreibung der V642- Diagnose. Es wird sofort klar, dass dies ein echter Fehler ist. Darüber hinaus kann es zu Sicherheitslücken kommen.

Vielleicht schien das Beispiel nicht zu überzeugen. Immerhin schlug der Analysator einen Code vor, der wahrscheinlich besser ist. Ok Schauen wir uns zur Abwechslung ein weiteres Beispiel für Pseudocode in Java an.

 ObjectOutputStream out = new ObjectOutputStream(....); SerializedObject obj = new SerializedObject(); obj.state = 100; out.writeObject(obj); obj.state = 200; out.writeObject(obj); out.close(); 

Es gibt eine Art Objekt. Es ist serialisiert. Dann ändert sich der Status des Objekts und es wird erneut serialisiert. Alles scheint in Ordnung zu sein. Stellen Sie sich nun vor, der Analysator mag diesen Code plötzlich nicht mehr und schlägt vor, ihn durch folgenden Code zu ersetzen:

 ObjectOutputStream out = new ObjectOutputStream(....); SerializedObject obj = new SerializedObject(); obj.state = 100; out.writeObject(obj); obj = new SerializedObject(); //    obj.state = 200; out.writeObject(obj); out.close(); 

Anstatt das Objekt zu ändern und erneut aufzuzeichnen, wird ein neues Objekt erstellt und es ist bereits serialisiert.

Es gibt keine Beschreibung des Problems. Keine Dokumentation. Der Code ist länger geworden. Aus irgendeinem Grund wurde die Erstellung eines neuen Objekts hinzugefügt. Sind Sie bereit, eine solche Änderung in Ihrem Code vorzunehmen?

Sie werden sagen, dass es nicht klar ist. In der Tat ist es nicht klar. Und so wird es die ganze Zeit unverständlich sein. Die Arbeit mit einem solchen "stillen" Analysegerät ist eine endlose Untersuchung, um zu verstehen, warum das Analysegerät etwas nicht mag.

Wenn es Dokumentation gibt, wird alles transparent. Die für die Serialisierung verwendete Klasse java.io.ObjectOuputStream speichert beschreibbare Objekte im Cache. Dies bedeutet, dass dasselbe Objekt nicht zweimal serialisiert wird. Sobald die Klasse das Objekt serialisiert und das zweite Mal einfach einen Link zum selben ersten Objekt in den Stream schreibt. Weiterlesen : V6076 - Bei der wiederkehrenden Serialisierung wird der Status des zwischengespeicherten Objekts aus der ersten Serialisierung verwendet.

Wir hoffen, wir konnten die Wichtigkeit der Dokumentation erklären. Und jetzt die Frage. Wie erscheint die Dokumentation für ein auf ML basierendes Analysegerät?

Wenn ein klassischer Code-Analysator entwickelt wird, ist alles einfach und klar. Es gibt ein bestimmtes Fehlermuster. Wir beschreiben es in der Dokumentation und führen die Diagnose durch.

Im Fall von ML ist das Gegenteil der Fall. Ja, der Analysator kann eine Abweichung im Code feststellen und darauf hinweisen. Aber er weiß nichts über das Wesen des Mangels. Er versteht nicht und wird nicht sagen, warum Code nicht so geschrieben werden kann. Dies sind zu hochrangige Abstraktionen. Dann muss der Analysator auch lernen, die Dokumentation der Funktionen zu lesen und zu verstehen .

Wie gesagt, da das Thema Dokumentation in Artikeln zum maschinellen Lernen behandelt wird, sind wir nicht bereit, weiter zu sprechen. Nur eine weitere große Nuance, die wir zur Überprüfung brachten.

Hinweis Es kann argumentiert werden, dass die Dokumentation optional ist. Der Analyzer kann zu vielen Beispielen für Korrekturen auf GitHub führen, und eine Person, die sich Commits und Kommentare dazu ansieht, wird herausfinden, was was ist. Ja das stimmt. Aber die Idee sieht nicht attraktiv aus. Anstelle eines Assistenten fungiert der Analysator als Werkzeug, das den Programmierer weiter verwirrt.

Die vierte Nuance. Hochspezialisierte Sprachen.

Der beschriebene Ansatz ist nicht für hochspezialisierte Sprachen anwendbar, für die die statische Analyse ebenfalls äußerst nützlich sein kann. Der Grund dafür ist, dass GitHub und andere Quellen nicht über eine ausreichende Quellcodebasis verfügen, um ein effektives Training zu ermöglichen.

Betrachten Sie dies anhand eines konkreten Beispiels. Um zu beginnen, gehen Sie zu GitHub und suchen Sie nach Repositorys für die beliebte Java-Sprache.

Ergebnis: Sprache: "Java": 3.128.884 verfügbare Repository-Ergebnisse

Nehmen wir nun die Fachsprache "1C Enterprise", die in Buchhaltungsanwendungen der russischen Firma 1C verwendet wird .

Ergebnis: Sprache: "1C Enterprise": 551 verfügbare Repository-Ergebnisse

Möglicherweise werden keine Analysatoren für diese Sprache benötigt? Gebraucht werden. Es besteht ein praktischer Bedarf für die Analyse derartiger Programme, und entsprechende Analysegeräte existieren bereits. Zum Beispiel gibt es ein SonarQube 1C (BSL) -Plugin, das von Silver Bullet hergestellt wird .

, - , .

. C, C++, #include .

, ML, , Java, JavaScript, Python. . C C++ - , .

, /, , C C++ . «» .

c/cpp- . , GitHub, - cpp- . , ML.

, . GitHub . , . , . , .cpp- .

. . . , , . .

. :

 bool Class::IsMagicWord() { return m_name == "ML"; } 

:

 bool Class::IsMagicWord() { return strcmp(m_name, "ML") == 0; } 

, (x == «y») strcmp(x, «y»)?

, , m_name . , , :

 class Class { .... char *m_name; }; class Class { .... std::string m_name; }; 

, . , ( std::string ).

, , .h . , . , C C++.

- , , , C C++.

, . , , . , cpp-.

. (, , ). , . , , .



, GitHub . , , . - . - , . . « ». , , .cpp (.i) . .

, , , . , . . , - , , .

, . . C C++ , GitHub, . , .

. , . GitHub C++ , .cpp . :).

, C C++ .

. .

, .

V789 , Range-based for loop. , , . , , , . , :

 std::vector<int> numbers; .... for (int num : numbers) { if (num < 5) { numbers.push_back(0); break; // , , return } } 

, . . PVS-Studio 26 .

, . , , , .

, . , , , ML. Das heißt .

. .

, . (, WinAPI, ..).

C, strcmp , . GitHub, available code results:

  • strcmp — 40,462,158
  • stricmp — 1,256,053

, . , , , :
  • , . .
  • , NULL. .
  • , . .
  • .

? Nein. « ». « » . Top50 . , , , 100 , . , , , . , - Amazon.com , 130 « ».

. , . , :

  • g_ascii_strncasecmp — 35,695
  • lstrcmpiA — 27,512
  • _wcsicmp_l — 5,737
  • _strnicmp_l — 5,848
  • _mbscmp_l — 2,458
  • usw.

, , . . . , , . « ».

PVS-Studio . , C ++ 7200 . :

  • WinAPI
  • C,
  • (STL),
  • glibc (GNU C Library)
  • Qt
  • MFC
  • zlib
  • libpng
  • OpenSSL
  • usw.

, . . , .

. ML? , .

, , ML, . , . , strcmp malloc .

C . , . , , , .

, _fread_nolock . , , fread . . , . , . :

 int buffer[10]; size_t n = _fread_nolock(buffer, size_of(int), 100, stream); 

PVS-Studio:

 C_"size_t _fread_nolock" "(void * _DstBuf, size_t _ElementSize, size_t _Count, FILE * _File);" ADD(HAVE_STATE | RET_SKIP | F_MODIFY_PTR_1, nullptr, nullptr, "_fread_nolock", POINTER_1, BYTE_COUNT, COUNT, POINTER_2). Add_Read(from_2_3, to_return, buf_1). Add_DataSafetyStatusRelations(0, 3); 

, , , , . , write-only . . .

ML. GitHub . 15000 . . :

 #define fread_unlocked _fread_nolock 

?

  1. . .
  2. , , . , , . .
  3. , , . , . ML :). .

, ML .

, ML, , , , . , , , .

. , , WinAPI. , , ? , Google, , . , . _fread_nolock , . , , C++. , 20.

, . , memmove . - :

 void *memmove (void *dest, const void *src, size_t len) { return __builtin___memmove_chk(dest, src, len, __builtin_object_size(dest, 0)); } 

__builtin___memmove_chk ? intrinsic , . .

memmove - : . , - .

Ok, . , . , ML , , .

. . . , , . , AI? , AI , . , . , 20 .



, , . . , .
  • . , , . , - . Ein Beispiel. C++ auto_ptr . unique_ptr .
  • . , C C++ , . , . , . , long Windows 32/64 32 . Linux 32/64 . , . -. , , . , ( ). , .
  • . ML, , , . Das heißt , — , , . , . , , — , . . , / , , , . . : " PVS-Studio: ". , , .


, , . ML , , ( ) . , , ML .

, , ML. , , .

, ML . , . ML «» .

, , , , .

ML, .

PS


, - , ML, .

Luddite Einhörner


, . PVS-Studio. ML. , . , , , if- :). , :).

, -, .

. " PVS-Studio ".



, : Andrey Karpov, Victoria Khanieva. Machine Learning in Static Analysis of Program Source Code .

Source: https://habr.com/ru/post/de484208/


All Articles