Top 10 Bugs in Java-Projekten für 2019


Das Jahr 2019 neigt sich dem Ende zu und das PVS-Studio-Team fasst die Ergebnisse des neuen Jahres zusammen. Anfang 2019 haben wir die Fähigkeiten des Analysators durch die Unterstützung der Java-Sprache erweitert. Aus diesem Grund wurde die Liste unserer Veröffentlichungen zum Überprüfen geöffneter Projekte mit Überprüfungen von Java-Projekten ergänzt. Im Laufe des Jahres wurden viele Fehler festgestellt, und wir haben uns entschlossen, die 10 interessantesten davon vorzubereiten.


Zehnter Platz: ikonisches Byte


Quelle: Analyse des Quellcodes des RPC-Frameworks Apache Dubbo durch den statischen Analysator PVS-Studio

V6007 Ausdruck 'endKey [i] <0xff' ist immer wahr. OptionUtil.java (32)

public static final ByteSequence prefixEndOf(ByteSequence prefix) { byte[] endKey = prefix.getBytes().clone(); for (int i = endKey.length - 1; i >= 0; i--) { if (endKey[i] < 0xff) { // <= endKey[i] = (byte) (endKey[i] + 1); return ByteSequence.from(Arrays.copyOf(endKey, i + 1)); } } return ByteSequence.from(NO_PREFIX_END); } 

Viele Programmierer glauben, dass ein benanntes Byte ohne Vorzeichen sein wird. Tatsächlich ist dies häufig in verschiedenen Sprachen der Fall. In C # ist der Bytetyp beispielsweise vorzeichenlos. In Java ist dies nicht der Fall.

In der Bedingung endKey [i] <0xff vergleicht der Autor der Methode eine Variable vom Typ Byte mit der in der hexadezimalen Darstellung dargestellten Zahl 255 (0xff). Offensichtlich hat der Entwickler beim Schreiben der Methode vergessen, dass der Wertebereich des Typs Byte in Java [-128, 127] ist. Diese Bedingung ist immer wahr, sodass die for- Schleife immer nur das letzte Element des endKey- Arrays verarbeitet.

Neunter Platz: zwei in einem


Quelle: PVS-Studio für Java wird an den Pfad gesendet. Nächste Station ist Elasticsearch

V6007 Ausdruck '(int) x <0' ist immer falsch. BCrypt.java (429)

V6025 Möglicherweise liegt der Index '(int) x' außerhalb des zulässigen Bereichs . BCrypt.java (431)

 private static byte char64(char x) { if ((int)x < 0 || (int)x > index_64.length) return -1; return index_64[(int)x]; } 

Heute haben wir ein Sonderangebot! Zwei Fehler auf einmal. Die Ursache für den ersten Fehler ist der in Java vorzeichenlose char- Typ, weshalb die Bedingung (int) x <0 immer falsch ist. Der zweite Fehler ist das banale Überschreiten der Grenzen des Arrays index_64, wenn (int) x == index_64.length ist . Diese Situation ist aufgrund der Bedingung (int) x> index_64.length möglich . Um die Grenzen des Arrays zu überschreiten, muss die Bedingung '>' durch '> =' ersetzt werden. Die korrekte Bedingung wäre: (int) x> = index_64.length .

Achte Stelle: Entscheidung und ihre Folgen


Quelle: CUBA-Plattform-Code-Analyse mit PVS-Studio

V6007 Ausdruck 'previousMenuItemFlatIndex> = 0' ist immer wahr. CubaSideMenuWidget.java (328)

 protected MenuItemWidget findNextMenuItem(MenuItemWidget currentItem) { List<MenuTreeNode> menuTree = buildVisibleTree(this); List<MenuItemWidget> menuItemWidgets = menuTreeToList(menuTree); int menuItemFlatIndex = menuItemWidgets.indexOf(currentItem); int previousMenuItemFlatIndex = menuItemFlatIndex + 1; if (previousMenuItemFlatIndex >= 0) { // <= return menuItemWidgets.get(previousMenuItemFlatIndex); } return null; } 

Der Autor der findNextMenuItem- Methode möchte das von der indexOf- Methode zurückgegebene -1 entfernen , wenn die menuItemWidgets- Liste kein currentItem enthält. Dazu addiert er eins zum Ergebnisindex (Variable menuItemFlatIndex ) und speichert den resultierenden Wert in der Variablen previousMenuItemFlatIndex , die in der Methode weiter verwendet wird. Diese Lösung für das -1-Problem ist nicht erfolgreich, da sie gleichzeitig zu mehreren Fehlern führt:

  • return null code wird niemals ausgeführt, da der Ausdruck previousMenuItemFlatIndex> = 0 immer true ist. Dies bedeutet, dass die Rückgabe der findNextMenuItem- Methode immer innerhalb der if- Methode erfolgt .
  • Eine IndexOutOfBoundsException wird ausgelöst, wenn die menuItemWidgets- Liste leer ist, da auf das erste Element der leeren Liste zugegriffen wird.
  • Eine IndexOutOfBoundsException- Ausnahme tritt auf, wenn das Argument currentItem das letzte Argument in der menuItemWidget- Liste ist.

Siebter Platz: Erstellen einer Datei aus dem Nichts


Quelle: Huawei Cloud: Heute ist es in PVS-Studio bewölkt

V6008 Mögliche Null-Dereferenzierung von 'dataTmpFile'. CacheManager.java (91)

 @Override public void putToCache(PutRecordsRequest putRecordsRequest) { .... if (dataTmpFile == null || !dataTmpFile.exists()) { try { dataTmpFile.createNewFile(); // <= } catch (IOException e) { LOGGER.error("Failed to create cache tmp file, return.", e); return; } } .... } 

Beim Schreiben der putToCache- Methode machte der Programmierer einen Tippfehler in der Bedingung dataTmpFile == null || ! dataTmpFile.exists () vor dem Erstellen einer neuen dataTmpFile.createNewFile () -Datei . Ein Tippfehler ist die Verwendung des Operators '==' anstelle von '! ='. Dieser Tippfehler löst eine NullPointerException aus, wenn die createNewFile- Methode aufgerufen wird. Die Bedingung nach der Korrektur eines Tippfehlers sieht folgendermaßen aus:

 if (dataTmpFile != null || !dataTmpFile.exists()) 

„Der Fehler wurde gefunden, behoben. Du kannst dich entspannen “, werden Sie denken. Aber egal wie!

Nachdem wir einen Fehler behoben hatten, fanden wir einen anderen. Jetzt kann beim Aufrufen von dataTmpFile.exists () eine NullPointerException auftreten. Um die Ausnahme zu beseitigen, muss der Operator '||' in der Bedingung ersetzt werden auf '&&'. Die Bedingung, unter der alle Fehler verschwinden, lautet wie folgt:

 if (dataTmpFile != null && !dataTmpFile.exists()) 

Sechster Platz: ein sehr seltsamer logischer Fehler


Quelle: PVS-Studio für Java

V6007 [CWE-570] Der Ausdruck "0" .equals (text) "ist immer falsch. ConvertIntegerToDecimalPredicate.java 46

 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'; } 

Diese Methode ist insofern interessant, als sie einen offensichtlichen logischen Fehler enthält. Wenn die satisfiedBy- Methode nach dem ersten if keinen Wert zurückgibt , wird bekannt, dass die Textzeichenfolge aus mindestens zwei Zeichen besteht. Aus diesem Grund ist der erste Haken bei "0" .Equals (Text) im nächsten If bedeutungslos. Was der Entwickler wirklich gemeint hat, bleibt ein Rätsel.

Fünfter Platz: Dies ist eine Wende!


Quelle: PVS-Studio besucht Apache Hive

V6034 Die Verschiebung um den Wert 'bitShiftsInWord - 1' kann mit der Größe des Typs 'bitShiftsInWord - 1' = [-1 ... 30] inkonsistent sein. UnsignedInt128.java (1791)

 private void shiftRightDestructive(int wordShifts, int bitShiftsInWord, boolean roundUp) { if (wordShifts == 0 && bitShiftsInWord == 0) { return; } assert (wordShifts >= 0); assert (bitShiftsInWord >= 0); assert (bitShiftsInWord < 32); if (wordShifts >= 4) { zeroClear(); return; } final int shiftRestore = 32 - bitShiftsInWord; // check this because "123 << 32" will be 123. final boolean noRestore = bitShiftsInWord == 0; final int roundCarryNoRestoreMask = 1 << 31; final int roundCarryMask = (1 << (bitShiftsInWord - 1)); // <= .... } 

Bei den Eingabeargumenten wordShifts = 3 und bitShiftsInWord = 0 ist die Variable roundCarryMask , die das Ergebnis der Bitverschiebung (1 << (bitShiftsInWord - 1)) speichert , eine negative Zahl. Vielleicht hat der Entwickler dieses Verhalten nicht erwartet.

Vierter Platz: Werden die Ausnahmen für einen Spaziergang herauskommen?


Quelle: PVS-Studio besucht Apache Hive

V6051 Die Verwendung der Anweisung 'return' im Block 'finally' kann zum Verlust nicht behandelter Ausnahmen führen. ObjectStore.java (9080)

 private List<MPartitionColumnStatistics> getMPartitionColumnStatistics(....) throws NoSuchObjectException, MetaException { boolean committed = false; try { .... /*some actions*/ committed = commitTransaction(); return result; } catch (Exception ex) { LOG.error("Error retrieving statistics via jdo", ex); if (ex instanceof MetaException) { throw (MetaException) ex; } throw new MetaException(ex.getMessage()); } finally { if (!committed) { rollbackTransaction(); return Lists.newArrayList(); } } } 

Die Deklaration der Methode getMPartitionColumnStatistics sagt uns, dass sie eine Ausnahme auslösen kann. Wenn bei try eine Ausnahme auftritt, bleibt die festgeschriebene Variable false . Daher gibt die return-Anweisung im finally- Block den Wert der Methode zurück, und alle ausgelösten Ausnahmen gehen verloren und können nicht außerhalb der Methode verarbeitet werden. Daher kann eine Ausnahme, die in dieser Methode ausgelöst wird, niemals daraus hervorgehen.

Dritter Platz: Ich drehe, drehe, ich möchte eine neue Maske bekommen


Quelle: PVS-Studio besucht Apache Hive

V6034 Die Verschiebung um den Wert 'j' kann mit der Größe des Typs inkonsistent sein: 'j' = [0 ... 63]. IoTrace.java (272)

 public void logSargResult(int stripeIx, boolean[] rgsToRead) { .... for (int i = 0, valOffset = 0; i < elements; ++i, valOffset += 64) { long val = 0; for (int j = 0; j < 64; ++j) { int ix = valOffset + j; if (rgsToRead.length == ix) break; if (!rgsToRead[ix]) continue; val = val | (1 << j); // <= } .... } .... } 

Ein weiterer Fehler betraf die bitweise Verschiebung, aber diesmal war nicht nur er in den Fall verwickelt. In der inneren for- Schleife wird die Variable j [0 ... 63] als Schleifenzähler verwendet. Dieser Zähler ist an einer Bitverschiebung von 1 << j beteiligt . Nichts ist ärgerlich, aber hier kommt das Integer-Literal '1' vom Typ int (32-Bit-Wert) ins Spiel. Daraus folgt, dass sich die Ergebnisse der Bitverschiebung zu wiederholen beginnen, nachdem j größer als 31 ist. Wenn das beschriebene Verhalten unerwünscht ist, muss die Einheit so lange dargestellt werden , beispielsweise 1L << j oder (lang) 1 << j .

Zweiter Platz: Initialisierungsauftrag


Quelle: Huawei Cloud: Heute ist es in PVS-Studio bewölkt

V6050 Klasseninitialisierungszyklus ist vorhanden. Die Initialisierung von 'INSTANCE' erscheint vor der Initialisierung von 'LOG'. UntrustedSSL.java (32), UntrustedSSL.java (59), UntrustedSSL.java (33)

 public class UntrustedSSL { private static final UntrustedSSL INSTANCE = new UntrustedSSL(); private static final Logger LOG = LoggerFactory.getLogger(UntrustedSSL.class); .... private UntrustedSSL() { try { .... } catch (Throwable t) { LOG.error(t.getMessage(), t); // <= } } } 

Die Reihenfolge, in der Felder in einer Klasse deklariert werden, ist wichtig, da die Felder in der Reihenfolge initialisiert werden, in der sie deklariert wurden. Wenn sie es jedoch vergessen, treten subtile Fehler wie dieser auf.

Der Analyzer hat angegeben, dass das statische LOG- Feld im Konstruktor dereferenziert wird, wenn es auf null initialisiert wird, was zur Ausnahmekette NullPointerException -> ExceptionInInitializerError führt .

"Warum ist das statische LOG- Feld zum Zeitpunkt des Konstruktoraufrufs null ?"

Die ExceptionInInitializerError- Ausnahme ist ein Hinweis. Tatsache ist, dass dieser Konstruktor verwendet wird, um das in der Klasse deklarierte statische INSTANCE- Feld vor dem LOG- Feld zu initialisieren. Zum Zeitpunkt des Konstruktoraufrufs ist das LOG- Feld daher noch nicht initialisiert. Damit der Code korrekt funktioniert, muss das LOG- Feld initialisiert werden, bevor der Konstruktor aufgerufen wird .

Erster Platz: Copy-Paste-orientierte Programmierung


Quelle: Apache Hadoop Code Qualität: Produktions-VS-Test

V6072 Es wurden zwei ähnliche Codefragmente gefunden. Möglicherweise ist dies ein Tippfehler, und die Variable 'localFiles' sollte anstelle von 'localArchives' verwendet werden. LocalDistributedCacheManager.java (183), LocalDistributedCacheManager.java (178), LocalDistributedCacheManager.java (176), LocalDistributedCacheManager.java (181)

 public synchronized void setup(JobConf conf, JobID jobId) throws IOException { .... // Update the configuration object with localized data. if (!localArchives.isEmpty()) { conf.set(MRJobConfig.CACHE_LOCALARCHIVES, StringUtils .arrayToString(localArchives.toArray(new String[localArchives // <= .size()]))); } if (!localFiles.isEmpty()) { conf.set(MRJobConfig.CACHE_LOCALFILES, StringUtils .arrayToString(localFiles.toArray(new String[localArchives // <= .size()]))); } .... } 

Und der erste Platz wird durch Kopieren und Einfügen eingenommen, oder vielmehr durch einen Fehler, der auf die Nachlässigkeit desjenigen zurückzuführen ist, der diese sündige Sache begangen hat. Es ist sehr wahrscheinlich, dass das zweite if durch Kopieren und Einfügen des ersten mit dem Ersetzen von Variablen erstellt wurde:

  • localArchives on localFiles ;
  • MRJobConfig.CACHE_LOCALARCHIVES bei MRJobConfig.CACHE_LOCALFILES .

Selbst mit solch einer einfachen Operation wurde jedoch ein Fehler gemacht, da die Variable localArchives in der zweiten if- Zeile des zweiten Analysators noch verwendet wurde , obwohl die Verwendung von localFiles höchstwahrscheinlich impliziert war.

Fazit


Die Korrektur von Fehlern in späteren Entwicklungsstadien oder nach der Freigabe eines Projekts erfordert erhebliche Ressourcen. Der statische Analysator PVS-Studio vereinfacht das Erkennen von Fehlern beim Schreiben von Code, wodurch der Aufwand für die Fehlerbehebung erheblich reduziert wird. Die ständige Verwendung des Analysators hat bereits das Leben der Entwickler vieler Unternehmen vereinfacht. Wenn Sie gerne programmieren möchten, probieren Sie unseren Analysator aus .

Unser Team wird hier nicht aufhören und den Analysator weiter verbessern und verbessern. Erwarten Sie im nächsten Jahr neue Diagnosen und Artikel mit noch interessanteren Fehlern.

Ich sehe zu, wie du Abenteuer liebst! Zuerst gewannen die 10 häufigsten Fehler in C # -Projekten für 2019 und jetzt konnte Java siegen! Willkommen auf der nächsten Ebene im Artikel über die besten Fehler des Jahres 2019 in C ++ - Projekten .





Wenn Sie diesen Artikel mit einem englischsprachigen Publikum teilen möchten, verwenden Sie bitte den Link zur Übersetzung: Valery Komarov. Die 10 häufigsten Fehler in Java-Projekten im Jahr 2019 .

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


All Articles