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-StudioV6007 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) {
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 ElasticsearchV6007 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-StudioV6007 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) {
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ölktV6008 Mögliche Null-Dereferenzierung von 'dataTmpFile'. CacheManager.java (91)
@Override public void putToCache(PutRecordsRequest putRecordsRequest) { .... if (dataTmpFile == null || !dataTmpFile.exists()) { try { dataTmpFile.createNewFile();
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 JavaV6007 [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)) {
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 HiveV6034 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;
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 HiveV6051 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 { .... 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 HiveV6034 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ölktV6050 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-TestV6072 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 { ....
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 .