ENSIIE - 2009 Pierre Guilbault Louis Volant

Synthèse du JML

Mme Dubois -- Programmation Raisonnée 1

Sommaire
Introduction au JML ............................................................................................................................3 Historique du JML ...........................................................................................................................3 La programmation par contrat ........................................................................................................3 Les assertions .....................................................................................................................................4 Les assertions associées à des classes..............................................................................................4 Les invariants ..............................................................................................................................4 Contrainte historique ..................................................................................................................5 Assertions associées à des méthodes ..............................................................................................6 Pré-condition ..............................................................................................................................6 Post-condition .............................................................................................................................6 Post-condition exceptionnelle .....................................................................................................6 Assertions dans le corps des méthodes ...........................................................................................8 Les modèles ....................................................................................................................................9 Les outils associés à JML ................................................................................................................... 10 Compilateur JML ........................................................................................................................... 10 Autres outils ................................................................................................................................. 10 JML Runtime Assertion Checker (RAC) ....................................................................................... 10 Test de conformité avec JML ..................................................................................................... 11 JML dans les JavaBeans ..................................................................................................................... 12 Conclusion ........................................................................................................................................ 13 Bibliographie .................................................................................................................................... 14

2

Introduction au JML
Le Java Modeling Language est un langage servant à donner du sens à un code source, via la spécification précise de celui-ci. Il se base pour cela sur la programmation par contrat, ainsi que la logique de Hoare. Les spécifications sont ajoutées via une syntaxe particulière (proche de celle des commentaires, ce qui permet à tout compilateur Java de compiler un code contenant du JML), mais il existe des outils qui, avant ou pendant la compilation, permettent de réaliser des tests sur le fonctionnement du programme et la bonne écriture de celui-ci, ce à partir du sens donné au code source par les spécifications en JML. Ces outils sont multiples : entre autres nous verrons jmlc, jmlrac et l'Extended Static Checker (ESC/Java).

Historique du JML
L’aspect programmation par contrat et spécification du code de JML peut se comparer à d’autres langages tels qu’Eiffel et ceux de la famille Larch. C’est Garry Leavens, professeur à l’Iowa State University, qui est à l’origine de JML (il a aussi beaucoup travaillé sur les langages Larch tels que Smalltalk et C++).

La programmation par contrat
La programmation par contrat est un principe de programmation suivant lequel le déroulement d’un programme suit des règles, ou assertions, qui vont former un contrat entre « un client » et « un fournisseur » : le but de ces contrats est de définir formellement le comportement de chaque bout de code, et donc de réduire la quantité de bugs.

3

Les assertions
Des règles régissant le comportement d’un programme Java (appelée assertions) peuvent être incluses dans les commentaires de ce programme, avec quelques différences :
/*@ invariant condition ; */ Ou //@ invariant condition ; Ou /** * <pre><jml> * * invariant condition ; * * </jml></pre> */

Les assertions sont en fait des conditions qui doivent être vraies ou fausses (c’est le principe de la programmation par contrat) : elles s’écrivent donc sous forme d’expressions booléennes. Toutes les expressions sont purement fonctionnelles : c’est-à-dire qu’elles ne doivent pas engendrer d’effet de bord (elles ne doivent rien modifier d’autre que leur valeur de retour). Lorsqu’une méthode Java est purement fonctionnelle, on peut l’utiliser dans le code JML en plaçant l’assertion juste avant le corps de cette méthode. /*@ pure */ Le JML fonctionne à partir des états visibles, c’est-à-dire les états que l’on peut constater avant ou après l’exécution d’une méthode.

Les assertions associées à des classes
On peut attribuer plusieurs types d’assertions à une classe. On peut attribuer des invariants, des contraintes historiques, et des assertions appliquées aux méthodes de cette classe. Cette section ne traitera que des premières citées.

Les invariants
Un invariant est une condition portant sur les attributs de la classe et devant être vérifiée quelque soit la visibilité choisie. De la même manière qu’en Java, on distingue en JML les invariants d'instance et les invariants de classe. Les invariants de classe sont préfixés par le mot 4

clé static et ne peuvent méthodes statiques).

faire

référence

qu'à

des

constituants de

classe (attributs

et

Comme les attributs et méthodes en Java, un invariant peut en JML avoir différents modes de visibilité : public (public), protégé (protected) et privé (private).

Contrainte historique
Une contrainte d'historique a pour rôle de poser une condition d'évolution d’un élément. Cette condition porte sur l'état avant l'appel d'une méthode quelle qu’elle soit et sur l’état après son appel. On a la possibilité de faire référence à une expression exp évaluée avant l'appel de la méthode avec le mot clé suivant : \old(exp) De la même manière que pour les invariants, on fait la différence entre les contraintes d'historique de classe et les contraintes d'historique d'instance. On a aussi les différents niveaux (3 au total) de visibilité d'historique : public (public), protégé (protected) et privé (private). Exemple On définit la classe Compteur : pour les contraintes

class Compteur { /*@ @ private invariant valeur >= 0 ; @ @ private constraint valeur >= \old(valeur) ; @*/ // variable stockant la valeur du compteur private int valeur ; ... }

Cette classe possède un invariant et une contrainte d'historique. L'invariant est : private invariant valeur >= 0 ; Il oblige à ce que la valeur du compteur soit toujours positive ou nulle. 5

La contrainte d'historique est : private constraint valeur >= \old(valeur) ; Elle spécifie qu'aucune méthode ne doit faire décrémenter ce compteur.

Assertions associées à des méthodes
On peut associer à des méthodes des pré-conditions, des post-conditions ainsi que des postconditions exceptionnelles.

Pré-condition
Une pré-condition au niveau d’une méthode porte sur :
 

l'état de l'objet appelant cette méthode les paramètres de la méthode

Cette condition doit être satisfaite avant chaque appel de la méthode en question. En JML, une pré-condition est introduite par le mot clé pre ou requires, les deux étant équivalents.

Post-condition
Une post-condition pour une méthode est une condition qui porte sur :
   

les paramètres de la méthode le résultat de la méthode l'état de l'objet avant l'exécution de la méthode l'état de l'objet après l'exécution de la méthode

Elle doit être satisfaite après chaque appel de la méthode (sans toutefois lever d’exception). Une post-condition est introduite par post ou ensures, les deux étant là aussi équivalents.

Post-condition exceptionnelle
Une post-condition exceptionnelle porte sur les mêmes éléments qu'une post-condition sauf que la méthode en question doit lever une exception. Elle est introduite en JML par signals ou exsures qui sont des termes équivalents. Exemple On reprend notre exemple précédent en y ajoutant deux méthodes : 6

 

la méthode int getVal() retourne la valeur courante du compteur ; la méthode void incr(int n) throws Debordement permet d'incrémenter le compteur de n unités. Cette méthode lève l'exception Debordement si la nouvelle valeur est trop grande pour être stockée dans un entier.

Pour cela, on doit définir l’exception qui porte le nom Debordement :

/** * Si le compteur déborde alors on lève une exception. */ public class Debordement extends Exception { }

Code de la méthode getVal() :

/** * Retourne la valeur du compteur. */ public /*@ pure */ int getValeur() { return valeur ; }

On indique que la méthode getVal() est « pure », ce qui veut dire qu’elle ne produit pas d’effet de bord. On peut donc l’utiliser en JML notamment pour définir les spécifications de la méthode permettant d’incrémenter le compteur.

/*@ @ On incrémente la valeur du compteur de n. @ @ requires n >= 0 ; @ @ ensures getValeur() == \old(getValeur()) + n ; @ @ signals (Debordement debordement) getValeur() == \old(getValeur()) ; @*/ public void incr(int n) throws Debordement { int nouvelle_valeur = valeur + n ; if (nouvelle_valeur >= 0) { valeur = nouvelle_valeur ; } else { throw new Debordement() ; } } 7

La pré-condition est ici : requires n >= 0 ; Elle spécifie qu'on ne peut pas incrémenter le compteur avec une valeur négative.

La post-condition est : ensures getValeur() == \old(getValeur()) + n ; Elle spécifie que si la méthode incr termine normalement – sans lever une exception - alors on incrémente la valeur du compte de n. La post-condition exceptionnelle est : signals (Debordement debordement) getValeur() == \old(getValeur()) ; Elle spécifie que si la méthode incr termine en levant une exception, alors la valeur du compteur reste inchangée.

Assertions dans le corps des méthodes
Le JML permet de placer des spécifications à l’intérieur des méthodes. Pour cela, on utilise le mot clé assert. On peut ajouter, de manière ponctuelle, une assertion à l’intérieur de la méthode si elle est de taille raisonnable :

public void incr(int n) throws Debordement { int nouvelle_valeur = valeur + n ; if (nouvelle_valeur >= 0) { valeur = nouvelle_valeur ; //@ assert valeur >= 0 ; } else { throw new Debordement() ; } } L'assertion est : assert valeur >= 0 ; et spécifie que l’attribut valeur est positif ou nul.

8

Les modèles
Le JML fournit un certain nombre d’options permettant d’améliorer le niveau d’abstraction dans les spécifications. On a alors la possibilité de spécifier des éléments model comme des modèles d’attributs ou des modèles de méthodes. Ces modèles ne sont utilisables que dans les spécifications (il ne faut pas les utiliser dans le code Java). Les modèles permettent de spécifier des types différents. Le vérificateur d’assertions à runtime peut gérer les modèles qui correspondent à des modèles déjà présents dans le code Java, tant que ceux-ci ont également été définis dans celui-ci. Ainsi, il faut déclarer un attribut abstrait JML qui représente l'attribut Java dans les spécifications. L’attribut modèle ne doit apparaître que dans les spécifications, jamais dans le code. Il présente notamment comme avantage de permettre une abstraction supplémentaire, ce qui est mieux compréhensible pour la relecture des spécifications. Exemple : Public class Disk { /** size of the disk */ //@ public model int size; private int itsSize; //@ private represents this.size <- this.itsSize; //@ requires t > 0; //@ ensures this.size == t; public Disk(int t) {this.itsSize = t;} … }

Dans l’exemple ci-dessus, on notera que la corrélation entre les valeurs de size et itsSize est faite grâce à la clause represents.

9

Les outils associés à JML

Compilateur JML
Le compilateur JML (jmlc) permet de compiler des classes Java accompagnées de spécifications JML en bytecode Java. Ce compilateur produit, à partir de fichiers Java (extension .java) et de fichiers purement JML (extension .jml) des fichiers en bytecode (extension .class) qui comportent du code qui vérifie à l'exécution les assertions JML. Pour chaque appel de méthode, les actions suivantes sont effectuées : 1. 2. 3. 4. vérification de la pré-condition de la méthode ; vérification de l'invariant de la classe ; appel de la méthode ; vérification de la post-condition, si la méthode a terminé normalement ; vérification de la post-condition exceptionnelle, si la méthode a terminé en levant une exception spécifiée ; 5. vérification de l'invariant de la classe ; 6. vérification de la contrainte d'historique de la classe. Si une assertion n'est pas vérifiée, une exception spécifique est levée.

Autres outils
Il existe de nombreux outils offrant des fonctionnalités basées sur les annotations JML. L'outil Iowa State JML permet de convertir les annotations JML en exécutable d'assertions via un compilateur de vérification d'assertion jmlc, de générer une Javadoc améliorée incluant des informations tirées des spécifications JML, et de générer des tests unitaires pour JUnit via le générateur jmlunit. En plus de cet outil, un grand nombre de groupes indépendants travaillent sur des outils utilisant JML. Parmi ceux-ci : ESC/Java2, Extended Static Checker qui utilise les annotations JML pour effectuer une vérification statique plus rigoureuse que celle autrement possible; Daikon, un générateur d'invariant dynamique; KeY, un vérificateur de théorèmes; Krakatoa, un vérificateur de théorèmes basé sur l'assistant de preuve Coq; et JMLeclipse, un plugin pour Eclipse l'environnement de développement intégré.

JML Runtime Assertion Checker (RAC)
Le JMLrac est un outil de base de JML qui exécute un code Java, tout en soulevant des exceptions spécifiques au JML lorsqu’il rencontre une erreur sur les annotations JML. Il vérifie donc le bon fonctionnement du programme à runtime.

10

Test de conformité avec JML
Le test de conformité d'un programme consiste à exécuter ce programme en vérifiant que l'exécution est conforme à sa spécification. On parle également de « test basé sur les spécifications », ou « specification-based testing ». Le test de conformité d'un programme peut être réalisé en reformulant les spécifications du programme sous forme d'assertions incluses et évaluées lors de son l'exécution. Lorsqu'une assertion est évaluée à faux, cela signifie qu'une spécification n'est pas respectée, et qu'il y a donc une erreur dans le programme ou dans la spécification. Si le programme est spécifié en JML, on peut vérifier que ce programme est conforme à sa spécification en évaluant les assertions JML. Cela peut être réalisé très simplement en utilisant le compilateur d'assertions JML (jmlc). Si une des assertions est évaluée à faux, une exception spécifique est levée et on en déduit qu'il y a une erreur dans le programme ou dans la spécification. Les assertions JML sont donc utilisées comme oracle de test. Cet oracle peut être plus ou moins bon selon le degré de spécification du programme. Si le programme est peu spécifié, c'est-à-dire comporte des assertions JML qui posent peu de contraintes, comme des post-conditions réduites à true, l'oracle ne sera pas performant et ne permettra pas de mettre en évidence certaines erreurs. Par contre, si les assertions JML posent des contraintes suffisantes, cet oracle pourra mettre en évidence un maximum d'erreurs. L'utilisation de JML comme oracle de test a été en particulier implantée dans l'outil jmlunit, qui permet de générer des tests unitaires pour des méthodes Java. Ces tests unitaires, conçus pour pouvoir être exécutés avec JUnit, réalisent une série de tests constitués essentiellement d'un appel à une méthode.

11

JML dans les JavaBeans
La création de programmes vendables clef-en-main est de plus en plus populaire. Mais le fait que les informations échangées entre les fournisseurs et les utilisateurs des composants sont limitées est un sérieux problème. A cause de cela, les fournisseurs doivent faire la vérification sur leurs composants, mais les utilisateurs aussi. La vérification d’assertion est une méthode efficace pour améliorer la qualité des vérifications logicielles. Une des méthodes de vérification d’assertion est le langage JML. A l’origine, ces vérifications étaient placées directement dans le code compilé, et n’étaient pas fournis aux clients. Aujourd’hui, et grâce aux enveloppes de vérification des JavaBeans, on peut inclure des tests de vérification directement dans le livrable compilé (sans accès au code source), et donc permettre au client de :    Contrôle la qualité des connexions entre les composants Gagner du temps sur les temps de tests de l’intégration des nouveaux composants avec l’existant Choisir d’activer ou de désactiver les vérifications sur les composants, et donc de lui laisser la possibilité d’améliorer ou nom ses performances.

12

Conclusion
Le JML ne change pas le langage, ne le fait pas fonctionner plus rapidement. Le JML est un langage qui est à considérer parmi la batterie d’outil pour faire des tests unitaires sur le code source, et ainsi aller plus loin dans la recherche d’erreur qu’un simple debugging. Le JML sert à donner un sens à la programmation, sens qui peut être compris par les outils associés à JML, et qui peut donc voir le code de façon à vérifier que celui-ci reste bien dans la mission qui lui a été confié. Le JML permets donc une plus grande fiabilité de son code au final, mais demande un investissement de temps conséquent au départ, afin d’écrire des spécifications en nombre suffisants (pour être utiles) et elles aussi, valides. Prendre le temps d’écrire les spécifications JML est donc un investissement en temps, et cet investissement n’est valable que sur des projets conséquents, car la recherche de bugs (ou bien les implications que peuvent avoir les différentes erreurs de code) se révèlera bien plus rapide et efficace grâce à l’utilisation du JML.

13

Bibliographie

Beyond Assertions: Advanced Specification and Verification with JML and ESC/JAVA2 (Patrice Chalin, Joseph R. Kiniry, Gary T. Leavens, and Erik Poll) http://www.eecs.ucf.edu/~leavens/JML/fmco.pdf A Runtime Assertion Checker for the Java Modeling Language (JML) - (Yoonsik Cheon and Gary T. Leavens) - ftp://ftp.cs.iastate.edu/pub/techreports/TR02-05/TR.pdf Using Wrappers to Add Run-Time Verification Capability to Java Beans - (Vladimir Glina & Stephen Edwards) - http://www.eecs.ucf.edu/~leavens/SAVCBS/2004/posters/Glina-Edwards.pdf A Contextual Interpretation of Undefinedness for Runtime Assertion Checking - (Yoonsik Cheon and Gary T. Leavens) - http://cs.utep.edu/cheon/techreport/tr05-10.pdf How the Design of JML Accommodates Both Runtime Assertion Checking and Formal Verification - (Gary T. Leavens, Yoonsik Cheon, Curtis Clifton, Clyde Ruby, and David R. Cok) ftp://ftp.cs.iastate.edu/pub/techreports/TR03-04/TR.pdf

14