Vous êtes sur la page 1sur 14

2010

Programmation
Raisonnée

Pierre Guilbault - Louis


Volant

[VERIFICATEUR D’ASSERTION
A RUNTIME]
L’objet de ce projet était de créer dans un langage simplifié d’annotations, un vérificateur d’assertion
à runtime.
Sommaire
Introduction........................................................................................................................................2
1. Langage PL ......................................................................................................................................3
1.1 Syntaxe abstraite .......................................................................................................................3
1.2 L’interprète PL ...........................................................................................................................5
1.3 Perspectives ..............................................................................................................................6
2. Langage d’assertion PLML ...............................................................................................................8
2.1 Syntaxe abstraite .......................................................................................................................8
2.2 Le transformateur .....................................................................................................................9
2.3 Tests ....................................................................................................................................... 10
2.4 Perspectives ............................................................................................................................ 12
Conclusion ........................................................................................................................................ 13

1
Introduction

L’objectif de ce projet était de réaliser un vérificateur d’assertions à runtime. Pour cela, le


langage impératif PL et le langage d’annotations noté PLML (similaire à JML) ont été introduits. Ce
langage d’annotations se base sur le paradigme de programmation par contrat. Les annotations
PLML insérées dans le langage PL sont traduites en instructions de vérification c'est-à-dire que ces
nouvelles instructions seront chargées de vérifier si le contrat a été respecté ou non. Pour mettre en
place ce vérificateur d’assertions à runtime, nous avons utilisé OCaml pour définir d’une part le
langage PL et d’autre part le langage d’annotations PLML.

2
1. Langage PL

Le langage retenu pour mettre en œuvre le vérificateur d’assertions à runtime est appelé langage PL
(Petit Langage). C’est un langage impératif muni des éléments suivants :

- affectation
- fonctions
- structure conditionnelle IF
- séquence d’instructions
- Arrêt brutal
- Boucle While

La première étape va être de définir la syntaxe abstraite de ce langage avant de réaliser l’interprète
PL qui va se charger d’exécuter les instructions modifiant la mémoire (un environnement dans notre
cas).

1.1 Syntaxe abstraite

Le langage PL est composé d’une multitude de types qui vont permettre de structurer ce langage.
Dans ce projet nous avons décidé de retenir les types suivants :

- Type expression
- Type condition
- Type instruction
- Type fonction
- Type environnement
- Type programme

Dans notre cas, nous avons considéré qu’une expression pouvait être soit un entier, une variable, un
booléen (true ou false), ou bien une opération arithmétique (addition ou soustraction).

Ici, une condition peut être un booléen, ou bien une comparaison entre deux expressions possédant
les signes inférieur, inférieur strict, supérieur, supérieur strict, égal ou non-égal. Concrètement, cela
permet de construire la partie conditionnelle de la structure If-Then-Else (par exemple, x=3, y<4,
z<>5, etc).

3
Une instruction peut être une séquence d’instructions, une structure If-Then-Else, une boucle While,
un arrêt brutal (Abort), une affectation ou bien un appel de fonction.

Le type fonction est composé du triplet (nom_fonction, liste_arguments, liste_instructions).

Le type environnement est une liste de couples (variable, valeur). Il permet de traduire la notion de
mémoire du langage PL.

Le type programme est la combinaison de l’environnement et d’une liste d’instructions (instructions


du programme).

4
1.2 L’interprète PL

L’interprète PL va permettre d’évaluer l’environnement après chaque instruction. Il prend en


argument l’environnement existant (nul au début du programme) puis va effectuer des opérations
dessus.

Dans le cas d’une affectation (Assign), l’interprète va tout simplement insérer dans l’environnement
la variable avec sa valeur (évaluée s’il s’agit d’une expression). Si cette variable était déjà présente
dans l’environnement avant l’instruction, sa valeur est tout simplement mise à jour (l’ancienne
valeur est écrasée).

La séquence d’instructions fait appel à une fonction annexe (instr_list) qui va dérouler chacune des
instructions de la liste et modifier l’environnement en conséquence.

La structure conditionnelle If-Then-Else prend en en arguments l’expression booléenne à évaluer


(cond) et les deux listes d’instructions correspondant respectivement au cas Then ou au cas Else.

L’arrêt brutal est ici réalisé grâce à l’instruction Abort qui va simplement déclencher une exception
en affichant le message passé en argument. Ainsi, cela permet de connaître précisément le problème
rencontré.

La boucle While du langage PL va tout d’abord tester la condition passée en argument. Si celle-ci se
trouve être vraie, la liste d’instructions est exécutée. Pour réaliser la structure While, on se sert de la
structure conditionnelle If-Then-Else définie auparavant. Cela permet donc de boucler jusqu’à ce que
la condition soit fausse.

5
L’appel de fonctions (Call) va s’occuper de charger dans l’environnement les arguments de la
fonction puis va exécuter les instructions qui constituent le corps de cette fonction.

1.3 Perspectives

Bien que la plupart des instructions gérées soient fonctionnelles, il subsiste un souci concernant
l’appel de fonctions. En effet, celui-ci effectue en réalité de l’inlining. Autrement dit, l’instruction Call
insère à chaque exécution le corps de la fonction ce qui revient donc à réaliser une simple liste
d’instructions.

Pour corriger cela et implémenter un « vrai » appel de fonctions, il faut effectuer plusieurs
changements. Tout d’abord, il est nécessaire de revoir la définition de l’environnement. A la base,
celui-ci est constitué d’une liste de couples (variable, valeur). Pour pouvoir gérer la définition de
fonctions en amont, il est plus judicieux d’adopter pour le type environnement suivant :

Un environnement est donc alors composé de valeurs (V) et de fonctions (F) où F est accompagné du
triplet (nom, liste_arguments, liste_instructions). Aucune valeur numérique n’est ici passée en
argument. Cette liste est uniquement composée des variables (chaînes de caractères).

De plus, il est nécessaire de définir un ensemble de fonctions permettant de récupérer et d’insérer


des fonctions dans l’environnement.

Concernant l’appel de fonctions à l’intérieur de l’interprète, celui-ci n’est pas encore fonctionnel mais
son rôle est de récupérer la fonction depuis l’environnement afin d’exécuter la liste d’instructions.

On prend soin, avant l’exécution de cette liste, de fusionner les arguments de la fonction avec les
valeurs fournies et d’insérer le résultat dans l’environnement courant. La fonction chargée
d’effectuer cette fusion s’appelle fusion_var_val.

6
7
2. Langage d’assertion PLML

Le vérificateur d'assertions prend en entrée le programme source annoté et le transforme en


un programme PL sans annotations instrumenté pour faire les vérifications des assertions. Ce
programme instrumenté doit échouer dès qu'une assertion n'est pas vérifiée.

Le but est donc de définir la syntaxe de ce que seront les annotations, puis de créer le
« transformateur », celui-ci ayant pour but de rajouter dans le code du programme les conditions et
instructions pour le faire échouer lorsqu’une condition n’est pas remplie.

2.1 Syntaxe abstraite

Les types choisis sont divisés en plusieurs parties :

D’abord, un type de base : exprML, reprend le principe du type expr précédemment définit pour
l’interprète PL.

De la même façon que la syntaxe du JML est proche de celle du Java, le but de la syntaxe choisie pour
le PLML est d’être visiblement différente (via l’ajout du suffixe ML derrières les types), tout en
restant sensiblement proche de celle du PL.

Le type CondML sert à fournir le cœur des assertions (c’est-à-dire les conditions que devront remplir
les variables), tandis que les types Invariants, Requires et Ensures correspondent exactement aux
même assertions qu’en JML, c’est-à-dire les invariants, les pré-conditions et les post-conditions.

8
Ceux-ci sont regroupés au sein d’un type appelé LigneML, qui est comparable au bloc de code JML
que l’on met au-dessus des méthodes à spécifier en Java. La LigneML prend quatre arguments : son
nom, puis une listes d’assertions qui seront des invariants, une liste d’assertions qui seront des pré-
conditions, et enfin une liste d’assertions qui seront des post-conditions.

2.2 Le transformateur

Nous n’avons pas parlé de « vérificateur d’assertions », dans la mesure où il n’y a en fait pas de
vérification au moment où celui-ci est exécuté (la vérification se fait à runtime), donc nous avons
préféré parler de « transformateur » : celui-ci prend une liste d’assertions en entrée, ainsi qu’une
fonction, et va transformer la fonction en lui ajoutant les conditions correspondantes aux assertions
PLML.

A partir des arguments que reçoit le transformateur, celui-ci va appeler trois fonctions différentes
(pour les trois groupes d’assertions différentes), et concaténer ce qu’elles leur renvoient pour fournir
une liste d’instructions, qui correspondra à la liste d’instructions annotées.

Ce qui est renvoyé par la fonction transform1 est donc une liste d’instructions avec, en premier, les
instructions annotées de pré-conditions, puis ensuite les instructions d’origines, annotées avec les
instructions d’invariants, et enfin la liste des instructions annotées de post-conditions.

Il faut préciser que la convention de nommage choisie pour les fonctions PLML a été la suivante :
« transform » étant le tronc commun des noms de fonctions intervenant dans la transformation, le
chiffre suivant étant le niveau d’appel, et la lettre majuscule associée (à partir du niveau 2) étant la
majuscule associée au groupe d’assertions (R pour Requires, I pour Invariants, et E pour Ensures).

Les fonctions transformR2 et transformE2 sont assez similaires : elles traduisent en conditions PL les
assertions PLML :

9
Pour chaque condition PLML, elles insèrent une instruction IfThenElse sans instruction en cas de
validité, mais levant un arrêt brutal Abort en cas de non validité de la condition. L’Abort est
personnalisé avec le nom de la condition (SupEgal, InfEgal, etc) afin de préciser en cas d’arrêt brutal
sur quel type de test il y a eu l’arrêt, et également avec le mot PreCondition ou PostCondition pour
indiquer à quel moment de l’exécution de la fonction l’arrêt brutal a eu lieu.

On remarquera également l’utilisation de la fonction PLMLtoPL, qui permet de passer d’un type PLML
à un type PL pour la même condition :

Enfin, les invariants sont traités par les fonctions transformI2 et transformI3 : elles insèrent la liste
des conditions données par les assertions invariantes avant et après chaque instruction.

NB : Le cas dans lequel la liste d’instruction de la fonction non annoté contient plus de une assertion
a été traité, et pour ce cas, le fait qu’il y ait une liste d’annotations avant et après chaque instruction
introduisait des redondances dans les annotations, comme on peut le voir dans le schéma ci-
dessous :

2.3 Tests

Le transformateur a été testé et fonctionne avec succès.

En prenant une fonction avec plusieurs instructions, et deux environnements de départ différents, on
fait faire deux tests (un pour chaque environnement de départ), permettant de renvoyer deux états

10
différents de l’exécution de la fonction annotée. Le premier, positif, montre un environnement
donné en réponse (donc la fonction fonctionne bien, et renvoie correctement les valeurs en
environnement). Le deuxième prend un environnement qui doit renvoyer une mauvaise post-
condition.

L’exécution du code met en évidence ce qui devait être testé :

11
A l’issue des différentes définitions, on remarque la transformation de la fonction (en l2), dans
laquelle on retrouve bien le bloc de pré-conditions, puis les différentes instructions d’origine
« encadrées » par des tests d’invariance, et enfin le bloc d’instructions de post-conditions.

Le test l3 fonctionne et donc ne provoque pas d’arrêt brutal. L3 renvoie donc un environnement,
comme l’aurait fait la fonction originale, dans le même environnement, mais non annotée.

Le test l4 s’arrête brutalement, en indiquant qu’il s’agit d’un problème sur la PostCondition InfEgal,
ce qui l’on retrouve bien (avec le deuxième environnement, z vaudrait 6 à l’arrivée).

2.4 Perspectives

La définition de ce vérificateur pourrait aller plus loin, avec d’abord l’ajout de fonctionnalités dans les
assertions du PLML, comme par exemple les quantificateurs et les fonctions pures.

12
Conclusion

Ce projet nous a permis de créer un langage simple, puis de reprendre certaines


caractéristiques d’un langage d’assertions, le JML, ce afin de créer un petit langage d’assertions
fonctionnant par-dessus le PL.

Nous avons ensuite fait fonctionner un interpréteur d’instructions et de fonctions PL, fonctionnant
comme une fonction de transfert, c’est-à-dire prenant un environnement (et des arguments) dans
état initial, et renvoyant celui-ci (après traitement), dans un état final, ce via une liste d’instructions.

Après cela, nous avons créé un transformateur d’assertions PLML, prenant en entrée une liste
d’assertions PLML ainsi qu’une liste d’instructions (ou une fonction) PL, et renvoyant une liste
annotée d’instructions PL, c’est-à-dire une liste d’instructions dans laquelle les assertions ont été
intégré après avoir été transcrites en PL. Ces assertions prennent alors la forme de conditions levant
un arrêt brutal en cas d’échec de leur test.

L’étape suivante serait de réécrire ce vérificateur d’assertions en COQ, puis prouver que pour un
programme qui termine, le programme annoté terminerait également et produirait le même état
final que le programme original.

Dans l’industrie, ce type de vérificateur introduit un gain en terme de qualité du programme, et donc
de confiance que l’on peut y accorder, même si cela se fait avec une perte potentielle de
performance, due à l’introduction dans le code de vérifications supplémentaires.

13