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-StudioV6007 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) {
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 ElasticsearchV6007 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-StudioV6007 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) {
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'huiV6008 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();
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 JavaV6007 [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)) {
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 HiveV6034 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;
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 HiveV6051 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 { .... 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 HiveV6034 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'huiLe 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 testV6072 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 { ....
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 .