Top 10 des bugs dans les projets Java pour 2019


2019 touche à sa fin et l'équipe PVS-Studio résume les résultats de l'année sortante. Début 2019, nous avons étendu les capacités de l'analyseur en prenant en charge le langage Java. Par conséquent, la liste de nos publications sur la vérification des projets ouverts a été remplie avec des revues de projets Java. De nombreuses erreurs ont été constatées au cours de l'année, et nous avons décidé de préparer le Top 10 des plus intéressants d'entre eux.


Dixième place: octet iconique


Source: Analyse du code source du framework RPC Apache Dubbo par l'analyseur statique PVS-Studio

V6007 L' expression 'endKey [i] <0xff' est toujours vraie. 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); } 

De nombreux programmeurs pensent qu'un type nommé octet ne sera pas signé. Et en effet, souvent dans différentes langues, c'est exactement le cas. Par exemple, en C #, le type d' octet n'est pas signé. En Java, ce n'est pas le cas.

Dans la condition endKey [i] <0xff, l' auteur de la méthode compare une variable de type octet avec le nombre 255 (0xff), représenté dans la représentation hexadécimale. Apparemment, lors de l'écriture de la méthode, le développeur a oublié que la plage de valeurs de type octet en Java est [-128, 127]. Cette condition est toujours vraie, donc la boucle for ne traitera toujours que le dernier élément du tableau endKey .

Neuvième place: deux en un


Source: PVS-Studio pour Java est envoyé vers le chemin. Le prochain arrêt est Elasticsearch

V6007 L' expression '(int) x <0' est toujours fausse. BCrypt.java (429)

V6025 L' index '(int) x' est peut-être hors limites. 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]; } 

Aujourd'hui, nous avons une offre spéciale! Deux erreurs dans une méthode à la fois. La cause de la première erreur est le type char , qui n'est pas signé en Java, c'est pourquoi la condition (int) x <0 est toujours fausse. La deuxième erreur est le banal sortant des limites du tableau index_64 lorsque (int) x == index_64.length . Cette situation est possible en raison de la condition (int) x> index_64.length . Pour se débarrasser de sortir des limites du tableau, il est nécessaire de remplacer la condition '>' par '> ='. La condition correcte serait: (int) x> = index_64.length .

Huitième place: décision et ses conséquences


Source: CUBA Platform Code Analysis avec PVS-Studio

V6007 L' expression 'previousMenuItemFlatIndex> = 0' est toujours vraie. 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; } 

L'auteur de la méthode findNextMenuItem souhaite se débarrasser du -1 renvoyé par la méthode indexOf si la liste menuItemWidgets ne contient pas currentItem . Pour ce faire, il en ajoute un au résultat indexOf (variable menuItemFlatIndex ) et stocke la valeur résultante dans la variable précédenteMenuItemFlatIndex , qui est ensuite utilisée dans la méthode. Cette solution au problème -1 échoue car elle conduit à plusieurs erreurs à la fois:

  • return null code ne sera jamais exécuté, car l'expression previousMenuItemFlatIndex> = 0 est toujours vraie, ce qui signifie que le retour de la méthode findNextMenuItem se produira toujours à l'intérieur du if ;
  • une IndexOutOfBoundsException sera levée lorsque la liste menuItemWidgets est vide, car le premier élément de la liste vide sera accessible;
  • une exception IndexOutOfBoundsException se produit lorsque l'argument currentItem est le dernier de la liste menuItemWidget .

Septième place: créer un fichier à partir de rien


Source: Huawei Cloud: il est nuageux dans PVS-Studio aujourd'hui

V6008 Déréférence nulle potentielle de '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; } } .... } 

Lors de l'écriture de la méthode putToCache, le programmeur a fait une faute de frappe dans la condition dataTmpFile == null || ! dataTmpFile.exists () avant de créer un nouveau fichier dataTmpFile.createNewFile (). Une faute de frappe est l'utilisation de l'opérateur '==' au lieu de '! ='. Cette faute de frappe lèvera une exception NullPointerException lors de l'appel de la méthode createNewFile . La condition après avoir corrigé une faute de frappe ressemble à ceci:

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

«L'erreur a été trouvée, corrigée. Vous pouvez vous détendre », penserez-vous. Mais peu importe comment!

Après avoir corrigé une erreur, nous en avons trouvé une autre. Maintenant, une exception NullPointerException peut se produire lors de l'appel de dataTmpFile.exists () . Maintenant, pour se débarrasser de l'exception, il est nécessaire de remplacer l'opérateur '||' dans la condition sur '&&'. La condition dans laquelle toutes les erreurs disparaissent sera la suivante:

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

Sixième place: une erreur logique très étrange


Source: PVS-Studio pour Java

V6007 [CWE-570] L'expression "" 0 ".equals (texte)" est toujours fausse. 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'; } 

Cette méthode est intéressante en ce qu'elle contient une erreur logique évidente. Si la méthode satisfaitBy ne renvoie pas de valeur après le premier if , il devient connu que la chaîne de texte se compose d'au moins deux caractères. Pour cette raison, la première coche «0» .equals (texte) dans la suivante si n'a pas de sens. Ce que le développeur voulait vraiment dire reste un mystère.

Cinquième place: c'est un tour!


Source: PVS-Studio visite Apache Hive

V6034 Le décalage de la valeur de 'bitShiftsInWord - 1' peut ne pas correspondre à la taille du type: 'bitShiftsInWord - 1' = [-1 ... 30]. 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)); // <= .... } 

Avec les arguments d'entrée wordShifts = 3 et bitShiftsInWord = 0 , la variable roundCarryMask , qui stocke le résultat du décalage de bit (1 << (bitShiftsInWord - 1)) , se révélera être un nombre négatif. Peut-être que le développeur ne s'attendait pas à ce comportement.

Quatrième place: les exceptions sortiront-elles pour une promenade?


Source: PVS-Studio visite Apache Hive

V6051 L'utilisation de l'instruction 'return' dans le bloc 'finalement' peut entraîner la perte d'exceptions non gérées. 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(); } } } 

La déclaration de la méthode getMPartitionColumnStatistics nous ment, disant qu'elle peut lever une exception. Lorsqu'une exception se produit dans try , la variable validée reste fausse , par conséquent, dans le bloc finally , l' instruction return renvoie la valeur de la méthode et toutes les exceptions levées sont perdues et ne peuvent pas être traitées en dehors de la méthode. Ainsi, aucune exception levée dans cette méthode ne pourra jamais en sortir.

Troisième place: je tourne, je tourne, je veux obtenir un nouveau masque


Source: PVS-Studio visite Apache Hive

V6034 Le décalage de la valeur de 'j' peut être incompatible avec la taille du type: '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); // <= } .... } .... } 

Une autre erreur liée au décalage au niveau du bit, mais cette fois non seulement il a été impliqué dans l'affaire. Dans la boucle for interne, la variable j [0 ... 63] est utilisée comme compteur de boucle. Ce compteur est impliqué dans un décalage binaire de 1 << j . Rien ne présage de problème, cependant, le littéral entier '1' de type int (valeur 32 bits) entre en jeu ici. Il s'ensuit que les résultats du décalage binaire commenceront à se répéter après que j soit supérieur à 31. Si le comportement décrit n'est pas souhaitable, alors l'unité doit être représentée aussi longtemps , par exemple, 1L << j ou (long) 1 << j .

Deuxième place: ordre d'initialisation


Source: Huawei Cloud: il est nuageux dans PVS-Studio aujourd'hui

Le cycle d'initialisation de la classe V6050 est présent. L'initialisation de 'INSTANCE' apparaît avant l'initialisation de '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); // <= } } } 

L'ordre dans lequel les champs sont déclarés dans une classe est important car les champs sont initialisés dans l'ordre dans lequel ils sont déclarés. Cependant, quand ils l'oublient, des erreurs subtiles se produisent, comme celle-ci.

L'analyseur a indiqué que le champ LOG statique est déréférencé dans le constructeur lorsqu'il est initialisé à null , ce qui conduit à la chaîne d'exceptions NullPointerException -> ExceptionInInitializerError .

«Pourquoi, au moment de l'appel du constructeur, le champ LOG statique est-il nul ?», Vous demandez-vous.

L'exception ExceptionInInitializerError est un indice. Le fait est que ce constructeur est utilisé pour initialiser le champ statique INSTANCE déclaré dans la classe plus tôt que le champ LOG . Par conséquent, au moment de l'appel du constructeur, le champ LOG n'est toujours pas initialisé. Pour que le code fonctionne correctement, vous devez initialiser le champ LOG avant d'appeler le constructeur.

Première place: programmation orientée copier-coller


Source: Apache Hadoop Code Qualité: production VS test

V6072 Deux fragments de code similaires ont été trouvés. Il s'agit peut-être d'une faute de frappe et la variable «localFiles» devrait être utilisée à la place de «localArchives». 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()]))); } .... } 

Et la première place est prise par copier-coller, ou plutôt, une erreur qui est survenue en raison de la négligence de celui qui a commis cette chose pécheresse. Il est fort probable que le second if ait été créé par copier-coller du premier avec le remplacement des variables:

  • localArchives sur localFiles ;
  • MRJobConfig.CACHE_LOCALARCHIVES à MRJobConfig.CACHE_LOCALFILES .

Cependant, même avec une opération aussi simple, une erreur a été commise, car la variable localArchives était toujours utilisée dans la deuxième ligne if dans le deuxième analyseur , bien que l'utilisation de localFiles soit très probablement implicite.

Conclusion


La correction des erreurs détectées dans les derniers stades de développement ou après la publication d'un projet nécessite des ressources importantes. L'analyseur statique PVS-Studio simplifie la détection des erreurs lors de l'écriture de code, ce qui réduit considérablement la quantité de ressources dépensées pour les corriger. L'utilisation constante de l'analyseur a déjà simplifié la vie des développeurs de nombreuses entreprises . Si vous souhaitez programmer avec grand plaisir, essayez notre analyseur .

Notre équipe ne s'arrêtera pas là et continuera d'améliorer et d'améliorer l'analyseur. Attendez-vous à de nouveaux diagnostics et articles avec des bogues encore plus intéressants l'année prochaine.

Je te regarde aimer l'aventure! Tout d'abord, le top 10 des erreurs dans les projets C # pour 2019 a gagné, et maintenant Java a pu surmonter! Bienvenue au niveau suivant dans l'article sur les meilleures erreurs de 2019 dans les projets C ++ .





Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Valery Komarov. Top 10 des bugs trouvés dans les projets Java en 2019 .

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


All Articles