Dans la septième version de l'analyseur statique PVS-Studio, nous avons ajouté la prise en charge du langage Java. Il est temps de raconter brièvement comment nous avons commencé à prendre en charge le langage Java, jusqu'où nous en sommes et ce qui est dans nos plans futurs. Bien sûr, cet article répertorie les premiers essais d'analyseur sur des projets open source.
PVS-Studio
Voici une brève description de PVS-Studio pour les développeurs Java qui n'en ont pas entendu parler auparavant.
Cet outil est conçu pour détecter les erreurs et les vulnérabilités potentielles dans le code source des programmes, écrits en C, C ++, C # et Java. Il fonctionne dans l'environnement Windows, Linux et macOS.
PVS-Studio effectue une analyse de code statique et génère un rapport qui aide un développeur à trouver et à éliminer les défauts. Pour ceux qui sont intéressés par la façon exacte dont PVS-Studio recherche les erreurs, je suggère de consulter l'article "
Technologies utilisées dans l'analyseur de code PVS-Studio pour trouver des bogues et des vulnérabilités potentielles ".
Début
J'aurais pu trouver une histoire intelligente sur la façon dont nous avons spéculé sur la prochaine langue à prendre en charge dans PVS-Studio. À propos d'un choix judicieux de Java, qui est basé sur une grande popularité de ce langage, etc.
Cependant, comme cela arrive dans la vie, le choix n'a pas été fait par une analyse approfondie, mais par une expérience :). Oui, nous avons réfléchi à la direction du développement ultérieur de l'analyseur PVS-Studio. Nous avons considéré ces langages comme: Java, PHP, Python, JavaScript, IBM RPG. Nous étions même enclins au langage Java mais le choix final n'a pas été fait. Pour ceux dont le regard reposait sur un RPG IBM inconnu, je voudrais vous diriger vers cette
note , à partir de laquelle tout deviendra clair.
À la fin de 2017, mon collègue Egor Bredikhin a examiné les bibliothèques prêtes à l'emploi de code d'analyse (en d'autres termes - analyseurs) pour de nouvelles directions de développement, intéressantes pour nous. Finalement, il est tombé sur plusieurs projets pour analyser le code Java. Il a réussi à créer rapidement un prototype d'analyseur avec quelques diagnostics basés sur
Spoon . De plus, il est devenu clair que nous pourrions utiliser dans l'analyseur Java certains mécanismes de l'analyseur C ++ utilisant
SWIG . Nous avons regardé ce que nous avions obtenu et réalisé que notre prochain analyseur serait pour Java.
Nous tenons à remercier Egor pour son entreprise et le travail acharné qu'il a accompli sur l'analyseur Java. Le processus de développement lui-même a été décrit par lui dans l'article "
Développement d'un nouvel analyseur statique: PVS-Studio Java ".
Et les concurrents?
Il existe de nombreux analyseurs de code statique gratuits et commerciaux pour Java à travers le monde. Il est inutile de tous les énumérer dans l'article. Je vais juste laisser le lien vers la "
Liste des outils pour l'analyse de code statique " (voir la section Java et multi-langage).
Cependant, je sais que nous serons d'abord et avant tout interrogés sur IntelliJ IDEA, FindBugs et SonarQube (SonarJava).
IntelliJ IDEAUn analyseur de code statique très puissant est intégré à IntelliJ IDEA. De plus, l'analyseur évolue, ses auteurs suivent de près nos activités. Ainsi, IntelliJ IDEA est un cookie difficile pour nous. Nous ne pourrons pas dépasser IntelliJ IDEA en termes de capacités de diagnostic, du moins pour l'instant. Par conséquent, nous nous concentrerons sur nos autres avantages.
L'analyse statique dans IntelliJ IDEA est principalement l'une des caractéristiques de l'environnement, qui lui impose certaines limites. Quant à nous, nous avons la liberté dans ce que nous pouvons faire avec notre analyseur. Par exemple, nous pouvons l'adapter rapidement aux besoins spécifiques des clients. Un soutien rapide et approfondi est notre avantage concurrentiel. Nos clients communiquent directement avec les développeurs, travaillant sur l'une ou l'autre partie de PVS-Studio.
Dans PVS-Studio, il existe de nombreuses opportunités pour l'intégrer dans un cycle de développement de grands projets anciens. Par exemple, c'est notre
intégration avec SonarQube . Il inclut également
la suppression en masse des avertissements de l'analyseur, ce qui vous permet de commencer immédiatement à utiliser l'outil dans un grand projet de suivi des bogues uniquement dans le code nouveau ou modifié. PVS-Studio
peut être construit dans un processus d'intégration continue. Je pense que ces fonctionnalités et d'autres aideront notre analyseur à trouver une place sous le soleil dans le monde Java.
FindbugsLe projet FindBugs est abandonné. Néanmoins, nous devons le mentionner en raison du fait qu'il s'agit peut-être de l'analyseur statique gratuit le plus célèbre du code Java.
SpotBugs pourrait être appelé le successeur de FindBugs. Cependant, il est moins populaire et on ne sait pas encore ce qui va en résulter.
De manière générale, nous pensons que même si FindBugs a été et reste extrêmement populaire, et en plus d'un analyseur gratuit, nous ne devrions pas nous y attarder. Ce projet deviendra tranquillement une histoire.
PS Soit dit en passant, maintenant PVS-Studio peut également être
utilisé gratuitement lorsque vous travaillez avec des projets ouverts.
SonarQube (SonarJava)Nous pensons que nous ne sommes pas en concurrence avec SonarQube, mais le complétons. PVS-Studio s'intègre à SonarQube, ce qui permet aux développeurs de trouver plus de bogues et de failles de sécurité potentielles dans leurs projets. Nous expliquons régulièrement comment intégrer l'outil PVS-Studio et d'autres analyseurs dans SonarQube sur des master classes que nous organisons en fonction de différentes conférences.
Comment exécuter PVS-Studio pour Java
Nous avons mis à la disposition des utilisateurs les moyens les plus populaires d'intégration de l'analyseur dans le système de build:
- Plugin pour Maven;
- Plugin pour Gradle;
- Plugin pour IntelliJ IDEA
Au cours de la phase de test, nous avons rencontré de nombreux utilisateurs qui ont des systèmes de construction auto-écrits, en particulier dans le domaine du développement mobile. Ils ont profité de l'occasion pour exécuter l'analyseur directement, répertoriant les sources et le chemin de classe.
Vous pouvez trouver des informations détaillées sur toutes les manières d'exécuter l'analyseur sur la page de documentation "
Comment exécuter PVS-Studio Java ".
Nous ne pouvions pas éviter la plate-forme
SonarQube de contrôle de la qualité du code, qui est si populaire parmi les développeurs Java, nous avons donc ajouté la prise en charge du langage Java dans notre
plugin pour SonarQube .
Plans supplémentaires
Nous avons beaucoup d'idées qui pourraient nécessiter une enquête plus approfondie, mais certains plans spécifiques, inhérents à l'un de nos analyseurs, sont les suivants:
- Création de nouveaux diagnostics et amélioration des diagnostics existants;
- Amélioration de l'analyse des flux de données;
- Augmentation de la fiabilité et de la convivialité.
Peut-être, nous trouverons le temps d'adapter le plugin IntelliJ IDEA pour CLion. Salut aux développeurs C ++ qui ont lu sur l'analyseur Java :-)
Exemples d'erreurs trouvées dans les projets Open Source
Frissonne mes bois si je ne montre pas dans l'article quelques bugs trouvés avec le nouvel analyseur! Eh bien, nous aurions pu prendre un gros projet Java open source et écrire un article classique sur les erreurs, comme
nous le
faisons habituellement .
Cependant, j'anticipe immédiatement des questions sur ce que nous pouvons trouver dans des projets tels que IntelliJ IDEA, FindBugs, etc. Je n'ai donc pas d'autre issue que de commencer avec ces projets. J'ai donc décidé de vérifier et d'écrire rapidement plusieurs exemples intéressants d'erreurs des projets suivants:
- IntelliJ IDEA Community Edition . Je pense qu'il n'est pas nécessaire d'expliquer pourquoi ce projet a été choisi :).
- SpotBugs Comme je l'ai écrit plus tôt, le projet FindBugs ne progresse pas. Regardons donc à l'intérieur du projet SpotBugs, qui est le successeur de FindBugs. SpotBugs est un analyseur statique classique de code Java.
- Quelque chose des projets de la société SonarSource, qui développe un logiciel de surveillance continue de la qualité du code. Regardons maintenant à l'intérieur de SonarQube et SonarJava .
Écrire sur les bugs de ces projets est un défi. Le fait est que ces projets sont de très haute qualité. En fait, ce n'est pas surprenant. Nos observations montrent que les analyseurs de code statique sont toujours bien testés et vérifiés à l'aide d'autres outils.
Malgré tout cela, je vais devoir commencer exactement avec ces projets. Je n'aurai pas la deuxième chance d'écrire à leur sujet. Je suis sûr qu'après la sortie de PVS-Studio pour Java, les développeurs des projets répertoriés intégreront PVS-Studio et commenceront à l'utiliser pour des vérifications régulières ou, au moins, occasionnelles de leur code. Par exemple, je sais que Tagir Valeev (
lany ), l'un des développeurs de JetBrains, travaillant sur l'analyseur de code statique IntelliJ IDEA, au moment où j'écris, l'article joue déjà avec la version Beta de PVS-Studio . Il nous a écrit environ 15 e-mails avec des rapports de bogues et des recommandations. Merci, Tagir!
Heureusement, je n'ai pas besoin de trouver autant de bogues dans un projet particulier. Pour le moment, ma tâche est de montrer que l'analyseur PVS-Studio pour Java n'est pas apparu en vain et sera en mesure de remplir une gamme d'autres outils conçus pour améliorer la qualité du code. Je viens de parcourir les rapports de l'analyseur et d'énumérer quelques erreurs qui semblaient intéressantes. Si possible, j'ai essayé de citer différents types d'erreurs. Voyons comment cela s'est avéré.
IntelliJ IDEA, Integer Division
private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2;
Avertissement PVS-Studio: V6011 [CWE-682] Le littéral '0,2' du type 'double' est comparé à une valeur du type 'int'. TitleCapitalizationInspection.java 169
Le point était que la fonction devrait retourner true si moins de 20% des mots commencent par une majuscule. En fait, la vérification ne fonctionne pas, car une division entière se produit. En raison de la division, nous ne pouvons obtenir que deux valeurs: 0 ou 1.
La fonction retournera false, uniquement si tous les mots commencent par une majuscule. Dans tous les autres cas, l'opération de division donnera 0 et la fonction renverra true.
IntelliJ IDEA, boucle suspecte
public int findPreviousIndex(int current) { int count = myPainter.getErrorStripeCount(); int foundIndex = -1; int foundLayer = 0; if (0 <= current && current < count) { current--; for (int index = count - 1; index >= 0; index++) {
Avertissement PVS-Studio: V6007 [CWE-571] L'expression 'index> = 0' est toujours vraie. Updater.java 184
Tout d'abord, regardez la condition
(0 <= courant && courant <compte) . Elle n'est exécutée que si la valeur de la variable de
comptage est supérieure à 0.
Regardez maintenant la boucle:
for (int index = count - 1; index >= 0; index++)
L'
index variable est initialisé avec un
nombre d' expressions
- 1 . Comme la variable de
comptage est supérieure à 0, la valeur initiale de la variable d'
index sera toujours supérieure ou égale à 0. Il s'avère que la boucle sera exécutée jusqu'à ce qu'un débordement de la variable d'
index se produise.
Très probablement, c'est juste une faute de frappe, et un décrément, pas un incrément d'une variable doit être exécuté:
for (int index = count - 1; index >= 0; index--)
IntelliJ IDEA, copier-coller
@NonNls public static final String BEFORE_STR_OLD = "before:"; @NonNls public static final String AFTER_STR_OLD = "after:"; private static boolean isBeforeOrAfterKeyword(String str, boolean trimKeyword) { return (trimKeyword ? LoadingOrder.BEFORE_STR.trim() : LoadingOrder.BEFORE_STR).equalsIgnoreCase(str) || (trimKeyword ? LoadingOrder.AFTER_STR.trim() : LoadingOrder.AFTER_STR).equalsIgnoreCase(str) || LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str) ||
Avertissement PVS-Studio: V6001 [CWE-570] Il existe des sous-expressions identiques 'LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase (str)' à gauche et à droite de '||' opérateur. Vérifiez les lignes: 127, 128. ExtensionOrderConverter.java 127
Bon vieil
effet de la dernière ligne . Un développeur a sauté le pistolet et ayant multiplié la ligne de code, a oublié de le réparer. Par conséquent, une chaîne
str est comparée à
BEFORE_STR_OLD deux fois. Très probablement, l'une des comparaisons doit être avec
AFTER_STR_OLD .
IntelliJ IDEA, Typo
public synchronized boolean isIdentifier(@NotNull String name, final Project project) { if (!StringUtil.startsWithChar(name,'\'') && !StringUtil.startsWithChar(name,'\"')) { name = "\"" + name; } if (!StringUtil.endsWithChar(name,'"') && !StringUtil.endsWithChar(name,'\"')) { name += "\""; } .... }
Avertissement PVS-Studio: V6001 [CWE-571] Il existe des sous-expressions identiques '! StringUtil.endsWithChar (nom,' "')' à gauche et à droite de l'opérateur '&&'. JsonNamesValidator.java 27
Ce fragment de code vérifie que le nom est placé entre guillemets simples ou doubles. Si ce n'est pas le cas, des guillemets doubles sont ajoutés automatiquement.
En raison d'une faute de frappe, la fin du nom n'est vérifiée que pour la présence de guillemets doubles. Par conséquent, le nom entre guillemets simples sera traité de manière incorrecte.
Le nom
'Abcd'
en raison de l'ajout de guillemets supplémentaires se transformera en:
'Abcd'"
IntelliJ IDEA, Protection incorrecte contre le dépassement de baie
static Context parse(....) { .... for (int i = offset; i < endOffset; i++) { char c = text.charAt(i); if (c == '<' && i < endOffset && text.charAt(i + 1) == '/' && startTag != null && CharArrayUtil.regionMatches(text, i + 2, endOffset, startTag)) { endTagStartOffset = i; break; } } .... }
Avertissement PVS-Studio: V6007 [CWE-571] L'expression 'i <endOffset' est toujours vraie. EnterAfterJavadocTagHandler.java 183
La sous-expression
i <endOffset dans la condition de l'opérateur
if n'a pas de sens. La variable
i est toujours inférieure à
endOffset dans tous les cas, ce qui découle de la condition de l'exécution de la boucle.
Très probablement, un développeur souhaitait se protéger d'un dépassement de chaîne lors de l'appel de fonctions:
- text.charAt (i + 1)
- CharArrayUtil.regionMatches (texte, i + 2, endOffset, startTag)
Dans ce cas, la sous-expression pour vérifier l'index doit être:
(i) <endOffset-2 .
IntelliJ IDEA, Vérification répétée
public static String generateWarningMessage(....) { .... if (buffer.length() > 0) { if (buffer.length() > 0) { buffer.append(" ").append( IdeBundle.message("prompt.delete.and")).append(" "); } } .... }
Avertissement PVS-Studio: V6007 [CWE-571] L'expression 'buffer.length ()> 0' est toujours vraie. DeleteUtil.java 62
Cela peut être un code redondant inoffensif ou une erreur cruciale.
Si un chèque en double est apparu accidentellement, par exemple lors d'une refactorisation, il n'y a rien de mal à cela. Vous pouvez simplement supprimer le deuxième chèque.
Un autre scénario est également possible. La deuxième vérification doit être très différente et le code ne se comporte pas comme prévu. Alors c'est une vraie erreur.
Remarque Soit dit en passant, il existe de nombreux contrôles redondants. Eh bien, il est souvent clair que ce n'est pas une erreur. Cependant, nous ne pouvons pas considérer les avertissements de l'analyseur comme de faux positifs. Pour une explication, je voudrais citer un tel exemple, également tiré d'IntelliJ IDEA:
private static boolean isMultiline(PsiElement element) { String text = element.getText(); return text.contains("\n") || text.contains("\r") || text.contains("\r\n"); }
L'analyseur indique que la fonction
text.contains ("\ r \ n") renvoie toujours false. En effet, si les caractères "\ n" et "\ r" ne sont pas trouvés, il ne sert à rien de rechercher "\ r \ n". Ce n'est pas un bug, et le code est mauvais uniquement parce qu'il fonctionne légèrement plus lentement, effectuant une recherche vide de sens pour une sous-chaîne.
Comment gérer un tel code, dans chaque cas, est une question pour les développeurs. Lors de la rédaction d'articles, je ne fais généralement pas attention à ce code.
IntelliJ IDEA, quelque chose ne va pas
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'; }
Avertissement PVS-Studio: V6007 [CWE-570] L'expression "" 0 ".equals (texte)" est toujours fausse. ConvertIntegerToDecimalPredicate.java 46
Le code contient à coup sûr une erreur logique. J'ai du mal à dire exactement ce que le programmeur voulait vérifier et comment corriger le défaut. Alors ici, je vais juste signaler un chèque sans signification.
Au début, il faut vérifier que la chaîne contient au moins deux symboles. Si ce n'est pas le cas, la fonction renvoie
false .
Vient ensuite le chèque
"0" .equals (texte) . Cela n'a pas de sens, car aucune chaîne ne peut contenir qu'un seul caractère.
Donc, quelque chose ne va pas ici, et le code devrait être corrigé.
SpotBugs (Successeur de FindBugs), erreur de limitation sur le nombre d'itérations
public static String getXMLType(@WillNotClose InputStream in) throws IOException { .... String s; int count = 0; while (count < 4) { s = r.readLine(); if (s == null) { break; } Matcher m = tag.matcher(s); if (m.find()) { return m.group(1); } } throw new IOException("Didn't find xml tag"); .... }
Avertissement PVS-Studio: V6007 [CWE-571] L'expression 'count <4' est toujours vraie. Util.java 394
En théorie, une recherche de la balise xml ne doit être effectuée que dans les quatre premières lignes du fichier. Mais du fait que l'on a oublié d'incrémenter la variable
count , le fichier entier sera lu.
Premièrement, cela peut être une opération très lente, et deuxièmement, quelque part au milieu du fichier, quelque chose pourrait être trouvé qui serait perçu comme une balise xml, et non comme ça.
SpotBugs (Successeur de FindBugs), effacement d'une valeur
private void reportBug() { int priority = LOW_PRIORITY; String pattern = "NS_NON_SHORT_CIRCUIT"; if (sawDangerOld) { if (sawNullTestVeryOld) { priority = HIGH_PRIORITY;
Avertissement PVS-Studio: V6021 [CWE-563] La valeur est affectée à la variable "priorité" mais n'est pas utilisée. FindNonShortCircuit.java 197
La valeur de la variable
prioritaire est définie en fonction de la valeur de la variable
sawNullTestVeryOld . Cependant, cela n'a pas d'importance du tout. Après cela, la variable de
priorité se verra attribuer une autre valeur dans tous les cas. Une erreur évidente dans la logique de la fonction.
SonarQube, copier-coller
public class RuleDto { .... private final RuleDefinitionDto definition; private final RuleMetadataDto metadata; .... private void setUpdatedAtFromDefinition(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } private void setUpdatedAtFromMetadata(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } .... }
PVS-Studio: V6032 Il est étrange que le corps de la méthode 'setUpdatedAtFromDefinition' soit entièrement équivalent au corps d'une autre méthode 'setUpdatedAtFromMetadata'. Vérifiez les lignes: 396, 405. RuleDto.java 396
Un champ de
définition est utilisé dans la méthode
setUpdatedAtFromMetadata . Très probablement, le champ de
métadonnées doit être utilisé. Ceci est très similaire aux effets d'un copier-coller échoué.
SonarJava, doublons lors de l'initialisation de la carte
private final Map<JavaPunctuator, Tree.Kind> assignmentOperators = Maps.newEnumMap(JavaPunctuator.class); public KindMaps() { .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... }
Avertissement PVS-Studio: V6033 [CWE-462] Un élément avec la même clé 'JavaPunctuator.PLUSEQU' a déjà été ajouté. Vérifiez les lignes: 104, 100. KindMaps.java 104
La même paire clé-valeur est définie deux fois dans la carte. Très probablement, cela s'est produit par inadvertance et il n'y a en fait aucune erreur réelle. Cependant, ce code doit être vérifié dans tous les cas, car, peut-être, on a oublié d'ajouter une autre paire.
Conclusion
Pourquoi écrire une conclusion quand c'est si évident?! Je vous suggère à tous de télécharger PVS-Studio dès maintenant et d'essayer de vérifier vos projets de travail sur le langage Java!
Téléchargez PVS-Studio .
Merci à tous pour votre attention. J'espère que bientôt nous plairons à nos lecteurs avec une série d'articles sur la vérification de divers projets Java open source.