2019 touche à sa fin et l'équipe de PVS-Studio revient sur les réalisations de cette année. Au début de 2019, nous avons amélioré les capacités de diagnostic de notre analyseur en ajoutant la prise en charge Java, ce qui nous a également permis de vérifier et d'examiner les projets Java. Nous avons trouvé beaucoup de bogues au cours de cette année, et voici notre Top 10 des bogues trouvés dans les projets Java.
Non. 10: octet signé
Source:
Analyse du framework RPC Apache Dubbo par l'analyseur de code 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 que le type d'
octet n'est pas signé. C'est en effet le cas dans de nombreux langages de programmation. Par exemple, cela est vrai pour C #. Mais ce n'est pas le cas en Java.
Dans la condition
endKey [i] <0xff , une variable de type
octet est comparée au nombre 255 (0xff) représenté sous forme hexadécimale. Le développeur a probablement oublié que la plage du type d'
octet Java est [-128, 127]. Cette condition sera toujours vraie et la boucle
for ne traitera toujours que le dernier élément du tableau
endKey .
Non. 9: Deux en un
Source:
PVS-Studio pour Java prend la route. 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]; }
Remise! Une méthode - deux bugs! Le premier concerne le type
char : puisqu'il n'est pas signé en Java, la condition
(int) x <0 sera toujours fausse. Le deuxième bogue est l'indexation ordinaire au-delà des limites du tableau
index_64 lorsque
(int) x == index_64.length . Cela se produit en raison de la condition
(int) x> index_64.length . Le bogue peut être corrigé en changeant l'opérateur '>' en '> =':
(int) x> = index_64.length .
Non. 8: Une solution et ses implications
Source:
Analyse du code de la plateforme CUBA 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 voulait se débarrasser de la valeur -1 renvoyée par la méthode
indexOf lorsque la liste
menuItemWidgets ne contient pas
currentItem . Pour ce faire, le programmeur ajoute 1 au résultat d'
indexOf (la variable
menuItemFlatIndex ) et écrit la valeur résultante dans la variable
previousMenuItemFlatIndex , qui est ensuite utilisée dans la méthode. L'ajout de -1 s'avère être une mauvaise solution car il conduit à plusieurs erreurs à la fois:
- L'instruction return null ne sera jamais exécutée car l'expression précédenteMenuItemFlatIndex> = 0 sera toujours vraie; par conséquent, la méthode findNextMenuItem retournera toujours à partir de l'instruction if ;
- une IndexOutOfBoundsException sera levée lorsque la liste menuItemWidgets est devenue vide puisque le programme tentera d'accéder au premier élément de la liste vide;
- une IndexOutOfBoundsException sera levée lorsque l'argument currentItem est devenu le dernier élément de la liste menuItemWidget .
Non. 7: Créer un fichier à partir de rien
Source:
Huawei Cloud: c'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 () : ils ont écrit l'opérateur '==' au lieu de '! ='. Cette faute de frappe entraînera la
levée d'une exception NullPointerException lors de l'appel de la méthode
createNewFile . Voici à quoi ressemble la condition avec la faute de frappe corrigée:
if (dataTmpFile != null || !dataTmpFile.exists())
Vous pourriez penser: «Eh bien, d'accord, nous pouvons nous détendre maintenant.» Pas encore!
Maintenant qu'un bug a été corrigé, un autre est apparu. Une
exception NullPointerException peut être levée lors de l'appel de la méthode
dataTmpFile.exists () . Pour résoudre ce problème, nous devons remplacer le '||' opérateur avec '&&'. Il s'agit de la version finale de la condition, après toutes les corrections:
if (dataTmpFile != null && !dataTmpFile.exists())
Non. 6: 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)) {
La chose intéressante à propos de cette méthode est qu'elle contient une erreur logique évidente. Si la méthode
satisfaiteBy ne retourne pas après la première instruction
if , cela signifie que la chaîne de
texte comporte au moins deux caractères. Cela signifie également que la toute première vérification
"0" .equals (texte) dans la prochaine instruction
if n'a pas de sens. Ce que le programmeur voulait réellement dire par là reste un mystère.
Non. 5: Quelle torsion!
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 au niveau du bit
(1 << (bitShiftsInWord - 1)) , deviendra un nombre négatif. Le développeur ne s'y attendait probablement pas.
Non. 4: Pouvons-nous voir les exceptions s'il vous plaît?
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 est trompeuse car elle dit qu'elle pourrait lever une exception. En réalité, quelle que soit l'exception générée dans le bloc
try , la variable validée sera toujours
fausse , donc l'instruction
return dans le bloc
finally renverra une valeur, tandis que toutes les exceptions levées seront perdues et ne pourront pas être traitées en dehors de la méthode. Ainsi, aucune des exceptions levées dans cette méthode ne pourra la quitter.
Non. 3: Hocus-pocus, ou essayer d'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);
Ce bug, aussi, a à voir avec un décalage au niveau du bit, mais pas seulement. La variable
j est utilisée comme compteur sur la plage [0 ... 63] dans la boucle
for interne. Ce compteur participe à un décalage au niveau du bit
1 << j . Tout semble correct, mais voici où le littéral entier '1' de type
int (une valeur de 32 bits) entre en jeu. À cause de cela, le décalage au niveau du bit commencera à renvoyer les valeurs précédemment renvoyées lorsque la valeur de la variable
j a dépassé 31. Si ce comportement n'est pas ce que le programmeur voulait, la valeur 1 doit être représentée aussi
longtemps :
1L << j ou
(long) 1 << j .
Non. 2: Ordre d'initialisation
Source:
Huawei Cloud: c'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 fait une différence car ils sont initialisés dans le même ordre qu'ils sont déclarés. Oublier ce fait conduit à des bugs insaisissables comme celui ci-dessus.
L'analyseur souligne que le champ statique
LOG est déréférencé dans le constructeur au moment où il est initialisé à la valeur
null , ce qui conduit à lever une série d'exceptions
NullPointerException ->
ExceptionInInitializerError .
Mais pourquoi le champ statique
LOG a-t-il la valeur
null au moment d'appeler le constructeur?
L'
ExceptionInInitializerError est l'indice. Le constructeur initialise le champ statique
INSTANCE , qui est déclaré avant le champ
LOG . C'est pourquoi le champ
LOG n'est pas encore initialisé lorsque le constructeur est appelé. Pour que ce code fonctionne correctement, le champ
LOG doit être initialisé avant l'appel.
Premièrement: 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 { ....
La première place sur notre liste Top 10 est attribuée au copier-coller, ou plutôt à un bug qui découle de l'utilisation imprudente de cette technique. La seconde instruction
if ressemble beaucoup à une copie de la première, avec certaines des variables modifiées:
- localArchives a été remplacé par localFiles ;
- MRJobConfig.CACHE_LOCALARCHIVES a été remplacé par MRJobConfig.CACHE_LOCALFILES .
Mais le programmeur a réussi à faire une erreur même dans cette opération simple: l'instruction
if dans la ligne indiquée par l'analyseur utilise toujours la variable
localArchives au lieu de la variable
localFiles apparemment voulue.
Conclusion
La correction des bogues détectés aux stades de développement ultérieurs ou après la publication nécessite beaucoup de ressources. L'analyseur statique PVS-Studio vous permet de détecter les bogues au stade du codage, ce qui le rend beaucoup plus facile et moins cher. De nombreuses
entreprises ont déjà simplifié la vie de leurs développeurs en commençant à utiliser régulièrement l'analyseur. Si vous voulez vraiment apprécier votre travail, essayez
PVS-Studio .
Nous n'allons pas nous arrêter à cela et prévoyons continuer à améliorer et à améliorer notre outil. Restez à l'écoute pour de nouveaux diagnostics et articles avec des bogues encore plus intéressants l'année prochaine.
Je te vois comme des aventures! Tout d'abord, vous avez vaincu
les 10 principaux bogues du projet C # en 2019 et maintenant vous avez également géré Java! Bienvenue au niveau suivant - l'article sur les
meilleures erreurs dans les projets C ++ en 2019 .