Published by

Il y a 15 ans -

Temps de lecture 7 minutes

Ajouter un détecteur personnalisé à FindBugs

Les outils d’analyse statique du code permettent de détecter automatiquement certaines anomalies d’une application. Plus les anomalies sont détectées rapidement moins leur coût de correction est élevé. Certains estiment que si la correction d’un bug coûte ‘1’ durant la phase de développement, elle coûtera ’10’ en phase de recette et ‘100’ en production. Les objectifs de ces outils sont nobles : détecter un maximum d’anomalies durant la phase de développement et réduire le nombre de bugs tout en rendant le code plus performant et homogène. Il s’agit d’un des outils de QA dont disposent les développeurs afin de garantir la qualité de leur code. Les plus connus et utilisés dans le monde Java sont Checkstyle, PMD et FindBugs. Checkstyle permet, par exemple, de lever des alertes en cas d’utilisation de valeurs _en dur_ dans le code alors qu’une constante serait la bienvenue. PMD détecte entre autres la présence de blocs catch vides … et Findbugs détecte l’utilisation de la méthode equals sur des objets n’ayant pas le même type … David Hovemeyer et William Pugh, les créateurs de FindBugs, ont décidé de ne pas contrôler les problèmes de style ou de format et de se limiter à la recherche de véritables bugs.

Le principal danger de ce type d’outil est de noyer le développeur dans une foule d’informations ne correspondant pas à son besoin. Les règles étant en général prédéfinies par ces outils, elles ne peuvent pas toutes s’appliquer à tous les contextes. Pour éviter cette mauvaise analyse et la levée de false positives, il est en général possible de configurer les outils pour activer, désactiver ou modifier ces règles à la demande.

Par ailleurs, aussi conséquent soit-il, le nombre de règles proposé reste limité. Il serait pourtant pratique d’ajouter au besoin nos propres règles afin de profiter aux mieux de ces outils. C’est ce qu’on nous allons faire dans la suite du billet. Comme l’ajout de règle est relativement bien documenté pour checkstyle et PMD, nous nous intéresserons ici à la création d’un nouveau détecteur FindBugs.

FindBugs

FindBugs est donc un outil d’analyse statique qui examine les classes à la recherche d’éventuels problèmes. Pour effectuer cela, il analyse le bytecode à la recherche de certains patterns connus. Il ne se limite pas à une recherche par expressions régulières, il essaye de comprendre ce que le programme veut faire.

Un plugin FindBugs se présente sous la forme d’un fichier JAR regroupant un ou plusieurs détecteurs. Les détecteurs fournis en standard sont disponibles dans le JAR ‘coreplugin.jar’ disponible dans le répertoire ‘plugin’ de l’application. Ces mêmes détecteurs vous seront d’une aide précieuse. Il s’agit probablement de la meilleure documentation disponible sur le sujet. C’est pourquoi nous vous conseillons vivement de vous inspirer de ceux-ci avant de vous lancer. Il y a certainement un détecteur existant qui ressemble au futur votre.

Lors du démarrage de l’application, tous les JAR présents dans le dossier ‘plugin’ sont chargés les uns à la suite des autres. Dans la suite du billet, nous allons, vous l’aurez compris, créer notre propre JAR et le copier dans ce même répertoire. En étudiant les détecteurs existants, vous remarquerez que ceux-ci étendent, pour la plupart, l’une de ces deux classes : BytecodeScanningDetector et BytecodePatternDetector. BytecodePatternDetector permet la détection de séquences précises de bytecode. Il est plus facile à manier que son homologue BytecodeScanningDetector mais son utilisation reste plus limitée. C’est certainement pourquoi la majeure partie des détecteurs existants se basent sur ce dernier (BytecodeScanningDetector). Pour manipuler le bytecode, FindBugs utilise la library BCEL.

Les détecteurs sont basés sur le pattern visiteur. En informatique de gestion, on a rarement l’occasion d’utiliser ce pattern, c’est peut-être l’occasion rêvée de l’utiliser dans un contexte adapté. De toute façon, les créateurs de FindBugs vous ont évidemment mâché le travail en vous fournissant une implémentation par défaut de celui-ci. Vous n’avez qu’à surcharger les méthodes désirées.

Deux méthodes nous intéressent particulièrement :

  • visit(Code) est appelée par FindBugs lors du parcours du bytecode d’une classe.
  • sawOpcode(int) est appelée par visit(), elle permet d’analyser chaque opération du bytecode

Création d’un détecteur pas à pas

Plantons le décor : à la suite d’une utilisation massive et injustifiée de la classe thread-safe StringBuffer, le responsable technique à décidé d’interdire son utilisation au profit de la classe StringBuilder, plus rapide. Vous êtes en charge de développer un détecteur FindBugs vérifiant l’application de cette règle.

Désassembler un exemple

La première étape consiste à créer une classe exemple et à décompiler celle-ci pour en voir bytecode associé. Celle-ci vous servira également de classe de test par la suite. Pour désassembler une classe, plusieurs choix s’offrent à vous. Soit vous utilisez manuellement la commande javap founie par le jdk, soit vous utilisez une surcouche plus conviviale, comme un plugin eclipse.

Voici notre classe de test :

public void useThem() {
	StringBuffer s1 = new StringBuffer();
	StringBuilder s2 = new StringBuilder();
}

La première ligne de la méthode correspond au bytecode suivant :

L0
    LINENUMBER 11 L0
    NEW java/lang/StringBuffer
    DUP
    INVOKESPECIAL java/lang/StringBuffer.()V
    ASTORE 1

Plusieurs options s’offrent à nous pour interdire l’utilisation d’un StringBuffer. La plus simple est de détecter son instanciation. La ligne suivante du bytecode ‘NEW java/lang/StringBuffer’ nous intéresse donc particulièrement.

Surcharge du visiteur : création du détecteur

Commençons par créer une classe étendant BytecodeScanningDetector, classe à laquelle nous ajouterons un constructeur prenant en paramètre un BugReporter. Comme son nom laisse à deviner, nous utiliserons celui-ci par la suite pour ajouter des alertes.

package fr.xebia.findbugs;

import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;

public class XebiaNoStringBufferDetector extends BytecodeScanningDetector {

	private BugReporter bugReporter;
	public XebiaNoStringBufferDetector(BugReporter bugReporter) {
		this.bugReporter = bugReporter;
	}

}

Maintenant que le squelette de notre détecteur est mis en place, il est temps de s’intéresser à son implémentation. Pour cela, nous avons juste besoin de surcharger la méthode sawOpcode(). Nous avons relevé précédemment le bytecode qui nous intéresse : ‘NEW’, filtrons le code en conséquence. Il ne reste ensuite qu’à lancer une alerte de bug lorsque que l’opérande java/lang/StringBuffer est détectée.

@Override
public void sawOpcode(int seen) {
  if (seen == NEW) {
    if (getClassConstantOperand().equals("java/lang/StringBuffer")) {
      BugInstance bug = new BugInstance("XEBIA_STRINGBUFFER", NORMAL_PRIORITY);
      // Avant d'appeler la méthode sawOpcode(int), le "visiteur" a mis à jour l'instance courante de la classe.
      // De cette manière, via la référence this, on peut obtenir des infos sur la ligne de bytecode en cours d'analyse
      bug.addClassAndMethod(this);
      bug.addSourceLine(this);
      bugReporter.reportBug(bug);
    }
  }
}

La création d’une alerte FindBugs passe par la création d’un objet BugInstance. Deux paramètres sont passés dans son constructeur : un type et une priorité. Le type de bug correspond en fait à une sorte d’identifiant, nous réutiliserons celui-ci dans les étapes suivantes. Il est également possible de lier le bug au code courant. Cela permettra par la suite d’utiliser certaines informations de celui-ci (dont le numéro de ligne) dans les messages décrivant l’anomalie.

Déclaration du détecteur dans le descripteur de plugin FindBugs

Passons à la création du descripteur de plugin. Il s’agit d’un fichier XML devant se trouver dans le répertoire « resource » à la racine du JAR. Son nom est figé : ‘findbugs.xml’. Déclarons tout d’abord dans celui-ci notre nouveau détecteur. Pour cela, il suffit d’ajouter une balise contenant les informations en suivant le modèle ci-dessous. Maintenant que notre détecteur est déclaré, il ne nous reste plus qu’à l’ajouter à une catégorie (CORRECTNESS, MALICIOUS_CODE, STYLE, PERFORMANCE). Ici nous utilisons la catégorie ‘PERFORMANCE’.



  
  
  
  

Internationalisation des messages

Passons à la phase de traduction des messages. C’est ici que nous allons traduire les différentes descriptions et chaînes de caractères que nous avons utilisés jusqu’à présent dans notre exemple. Un autre fichier XML permet d’effectuer cela : ‘messages.xml’. Bien que sa syntaxe soit spécifique à FindBugs, son fonctionnement reste identique aux fichiers i18n traditionnels. Il est ainsi possible de traduire en plusieurs langues les différentes ressources en les plaçant dans différents fichiers. Par exemple, le fichier ‘messages.xml’ pourra contenir la traduction par défaut de notre détecteur (par exemple en anglais) et le fichier ‘messages_fr.xml’ contiendra les traductions françaises.



  
    Xebia FindBugs Plugins
    
Xebia FindBugs Plugins
Appel à StringBuffer Appel à StringBuffer dans {1}
Xebia FindBugs Plugins

Déployer notre plugin

Il nous ne reste plus qu’à générer un JAR contenant tout cela et à le copier dans le répertoire ‘plugin’ de FindBugs. Ensuite, si vous lancez celui-ci sur les classes de test, vous devriez obtenir un résultat semblable à la capture d’écran ci-dessous.

  • FindBugs a bien détecté nos bugs grâce à notre nouveau détecteur
  • Il les a classés dans la catégorie « PERFORMANCE »
  • Les bugs sont regroupés par « nom » de plugin en l’occurence « Xebia FindBugs Plugins » comme nous l’avions défini dans le fichier « messages.xml »
  • Le détail du bug contient, une description générale du bug rencontré ainsi que l’emplacement exact de celui-ci.
findbugs.jpg

Les sources ayant servi de base à ce billet sont disponibles :
vous pouvez les télécharger avec le lien ci-dessous sous la forme d’un projet eclipse.


Telecharger le projet eclipse

Published by

Publié par Erwan Alliaume

Passionné par les technologies Java/JEE depuis sa sortie de l'EPITA, Erwan Alliaume est aujourd'hui consultant chez Xebia. Il intervient depuis 2 ans sur des missions de développement et d'architecture pour l’un des leaders mondiaux de la production et de la distribution de contenus multimédia. Expert technique polyvalent, Erwan a été amené très tôt à prendre des responsabilités sur des projets de taille significative. Il a notamment développé des compétences clé dans la mise en place de socle de développement, la métrologie et les audits de performance et de qualité. Erwan participe également activement au blog Xebia ou il traite aussi bien de sujets d’actualités que de problématiques techniques.

Commentaire

2 réponses pour " Ajouter un détecteur personnalisé à FindBugs "

  1. Published by , Il y a 15 ans

    Merci une fois de plus pour cette article intéressant.

    Néanmoins pour votre problème initiale qui est de vérifier que vous n’utilisez pas une certaine classe, il vaut mieux utiliser macker qui (à mon avis) est un outil mieux adapté que Findbugs. Macker : http://innig.net/macker/. Un plugin maven existe depuis aout 2007 (cf. http://www.nabble.com/Maven-and-macker-plugin-working—UPDATES–tt12128641s177.html#a12128641 et http://mojo.codehaus.org/macker-maven-plugin/).

    L’article garde toute de même tout son intérêt pour pouvoir ajouter une nouvelle règle.

  2. Published by , Il y a 14 ans

    Bonjour a tous,

    J ‘utilise FindDebug pour corriger mon code , le rendre optimal .
    En effet je cherche cette régle :

    RCN: Nullcheck of value previously dereferenced (RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE)

    Mais je ne la trouve pas dans les règles prédéfinies lors de l’installation de FindDebug .

    Je cherche comment l’importer d’un fichier XML pour l’ajouter comme un filtre mais je ne trouve pas.

    Sur le site officiel on trouve toutes les descriptions des règles mais lors de l’installation on ne trouve pas toutes ces règles, j’ai cherché je ne trouve pas la

    régle dont j’ai besoin ! Comment rajouter cette nouvelle régle ?

    Merci

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.