Analyse Statique de programmes - TP Frama-C
ENSIIE 3ème année - année 2013/2014
Enseignants : Virgile Prevosto et Julien Signoles
10 janvier 2014
Prise en main de Frama-C
Frama-C est un outil d’analyse de programmes C. Nous nous intéresserons dans cette séance à son greffon d’analyse de valeur par interprétation abstraite, lancé avec l’option =-val=. La version installée à l’ENSIIE est Oxygen
Premier exemple
On s’intéresse au fichier exercice1.c
int x,y, *z, *idx, a[101], t;
float f;
int main (int c) {
  if(0<= c && c<=9) z = &x; else z = &y;
  if(c==5) x = 1; else x = 2;
  switch (c) {
    case 0: y = 3; break;
    case 1: y = 42; break;
    case 2: y = 36; break;
    case 3: y = 25; break;
    case 4: y = 10; break;
    case 5: y = 100; break;
    case 6: y = 18; break;
    case 7: y = 75; break;
    case 8: y = 83; break;
    case 9: y = 98; break;
    default: y = 2; break;
  }
  idx = &a[y];
  if (c) f = 1.5; else f = 0x1p-2;
  return y + *z;
} - Lancer Frama-C de la manière suivante 
frama-c -val exercice1.cet observer les résultats fournis par l’analyse pour les différentes variables du programme. - Faire de même en utilisant l’interface graphique avec la commande 
frama-c-gui -val exemple1.c - Insérer l’instruction 
Frama_C_show_each_res(c,y,z);après le switch, et relancer l’analyse de valeur. Qu’observe-t-on? Plus généralement,Frama_C_show_each_xxx(args)demande à l’analyse de valeur d’afficher les valeurs abstraites deargschaque fois qu’elle arrive à cette instruction. - l’option 
-slevel <n>permet de demander à l’analyse de valeur de propager jusqu’ànétats distincts avant de commencer à faire des unions. Essayer différentes valeur den. Obtient-on une amélioration de la précision des résultats sur la valeur de retour? S’agit-il du résultat le plus précis que l’on puisse obtenir? Sinon, d’où provient l’imprécision? - Il est possible (quand le 
slevelest suffisant) de forcer l’analyse de valeur à séparer un état en utilisant une annotation logique ACSL sous forme de disjonction. Par exemple/*@ assert c<=0 || c > 0; */permettra d’analyser séparément les cas oùcest négatif et ceux oùcest positif. En utilisant une telle assertion, améliorer la précision du résultat obtenu. - Une autre façon d’améliorer la précision est d’augmenter le nombre d’éléments que peut contenir un ensemble avant d’être transformé en intervalle: c’est l’option 
-val-ilevelEssayer différentes valeurs pour l’option, en conjonction ou non avec-slevel. Quelle est la précision des résultats obtenus? 
Alarmes
On considère le programme suivant (fichier exercice2.c)
  int main(int c) {
    int x, y, z[5];
    if(c) { x = c; } else { y = 4; }
    if(c) { y = x; } else { x = y; }
    z[0] = x;
    z[1] = y;
    return x/y;
  } - En plus des options indiquées dans l’exercice précédent, on utilise maintenant l’option <tt>-val-signed-overflow-alarms</tt> qui demande à l’analyse de valeur de traiter les débordement arithmétiques lors
 
d’opérations entre entiers signés comme des problèmes. Que dit l’analyse de valeur sur ce code?
- Les 
warningsont des *alarmes*: l’analyse signale qu’il y a potentiellement un problème. Pour chacun d’entre eux, vérifier s’il s’agit d’une fausse alarme ou d’un vrai problème. Le cas échéant, corriger le code. - À l’aide de 
-slevelet d’éventuelles assertions, faire disparaître les fausses alarmes. 
Dans toute la suite, si une alarme est levée, on cherchera à la faire disparaître, soit en corrigeant le code si c’est un vrai bug, soit en améliorant la précision de l’analyse.
Définition d’un contexte initial
Dans les exercices précédents, c est considéré par l’analyse de valeur comme pouvant être n’importe quel entier (entre INT_MIN et INT_MAX). Il est fréquent qu’on veuille mener une analyse dans un contexte plus restreint. On considère le programme suivant (fichier exercice3.c):
int f(int x) {
  int S = 0;
  for (int i=0; i<=x; i++) {
    if (S>=i) S-=i; else S+=i;
  }
  return S;
}
int main() {
  int x = 10;
  int res = f(x);
  return res;
} - Que vaut 
resà la fin de la fonctionmain? - On veut maintenant savoir ce que peut valoir 
resquandxest compris entre 10 et 100. Pour cela, on va utiliser les fonctions déclarées dans le fichierbuiltin.hsitué dans le répertoire de données de Frama-C, qu’on peut obtenir par la commandeframa-c -print-share-pathDes implantations sont disponibles dans le fichierbuiltin.c, situé au même endroit. Modifier la fonctionmainpour qu’elle appellefavec un argument compris entre 10 et- On utilisera l’option 
-cpp-extra-args="-I$(frama-c -print-share-path)"pour indiquer au pré-processeur qu’il doit considérer le répertoire de frama-c lorsqu’il cherche des fichiers à inclure. 
 - On utilisera l’option 
 - Une autre possibilité consiste à lancer l’analyse directement depuis 
f, avec l’option-main f, en ajoutant un contrat ACSL à cette fonction qui va contraindre son argument. Par exemple/*@ requires x == 10; */signale quefne peut être appelée qu’avec un argument valant 10. Utiliser cette nouvelle méthode pour retrouver le résultat de la question précédente. 
Portes
Cet exercice est tiré du problème des 1000 portes. On considère le programme suivant (fichier porte.c):
#include "builtin.h"
int portes[100];
int main() {
  int N = Frama_C_interval(2,100);
  for(int i = 1; i<= N; i++) {
    for(int j=i; j<=N; j+=i) portes[j-1] = 1 - portes[j-1];
  }
} - Vérifier qu’à la fin de la fonction, les seules cases pouvant contenir 1 ont un indice de la forme n²-1.
 - Comment modifier ce programme pour s’assurer que les cases d’indice n²-1 pour 
n <= Ncontiennent bien 1? 
Racine carrée
On s’intéresse au programme suivant, donné dans le fichier sqrt.c:
#define MAX_IDX 256
extern double exp(double);
double args[MAX_IDX];
double res[MAX_IDX];
void init () {
for(int i = -MAX_IDX+2; i<=MAX_IDX; i+=2) {
  args[(MAX_IDX+i)/2-1] = exp(i);
  res[(MAX_IDX+i)/2-1] = exp(i/2);
 }
}
int main () {
  init();
  double x = 3.
  int i;
  double d;
  for (i = 0; i<MAX_IDX && args[i] < x; i++);
  if (i==MAX_IDX) d = res[MAX_IDX];
  else if (x-args[i] < args[i+1] - x) d = res[i];
  else d = res[i+1];
  for(int j = 0; j < 4; j++) {
    d = (d + x/d) / 2.;
  }
  return 0;
} La fonction exp n’est pas définie, mais l’analyse de valeur dispose d’un built-in nommé Frama_C_exp, activable avec l’option -val-builtin exp:Frama_C_exp
- Quelle est la valeur finale trouvée pour =d=?
 - On cherche maintenant à voir l’intervalle calculé pour 
dquandxest un flottant compris entre0x1p-256et0x1p256. Quel résultat donne l’analyse de valeur? - On ajoute maintenant au programme un prototype 
sqrtdouble sqrt(double);Là encore, l’analyse de valeur dispose d’un built-in, nomméFrama_C_sqrtqui calcule le résultat attendu. Modifiermainpour assigner dans une variable locale l’erreur relative entredetsqrt(x). Quelle est l’intervalle calculé par l’analyse de valeur pour cette variable? - Améliorer la précision de l’analyse de valeur sur le calcul de l’erreur relative en utilisant une assertion ACSL qui découpe l’intervalle de valeurs de 
xen sous-intervalles 
Deviner un nombre
On considère la fonction suivante (fichier devin.c):
#include "builtin.h"
int devine(int secret, int max) {
  int tentative = max / 2, inf = 0, sup = max, nb = 1;
  while(tentative != secret) {
    nb++;
    if (tentative < secret) inf = tentative+1;
    if (tentative > secret) sup = tentative-1;
    tentative = (inf+sup) / 2;
  }
  return nb;
} devinene peut terminer que sisecretest dans un intervalle particulier. Écrire le contrat correspondant.- Écrire une fonction 
mainqui appelledevineavecsecretcompris entre 0 et 100 - Quelles sont alors les valeurs que peut retourner 
devine? - Mêmes questions (en utilisant l’option 
-val-signed-overflow-alarms), sisecretest compris entreINT_MAX-5000etINT_MAX. On rappelle queINT_MAXest défini dans le fichierlimits.h. 
Somme d’entiers
On s’intéresse dans cet exercice au premier problème du Projet Euler: trouver la somme des entiers inférieurs à 1000 qui sont multiples de 3 ou multiples de 5. Une implantation est proposée dans le fichier euler1.c:
int total = 0;
void add(int max, int x) { total += x; }
void sum(int max) {
  int i=1;
  while ( i <= max / 3) {
    add(max,3*i);
    if (i%3 !=0) add(max, 5*i);
    i++;
  }
}
int main() {
  sum(1000);
  return total;
} - Utiliser Frama-C pour trouver la valeur de 
totalà la fin de la fonctionmain - On veut maintenant utiliser l’analyse de valeur pour vérifier certaines propriétés du programme. Donner un contrat pour la fonction 
addqui vérifie que l’argumentxfait bien partie des entiers qu’il faut additionner. Utiliser l’analyse de valeur pour voir sisumappelleaddconformément à son contrat. Le cas échéant, corriger le code. On pourra utiliser des disjonction et-slevelpour séparer différents états. - Reprendre la question précédente dans le cas où l’argument donné à 
sumest un entier quelconque entre 1 et 1000 
