In der siebten Version des statischen Analysators PVS-Studio haben wir die Unterstützung der Java-Sprache hinzugefügt. Es ist Zeit für eine kurze Geschichte darüber, wie wir begonnen haben, die Java-Sprache zu unterstützen, wie weit wir gekommen sind und was in unseren weiteren Plänen steht. In diesem Artikel werden natürlich erste Analysatorversuche zu Open Source-Projekten aufgeführt.
PVS-Studio
Hier finden Sie eine kurze Beschreibung von PVS-Studio für Java-Entwickler, die noch nichts davon gehört haben.
Dieses Tool wurde entwickelt, um Fehler und potenzielle Schwachstellen im Quellcode von Programmen zu erkennen, die in C, C ++, C # und Java geschrieben wurden. Es funktioniert in Windows-, Linux- und MacOS-Umgebungen.
PVS-Studio führt eine statische Code-Analyse durch und generiert einen Bericht, der einem Entwickler hilft, Fehler zu finden und zu beseitigen. Für diejenigen, die daran interessiert sind, wie genau PVS-Studio nach Fehlern sucht, empfehle ich den Artikel "
Technologien, die im PVS-Studio-Code-Analysator zum Auffinden von Fehlern und potenziellen Schwachstellen verwendet werden ".
Anfang
Ich hätte mir eine clevere Geschichte einfallen lassen können, wie wir darüber spekuliert haben, welche nächste Sprache in PVS-Studio unterstützt werden soll. Über eine vernünftige Wahl von Java, die auf einer hohen Popularität dieser Sprache basiert, und so weiter.
Wie es im Leben passiert, wurde die Wahl jedoch nicht durch eine gründliche Analyse getroffen, sondern durch ein Experiment :). Ja, wir haben über die Richtung der Weiterentwicklung des PVS-Studio-Analysators nachgedacht. Wir haben folgende Sprachen betrachtet: Java, PHP, Python, JavaScript, IBM RPG. Wir waren sogar zur Java-Sprache geneigt, aber die endgültige Wahl wurde nicht getroffen. Für diejenigen, deren Blick auf einem unbekannten IBM-RPG beruhte, möchte ich Sie auf diese
Notiz verweisen, aus der alles klar wird.
Ende 2017 überprüfte mein Kollege Egor Bredikhin die für uns interessanten Standardbibliotheken für Parsing-Code (mit anderen Worten Parser) auf neue Entwicklungsrichtungen. Schließlich stieß er auf mehrere Projekte zum Parsen von Java-Code. Es gelang ihm, schnell einen Analysator-Prototyp mit einigen auf
Spoon basierenden Diagnosen zu
erstellen . Darüber hinaus ist klar geworden, dass wir im Java-Analysator einige Mechanismen des C ++ - Analysators mit
SWIG verwenden können . Wir haben uns angesehen, was wir bekommen haben, und festgestellt, dass unser nächster Analysator für Java sein wird.
Wir möchten Egor für sein Engagement und seine harte Arbeit am Java-Analysator danken. Der Entwicklungsprozess selbst wurde von ihm im Artikel "
Entwicklung eines neuen statischen Analysators: PVS-Studio Java " beschrieben.
Was ist mit Wettbewerbern?
Es gibt weltweit viele kostenlose und kommerzielle statische Code-Analysatoren für Java. Es macht keinen Sinn, sie alle im Artikel aufzulisten. Ich lasse einfach den Link zur "
Liste der Tools für die statische Code-Analyse " (siehe Abschnitt Java und Mehrsprachigkeit).
Ich weiß jedoch, dass wir in erster Linie nach IntelliJ IDEA, FindBugs und SonarQube (SonarJava) gefragt werden.
IntelliJ IDEEIn IntelliJ IDEA ist ein sehr leistungsfähiger statischer Code-Analysator integriert. Darüber hinaus entwickelt sich der Analysator weiter und seine Autoren verfolgen unsere Aktivitäten genau. IntelliJ IDEA ist für uns also ein harter Keks. Zumindest vorerst werden wir IntelliJ IDEA in Bezug auf diagnostische Fähigkeiten nicht übertreffen können. Deshalb werden wir uns auf unsere anderen Vorteile konzentrieren.
Die statische Analyse in IntelliJ IDEA ist in erster Linie eines der Merkmale der Umgebung, das ihr bestimmte Einschränkungen auferlegt. Wir haben die Freiheit, was wir mit unserem Analysegerät tun können. Zum Beispiel können wir es schnell an spezifische Kundenbedürfnisse anpassen. Schnelle und umfassende Unterstützung ist unser Wettbewerbsvorteil. Unsere Kunden kommunizieren direkt mit Entwicklern und arbeiten an dem einen oder anderen Teil von PVS-Studio.
In PVS-Studio gibt es viele Möglichkeiten, es in einen Zyklus der Entwicklung großer alter Projekte zu integrieren. Zum Beispiel ist es unsere
Integration mit SonarQube . Es enthält auch eine
Massenunterdrückung von Analysatorwarnungen, mit der Sie das Tool sofort in einem großen Projekt verwenden können, um Fehler nur im neuen oder geänderten Code zu verfolgen. PVS-Studio
kann in einem kontinuierlichen Integrationsprozess erstellt werden. Ich denke, diese und andere Funktionen werden unserem Analysator helfen, einen Platz unter der Sonne in der Java-Welt zu finden.
FindbugsDas FindBugs-Projekt wird abgebrochen. Trotzdem sollten wir es erwähnen, weil es vielleicht der bekannteste kostenlose statische Analysator für Java-Code ist.
SpotBugs kann als Nachfolger von FindBugs bezeichnet werden. Es ist jedoch weniger beliebt und es ist noch nicht klar, was damit passieren wird.
Generell sind wir der Meinung, dass FindBugs, obwohl es äußerst beliebt war und ist, und darüber hinaus ein kostenloser Analysator, nicht weiter darauf eingehen sollten. Dieses Projekt wird nur leise eine Geschichte werden.
PS Übrigens kann PVS-Studio jetzt auch
kostenlos bei der Arbeit mit offenen Projekten verwendet werden.
SonarQube (SonarJava)Wir glauben, dass wir nicht mit SonarQube konkurrieren, sondern es ergänzen. PVS-Studio ist in SonarQube integriert, wodurch Entwickler mehr Fehler und potenzielle Sicherheitslücken in ihren Projekten finden können. Wir erklären regelmäßig, wie das PVS-Studio-Tool und andere Analysegeräte in SonarQube in Meisterklassen integriert werden können, die wir in Bezug auf verschiedene Konferenzen abhalten.
So führen Sie PVS-Studio für Java aus
Wir haben den Benutzern die beliebtesten Methoden zur Integration des Analysators in das Build-System zur Verfügung gestellt:
- Plugin für Maven;
- Plugin für Gradle;
- Plugin für IntelliJ IDEA
Während der Testphase haben wir viele Benutzer getroffen, die über selbst geschriebene Build-Systeme verfügen, insbesondere im Bereich der mobilen Entwicklung. Sie genossen die Gelegenheit, den Analysator direkt auszuführen und die Quellen und den Klassenpfad aufzulisten.
Detaillierte Informationen zu allen Möglichkeiten zum Ausführen des Analysators finden Sie auf der Dokumentationsseite "
Ausführen von PVS-Studio Java ".
Wir konnten uns nicht vor der
SonarQube- Plattform zur Codequalitätskontrolle scheuen, die bei Java-Entwicklern so beliebt ist. Deshalb haben wir die Unterstützung der Java-Sprache in unser
Plugin für SonarQube aufgenommen .
Weitere Pläne
Wir haben viele Ideen, die möglicherweise weiterer Untersuchungen bedürfen, aber einige spezifische Pläne, die jedem unserer Analysegeräte eigen sind, lauten wie folgt:
- Erstellung neuer Diagnosen und Verbesserung der bestehenden;
- Verbesserung der Datenflussanalyse;
- Steigerung der Zuverlässigkeit und Benutzerfreundlichkeit.
Vielleicht finden wir Zeit, um das IntelliJ IDEA-Plugin für CLion anzupassen. Hallo an C ++ - Entwickler, die über den Java-Analysator gelesen haben :-)
Beispiele für Fehler in Open Source-Projekten
Zittern Sie meine Hölzer, wenn ich im Artikel nicht einige Fehler zeige, die mit dem neuen Analysegerät gefunden wurden! Nun, wir hätten ein großes Open-Source-Java-Projekt nehmen und einen klassischen Artikel schreiben können, in dem Fehler überprüft werden, wie
wir es normalerweise tun .
Ich erwarte jedoch sofort Fragen darüber, was wir in Projekten wie IntelliJ IDEA, FindBugs usw. finden können. Ich habe einfach keinen Ausweg, außer mit diesen Projekten zu beginnen. Deshalb habe ich mich entschlossen, einige interessante Fehlerbeispiele aus den folgenden Projekten schnell zu überprüfen und aufzuschreiben:
- IntelliJ IDEA Community Edition . Ich denke, es ist nicht nötig zu erklären, warum dieses Projekt ausgewählt wurde :).
- SpotBugs Wie ich bereits geschrieben habe, schreitet das FindBugs-Projekt nicht voran. Schauen wir uns also das SpotBugs-Projekt an, das der Nachfolger von FindBugs ist. SpotBugs ist ein klassischer statischer Analysator von Java-Code.
- Etwas aus dem SonarSource-Unternehmensprojekt, das Software zur kontinuierlichen Überwachung der Codequalität entwickelt. Schauen wir uns nun SonarQube und SonarJava an .
Das Schreiben über Fehler dieser Projekte ist eine Herausforderung. Tatsache ist, dass diese Projekte von sehr hoher Qualität sind. Eigentlich ist es nicht überraschend. Unsere Beobachtungen zeigen, dass statische Codeanalysatoren immer gut mit anderen Tools getestet und verifiziert werden.
Trotz alledem muss ich genau mit diesen Projekten beginnen. Ich werde nicht die zweite Chance haben, darüber zu schreiben. Ich bin sicher, dass Entwickler der aufgeführten Projekte nach der Veröffentlichung von PVS-Studio für Java PVS-Studio an Bord nehmen und es für regelmäßige oder zumindest gelegentliche Überprüfungen ihres Codes verwenden werden. Ich weiß zum Beispiel, dass Tagir Valeev (
lany ), einer der Entwickler von JetBrains, der gerade am IntelliJ IDEA Static Code Analyzer arbeitet, als ich den Artikel schreibe, bereits mit der Beta-Version von PVS-Studio spielt . Er schrieb uns ungefähr 15 E-Mails mit Fehlerberichten und Empfehlungen. Danke, Tagir!
Glücklicherweise muss ich nicht so viele Fehler in einem bestimmten Projekt finden. Im Moment ist es meine Aufgabe zu zeigen, dass der PVS-Studio Analyzer für Java nicht umsonst erschien und in der Lage sein wird, eine Reihe anderer Tools zur Verbesserung der Codequalität zu füllen. Ich habe nur die Analyseberichte durchgesehen und einige Fehler aufgelistet, die interessant erschienen. Wenn möglich, habe ich versucht, verschiedene Arten von Fehlern zu zitieren. Mal sehen, wie es ausgegangen ist.
IntelliJ IDEA, Integer Division
private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2;
PVS-Studio-Warnung: V6011 [CWE-682] Das Literal '0.2' vom Typ 'double' wird mit einem Wert vom Typ 'int' verglichen. TitleCapitalizationInspection.java 169
Der Punkt war, dass die Funktion true zurückgeben sollte, wenn weniger als 20% der Wörter mit einem Großbuchstaben beginnen. Tatsächlich funktioniert die Prüfung nicht, da eine Ganzzahldivision auftritt. Als Ergebnis der Division können wir nur zwei Werte erhalten: 0 oder 1.
Die Funktion gibt nur dann false zurück, wenn alle Wörter mit einem Großbuchstaben beginnen. In allen anderen Fällen führt die Divisionsoperation zu 0 und die Funktion gibt true zurück.
IntelliJ IDEA, Verdächtige Schleife
public int findPreviousIndex(int current) { int count = myPainter.getErrorStripeCount(); int foundIndex = -1; int foundLayer = 0; if (0 <= current && current < count) { current--; for (int index = count - 1; index >= 0; index++) {
PVS-Studio-Warnung: V6007 [CWE-571] Der Ausdruck 'index> = 0' ist immer wahr. Updater.java 184
Schauen Sie sich zunächst die Bedingung an
(0 <= aktuell && aktuell <Anzahl) . Es wird nur ausgeführt, wenn der Wert der
Zählvariablen größer als 0 ist.
Schauen Sie sich nun die Schleife an:
for (int index = count - 1; index >= 0; index++)
Der Variablenindex wird mit einem Ausdruck
count - 1 initialisiert. Da die Zählvariable größer als 0 ist, ist der Anfangswert der
Indexvariablen immer größer oder gleich 0. Es stellt sich heraus, dass die Schleife ausgeführt wird, bis ein Überlauf der
Indexvariablen auftritt.
Höchstwahrscheinlich handelt es sich nur um einen Tippfehler, und es muss ein Dekrement und kein Inkrement einer Variablen ausgeführt werden:
for (int index = count - 1; index >= 0; index--)
IntelliJ IDEA, Kopieren-Einfügen
@NonNls public static final String BEFORE_STR_OLD = "before:"; @NonNls public static final String AFTER_STR_OLD = "after:"; private static boolean isBeforeOrAfterKeyword(String str, boolean trimKeyword) { return (trimKeyword ? LoadingOrder.BEFORE_STR.trim() : LoadingOrder.BEFORE_STR).equalsIgnoreCase(str) || (trimKeyword ? LoadingOrder.AFTER_STR.trim() : LoadingOrder.AFTER_STR).equalsIgnoreCase(str) || LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str) ||
PVS-Studio-Warnung: V6001 [CWE-570] Links und rechts vom '||' befinden sich identische Unterausdrücke 'LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase (str)'. Betreiber. Überprüfen Sie die Zeilen: 127, 128. ExtensionOrderConverter.java 127
Guter alter
Effekt der letzten Zeile . Ein Entwickler sprang die Waffe und nachdem er die Codezeile multipliziert hatte, vergaß er, sie zu reparieren. Infolgedessen wird eine Zeichenfolge zweimal mit
BEFORE_STR_OLD verglichen. Höchstwahrscheinlich muss einer der Vergleiche mit
AFTER_STR_OLD durchgeführt werden .
IntelliJ IDEA, Tippfehler
public synchronized boolean isIdentifier(@NotNull String name, final Project project) { if (!StringUtil.startsWithChar(name,'\'') && !StringUtil.startsWithChar(name,'\"')) { name = "\"" + name; } if (!StringUtil.endsWithChar(name,'"') && !StringUtil.endsWithChar(name,'\"')) { name += "\""; } .... }
PVS-Studio-Warnung: V6001 [CWE-571] Links und rechts vom Operator '&&' befinden sich identische Unterausdrücke '! StringUtil.endsWithChar (Name,' "'). JsonNamesValidator.java 27
Dieses Codefragment überprüft, ob der Name in einfache oder doppelte Anführungszeichen eingeschlossen ist. Ist dies nicht der Fall, werden doppelte Anführungszeichen automatisch hinzugefügt.
Aufgrund eines Tippfehlers wird das Ende des Namens nur auf doppelte Anführungszeichen überprüft. Infolgedessen wird der Name in einfachen Anführungszeichen falsch verarbeitet.
Der Name
'Abcd'
Durch das Hinzufügen zusätzlicher doppelter Anführungszeichen wird Folgendes angezeigt:
'Abcd'"
IntelliJ IDEA, Falscher Schutz vor Array-Überlauf
static Context parse(....) { .... for (int i = offset; i < endOffset; i++) { char c = text.charAt(i); if (c == '<' && i < endOffset && text.charAt(i + 1) == '/' && startTag != null && CharArrayUtil.regionMatches(text, i + 2, endOffset, startTag)) { endTagStartOffset = i; break; } } .... }
PVS-Studio-Warnung: V6007 [CWE-571] Der Ausdruck 'i <endOffset' ist immer wahr. EnterAfterJavadocTagHandler.java 183
Der Unterausdruck
i <endOffset im Zustand des
if- Operators ist nicht sinnvoll. Die Variable
i ist in jedem Fall immer kleiner als
endOffset , was sich aus der Bedingung der Schleifenausführung ergibt.
Höchstwahrscheinlich wollte ein Entwickler beim Aufrufen von Funktionen vor einem Überlaufen von Zeichenfolgen schützen:
- text.charAt (i + 1)
- CharArrayUtil.regionMatches (Text, i + 2, endOffset, startTag)
In diesem Fall muss der Unterausdruck zum Überprüfen des Index sein:
(i) <endOffset-2 .
IntelliJ IDEA, Wiederholte Überprüfung
public static String generateWarningMessage(....) { .... if (buffer.length() > 0) { if (buffer.length() > 0) { buffer.append(" ").append( IdeBundle.message("prompt.delete.and")).append(" "); } } .... }
PVS-Studio-Warnung: V6007 [CWE-571] Der Ausdruck 'buffer.length ()> 0' ist immer wahr. DeleteUtil.java 62
Dies kann entweder ein harmloser redundanter Code oder ein entscheidender Fehler sein.
Wenn versehentlich eine doppelte Prüfung aufgetreten ist, z. B. während des Refactorings, ist daran nichts auszusetzen. Sie können den zweiten Scheck einfach löschen.
Ein anderes Szenario ist ebenfalls möglich. Die zweite Prüfung muss ganz anders sein und der Code verhält sich nicht wie beabsichtigt. Dann ist es ein echter Fehler.
Hinweis Übrigens gibt es viele verschiedene redundante Prüfungen. Nun, oft ist klar, dass es kein Fehler ist. Wir können die Warnungen des Analysators jedoch nicht als falsch positiv betrachten. Zur Erklärung möchte ich ein solches Beispiel anführen, das ebenfalls aus IntelliJ IDEA stammt:
private static boolean isMultiline(PsiElement element) { String text = element.getText(); return text.contains("\n") || text.contains("\r") || text.contains("\r\n"); }
Der Analysator sagt, dass die Funktion
text.contains ("\ r \ n") immer false zurückgibt. Wenn das Zeichen "\ n" und "\ r" nicht gefunden wird, macht es keinen Sinn, nach "\ r \ n" zu suchen. Es ist kein Fehler, und der Code ist nur deshalb schlecht, weil er etwas langsamer arbeitet und eine bedeutungslose Suche nach einem Teilstring durchführt.
Wie mit einem solchen Code umzugehen ist, ist jeweils eine Frage für Entwickler. Beim Schreiben von Artikeln achte ich normalerweise nicht auf solchen Code.
IntelliJ IDEE, etwas stimmt nicht
public boolean satisfiedBy(@NotNull PsiElement element) { .... @NonNls final String text = expression.getText().replaceAll("_", ""); if (text == null || text.length() < 2) { return false; } if ("0".equals(text) || "0L".equals(text) || "0l".equals(text)) { return false; } return text.charAt(0) == '0'; }
PVS-Studio-Warnung: V6007 [CWE-570] Der Ausdruck '"0" .equals (text)' ist immer falsch. ConvertIntegerToDecimalPredicate.java 46
Der Code enthält mit Sicherheit einen logischen Fehler. Es fällt mir schwer zu sagen, was genau der Programmierer überprüfen wollte und wie der Fehler behoben werden kann. Also werde ich hier nur auf einen bedeutungslosen Scheck hinweisen.
Zu Beginn muss überprüft werden, ob die Zeichenfolge mindestens zwei Symbole enthält. Ist dies nicht der Fall, gibt die Funktion
false zurück .
Als nächstes kommt das Häkchen
"0" .equals (Text) . Es ist bedeutungslos, weil keine Zeichenfolge nur ein Zeichen enthalten kann.
Hier stimmt also etwas nicht, und der Code sollte korrigiert werden.
SpotBugs (Nachfolger von FindBugs), Fehler bei der Begrenzung der Anzahl der Iterationen
public static String getXMLType(@WillNotClose InputStream in) throws IOException { .... String s; int count = 0; while (count < 4) { s = r.readLine(); if (s == null) { break; } Matcher m = tag.matcher(s); if (m.find()) { return m.group(1); } } throw new IOException("Didn't find xml tag"); .... }
PVS-Studio-Warnung: V6007 [CWE-571] Der Ausdruck 'count <4' ist immer wahr. Util.java 394
Theoretisch darf eine Suche des XML-Tags nur in den ersten vier Zeilen der Datei durchgeführt werden. Aufgrund der Tatsache, dass man vergessen hat, die
Zählvariable zu erhöhen, wird die gesamte Datei gelesen.
Erstens kann dies ein sehr langsamer Vorgang sein, und zweitens kann irgendwo in der Mitte der Datei etwas gefunden werden, das als XML-Tag wahrgenommen wird, nicht als XML-Tag.
SpotBugs (Nachfolger von FindBugs), Löschen eines Wertes
private void reportBug() { int priority = LOW_PRIORITY; String pattern = "NS_NON_SHORT_CIRCUIT"; if (sawDangerOld) { if (sawNullTestVeryOld) { priority = HIGH_PRIORITY;
PVS-Studio-Warnung: V6021 [CWE-563] Der Wert wird der Variablen 'priority' zugewiesen, jedoch nicht verwendet. FindNonShortCircuit.java 197
Der Wert der
Prioritätsvariablen wird abhängig vom Wert der Variablen
sawNullTestVeryOld festgelegt . Es spielt jedoch überhaupt keine Rolle. Danach wird der
Prioritätsvariablen in jedem Fall ein anderer Wert zugewiesen. Ein offensichtlicher Fehler in der Funktionslogik.
SonarQube, Kopieren-Einfügen
public class RuleDto { .... private final RuleDefinitionDto definition; private final RuleMetadataDto metadata; .... private void setUpdatedAtFromDefinition(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } private void setUpdatedAtFromMetadata(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } .... }
PVS-Studio: V6032 Es ist merkwürdig, dass der Hauptteil der Methode 'setUpdatedAtFromDefinition' dem Hauptteil einer anderen Methode 'setUpdatedAtFromMetadata' vollständig entspricht. Überprüfen Sie die Zeilen: 396, 405. RuleDto.java 396
In der Methode
setUpdatedAtFromMetadata wird ein
Definitionsfeld verwendet. Höchstwahrscheinlich sollte das
Metadatenfeld verwendet werden. Dies ist den Auswirkungen eines fehlgeschlagenen Kopierens und Einfügens sehr ähnlich.
SonarJava, Duplikate beim Initialisieren der Karte
private final Map<JavaPunctuator, Tree.Kind> assignmentOperators = Maps.newEnumMap(JavaPunctuator.class); public KindMaps() { .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... }
PVS-Studio-Warnung: V6033 [CWE-462] Ein Element mit demselben Schlüssel 'JavaPunctuator.PLUSEQU' wurde bereits hinzugefügt. Überprüfen Sie die Zeilen: 104, 100. KindMaps.java 104
Das gleiche Schlüssel-Wert-Paar wird zweimal in der Karte festgelegt. Höchstwahrscheinlich ist es versehentlich passiert und tatsächlich gibt es keinen wirklichen Fehler. Dieser Code muss jedoch auf jeden Fall überprüft werden, da man möglicherweise vergessen hat, ein anderes Paar hinzuzufügen.
Fazit
Warum eine Schlussfolgerung schreiben, wenn es so offensichtlich ist ?! Ich schlage vor, dass Sie alle jetzt PVS-Studio herunterladen und versuchen, Ihre Arbeitsprojekte in der Java-Sprache zu überprüfen!
Laden Sie PVS-Studio herunter .
Vielen Dank für Ihre Aufmerksamkeit. Ich hoffe, dass wir unseren Lesern bald eine Reihe von Artikeln zur Überprüfung verschiedener Open-Source-Java-Projekte gefallen werden.