Vous êtes sur la page 1sur 46

26/01/2024

Institut Supérieur d'Informatique et de Mathématiques de


Monastir (ISIMM)

Frama-c et Preuve de programmes

M. ABBASSI Imed
Enseignant chercheur à ISIMM
LR-OASIS, Université de Tunis El Manar, Tunis
imed.abbassi@enit.utm.tn

A propos du cours

Objectifs:
• Apprendre les concepts de base de la preuve de
programmes (Logique Hoare, assertions, etc.).
• Apprendre le langage ACSL (Ansi c specification language).

Prérequis: langage C
Références :
• Hoare, C. A. R. (1983). An axiomatic basis for computer programming.
Communications of the ACM, 26(1), 53-56.
• BURGHARDT, Jochen, GERLACH, Jens, HARTIG, Kerstin, et al. ACSL by
Example. DEVICE-SOFT project publication. Fraunhofer FIRST Institute,
2010.

1
26/01/2024

Plan du cours

I. Introduction à la preuve de programmes


II. Logique de Hoare
III. Langage ACSL

Introduction à la preuve de
programmes

2
26/01/2024

Motivation (1/3)

• La programmation sans erreur est un processus difficile


pour plusieurs raisons (la taille de logiciels, etc.).
• Quelques bugs célèbres de logiciels:
– Accidents d'irradiation de Therac-25 (1985): Des fautes de
conception du logiciel provoquant la mort de plusieurs patients.
– Programme ver de Morris (1988):En exploitant une faute dans
le dispositif de protection du système Unix, Robert Morris
provoqua ainsi le blocage d’environ 6 000 ordinateurs.
– Panne du réseau téléphonique AT&T (1990): Une faute dans
le programme de recouvrement d’erreur dans les commutateurs
téléphoniques a provoqué une interruption du trafic pendant 9
heures sur le réseau AT&T.
– Bug du Pentium (1994): En raison d’une faute de conception de
l'algorithme de division flottante du processeur Pentium, le
résultat de cette division pouvait être incorrect.
5

Motivation (2/3)

Définition (Programme correct): Un programme est


déclaré correct s’il effectue, sans se tromper, la tâche
attendue, et ce dans tous les cas possibles.
• Considérons la procédure suivante calculant la valeur
absolue d’un entier passé en paramètre.
int abs(const int x){
if (x < 0) return -x;
else /* x >= 0 */
return x;
}
• Questions:
– Ce programme est-il correct ?
– Si ce n’est pas le cas, comment le corriger?
6

3
26/01/2024

Motivation (3/3)

Réponse: Bien qu'il soit simple, le programme n'est pas


correct et contient une erreur.
• Si x = -2 , 2 n'existe pas (seulement 2 − 1);
• Donc, le plus petit entier qu’on peut calculer sa valeur
absolue est -2 + 1.
• Pour corriger l’erreur, il suffit de rajouter la condition
suivante au début du programme:
assert(x > INT_MIN);

– INT_MIN désigne l’entier -2 et définit dans la
bibliothèque « limits.h ».

Analyse des programmes (1/3)

• Le test de programmes est une tâche primordiale en


développement logiciel.
• Ainsi, toutes les entreprises logicielles font du test.
Test dynamique:
• Test qui repose sur l’exécution de logiciel d’un
composant ou d’un système.
• Le test dynamique peut être utilisé pour montrer la
présence de bugs d’un programme mais jamais pour
montrer leur absence.
• Le test exhaustif de programme est impossible (on ne
peut tester qu’un nombre fini de cas.)
Ceci ne permet donc pas en général de garantir que le
programme testé soit correcte.
8

4
26/01/2024

Analyse des programmes (2/3)

Test statique:
• C’est le test d’un artefact de logiciel, par exemple, des
exigences, de conception ou de code, sans l’exécution
de ces artefacts.
• Les tests statiques regroupent, par exemple, les revues
(revue de code, de spécification) et les analyses de code.
• Les revues de code peuvent être exécutées très tôt dans
le processus de développement, car ils n’exigent pas un
logiciel en état de fonctionnement.
• Tout bug détecté par les revues est facilement et
rapidement corrigeable avec moindre coût.

Analyse des programmes (3/3)

• Avec les analyses de code, on peut faire plusieurs tests:


– Tests fonctionnels (le code conforme-t-il aux
spécifications?).
– Tests de sécurité (il y a par exemple l’outil FortiFy).
– Tests de performance avec l’analyse d’algorithmes.
• L’analyse statique outillée se base sur des outils afin de
repérer des erreurs ou de suivis de bonne pratique.
• Elles ne portent pas uniquement sur des codes de
l’application, mais aussi peuvent aller à vérifier la
cohérence des spécifications.
• Dans ce cours, on se focalise sur le test statique outillé
de codes C en utilisant le langage ACSL (ANSI/ISO C
Specification Language)
10

10

5
26/01/2024

Présentation d’ACSL (1/2)

• ACSL est un langage formel de spécification des


propriétés des programmes C. Ces spécifications sont
définies sous forme de commentaires et commencent par
le symbole @ (//@... ou bien /*@ ... */).
• Le langage ACSL permet de créer des contrats de
fonction contenant des pré(et post)-conditions.
• Il est possible de définir des invariants de type et globaux
ainsi que des spécifications logiques, comme les
prédicats, des lemmes, des axiomes ou des fonctions
logiques.
• De plus, ACSL autorise les annotations d'instructions
telles que les assertions ou les annotations en boucle.
11

11

Présentation d’ACSL (2/2)

• L'outil de vérification actuel pour ACSL est Frama-C


(FRAmework for Modular Analysis of C code).
• Frama-C est développé par CEA – LIST et INRIA-
Saclay.
• Elle fournit des plug-ins pouvant collaborer, soit en
communiquant directement entre eux, soit en utilisant le
langage ACSL.
• Le plug-in Frama-C/Wp utilise le calcul de plus faibles
préconditions (WP:Weakest Precondition) de Hoare
pour prouver les propriétés ACSL du code C.
• Les détails d'installation sont disponibles sur le site Web
de Frama-C: https://frama-c.com/html/get-frama-c.html
12

12

6
26/01/2024

Logique de Hoare

13

13

Prédicats (1)

Définition 1: Un prédicat logique non quantifié F


se définie en Event-B selon l’une des formes
suivantes:
1. F= x, avec x ∈ {⊤, ⊥, p, q, r, …}
2. F=¬(G)
3. F=(G α H)

– α ∈ {∧, ∨, ⇒, ⇔}, et
– G et H sont des prédicats non quantifiés.
Exemple: le prédicat « x≠ 0 ∧ 𝑦 ÷ 𝑥 = 3 » est non
quantifié.
14

14

7
26/01/2024

Prédicats (2)

Définition 2: Une quantification universelle F est


un prédicat Event-B qui se définit selon la syntaxe
suivante:

• P est un prédicat (simple ou quantifié) définit en


fonction des paramètres 𝑥 , … , 𝑥 .

• La quantification F est vraie si est seulement si le


prédicat P est vrai pour toutes les valeurs de
𝑥 ,…,𝑥 .

15

15

Prédicats (3)

Définition 3: Une quantification existentielle F est


un prédicat Event-B qui se définit selon la syntaxe
suivante:
∃ 𝑥 ,…,𝑥 .𝑷 𝑥 ,…,𝑥
• P est un prédicat (simple ou quantifié) définit en
fonction des paramètres 𝑥 , … , 𝑥 .
• La quantification F est vraie si est seulement si le
prédicat P est vrai pour au moins une valeur de
𝑥 ,…,𝑥 .
• La négation d’une quantification universelle est
une quantification existentielle.
16

16

8
26/01/2024

Prédicats (4)

Règles d’équivalence:
• On dit que deux prédicats P et Q sont équivalents si et
seulement si le prédicat P⇔Q est toujours vrai (appelé
tautologie ou axiome).
• ⊥ est l’élément absorbant de la conjonction:
⊥∧x≡⊥
• ⊤ est l’élément absorbant de disjonction:
⊤∨x≡⊤
• Idempotence de la disjonction:
x∨x≡x
• Idempotence de la conjonction:
x∧x≡x
17

17

Prédicats (5)

• La disjonction est :
1. Associative: x∨(y∨z)≡(x∨y)∨z
2. Commutative: x∨y ≡ y∨x
3. Distributive sur la conjonction: x∨(y∧z)≡(x∨y)∧(x∨z)
4. ⊥ est l'élément neutre de la disjonction: ⊥∨x≡x
• La conjonction est :
1. Associative: x∧(y∧z)≡(x∧y)∧z
2. Commutative: x∧y≢y∧x
3. Distributive sur la disjonction: x∧(y∨z)≡(x∧y)∨(x∧z)
4. ⊤ est l'élément neutre de la conjonction: ⊤∧x≡x
• Règle de l’implication: x⇒y ≡ ¬x∨y
• Règle de l’équivalence : x⇔y ≡ (x∧y)∨(¬x∧¬y)
18

18

9
26/01/2024

Prédicats (6)

• Les lois de la négation :


1. x∧¬x≡ ⊥
2. x∨¬x≡ ⊤
3. ¬¬x≡x
• Lois de de Morgan:
1. ¬(x∧y)≡¬x∨¬y
2. ¬(x∨y)≡¬x∧¬y
• Lois de simplification
1. x∨(x∧y)≡x
2. x∧(x∨y)≡x
3. x∨(¬x∧y)≡x∨y et x∧(¬x∨y)≡x∧y
19

19

Prédicats (7)

Exemples: les prédicats qui se trouvent sur une


même ligne sont équivalents:

1.
2.
3.
4.
5.
6.

20

20

10
26/01/2024

Prédicats (8)

Conséquence logique:
• Un prédicat G est une conséquence logique d’un
ensemble de prédicats H={H1,…Hn}, et on note
H1,H2,…,Hn ⊨ G, si et seulement si le prédicat
H1∧H2…∧Hn ⇒ G est toujours vrai.
• Quelques exemples:

21

21

Prédicats (9)

Système complet de connecteurs:


• On appelle système complet de connecteurs tout
ensemble de connecteurs permettant d’engendrer
tous les connecteurs.
• L’ensemble {∧, ∨, ¬} constitue un système complet
de connecteurs.
Définition 7: un système complet de connecteurs
est dit minimal lorsque aucun de ses sous-
ensembles stricts n’est un système complet.
• L’ensemble {∧, ∨, ¬} n’est pas minimal de
connecteurs logiques.
22

22

11
26/01/2024

Prédicats (10)

Théorème: L’ensemble {∨,¬} est un système


complet minimal de connecteurs logiques.
• Toute formule peut être transformée en une
formule équivalente utilisant uniquement les
connecteurs ∨ et ¬.
• Considérons un prédicat logique F.
1. Si F= A⇒B alors F≡ ¬A∨B
2. Si F= A⇔B alors F≡ ¬(¬A∨¬B) ∨¬(A∨B)
Remarque: L’ensemble {∧,¬} est aussi un système
complet minimal de connecteurs logiques.

23

23

Triplet de Hoare (1/2)

Origine: une méthode proposée par Tony Hoare en 1969


pour la preuve des programmes.
• Cette méthode permet de raisonner sur des programmes
et d’exprimer des propriétés d'un état et son évolution au
cours de l'exécution des instructions.
Définition (Triplets de Hoare): Le comportement d’un
programme prog est défini par le triplet de Hoare suivant:
{Précondition} prog {Postcondition},
où:
– Précondition: un prédicat logique vérifiant certaines propriétés
avant l’exécution du programme.
– Postcondition: un prédicat logique vérifiant certaines propriétés
après l’exécution du programme.

24

24

12
26/01/2024

Triplet de Hoare (2/2)

Exemple 1: Gestion de pile d’entiers


• Opération empiler:
– Précondition: pile créé
– Postcondition: nombre d’élément de pile est incrémenté.
• Opération dépiler:
– Précondition: pile non vide
– Postcondition: nombre d’élément de pile est décrémenté.
Exemple 2: un fragment de code C calculant le minimum
dans un tableau T de taille n.
– Précondition: un tableau non vide (n>=1)
– Postcondition:
∃𝑖, 𝑖 ∈ 0. . 𝑛 − 1 ∧ 𝑚𝑖𝑛 = 𝑇 𝑖 ∧
∀𝑗, 𝑗 ∈ 0. . 𝑛 − 1 ⇒ 𝑚𝑖𝑛 ≤ 𝑇 𝑗

25

25

Sémantique pré et post conditions (1/6)

• La logique de Hoare est basée sur la sémantique des pré et


postconditions.
• Dans une telle logique, chaque construction d’un LP (langage
de programmation) est définie formellement soit par un
axiome (prédicat logique) ou par une règle d'inférence.
𝑷 𝑺 𝑸 , 𝑷⇒𝑷 , 𝑸 ⇒𝑸
Règle de conséquence:
𝑷 𝑺 𝑸

– P,P’, Q et Q’ désignent des prédicats logiques de premier ordre.
– S dénote une séquence d’instruction ou un fragment de programme.

P’ Exécution de S Q
Q’
P

• Cette règle permet de renforcer une postcondition ou


d’affaiblir une précondition afin de prouver un programme.
26

26

13
26/01/2024

Sémantique pré et post conditions (2/6)

Règle d’instruction Nulle (skip):


• Il s’agit d’une instruction qui ne fait rien. Sa syntaxe dépend du
LP. Par exemple, elle correspond au caractère NULL en C.
• La sémantique de cette instruction est définie par l’axiome
suivant:
𝐴 = 𝑃 𝑁𝑢𝑙𝑙𝑒 {𝑃}
Règle d’affectation:
• L’affectation est une instruction impérative la plus basique
dans les LP.
• Sa sémantique est fournie sous forme de l’axiome suivant:
𝐴 = 𝑄[𝑣 ← 𝑒] 𝑣 ≔ 𝑒 𝑄
• La notation 𝑄 𝑣 ← 𝑒 désigne le prédicat obtenu par la
substitution de toutes les occurrences de v par e.
• Exemple: (𝑥 +y>0)[x ←y+1]≡ (𝑦 + 1) +y>0
27

27

Sémantique pré et post conditions (3/6)

Règle de conjonction:
𝑷 𝑺 𝑹,𝑷 𝑺 𝑸
𝑷 𝑺 𝑹∧𝑸

Règle de composition séquentielle:


𝑷 𝑺𝟏 𝑹 , 𝑹 𝑺𝟐 𝑸
𝑷 𝑺𝟏;𝑺𝟐 𝑸

Règle d’instruction conditionnelle:


• La structure de contrôle conditionnelle dépend d’un langage
LP à un autre.
• La sémantique de cette structure est définie par une règle
d’inférence:
𝑷∧𝑬 𝑺𝟏 𝑸 , 𝑷∧¬𝑬 𝑺𝟐 𝑸
𝑷 𝐢𝐟 𝐄 𝐭𝐡𝐞𝐧 𝐒𝟏 𝐞𝐥𝐬𝐞 𝐒𝟐 𝑸

28

28

14
26/01/2024

Sémantique pré et post conditions (4/6)

Règle de boucle:
• Une boucle permet de décrire un traitement itératif. Ce
traitement est souvent conditionné.
• Le comportement d’une boucle while est exprimé par le triplet
Hoare suivant:
𝑃 𝑤ℎ𝑖𝑙𝑒 𝐸 𝑑𝑜 𝑇 𝑑𝑜𝑛𝑒 {𝑄}
• La boucle a un objectif bien défini à attendre. Cet objectif est
basé sur une propriété appelée invariant et notée I.
Définition (Invariant): Un invariant de boucle est un prédicat
logique, reliant les variables du programme, et vérifié avant
l'entrée dans la boucle et à chaque passage dans celle-ci.

29

29

Sémantique pré et post conditions (5/6)

• Considérons le fragment de code suivant calculant le


minimum dans un tableau.
min=a[0]; Invariant de boucle:
i=0; 𝐼 ≡ ∀𝑗, 𝑗 ∈ 0. . (𝑖 − 1) ⇒ 𝑚𝑖𝑛 ≤ 𝑎 𝑗
// 𝐈 ∧ (i<n)
while (i<n){ Précondition de boucle:
if (a[i]<min) min=a[i]; 𝑃 ≜ 𝐼 ≡ ∀𝑗, 𝑗 ∈ 0. . (𝑖 − 1) ⇒ 𝑚𝑖𝑛 ≤ 𝑎 𝑗
i++;
Postcondition de boucle:
}
𝑄 ≜ 𝐼 ∧ ¬𝐸 ≡ 𝐼 ∧(i=n)
// 𝐈 ∧ i ≥ 𝑛
• On a besoin d’une règle d’inférence qui exprime la sémantique
pré et post conditions de boucle:
𝐈∧𝐄 𝐒 𝐈
𝑰𝑩𝒐𝒖𝒄𝒍𝒆 =
𝐈 𝐰𝐡𝐢𝐥𝐞 𝐄 𝐝𝐨 𝐒 𝐝𝐨𝐧𝐞 𝐈∧ ¬

30

30

15
26/01/2024

Sémantique pré et post conditions (6/6)

• Pour prouver la terminaison de boucle, on fait appel à un


concept appelé variant et noté v.
Définition (Variant): Un variant est une expression de type
entier positif qui décroît à chaque itération de la boucle.
• Un variant v dois obéir aux deux conditions suivantes:
- (C1): la valeur de v doit être supérieur ou égale à 0 avant
l’exécution de boucle.
- (C2): l’exécution du corps de boucle doit décrémenter v d’aux
moins une unité sans le rendre négatif.
• La règle d’inférence de terminaison de boucle est la suivante:
𝐈∧𝑬⇒𝒗 𝟎, 𝐈∧𝐄 𝐳≔𝐯;𝐒 𝐈∧𝐳 𝐯
𝑰𝑻𝑩𝒐𝒖𝒄𝒍𝒆 =
𝐈 𝐰𝐡𝐢𝐥𝐞 𝐄 𝐝𝐨 𝐒 𝐝𝐨𝐧𝐞 𝐈∧ ¬

Où z représente une variable intermédiaire contenant la valeur


de variant avant l’exécution de boucle.
31

31

Plus faible précondition (1/6)

Origine: un concept de sémantique de transformation de


prédicats proposée par Dijkstra en 1975.
• Ce concept est introduit afin de répondre à la question
suivante:
Quelle est la pré condition P la plus faible pour que le
triplet {P}C{Q} soit valide ?
• Considérons par exemple le triplet de Hoare suivant:
{P} x := a+1 {x>0}
– Quelle est la précondition minimale WP (weakest-
precondition) pour que la postcondition {x >0} soit
respectée?
 La question qui se pose ici est de comment calculer P à
partir des éléments de Hoare C et Q.

32

32

16
26/01/2024

Plus faible précondition (2/6)

• Le calcul de prédicats WP se fait selon les instructions de


langages de programmation:
– L’affectation
– La séquence d’instructions
– L'instruction conditionnelle
– Les boucles
Définition (affectation): soit {P}x:=E{Q} un triplet de Hoare
d’affectation. Le calcul du prédicat minimal P se fait comme
suit:
𝑊𝑃(𝑥 ≔ 𝐸, 𝑄) = 𝑄(𝐸)
Illustration: WP(x:=a+1,x>0)
≡ a+1>0
≡ a>-1
33

33

Plus faible précondition (3/6)

Définition (Séquence d’instructions): soit {P}S1;S2{Q} un


triplet de Hoare de la séquence de deux instructions S1 et
S2. Le calcul du prédicat minimal P se fait comme suit:
𝑊𝑃 S1;S2, 𝑄 = 𝑊𝑃 𝑆1, 𝑊𝑃 𝑆2, 𝑄

Illustration: WP(a:=a+1;x:=a+1,x>0)
≡ WP(a:=a+1,WP(x:=a+1,x>0))
≡ WP(a:=a+1,a>-1)
≡ a+1>-1
≡ a > -2

34

34

17
26/01/2024

Plus faible précondition (4/6)

Définition (Instruction conditionnelle): soit {P} if C then


T1 else T2{Q} un triplet de Hoare de l’instruction
conditionnelle. Le calcul du prédicat minimal P se fait
comme suit:
WP(if C then T1 else T2,Q) = C ∧ WP(T1,Q) ∨ ¬C ∧ WP(T2,Q)

Illustration: WP(if x<12 then x:=x+1 else x:=1, x<=12 ∧ x>=1)


≡ (x<12∧WP(x:=x+1, x<=12 ∧ x>=1)) ∨
(x>=12∧ WP(x:=1, x<=12 ∧ x>=1))
≡ (x<12∧x>=0) ∨ x>=12.
Remarque: la structure conditionnelle «switch... case» peut être
reformulée au moyen d’instruction «if then else».

35

35

Plus faible précondition (5/6)

Définition (Boucle while): soit {P}while E do z:=V;T done


{ ¬E ∧ I } un triplet de Hoare de la boucle while disposant
d’un invariant I et d’un variant V. Le calcul du prédicat
minimal P se fait comme suit:
WP(while E do z:=V;T done,𝑸)= I
avec :
– (E ∧ I ∧ z=V) ⇒ WP(T,I ∧ z>V)
– I⇒V≥0
– ¬E ∧ I ⇒ Q

36

36

18
26/01/2024

Plus faible précondition (6/6)

Illustration: considérons le code de recherche de minimum


dans un tableau T.
Invariant:
#include <stdio.h> ∀𝑗, 𝑗 ∈ 0. . (𝑖 − 1) ⇒ 𝑚𝑖𝑛 ≤ 𝑎 𝑗
#define n 100
int main(){ Variant: (n-i)
int a[n];
int min=a[0]; Postcondition du boucle:
int i=0; ∀𝑗, 𝑗 ∈ 0. . (𝑛 − 1) ⇒ 𝑚𝑖𝑛 ≤ 𝑎 𝑗
while (i<n){
if (a[i]<min) min=a[i]; Précondition minimal calculée:
i++; ∀𝑗, 𝑗 ∈ 0. . (𝑖 − 1) ⇒ 𝑚𝑖𝑛 ≤ 𝑎 𝑗
}
}

37

37

Exercices d’application (1)

Exercice 1: Trouvez la précondition P pour chacun de


triplets de Hoare suivants:
- {P} a:=a+b{a=b}
- {P} if (m<12) then m:=m+1 else m:=1{m>0 ∧ m<=12}
- {P} x:= x+y {x>=0}
Exercice 2:
Considérons le programme suivant permettant l’échange de
variables x et y :
z:=x;
x:=y ;
y:=z ;

1- Écrivez une spécification de ce programme.


2- Prouvez la correction de ce programme.
38

38

19
26/01/2024

Exercices d’application (2)

Exercice 3:
1) Donnez la règle d’inférence permettant d’établir une
instruction conditionnelle simple (sans branchement sinon).
2) Donnez la règle de calcul de plus faible précondition de
l’instruction conditionnelle simple.
3) Trouvez la précondition P tel que le triplet suivant est
valide:
{P}
if solde > 20 then
solde := solde – montant
{solde >=20}

39

39

Exercices d’application (3)

Exercice 4:
Considérons le fragment de code suivant permettant de
calculer la somme des éléments d'un tableau d'entiers:
#define n 100
int a[n];
int somme (int x){
int s,i;
s=a[0];
for (i=1; i<n; i++){ s+=a[i];}
return s;
}

1) Trouvez l’invariant de boucle du programme.


2) Que peut-on dire de la terminaison du programme?

40

40

20
26/01/2024

Exercices d’application (4)

Exercice 5:
Considérons le programme ci-dessous :
#define n 100
int a[n];
int find (int info){
int i, r;
i=0; r=-1;
while (1){
if (a[i] == info){ r=i; break; }
if (i>=n) break;
else i++;
}
return r;
}

Trouvez le variant et l’invariant adéquats de la boucle de ce


programme.
41

41

Exercices d’application (5)

Exercice 6:
Considérons le programme ci-dessous :
#define n 100
int a[n];
void swap(int i, int j){
int tmp=a[i]; a[i]=a[j]; a[j]=tmp;
}
int main(){
int i=0,j=0,p=0;
for (i=0; i<n; i++){
p=getMinimum(i);
if (i!=p) swap(i,p);
}
}

Ce programme est-il correct ? Justifiez votre réponse.


42

42

21
26/01/2024

Correction d’Exercices (1)

Correction d’Exercice 1: Pour calculer la précondition P de


chacun des trois triplets, il suffit d’appliquer la règle
correspondante de WP.
1) WP(a:=a+b, a=b)
≡ a+b=b ≡ a=0
Donc on peut déduire que P={a=0}
2) WP(if (m<12) then m:=m+1 else m:=1, m>0 ∧ m<=12)
≡ (m<12 ∧ WP(m:=m+1, m>0∧m<=12))
∨ (m>=12 ∧ WP(m:=1, m>0∧m<=12) )
≡ (m<12 ∧ m>-1) ∨ (m>=12 ∧ true)
≡ (m<12 ∧ m>-1) ∨ (m>=12)
Donc la précondition P = {(m<12 ∧ m>-1) ∨ (m>=12)}
3) WP(x:= x+y, x>0)
≡ (x+y)>0 ≡ x>-y
donc P = {x>-y}
43

43

Correction d’Exercices (2)

Correction d’Exercice 2:
1) Le fragment de programme effectue la permutation les valeurs
de deux variables.
- Une spécification d’un tel fragment se fait sous forme
de pré/post condition.
- On va utiliser deux variables auxiliaires x0 et y0 mémorisant
le valeurs des variables x et y avant l’exécution.
a) Précondition: {x=x0 ∧ y=y0}
b) Post condition: { x=y0 ∧ y=x0}

2) Pour prouver la correction du programme, il faut démonter que


le triplet de Hoare suivant est valide:
{x=x0 ∧ y=y0}
z:=x; x:=y; y:=z ;
{ x=y0 ∧ y=x0}
44

44

22
26/01/2024

Correction d’Exercices (3)

Pour parvenir à démontrer la validité de ce triplet, on va appliquer


la règle de composition séquentielle de calcul de prédicats WP.
Cette règle s’applique comme suit:
WP(z:=x; x:=y; y:=z, x=y0∧y=x0)
≡ WP(z:=x, WP(x:=y, WP(y:=z, x=y0 ∧ y=x0)))

a) WP (y:=z, x=y0 ∧ y=x0) ≡ x=y0 ∧ z=x0


b) WP(x:=y, x=y0 ∧ z=x0) ≡ y=y0 ∧ z=x0
c) WP(z:=x, y=y0 ∧ z=x0) ≡ y=y0 ∧ x=x0

Le prédicat “y=y0 ∧ x=x0” est bel et bien la précondition définie


dans la spécification du programme.
 Donc, le programme est correct conformément à sa
spécification.

45

45

Correction d’Exercices (4)

Correction d’Exercice 3:
1) La sémantique de l’instruction conditionnelle simple est définie
par la règle d’inférence suivante :
∧ ,¬ ∧ ⟹
I =

2) La plus faible précondition de l’instruction conditionnelle


simple est calculée de la manière suivante:
WP(if E then S,Q) = E ∧ WP(S,Q) ∨ ¬E ∧ WP(skip,Q)
≡ WP(if E then S,Q) = E ∧ WP(S,Q) ∨ (¬E ∧ Q)
3) WP(if(solde > 20) then solde := solde – montant, solde >= 20)
≡ (solde >= 20 ∧WP(solde := solde – montant, solde >= 20)) ∨
(solde = 20)
≡ (solde = 20) ∨ (solde>=montant+20)
 Donc P={(solde = 20) ∨ (solde>=montant+20)}.

46

46

23
26/01/2024

Langage ACSL

47

47

Introduction (1/3)

• ACSL est un langage permettant la spécification des


propriétés de programmes C. Cette spécification est
basée sur la logique de Hoare.
• Une spécification ASCL est placée à l'intérieur des
commentaires C spéciaux;
• La définition de base d’un triplet de Hoare {P}C{Q} est la
suivante:
//@ assert P;
C;
//@ assert Q;

• La syntaxe des expressions logiques ressemble


étroitement aux opérateurs logiques et relationnels de C.

48

48

24
26/01/2024

Introduction (2/3)

• La syntaxe des expressions logiques (prédicats) est


étroitement semblable aux opérateurs logiques et
relationnels du langage C.
Syntaxe ASCL Nom Description
!P Négation P n’est pas vrai

P && Q Conjonction P est vrai et Q est vrai


P || Q Disjonction P est vrai ou Q est vrai
P ==> Q Implication Si P est vrai alors Q l’est aussi
P <==> Q Equivalence P est équivalent à Q

x < y == z Chaîne de relations La valeur x est inferieur à y et y est égale à z


\forall int x; P(x) Quantification universelle Le prédicat P(x) est vrai pour tout entier x.
\exists int x; P(x) Quantification existentielle Il existe un entier x tel que P(x) est vrai.

• Types de données: int, integer, real, boolean


49

49

Introduction (3/3)

• Nous montrons ici trois exemples de fragments de code


C et leurs annotations ACSL.
//@ assert x % 2 == 1; Si x a une valeur impaire avant l'exécution du code ++ x
x=x+1; alors x a une valeur paire par la suite.
//@ assert x % 2 == 0;

//@ assert 0 <= x <= y; Si la valeur de x appartient à l'intervalle [0, y] avant


x:=x+1; l'exécution du même code, alors la valeur de x doit être
//@ assert 0 <= x <= y + 1; dans la l'intervalle [0, y+1] après l'exécution.

//@ assert true;


while (--x != 0) Dans tous les cas, la valeur de x est zéro après
sum += a[x]; l'exécution du code de boucle.
//@ assert x == 0;

50

50

25
26/01/2024

Instructions de base (1/5)

• Considérons le triplet de Hoare suivant définissant le


comportement de l’affectation du langage C:
{P[x←e]} x=e {P}
• La spécification ACSL de ce triplet est la suivante:
//@ assert P[x←e];
x=e;
//@ assert P;
• Par exemple, si le prédicat P est x > 0 && a[2*x] == 0,
alors P[x←y+1] est le prédicat suivant:
y+1 > 0 && a[2*(y+1)] == 0
• On obtient le fragment de code suivant:
//@ assert y+1 > 0 && a[2*(y+1)] == 0;
x=y+1;
//@ assert x > 0 && a[2*x] == 0;
51

51

Instructions de base (2/5)

• Considérons le programme C suivant effectuant la


permutation de la valeur de deux variables:
void swap(int x, int y){
int tmp;
tmp=x;
x=y;
y=tmp;
}
int main(){
int a=10;
int b=15;
swap(a,b);
//@ assert a==15 && b==10;
}

Question: ce programme est-il correct ?

52

52

26
26/01/2024

Instructions de base (3/5)

• Pour qu’une séquence d’instructions soit valide, il faut


que sa précondition permet, par cette séquence, de
passer à la postcondition voulue.
• La règle de séquence combine deux fragments de code
Q et S en un seul fragment « Q;S ».
//@ assert P; //@ assert R; //@ assert P;
Q; et S;  Q ; S;
//@ assert R; //@ assert T; //@ assert T;

• Cette règle stipule que la postcondition pour Q doit être


identique à la précondition de S.
• Grâce à cette règle, nous pouvons «annoter» un
programme avec des formules entrecoupées, comme
cela peut se faire dans Frama-C.
53

53

Instructions de base (4/5)

• Pour prouver la validité d'une instruction conditionnelle, il


faut que prouver que la postcondition soit respectée par
les deux branches.
• La spécification ACSL suivante permet de vérifier la
validité des instructions conditionnelles:
//@ assert P;
if (C) X;
else Y;
//@ assert S;
• Ce code annoté est sémantiquement équivalent au code
suivant: //@ assert P && C;
X;
//@ assert P && !C;
Y;
//@ assert S;
54

54

27
26/01/2024

Instructions de base (5/5)

Illustration: L’exemple suivant décrit une spécification


ACSL de l’instruction conditionnelle imbriquée.
//@ assert 0 <= i < n;
if (i < n-1) {
//@ assert 0 <= i < n - 1;
//@ assert 1 <= i+1 < n;
i = i+1;
//@ assert 1 <= i < n;
//@ assert 0 <= i < n;
} else {
//@ assert 0 <= i == n-1 < n;
//@ assert 0 == 0 && 0 < n;
i = 0;
//@ assert i == 0 && 0 < n;
//@ assert 0 <= i < n;
}
//@ assert 0 <= i < n;
55

55

Boucles (1/7)

• Les boucles ont besoin d’un traitement de faveur dans la


vérification déductive de programmes.
• La spécification ACSL suivante décrit la règle utilisée
pour vérifier une boucle while.
//@ assert P;
while (B) {
S;
}
//@ assert !B && P;

• Cela nécessite de trouver une formule appropriée, P, qui


est préservée à chaque exécution du corps de la boucle.
• P est également appelé un invariant de boucle.

56

56

28
26/01/2024

Boucles (2/7)

• Pour y parvenir, il faut une certaine intuition dans de


nombreux cas. Pour cette raison, les prouveurs
automatiques de théorèmes ont généralement des
problèmes avec cette tâche.
• La règle de boucle qu’on vient de l’introduire ne garantit
pas que la boucle finira toujours par se terminer. Elle
assure tout simplement que, si la boucle est terminée, alors
la postcondition est valide.
• La preuve d'un programme se fait selon deux volets:
• Correction partielle: preuve des propriétés vérifiables
automatiquement en logique de Hoare.
• Correction totale: preuve des propriétés qui incluent la
terminaison du programme.
57

57

Boucles (3/7)

• En absence de boucle, la fonction WP est capable de


fournir automatiquement les propriétés vérifiables de nos
programmes.
• La procédure de recherche des invariants des boucles
de nos programmes est toujours la partie la plus difficile
de notre travail lorsque nous chercherons à prouver des
programmes.
• Il n'existe pas une procédure automatique permettant de
calculer les invariants de boucles d’un programme.
• On doit donc trouver et formuler correctement ces
invariants, et selon l’algorithme, celui-ci peut parfois être
très subtil.
58

58

29
26/01/2024

Boucles (4/7)

• Pour indiquer un invariant à une boucle, il suffit de


rajouter les annotations suivantes en début de boucle:
int main(){
int i = 0;
/*@
loop invariant 0 <= i <= 30;
*/
while(i < 30){
++i;
}
//@ assert i == 30;
}

• On remarque que la postcondition, i==30, est bel et bien


respectée après la terminaison de la boucle while.

59

59

Boucles (5/7)

Clause assigns pour les boucles:


• Cette clause permet de spécifier que certaines variables
seront modifiées par la boucle.
• Toute autre variable est considérée comme invariante.
Illustration:
int main(){
int i = 0, h = 42;
/*@ loop invariant 0 <= i <= 30;
loop assigns i; // indique que la variable i sera modifiée par la boucle.
*/
while(i < 30){
++i;
}
//@assert i == 30;
//@assert h == 42;
}
60

60

30
26/01/2024

Boucles (6/7)

Variants de boucles:
• Comme nous l’avons indiqué auparavant, le variant un
moyen pour prouver la correction totale d’un programme
et particulièrement la terminaison d’une boucle.
• Il ne s'agit pas d'une propriété, mais plutôt une
expression faisant intervenir des éléments modifiés par
la boucle et donnant une borne supérieure sur le nombre
d’itérations restant à effectuer à un tour de la boucle.
• C’est donc une expression supérieure à 0 et strictement
décroissante d’un tour de boucle à l’autre.

61

61

Boucles (7/7)

• Si nous reprenons notre programme précédent, nous


pouvons ajouter le variant de cette façon:
int main(){
int i = 0;
int h = 42;
/*@
loop invariant 0 <= i <= 30;
loop assigns i;
loop variant 30 - i;
*/
while(i < 30){
++i;
}
//@assert i == 30;
//@assert h == 42;
}

62

62

31
26/01/2024

Contrats de fonctions (1/7)

• Le principe d’un contrat de fonction est de poser les


conditions selon lesquelles la fonction s’exécutera.
• Un contrat est composé de deux parties:
– Précondition à propos des variables passées en
paramètres et de l’état de la mémoire globale;
– Postcondition à propos de l’état de la mémoire et de la
valeur de retour.
• Syntaxiquement, un contrat de fonction est définit en
ACSL de la manière suivante:
/*@
// ici, on définit la description ACSL du contrat
*/
<type_de_retour> nom_fonction(…){
...
}
63

63

Contrats de fonctions (2/7)

Postcondition:
• La postcondition d’une fonction est précisée avec la clause
ensures.
• Elle peut concerner la valeur de retour d’une fonction qui se
définit avec le mot-clé \result.
Illustration:
• La fonction suivante détermine la valeur absolue d’un entier
reçu en entrée.
• La postcondition est que la valeur retournée doit être
supérieur ou égal à 0.
//@ ensures \result >= 0;
int abs(int val){
if(val < 0) return -val;
return val;
}
64

64

32
26/01/2024

Contrats de fonctions (3/7)

• On peut spécifier plusieurs postconditions qui peuvent être


définies de deux façons:
– On combine les postconditions avec des opérateurs
logiques (&&, ||, etc.);
– On introduit chaque postcondition dans une nouvelle ligne
avec une clause ensures.
Illustration:
/*@
ensures \result >= 0;
ensures (val >= 0 ==> \result == val)
&& (val < 0 ==> \result == -val);
*/
int abs(int val){
if(val < 0) return -val;
return val;
}

65

65

Contrats de fonctions (4/7)

Précondition:
• Les préconditions sont des propriétés sur les entrées (et
potentiellement sur des variables globales) qui seront
supposées préalablement vraies lors de l’analyse de la
fonction.
• La preuve que celles-ci sont effectivement validées
n’interviendra qu’aux points où la fonction est appelée.
• Une précondition de fonction est spécifiée avec la clause
requires.
• On peut spécifier plusieurs préconditions soit en les combinant
avec les opérateurs logiques, ou en les introduire dans une
nouvelle ligne avec une clause requires.

66

66

33
26/01/2024

Contrats de fonctions (5/7)

Illustration: on reprend la fonction calculant la valeur absolue


d’entier passé en paramètre;
#include <limits.h>
/*@
requires INT_MIN < val;
ensures \result >= 0;
ensures (val >= 0 ==> \result == val);
ensures (val < 0 ==> \result == -val);
*/
int abs(int val){
if(val < 0) return -val;
return val;
}

• La précondition spécifiée décrit la validité de l’entier en entrée:


l’entier en entrée doit être strictement supérieur à un seuil
minimal défini par INT_MIN (-2 − 1).
67

67

Contrats de fonctions (6/7)

Comportements:
• Il se peut que, dans certains cas, une fonction peut avoir plusieurs
comportements.
• Ces comportements dépendent des paramètres de la fonction.
• En ACSL, les comportements d’une fonction peuvent définis selon la
syntaxe suivante:
/*@ …..
behavior B1:
assumes H1; // L’hypothèse du comportement B1;
ensures Q1; // La postcondition du comportement B1;
….
behavior BK:
assumes HK ; // L’hypothèse du comportement Bk;
ensures QK ; // La postcondition du comportement Bk;
[complete behaviors;]
[disjoint behaviors;]
*/
68

68

34
26/01/2024

Contrats de fonctions (7/7)

• Les comportements peuvent être disjoints et/ou complets:


– Disjoints: si pour toute entrée de la fonction, elle ne correspond aux
hypothèses (assumes) que d’un seul comportement.
– Complets: si les hypothèses recouvrent bien tout le domaine des
entrées de la fonction.
Illustration: la fonction abs dispose de deux comportements complets et
disjoints.
/*@ requires val > INT_MIN;
behavior pos:
assumes 0 <= val;
ensures \result == val;
behavior neg:
assumes val < 0;
ensures \result == -val;
complete behaviors;
disjoint behaviors;
*/
69

69

Appels des fonctions (1/3)

• Lorsqu'une fonction f est appelé, alors son contrat est


utilisé afin de déterminer la précondition P de l'appel.
• La postcondition d’une fonction f qui serait appelée dans
un programme n’est pas nécessairement la précondition
calculée pour le code qui suit l’appel de f.
Illustration:
• Considérons l’exemple suivant: x = f();c
- WP(c,Q)={0 ≤ x ≤ 10}
- Postcondition de f: Q’={1 ≤ x ≤ 9}
• Ici, nous avons besoin d'affaiblir la précondition réelle de
c vers celle que l’on a calculé: Q’ ⟹ WP(c,Q)
• Il est claire que 1 ≤ x ≤ 9 ==> 0 ≤ x ≤ 10

70

70

35
26/01/2024

Appels des fonctions (2/3)

• Une fonction peut avoir des effets de bord (peut modifier


les valeurs des variables non locales).
• Les valeurs référencées en entrée de fonction peuvent
être modifiées après l’appel à la fonction.
• Donc, le contrat devrait exprimer certaines propriétés à
propos des valeurs avant et après l’appel.
• Pour cela, les primitives ACSL suivantes peuvent être
utilisées:
– \valid(x): un prédicat qui exprime que la variable x est valide par
rapport à la norme du C.
– \old(x): retourne l’ancien valeur de x avant l’appel de fonction.
• La clause assigns permet de déterminer les variables
qui seront modifiées par la fonction.
71

71

Appels des fonctions (3/3)

Illustration:
/*@ requires valid: \valid(p);
requires valid: \valid(q);
assigns *p,*q;
ensures exchange: *p == \old(*q) && *q == \old(*p);
*/
void swap(int* p, int* q){
int save = *p;
*p = *q;
*q = save;
}
void main(){
int a=10, b=5;
swap(&a,&b);
//@ assert a==5 && b==10;
}

72

72

36
26/01/2024

Fonctions récursives (1/4)

• L’outil WP du Frama-C ne permet pas malheureusement


de vérifier pas la terminaison de toutes les fonctions.
• Si une fonction est seulement composée de boucles qui
terminent (dont le variant indiqué est vérifié) et d’appels
de fonctions qui elles-mêmes terminent, elle termine.
• En revanche, parfois ce raisonnement est insuffisant,
comme dans le cas des fonctions récursives et
mutuellement récursives.
• C’est la terminaison de ces fonctions qui n’est pas
encore vérifiée par WP.

73

73

Fonctions récursives (2/4)

• Considérons l’exemple suivant:


/*@ assigns \nothing ;
ensures \false ;
*/
void next(){ next();}
int main(){
next();
//@ assert \false ;
return 0;
}

• Nous pouvons voir que la fonction et l’assertion sont


prouvées.
• Nous considérons une correction partielle (puisque nous
ne pouvons pas prouver la terminaison), et cette fonction
ne se termine pas.
74

74

37
26/01/2024

Fonctions récursives (3/4)

• Toute assertion suivant l’appel de cette fonction est vraie:


elle est inatteignable.
• Une question que l’on peut alors se poser est:
Que peut-on faire dans ce cas?
• Nous pouvons à nouveau utiliser une notion de variant
pour borner le nombre d’appels récursifs.
• En ACSL, c’est le rôle de la clause decreases:
/*@
requires n >= 0 ;
decreases n ;
*/
void ends(int n){ if(n > 0) ends(n-1);}

75

75

Fonctions récursives (4/4)

• La clause decreases exprime essentiellement la même


idée que le loop variant.
• L’expression spécifiée par cette clause est une valeur
positive qui décroît strictement à chaque nouvel appel
récursif de la fonction.
• Cependant, cette clause n’est pas encore supportée par
l’outil WP de Frama-C, donc nous ne pouvons pas
encore faire la preuve totale d’une fonction récursive
avec le greffon WP.

76

76

38
26/01/2024

Terminaison (1/2)

• Dans certains cas, il y a des fonctions qui sont


supposées ne se terminent pas.
• En effet, la fonction main d’un programme réactif peut
contenir une boucle de la forme while (1){…} qui attend
indéfiniment le traitement d'un événement.
• Une fonction se termine uniquement si certaines
conditions préalables sont remplies.
• Dans ce cas, la clause suivante doit être ajoutée au
contrat de fonction:
/* @
….
terminates p; // p est la condition de terminaison.
*/
77

77

Terminaison (2/2)

• La sémantique d'une telle clause est la suivante: si p est


vrai, alors la fonction est garantie de se terminer (plus
précisément, sa terminaison doit être prouvée).
• Si cette clause n’existe pas dans le contrat, sa valeur par
défaut est « terminates \true; »
• En particulier, « termine \false; » n'implique pas que la
fonction va boucler indéfiniment.
• Une spécification possible pour une fonction qui ne se
termine jamais est la suivante:
/*@ ensures \false ;
terminates \false ;
*/
void f() { while(1); }
78

78

39
26/01/2024

Prédicats (1/7)

• La logique de prédicats constitue l’un des fondements de


la logique de Hoare. Elle permet d’exprimer diverses
propriétés importantes d’un programme C à prouver.
• Le langage ACSL offre un support des prédicats. Ainsi,
plusieurs prédicats sont prédéfinis par exemple \valid,
\valid_read, \separated.
• En ACSL, un nouveau prédicat peut être défini de façon
directe ou inductive (récursive).
Définition directe de prédicat:
/*@
predicate nom_prédicat [{Lbl0, Lbl1,...}] (type0 arg0, type1 arg1, ...) =
p;
*/
79

79

Prédicats (2/7)

• Le prédicat p est une expression logique mettant en


relation les paramètres et les éventuels labels (Here,
Post, Pre, etc.).
• Exemple 1: Définition directe d’un prédicat vérifiant la
validité d’un tableau t de taille n.
/*@
predicate valid_array(int* t, integer n) = (n > 0 && \valid(t + (0 .. n-1)));
requires valid_array(a, n);
assigns *pmax;
ensures \forall integer j; 0<=j<n ==> *pmax >= a[j];
*/
void getMax(int a[], int n, int *pmax){
int i=0; *pmax=a[0];
while (i<n) {if (a[i] > *pmax) *pmax=a[i]; i++;}
}

80

80

40
26/01/2024

Prédicats (3/7)

• Exemple 2: Un prédicat vérifiant le non-changement de


la valeur référencée par une variable.
/*@
predicate unchanged{L0, L1}(int* i) = \at(*i, L0) == \at(*i, L1);
*/
int main(){
int i = 13;
int j = 37;
Begin:
i = 23;
//@assert ! unchanged{Begin, Here}(&i) && unchanged{Begin, Here}(&j);
}

• Ce prédicat dispose de deux labels L0 et L1. Ces


derniers seront remplacés dans l’appel de prédicat par
des labels du programme (Begin et Here).

81

81

Prédicats (4/7)

Prédicats inductifs:
• Dans certains cas, il est difficile de définir de manière
directe un prédicat nécessitant un raisonnement par
induction.
• Pour cela, la définition inductive doit être utilisée selon la
syntaxe suivante:
/*@ inductive P(type0 arg0,type1 arg1,...) {
case c1 : p1;
...
case ck : pk;
}
*/
Où p1,…,pk désigne des formules logiques.
82

82

41
26/01/2024

Prédicats (5/7)

• Exemple 1: Définition inductive d’un prédicat exprimant


la propriété « si n est pair, alors n + 2 l’est aussi ».
/*@ inductive pair(integer n) {
case nul: pair(0); // C’est le cas trivial.
case not_nul: \forall integer x ;
x>0 ==>
pair(x-2) ==>
pair(x);
}
*/
void main(){
int a = 42 ;
int b= 41 ;
//@ assert pair(a) && !pair(b);
}

83

83

Prédicats (6/7)

• La définition inductive d’un prédicat P peut induire des


inconsistances;
• Par exemple, on peut avoir des cas où P peut être à la
fois vrai et faux.
• Donc, il faut s’assurer que tout prédicat inductif est bien
définit.
• Dans chaque cas général ci, il faut que la formule pi est
définie de la manière suivante:
\forall y1,...,ym, H1 ==>
H2 ==>
···
Hk ==>
P(t1,...,tn)
84

84

42
26/01/2024

Prédicats (7/7)

• On peut démonter facilement que cette nouvelle formule


est logiquement équivalente à la suivante:
\forall y1,...,ym, (!H1 || !H2 || …. || !Hk || P(t1…tn))
• H1, …, Hk désignent les conditions de positivité de P.
Exercice: Que peut-on dire de la spécification suivante?
/*@ inductive impair(integer n) {
case c_un: impair(1);
case c_not_1: \forall integer x ; x>1 ==>
impair(x-2) ==>
!impair(x-1) ==>
impair(x);
}
*/

85

85

Fonctions logiques (1/3)

• Les fonctions logiques sont utilisées pour décrire des


fonctions mathématiques.
• Elles permettent de renvoyer différents types, et de
recevoir divers labels et valeurs en paramètre.
/*@ logic type_retour nom_function{Lbl0, …}(type0 arg0,...) =
formule mettant en jeu les arguments ;
*/
• Exemple 1: Définition d'une fonction logique renvoyant la
valeur moyenne des éléments d'un tableau passé en
paramètre.
/*@ logic real avg_array(int*t, int n) =
(n==0) ? 0:
(\sum(0,n-1,(\lambda integer k; t[k])))/n;
*/
86

86

43
26/01/2024

Fonctions logiques (2/3)

• Les fonctions logiques ne seront utilisables que dans les


spécifications et jamais dans le code du programme.
• Exemple 2: L’utilisation de la fonction logique avg dans
le contrat de la fonction avgArray.
/*@ logic real avg(int*t, int n) = (\sum(0,n-1,(\lambda integer k; t[k])))/n;
requires n>0;
requires \valid(t+(0..n-1));
ensures \result == avg(t,n);
*/
float avgArray(int *t, int n){
float m=t[0]/n;
int i=1;
while (i<n){m+=t[i]/n; i++;}
return m;
}

87

87

Fonctions logiques (3/3)

• Comme le montre l’exemple suivant, les fonctions


logiques peuvent être définies récursivement:
/*@ logic integer f(integer n) = (n <= 0) ? 1 : n *f(n-1);
requires n<=12;
assigns \nothing ;
ensures \result == f(n) ;
*/
int factorielle(int n){
int res,i=2;
if (n<2) res=1;
/*@ loop invariant (2<=i<=n+1)&& res == f(i-1) ;
loop variant n+1-i;
*/
while(i<=n){res*=i; i++; }
return res;
}

88

88

44
26/01/2024

Lemmes (1/2)

• Les lemmes sont des propriétés générales à propos des


prédicats ou encore des fonctions logiques.
• Ils sont définis selon la syntaxe suivante:
/*@ lemma nom_de_lemme { Label0, ..., LabelN }:
property ;
*/

• Une des utilisations possibles des lemmes est de faciliter


la preuve de certaines propriétés de programmes.
• La preuve des lemmes pourrait être réalisée
indépendamment du reste de la preuve du programme.
• Les lemmes ne sont pas paramétrables et ils peuvent
avoir des labels.
89

89

Lemmes (2/2)

• Exemple : le trie d’un tableaux de taille n.


/*@
predicate sorted{L}(int* a, integer begin, integer end) =
\forall integer i, j ; begin <= i <= j < end
==>
\at(a[i],L) <= \at(a[j],L) ;
predicate unchanged{L1, L2}(int *a, integer begin, integer end) =
\forall integer i ; begin <= i < end
==>
\at(a[i], L1) == \at(a[i], L2) ;
lemma unchanged_sorted{L1, L2}:
\forall int* a, integer b, integer e ;
sorted{L1}(a, b, e) ==>
unchanged{L1, L2}(a, b, e) ==>
sorted{L2}(a, b, e) ;
*/

90

90

45
26/01/2024

Axiomes (1/2)

• Les axiomes sont des propriétés qui sont toujours vraies


quelle que soit la situation.
• Ils sont définis selon la syntaxe suivante et pouvant être
utilisable dans l’étape de preuve de programmes.
/*@ axiomatic nom_de_définition_axiomatique {
// ici nous pouvons définir ou déclarer des fonctions et prédicats;
axiom axiom_name { Label0, ..., LabelN }: p1;
axiom other_axiom_name { Label0, ..., LabelM }: p2;
...
}
*/

• C’est un moyen pratique pour définir des notions


complexes afin de rendre la procédure de preuve très
efficace.
91

91

Axiomes (2/2)

• Exemple: la spécification suivante définie de manière


axiomatique la fonction factorielle.
/*@
axiomatic AFactorielle{
logic integer factorielle (integer n);

axiom fact_0:
\forall integer i; i <= 0 ==> 1 == factorielle(i) ;

axiom fact_n:
\forall integer i; i > 0 ==>
i * factorielle(i-1) == factorielle(i) ;
}
*/

92

92

46

Vous aimerez peut-être aussi