Développement d'un nouvel analyseur statique: PVS-Studio Java

Image 3

L'analyseur statique PVS-Studio est connu dans le monde du C, C ++ et C # comme un outil pour détecter les erreurs et les vulnérabilités potentielles. Cependant, nous avons peu de clients du secteur financier, car il s'est avéré que Java et IBM RPG (!) Sont désormais en demande là-bas. Mais nous avons toujours voulu nous rapprocher du monde de l'entreprise, c'est pourquoi, après réflexion, nous avons décidé de commencer à créer un analyseur Java.

Présentation


Bien sûr, il y avait des préoccupations. Il est facile de conquérir le marché des analyseurs dans IBM RPG. Je ne suis pas du tout sûr qu'il existe des outils décents pour l'analyse statique de ce langage. Dans le monde Java, les choses sont complètement différentes. Il existe déjà une gamme d'outils pour l'analyse statique, et pour aller de l'avant, vous devez créer un analyseur vraiment puissant et cool.

Néanmoins, notre entreprise a de l'expérience dans l'utilisation de plusieurs outils pour l'analyse statique de Java, et nous sommes sûrs que nous pouvons faire beaucoup de choses mieux.

De plus, nous avons eu une idée de comment utiliser toute la puissance de notre analyseur C ++ dans un analyseur Java. Mais tout d'abord.

Arbre


Image 6


Tout d'abord, il a fallu décider comment obtenir l'arbre de syntaxe et le modèle sémantique.

L'arbre de syntaxe est l'élément de base autour duquel l'analyseur est construit. Lors de l'exécution des vérifications, l'analyseur se déplace dans l'arborescence de syntaxe et examine ses nœuds individuels. Sans un tel arbre, une analyse statique sérieuse est pratiquement impossible. Par exemple, la recherche d'erreurs à l'aide d'expressions régulières n'est pas prometteuse .

Il convient de noter qu'un simple arbre de syntaxe ne suffit pas. L'analyseur a également besoin d'informations sémantiques. Par exemple, nous devons connaître les types de tous les éléments de l'arbre, pouvoir aller à la déclaration de variable, etc.

Nous avons examiné plusieurs options pour obtenir un arbre de syntaxe et un modèle sémantique:


Nous avons abandonné l'idée d'utiliser ANTLR presque immédiatement, car cela compliquerait inutilement le développement de l'analyseur (l'analyse sémantique devrait être implémentée par nous-mêmes). Finalement, nous avons décidé de nous arrêter à la bibliothèque Spoon:

  • Ce n'est pas seulement un analyseur, mais un écosystème entier - il fournit non seulement un arbre d'analyse, mais aussi des opportunités d'analyse sémantique, par exemple, il vous permet d'obtenir des informations sur les types de variables, d'aller à la déclaration de variable, d'obtenir des informations sur la classe parente, etc.
  • Il est basé sur Eclipse JDT et est capable de compiler du code.
  • Il prend en charge la dernière version de Java et est constamment mis à jour.
  • Bonne documentation et API claire.

Voici un exemple de métamodèle fourni par Spoon et avec lequel nous travaillons lors de la création de règles de diagnostic:

Image 10


Ce métamodèle correspond au code suivant:
class TestClass { void test(int a, int b) { int x = (a + b) * 4; System.out.println(x); } } 

L'un des avantages de Spoon est qu'il simplifie l'arborescence de syntaxe (suppression et ajout de nœuds) pour le rendre plus facile à utiliser. Dans le même temps, l'équivalence sémantique du métamodèle simplifié de l'original est garantie.

Pour nous, cela signifie, par exemple, que nous n'avons plus à nous soucier de sauter des crochets supplémentaires lors de la traversée d'un arbre. De plus, chaque expression est placée dans un bloc, les importations sont révélées et d'autres simplifications similaires sont apportées.

Par exemple, un code comme celui-ci:

 for (int i = ((0)); (i < 10); i++) if (cond) return (((42))); 

sera présenté comme suit:

 for (int i = 0; i < 10; i++) { if (cond) { return 42; } } 

Sur la base de l'arbre de syntaxe, une analyse dite de modèle est effectuée. Il s'agit d'une recherche d'erreurs dans le code source d'un programme en utilisant des modèles de code d'erreur bien connus. Dans le cas le plus simple, l'analyseur recherche les endroits qui ressemblent à une erreur dans l'arborescence selon les règles décrites dans les diagnostics correspondants. Le nombre de ces modèles est important et leur complexité peut varier considérablement.

L'exemple le plus simple d'erreur détectée par l'analyse basée sur des modèles est le code suivant du projet jMonkeyEngine:

 if (p.isConnected()) { log.log(Level.FINE, "Connection closed:{0}.", p); } else { log.log(Level.FINE, "Connection closed:{0}.", p); } 

Les blocs then et else de l' instruction if sont identiques; il y a très probablement une erreur logique.

Voici un autre exemple similaire du projet Hive:

 if (obj instanceof Number) { // widening conversion return ((Number) obj).doubleValue(); } else if (obj instanceof HiveDecimal) { // <= return ((HiveDecimal) obj).doubleValue(); } else if (obj instanceof String) { return Double.valueOf(obj.toString()); } else if (obj instanceof Timestamp) { return new TimestampWritable((Timestamp)obj).getDouble(); } else if (obj instanceof HiveDecimal) { // <= return ((HiveDecimal) obj).doubleValue(); } else if (obj instanceof BigDecimal) { return ((BigDecimal) obj).doubleValue(); } 

Ce code contient deux conditions identiques dans une séquence de la forme if (....) else if (....) else if (....) . Il convient de vérifier cette section du code pour une erreur logique ou de supprimer le code en double.

Analyse du flux de données


En plus de l'arbre de syntaxe et du modèle sémantique, l'analyseur a besoin d'un mécanisme pour analyser le flux de données .

L'analyse du flux de données vous permet de calculer les valeurs valides des variables et des expressions à chaque point du programme et, grâce à cela, de trouver des erreurs. Nous appelons ces valeurs valides des valeurs virtuelles.

Les valeurs virtuelles sont créées pour les variables, les champs de classe, les paramètres de méthode et d'autres choses à la première mention. S'il s'agit d'une affectation, le mécanisme de flux de données calcule la valeur virtuelle en analysant l'expression de droite, sinon toute la plage valide de valeurs pour ce type de variable est prise comme valeur virtuelle. Par exemple:

 void func(byte x) // x: [-128..127] { int y = 5; // y: [5] ... } 

Chaque fois que la valeur d'une variable change, le moteur de flux de données recalcule la valeur virtuelle. Par exemple:

 void func() { int x = 5; // x: [5] x += 7; // x: [12] ... } 

Le moteur de flux de données traite également les instructions de contrôle:

 void func(int x) // x: [-2147483648..2147483647] { if (x > 3) { // x: [4..2147483647] if (x < 10) { // x: [4..9] } } else { // x: [-2147483648..3] } ... } 

Dans cet exemple, lors de la saisie de la fonction, il n'y a aucune information sur la plage de valeurs de la variable x , elle est donc définie en fonction du type de la variable (de -2147483648 à 2147483647). Ensuite, le premier bloc conditionnel impose une contrainte x > 3, et les plages sont combinées. Par conséquent, dans le bloc then , la plage de valeurs pour x est de 4 à 2147483647, et dans le bloc else de -2147483648 à 3. La deuxième condition x <10 est traitée de la même manière.

De plus, vous devez pouvoir effectuer des calculs purement symboliques. L'exemple le plus simple:

 void f1(int a, int b, int c) { a = c; b = c; if (a == b) // <= always true .... } 

Ici, la variable a reçoit la valeur c , la variable b reçoit également la valeur c , après quoi a et b sont comparés. Dans ce cas, pour trouver l'erreur, rappelez-vous simplement le morceau de bois correspondant au côté droit.

Voici un exemple légèrement plus complexe avec des calculs de caractères:

 void f2(int a, int b, int c) { if (a < b) { if (b < c) { if (c < a) // <= always false .... } } } 

Dans de tels cas, il est déjà nécessaire de résoudre le système des inégalités sous forme symbolique.

Le mécanisme de flux de données aide l'analyseur à trouver des erreurs très difficiles à trouver à l'aide d'une analyse basée sur des modèles.

Ces erreurs incluent:

  • Débordements;
  • Aller à l'étranger le tableau;
  • Accès par référence nulle ou potentiellement nulle;
  • Conditions sans signification (toujours vrai / faux);
  • Fuites de mémoire et de ressources;
  • Division par 0;
  • Et quelques autres.

L'analyse du flux de données est particulièrement importante lors de la recherche de vulnérabilités. Par exemple, si un certain programme reçoit une entrée de l'utilisateur, il est probable que l'entrée sera utilisée pour provoquer un déni de service ou pour prendre le contrôle du système. Les exemples incluent des erreurs qui provoquent des débordements de tampon pour certaines entrées ou, par exemple, des injections SQL. Dans les deux cas, pour que l'analyseur statique détecte ces erreurs et vulnérabilités, il est nécessaire de surveiller le flux de données et les valeurs possibles des variables.

Je dois dire que le mécanisme d'analyse de flux de données est un mécanisme complexe et étendu, et dans cet article, je n'ai abordé que les bases.

Regardons quelques exemples d'erreurs qui peuvent être détectées à l'aide du mécanisme de flux de données.

Projet Hive:

 public static boolean equal(byte[] arg1, final int start1, final int len1, byte[] arg2, final int start2, final int len2) { if (len1 != len2) { // <= return false; } if (len1 == 0) { return true; } .... if (len1 == len2) { // <= .... } } 

La condition len1 == len2 est toujours satisfaite, car la vérification inverse a déjà été effectuée ci-dessus.

Un autre exemple du même projet:

 if (instances != null) { // <= Set<String> oldKeys = new HashSet<>(instances.keySet()); if (oldKeys.removeAll(latestKeys)) { .... } this.instances.keySet().removeAll(oldKeys); this.instances.putAll(freshInstances); } else { this.instances.putAll(freshInstances); // <= } 

Ici, dans le bloc else , le déréférencement du pointeur nul est garanti de se produire. Remarque: ici, les instances sont les mêmes que celles-ci .

Exemple du projet JMonkeyEngine:

 public static int convertNewtKey(short key) { .... if (key >= 0x10000) { return key - 0x10000; } return 0; } 

Ici, la variable clé est comparée au nombre 65536, cependant, elle est de type court et la valeur maximale possible pour court est 32767. Par conséquent, la condition n'est jamais remplie.

Un exemple du projet Jenkins:
 public final R getSomeBuildWithWorkspace() { int cnt = 0; for (R b = getLastBuild(); cnt < 5 && b ! = null; b = b.getPreviousBuild()) { FilePath ws = b.getWorkspace(); if (ws != null) return b; } return null; } 

La variable cnt a été introduite dans ce code pour limiter le nombre de passes à cinq, mais a oublié de l'incrémenter, ce qui rend la vérification inutile.

Mécanisme d'annotation


De plus, l'analyseur a besoin d'un mécanisme d'annotation. Les annotations sont un système de balisage qui fournit à l'analyseur des informations supplémentaires sur les méthodes et les classes utilisées, en plus de ce qui peut être obtenu en analysant leur signature. Le balisage est fait manuellement, c'est un processus long et laborieux, car pour obtenir les meilleurs résultats, il est nécessaire d'annoter un grand nombre de classes et de méthodes standard du langage Java. Il est également judicieux d'annoter les bibliothèques populaires. En général, les annotations peuvent être considérées comme la base de connaissances de l'analyseur sur les contrats des méthodes et classes standard.

Voici un petit exemple d'une erreur qui peut être détectée à l'aide d'annotations:

 int test(int a, int b) { ... return Math.max(a, a); } 

Dans cet exemple, en raison d'une faute de frappe, la même variable a été transmise comme deuxième argument à la méthode Math.max comme premier argument. Une telle expression est dénuée de sens et suspecte.

Sachant que les arguments de la méthode Math.max doivent toujours être différents, l'analyseur statique pourra émettre un avertissement pour ce code.

Pour l'avenir, je vais donner quelques exemples de notre balisage de classes et méthodes intégrées (code C ++):

 Class("java.lang.Math") - Function("abs", Type::Int32) .Pure() .Set(FunctionClassification::NoDiscard) .Returns(Arg1, [](const Int &v) { return v.Abs(); }) - Function("max", Type::Int32, Type::Int32) .Pure() .Set(FunctionClassification::NoDiscard) .Requires(NotEquals(Arg1, Arg2) .Returns(Arg1, Arg2, [](const Int &v1, const Int &v2) { return v1.Max(v2); }) Class("java.lang.String", TypeClassification::String) - Function("split", Type::Pointer) .Pure() .Set(FunctionClassification::NoDiscard) .Requires(NotNull(Arg1)) .Returns(Ptr(NotNullPointer)) Class("java.lang.Object") - Function("equals", Type::Pointer) .Pure() .Set(FunctionClassification::NoDiscard) .Requires(NotEquals(This, Arg1)) Class("java.lang.System") - Function("exit", Type::Int32) .Set(FunctionClassification::NoReturn) 

Explications:

  • Classe - classe annotée;
  • Fonction - méthode de la classe annotée;
  • Pure - annotation montrant que la méthode est propre, c'est-à-dire déterministe et sans effets secondaires;
  • Set - définit un indicateur arbitraire pour la méthode.
  • FunctionClassification :: NoDiscard - un indicateur signifiant que la valeur de retour de la méthode doit être utilisée;
  • FunctionClassification :: NoReturn - un indicateur indiquant que la méthode ne renvoie pas de contrôle;
  • Arg1 , Arg2 , ... , ArgN - arguments de la méthode;
  • Retours - la valeur de retour de la méthode;
  • Nécessite - un contrat pour la méthode.

Il convient de noter qu'en plus du balisage manuel, il existe une autre approche de l'annotation - la sortie automatique des contrats basée sur le code octet. Il est clair que cette approche ne permet d'afficher que certains types de contrats, mais elle permet d'obtenir des informations supplémentaires en général de toutes les dépendances, et pas seulement de celles annotées manuellement.

À propos, il existe déjà un outil qui peut afficher des contrats comme @Nullable , NotNull basé sur bytecode - FABA . Si je comprends bien, le dérivé FABA est utilisé dans IntelliJ IDEA.

Maintenant, nous envisageons également d'ajouter une analyse de bytecode pour obtenir des contrats pour toutes les méthodes, car ces contrats pourraient bien compléter nos annotations manuelles.

Les règles de diagnostic au travail font souvent référence à des annotations. En plus des diagnostics, les annotations utilisent le mécanisme de flux de données. Par exemple, en utilisant l'annotation de la méthode java.lang.Math.abs , il peut calculer avec précision la valeur du module numérique. En même temps, vous n'avez pas besoin d'écrire de code supplémentaire - marquez juste la méthode correctement.

Prenons un exemple d'erreur du projet Hibernate qui peut être détectée via une annotation:

 public boolean equals(Object other) { if (other instanceof Id) { Id that = (Id) other; return purchaseSequence.equals(this.purchaseSequence) && that.purchaseNumber == this.purchaseNumber; } else { return false; } } 

Dans ce code, la méthode equals () compare l'objet PurchaseSequence avec lui-même. C'est sûrement une faute de frappe et à droite devrait être that.purchaseSequence , pas buySequence .

Comment le Dr Frankenstein a assemblé un analyseur à partir de pièces


Image 2


Étant donné que les mécanismes du flux de données et des annotations eux-mêmes ne sont pas très liés à un langage particulier, il a été décidé de réutiliser ces mécanismes à partir de notre analyseur C ++. Cela nous a permis d'obtenir rapidement toute la puissance du cœur de l'analyseur C ++ dans notre analyseur Java. En outre, cette décision a également été influencée par le fait que ces mécanismes ont été écrits en C ++ moderne avec un tas de métaprogrammation et de magie de modèle, et, par conséquent, ne sont pas très adaptés pour le transfert vers une autre langue.

Afin d'associer la partie Java au noyau C ++, nous avons décidé d'utiliser SWIG (Simplified Wrapper and Interface Generator) - un outil pour générer automatiquement des wrappers et des interfaces pour lier des programmes C et C ++ avec des programmes écrits dans d'autres langages. Pour Java, SWIG génère du code JNI (Java Native Interface) .

SWIG est idéal pour les cas où il existe déjà une grande quantité de code C ++ qui doit être intégré dans un projet Java.

Je vais donner un exemple minimal de travail avec SWIG. Supposons que nous ayons une classe C ++ que nous voulons utiliser dans un projet Java:

CoolClass.h

 class CoolClass { public: int val; CoolClass(int val); void printMe(); }; 

CoolClass.cpp

 #include <iostream> #include "CoolClass.h" CoolClass::CoolClass(int v) : val(v) {} void CoolClass::printMe() { std::cout << "val: " << val << '\n'; } 

Vous devez d'abord créer un fichier d'interface SWIG avec une description de toutes les fonctions et classes exportées. Dans ce fichier, si nécessaire, des paramètres supplémentaires sont également définis.

Exemple.i

 %module MyModule %{ #include "CoolClass.h" %} %include "CoolClass.h" 

Après cela, vous pouvez exécuter SWIG:

 $ swig -c++ -java Example.i 

Il générera les fichiers suivants:

  • CoolClass.java - une classe avec laquelle nous travaillerons directement dans un projet Java;
  • MyModule.java - une classe de module dans laquelle toutes les fonctions et variables libres sont placées;
  • MyModuleJNI.java - wrappers Java;
  • Example_wrap.cxx - wrappers C ++.

Il vous suffit maintenant d'ajouter les fichiers .java résultants au projet Java et le fichier .cxx au projet C ++.

Enfin, vous devez compiler le projet C ++ en tant que bibliothèque dynamique et le charger dans le projet Java à l'aide de System.loadLibary () :

App.java

 class App { static { System.loadLibary("example"); } public static void main(String[] args) { CoolClass obj = new CoolClass(42); obj.printMe(); } } 

Schématiquement, cela peut être représenté comme suit:

Image 8


Bien sûr, dans un vrai projet, tout n'est pas si simple et il faut faire un peu plus d'efforts:

  • Pour utiliser les classes et méthodes de modèle en C ++, elles doivent être instanciées pour tous les paramètres de modèle acceptés à l'aide de la directive % template ;
  • Dans certains cas, vous devrez peut-être intercepter les exceptions levées à partir de la partie C ++ dans la partie Java. Par défaut, SWIG n'intercepte pas les exceptions de C ++ (segfault se produit), cependant, il est possible de le faire en utilisant la directive % exception ;
  • SWIG vous permet d'étendre le code plus du côté Java à l'aide de la directive % extend . Par exemple, dans notre projet, nous ajoutons la méthode toString () aux valeurs virtuelles afin de pouvoir les visualiser dans le débogueur Java;
  • Afin d'émuler le comportement RAII de C ++, l'interface AutoClosable est implémentée dans toutes les classes d'intérêt;
  • Le mécanisme des directeurs permet l'utilisation du polymorphisme cross-language;
  • Pour les types qui sont alloués uniquement dans C ++ (sur leur pool de mémoire), les constructeurs et les finaliseurs sont supprimés pour améliorer les performances. Le garbage collector ignorera ces types.

Vous pouvez en savoir plus sur tous ces mécanismes dans la documentation SWIG .

Notre analyseur est construit en utilisant gradle, qui appelle CMake, qui, à son tour, appelle SWIG et compile la partie C ++. Pour les programmeurs, cela se produit presque imperceptiblement, nous ne rencontrons donc aucun inconvénient particulier pendant le développement.

Le cœur de notre analyseur C ++ est construit sous Windows, Linux, macOS, de sorte que l'analyseur Java fonctionne également dans ces systèmes d'exploitation.

Qu'est-ce qu'une règle de diagnostic?


Les diagnostics eux-mêmes et le code d'analyse sont écrits en Java. Cela est dû à une interaction étroite avec Spoon. Chaque règle de diagnostic est un visiteur, dont les méthodes sont surchargées, dans lequel les éléments qui nous intéressent sont contournés:

Image 9

Par exemple, le cadre de diagnostic V6004 ressemble à ceci:

 class V6004 extends PvsStudioRule { .... @Override public void visitCtIf(CtIf ifElement) { // if ifElement.thenStatement statement is equivalent to // ifElement.elseStatement statement => add warning V6004 } } 

Plugins


Pour l'intégration la plus simple de l'analyseur statique dans le projet, nous avons développé des plugins pour les systèmes d'assemblage Maven et Gradle. L'utilisateur peut uniquement ajouter notre plugin au projet.

Pour Gradle:

 .... apply plugin: com.pvsstudio.PvsStudioGradlePlugin pvsstudio { outputFile = 'path/to/output.json' .... } 

Pour Maven:

 .... <plugin> <groupId>com.pvsstudio</groupId> <artifactId>pvsstudio-maven-plugin</artifactId> <version>0.1</version> <configuration> <analyzer> <outputFile>path/to/output.json</outputFile> .... </analyzer> </configuration> </plugin> 

Après cela, le plugin recevra indépendamment la structure du projet et démarrera l'analyse.

De plus, nous avons développé un plugin prototype pour IntelliJ IDEA.

Image 1

Ce plugin fonctionne également dans Android Studio.

Un plugin pour Eclipse est actuellement en cours de développement.

Analyse incrémentale


Nous avons fourni un mode d'analyse incrémentielle qui vous permet de vérifier uniquement les fichiers modifiés et réduit ainsi considérablement le temps requis pour l'analyse de code. Grâce à cela, les développeurs pourront exécuter l'analyse aussi souvent que nécessaire.

L'analyse incrémentale comprend plusieurs étapes:

  • Mise en cache du métamodèle Spoon;
  • Reconstruire la partie modifiée du métamodèle;
  • Analyse des fichiers modifiés.

Notre système de test


Pour tester l'analyseur Java sur des projets réels, nous avons écrit une boîte à outils spéciale qui vous permet de travailler avec la base de données des projets ouverts. Il a été écrit en ^ W Python + Tkinter et est multi-plateforme.

Cela fonctionne comme suit:

  • Le projet de test d'une version spécifique est téléchargé depuis le référentiel sur GitHub;
  • Le projet est en cours de montage;
  • Notre plugin est ajouté à pom.xml ou build.gradle (en utilisant git apply);
  • L'analyseur statique est lancé à l'aide du plugin;
  • Le rapport résultant est comparé à la référence de ce projet.

Cette approche garantit que les bonnes réponses ne sont pas perdues à la suite de la modification du code de l'analyseur. Voici l'interface de notre utilitaire de test.

Image 11

Les projets dans les rapports qui présentent des différences avec la norme sont marqués en rouge. Le bouton Approuver vous permet d'enregistrer la version actuelle du rapport comme référence.

Exemples d'erreur


Par tradition, je citerai plusieurs erreurs de divers projets ouverts trouvés par notre analyseur Java. À l'avenir, il est prévu d'écrire des articles avec un rapport plus détaillé sur chaque projet.

Projet Hibernate


PVS-Studio Warning: La fonction V6009 'equals' reçoit des arguments impairs. Inspectez les arguments: ceci, 1. PurchaseRecord.java 57

 public boolean equals(Object other) { if (other instanceof Id) { Id that = (Id) other; return purchaseSequence.equals(this.purchaseSequence) && that.purchaseNumber == this.purchaseNumber; } else { return false; } } 

Dans ce code, la méthode equals () compare l'objet PurchaseSequence avec lui-même. Il s'agit très probablement d'une faute de frappe et à droite devrait être that.purchaseSequence , et non buySequence .

PVS-Studio Warning: La fonction V6009 'equals' reçoit des arguments impairs. Inspectez les arguments: ceci, 1. ListHashcodeChangeTest.java 232

 public void removeBook(String title) { for( Iterator<Book> it = books.iterator(); it.hasNext(); ) { Book book = it.next(); if ( title.equals( title ) ) { it.remove(); } } } 

Une opération similaire à la précédente - à droite devrait être book.title , pas title .

Projet ruche


Avertissement PVS-Studio: l' expression V6007 'colOrScalar1.equals ("Column")' est toujours fausse. GenVectorCode.java 2768

Avertissement PVS-Studio: l' expression V6007 'colOrScalar1.equals ("Scalar")' est toujours fausse. GenVectorCode.java 2774

Avertissement PVS-Studio: l' expression V6007 'colOrScalar1.equals ("Column")' est toujours fausse. GenVectorCode.java 2785

 String colOrScalar1 = tdesc[4]; .... if (colOrScalar1.equals("Col") && colOrScalar1.equals("Column")) { .... } else if (colOrScalar1.equals("Col") && colOrScalar1.equals("Scalar")) { .... } else if (colOrScalar1.equals("Scalar") && colOrScalar1.equals("Column")) { .... } 

Les opérateurs sont clairement confus ici et au lieu de « ||» utilisé ' &&' .

Projet JavaParser


Avertissement PVS-Studio: V6001 Il existe des sous-expressions identiques «tokenRange.getBegin (). GetRange (). IsPresent ()» à gauche et à droite de l'opérateur «&&». Node.java 213

 public Node setTokenRange(TokenRange tokenRange) { this.tokenRange = tokenRange; if (tokenRange == null || !(tokenRange.getBegin().getRange().isPresent() && tokenRange.getBegin().getRange().isPresent())) { range = null; } else { range = new Range( tokenRange.getBegin().getRange().get().begin, tokenRange.getEnd().getRange().get().end); } return this; } 

L'analyseur a constaté que les mêmes expressions se trouvent à gauche et à droite de l'opérateur && (alors que toutes les méthodes de la chaîne d'appel sont propres). Très probablement, dans le deuxième cas, il est nécessaire d'utiliser tokenRange.getEnd () , et non tokenRange.getBegin () .

PVS-Studio Warning: V6016 Accès suspect à l'élément de l'objet 'typeDeclaration.getTypeParameters ()' par un index constant à l'intérieur d'une boucle. ResolvedReferenceType.java 265

 if (!isRawType()) { for (int i=0; i<typeDeclaration.getTypeParams().size(); i++) { typeParametersMap.add( new Pair<>(typeDeclaration.getTypeParams().get(0), typeParametersValues().get(i))); } } 

L'analyseur a détecté un accès suspect à l'élément de collection à un index constant à l'intérieur de la boucle. Il peut y avoir une erreur dans ce code.

Projet Jenkins


Avertissement PVS-Studio: l' expression V6007 'cnt <5' est toujours vraie. AbstractProject.java 557

 public final R getSomeBuildWithWorkspace() { int cnt = 0; for (R b = getLastBuild(); cnt < 5 && b ! = null; b = b.getPreviousBuild()) { FilePath ws = b.getWorkspace(); if (ws != null) return b; } return null; } 

La variable cnt a été introduite dans ce code pour limiter le nombre de passes à cinq, mais a oublié de l'incrémenter, ce qui rend la vérification inutile.

Projet Spark


Avertissement PVS-Studio: l' expression V6007 'sparkApplications! = Null' est toujours vraie. SparkFilter.java 127

 if (StringUtils.isNotBlank(applications)) { final String[] sparkApplications = applications.split(","); if (sparkApplications != null && sparkApplications.length > 0) { ... } } 

La vérification de la valeur null du résultat renvoyé par la méthode split n'a aucun sens, car cette méthode renvoie toujours une collection et ne renvoie jamais null .

Projet Spoon


Avertissement PVS-Studio: V6001 Il existe des sous-expressions identiques "! M.getSimpleName (). StartsWith (" set ")" à gauche et à droite de l'opérateur "&&". SpoonTestHelpers.java 108

 if (!m.getSimpleName().startsWith("set") && !m.getSimpleName().startsWith("set")) { continue; } 

Dans ce code, les mêmes expressions se trouvent à gauche et à droite de l'opérateur && (toutes les méthodes de la chaîne d'appel sont propres). Très probablement, le code contient une erreur logique. Avertissement

PVS-Studio: l' expression V6007 'idxOfScopeBoundTypeParam> = 0' est toujours vraie. MethodTypingContext.java 243

 private boolean isSameMethodFormalTypeParameter(....) { .... int idxOfScopeBoundTypeParam = getIndexOfTypeParam(....); if (idxOfScopeBoundTypeParam >= 0) { // <= int idxOfSuperBoundTypeParam = getIndexOfTypeParam(....); if (idxOfScopeBoundTypeParam >= 0) { // <= return idxOfScopeBoundTypeParam == idxOfSuperBoundTypeParam; } } .... } 

Ici, ils ont scellé la deuxième condition et au lieu de idxOfSuperBoundTypeParam ont écrit idxOfScopeBoundTypeParam .

Projet de sécurité du printemps


PVS-Studio Warning: V6001 Il y a des sous-expressions identiques à gauche et à droite de '||' opérateur. Vérifiez les lignes: 38, 39. AnyRequestMatcher.java 38

 @Override @SuppressWarnings("deprecation") public boolean equals(Object obj) { return obj instanceof AnyRequestMatcher || obj instanceof security.web.util.matcher.AnyRequestMatcher; } 

L'opération est similaire à la précédente - ici le nom de la même classe est écrit de différentes manières.

PVS-Studio Warning: V6006 L'objet a été créé mais il n'est pas utilisé. Le mot clé «throw» est peut-être manquant. DigestAuthenticationFilter.java 434

 if (!expectedNonceSignature.equals(nonceTokens[1])) { new BadCredentialsException( DigestAuthenticationFilter.this.messages .getMessage("DigestAuthenticationFilter.nonceCompromised", new Object[] { nonceAsPlainText }, "Nonce token compromised {0}")); } 

Dans ce code, ils ont oublié d'ajouter throw avant l'exception. En conséquence, l'objet d'exception BadCredentialsException est levé , mais n'est utilisé en aucune façon, c'est-à-dire aucune exception n'est levée.

PVS-Studio Warning: V6030 La méthode située à droite du '|' les opérateurs seront appelés quelle que soit la valeur de l'opérande gauche. Peut-être vaut-il mieux utiliser '||'. RedirectUrlBuilder.java 38

 public void setScheme(String scheme) { if (!("http".equals(scheme) | "https".equals(scheme))) { throw new IllegalArgumentException("..."); } this.scheme = scheme; } 

Dans ce code, l'utilisation de | injustifié, car lors de son utilisation, le côté droit sera calculé même si le côté gauche est déjà vrai. Dans ce cas, cela n'a aucun sens pratique, donc l'opérateur | vaut la peine d'être remplacé par || .

Projet IntelliJ IDEA


PVS-Studio Warning: V6008 Déréférence nulle potentielle de 'éditeur'. IntroduceVariableBase.java:609

 final PsiElement nameSuggestionContext = editor == null ? null : file.findElementAt(...); // <= final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.INSTANCE.forLanguage(...); final boolean isInplaceAvailableOnDataContext = supportProvider != null && editor.getSettings().isVariableInplaceRenameEnabled() && // <= ... 

L'analyseur a détecté que dans ce code, une déréférence du pointeur nul de l' éditeur peut se produire . Cela vaut la peine d'ajouter une vérification supplémentaire.

Avertissement PVS-Studio: l' expression V6007 est toujours fausse. RefResolveServiceImpl.java:814

 @Override public boolean contains(@NotNull VirtualFile file) { .... return false & !myProjectFileIndex.isUnderSourceRootOfType(....); } 

Il m'est difficile de dire ce que l'auteur avait en tête, mais un tel code semble très suspect. Même si tout à coup il n'y a pas d'erreur ici, je pense que ça vaut la peine de réécrire cet endroit pour ne pas confondre l'analyseur et les autres programmeurs. Avertissement

PVS-Studio: l' expression "résultat [0]" de V6007 est toujours fausse. CopyClassesHandler.java:298

 final boolean[] result = new boolean[] {false}; // <= Runnable command = () -> { PsiDirectory target; if (targetDirectory instanceof PsiDirectory) { target = (PsiDirectory)targetDirectory; } else { target = WriteAction.compute(() -> ((MoveDestination)targetDirectory).getTargetDirectory( defaultTargetDirectory)); } try { Collection<PsiFile> files = doCopyClasses(classes, map, copyClassName, target, project); if (files != null) { if (openInEditor) { for (PsiFile file : files) { CopyHandler.updateSelectionInActiveProjectView( file, project, selectInActivePanel); } EditorHelper.openFilesInEditor( files.toArray(PsiFile.EMPTY_ARRAY)); } } } catch (IncorrectOperationException ex) { Messages.showMessageDialog(project, ex.getMessage(), RefactoringBundle.message("error.title"), Messages.getErrorIcon()); } }; CommandProcessor processor = CommandProcessor.getInstance(); processor.executeCommand(project, command, commandName, null); if (result[0]) { // <= ToolWindowManager.getInstance(project).invokeLater(() -> ToolWindowManager.getInstance(project) .activateEditorComponent()); } 

Je soupçonne qu'ici, ils ont oublié de changer la valeur du résultat . Pour cette raison, l'analyseur signale que vérifier si (résultat [0]) est inutile.

Conclusion


La direction Java est très polyvalente - elle est de bureau, androïde, et web, et bien plus encore, nous avons donc beaucoup de place pour l'activité. Tout d'abord, bien sûr, nous développerons les domaines les plus demandés.

Voici nos plans pour le futur proche:

  • Annotations de sortie basées sur le bytecode;
  • Intégration dans des projets sur Ant (quelqu'un d'autre l'utilise en 2018?);
  • Plugin pour Eclipse (en cours de développement);
  • Encore plus de diagnostics et d'annotations;
  • Amélioration du mécanisme du flux de données.

Je suggère également à ceux qui souhaitent participer au test de la version alpha de notre analyseur Java lorsqu'elle sera disponible. Pour ce faire, écrivez-nous à l'appui . Nous ajouterons votre contact à la liste et vous écrirons lorsque nous préparerons la première version alpha.


Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Egor Bredikhin. Développement d'un nouvel analyseur statique:

Avez-vous lu l'article et avez une question?

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


All Articles