You are on page 1of 14

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 faire référence qu'à des constituants de classe (attributs et
méthodes statiques).

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é pour les contraintes


d'historique : public (public), protégé (protected) et privé (private).

Exemple

On définit la classe Compteur :

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 post-
conditions 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. vérification de la pré-condition de la méthode ;


2. vérification de l'invariant de la classe ;
3. appel de la méthode ;
4. 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