• Aucun résultat trouvé

Sur-approximation et sous-approximation de fragmentations

Si les domaines de fragmentation sont libres d’approximer sans contraintes les fragmenta-tions symboliques concrètes, la précision de l’analyse dépend avant tout d’un choix d’approxi-mation cohérent. Nous parlerons ici de deux types d’approxid’approxi-mation caractéristiques : la sous-approximation et la sur-sous-approximation.

Si on accepte le parti pris au début de ce chapitre, et que l’on considère que toute instruction induit sur les cellules qu’elle manipule une propriété similaire à chaque exécution alors la sous-approximation a un intérêt direct. En effet, si on réussit à sous-approximer les cellules touchées par cette instruction, alors on obtiendra un fragment dont toutes les cellules ont cette propriété induite. Si on ne réussit pas à sous-approximer, on aura des cellules parasites dans le fragment qui affaibliront la propriété que l’on souhaitait synthétiser.

Ceci est particulièrement important dans les cas où on n’a pas le loisir d’affaiblir la propriété cherchée. [GMT08] considère des propriétés qui sont choisies parmi un ensemble de prédicats et qui ne peuvent donc pas être affaiblies. La même publication propose donc des méthodes pour calculer des sous-approximations des fragmentations.

Il est légitime néanmoins de se demander s’il est réellement possible de tirer profit des sous-approximations. Pour reprendre l’exemple de l’instruction, pouvons nous réellement être inté-ressés par un sous-ensemble strict des cellules lues ou écrites par une affectation ? Existe t-il un exemple dans lequel nous serions satisfaits par des propriétés ne concernant qu’une partie tronquée des structures de données ?

Il est tout à fait possible en imaginant un programmeur faisant preuve de trop de zèle d’in-venter un exemple où certaines cellules pourtant manipulées par le programme ne servent pas à la construction du résultat. Cependant, nous n’avons pas, dans notre petite base de tests, de programmes où une abstraction exacte d’un fragment est impossible tandis qu’une sous ap-proximation de ce fragment suffirait à la preuve du programme. En outre, la sous-approximation est un problème généralement difficile sur les abstractions courantes des fragmentations. Les al-gorithmes pour les calculer peuvent être imprécis et la non-unicité des solutions est dérangeante. Il y a une autre approche au problème de l’approximation. Si on n’a pas d’abstraction pour représenter exactement l’ensemble des cellules touchées par une instruction, on peut toujours lui rajouter des cellules jusqu’à obtenir un ensemble que l’on peut abstraire. Si les cellules ajoutées n’ont pas les mêmes propriétés que celles du fragment original, nous allons évidement perdre en précision. Mais par chance ces cellules peuvent tout de même avoir une partie de leurs propriétés

i ← 1

Tant quei≤nfaire

A[i] ← 0

A[i+1] ← 0

i ← i+2

i ← 1

Tant quei≤A[i]faire

SiA[i]<0alors

A[i] ← −A[i]

sinon

A[i] ← A[i]+1

i ← i+1

Figure4.10 :Ces deux programmes sont des cas heureux où la sur-approximation du critère de fragmentation donnera des résultats satisfaisants.

en commun, et on sera capable de conserver ces propriétés communes sur la sur-approximation du fragment. Cette idée est à l’origine des diagrammes de tranches, domaine de fragmentation auquel le chapitre 5 est consacré.

L’avantage de la sur-approximation, c’est qu’à tout moment on décrit absolument toutes les cellules manipulées par le programme et on ne risque donc pas de tronquer les structures de données. L’inconvénient c’est qu’il est possible que les propriétés qu’on obtient puissent être trop faibles pour la preuve du programme.

Nous pouvons donner deux usages heureux de la sur-approximation pour l’analyse des pro-grammes de la figure 4.10 Le premier est un simple déroulement de boucle sur un algorithme d’initialisation de tableau. Le second est un programme qui effectue sur chaque cellule d’un ta-bleau un traitement distinct en fonction du signe de la valeur de cette cellule. Imaginons que nous utilisions notre critère de fragmentation sémantique et que les seuls fragments que nous puis-sions décrire soient des tranches, c’est à dire des ensembles de cellules consécutives délimités par un indice minimum et un indice maximum.

Pour le premier programme, notre critère de fragmentation demandera que l’on considère deux fragments : ceux modifiés par la première affectation et ceux modifiés par la seconde. Si on disposait d’un domaine de fragmentation capable de distinguer les cellules selon leur parité, on pourrait parler des cellules paires d’indice compris entre 1 etiet celles impaires dans le même intervalle. Mais si on se restreint aux abstractions des fragments en tranches, on ne sera pas capable de décrire exactement ces deux ensembles.

Il y a une sur-approximation évidente de ces deux fragments : leur union, toutes les cellules d’indice compris entre 1 et i. Cette sur-approximation nous permet de dire que le tableau est effectivement constant et égal à 0.

Modifions le programme pour qu’au lieu d’initialiser à 0 la seconde affectation initialise à 1. Le programme construirait alors un tableau dans lequel les cellules d’indice impair valent 1 et celles d’indice pair valent 0. En sur-approximant les deux fragments par leur union, on pourrait dire que toutes les cellules du tableau ont une valeur comprise entre 0 et 1. C’est bien sûr moins précis que si on avait pu discriminer dans la fragmentation les indices pairs et les indices impairs. Mais cette information quoique affaiblie peut suffire pour prouver des propriétés du programme, comme l’absence de débordements arithmétiques, qui ne serait pas directement liée à l’alternance des valeurs.

le résultat ne serait pas très probant. Une sous approximation d’un des deux fragments ne contiendrait au plus qu’une cellule. Par exemple, pour le premier fragment, on pourrait prendre uniquement de la première cellule lorsque iest strictement supérieur à 1, et considérer ainsi la tranche

{A[`] | 1≤` <min{i,2} }

Or même si on peut établir à la fin du programme que la première cellule a pour valeur 0, cela risque de ne pas être suffisant.

Dans le second exemple, la réussite du test à chaque itération de la boucle dépend des valeurs de Aque nous considérons comme une entrée du programme. Il n’est pas possible de décrire les fragments des cellules validant le test et des cellules ne le validant pas à l’aide de tranches. La seule manière de distinguer ces deux fragments est de parler de « l’ensemble des cellules de valeur négative » et « l’ensemble des cellules de valeur positive ou nulle ». Ceci va au delà de l’expressivité que nous autorise la fragmentation en tranche. Mais cette fois encore, on peut sur-approximer ces deux fragments par l’union des deux. L’union des cellules validant le test et de celles ne le validant pas est l’ensemble des cellules d’indice compris entre 1 eti. Par chance il y a bien une propriété qui convienne à cette sur-approximation : toutes ces cellules, après avoir été mises à jour, deviennent strictement positives. Pour finir, on peut remarquer que comme pour le premier exemple, la sous-approximation n’apporterait rien d’intéressant.

Cette courte discussion sur le choix d’une approximation ne permet pas de conclure sur la su-périorité d’une méthode d’approximation. Elle permet tout au plus de comprendre les choix qui nous ont amenés à développer des abstractions des fragmentations basées sur leur sur-approximation comme celles développées dans le chapitre suivant.

La forme de fragment de tableau la plus courante est latranche. Une tranche est une suite de cellules consécutives d’un même tableau. Elle est omniprésente dans les exemples que nous avons vus jusqu’ici. La forme la plus simple d’un parcours de tableau est de le traverser du premier élément au dernier. Que ce soit dans ce sens ou dans le sens inverse, à tout instant de l’exécution, l’ensemble des éléments énumérés par un tel parcours constitue une tranche.

Il est donc important d’avoir un domaine de fragmentation qui soit en mesure de décrire des tranches. Bien sûr, certains fragments de tableaux ne sont pas de cette forme. Mais on pourra toujours combiner un domaine spécialisé dans la description des tranches avec un domaine dé-crivant d’autres formes, afin d’obtenir des fragments moins classiques.

Les diagrammes de tranches sont une représentation abstraite d’un ensemble de tranches permettant la résolution d’un certain nombre de problèmes algorithmiques que cette abstrac-tion pose. Les diagrammes permettent uniquement la définiabstrac-tion de fragments. Ils ne permettent pas la description d’éventuelles relations entre ces fragments qui pourraient s’exprimer par des contraintes relationnelles sur les indices des cellules.

5.1 Tranches et notations

Une tranche est une suite de cellules consécutives d’un même tableau. On peut la décrire de manière équivalente comme l’ensemble des cellules d’un tableau dont l’indice est compris dans un intervalle fixé. Elle est donc la donnée de trois paramètres : le tableau, (ou le segment de mémoire, suivant le modèle concret de mémoire adopté) ainsi que les bornes inférieures et supérieures d’indice. Ainsi, on peut parler de la tranche des cellules du tableau A d’indice compris entreaetbque nous noteronsA[a..b] :

A[a..b]={A[`]| a≤`≤b}

On peut aussi parler directement de l’intervalle d’indices : [a..b]={` |a≤`≤b}

Ces intervalles entiers d’indice peuvent être ouverts à droite et/ou à gauche : [a..b]=[a..b+1[=]a−1..b]=]a−1..b+1[

Quelle que soit la forme de l’intervalle d’indice, on pourra l’appliquer derrière un symbole de tableau pour construire la tranche correspondante :

A[a..b]=A[a..b+1[= A]a−1..b]= A]a−1..b+1[

Lorsque cet intervalle est réduit à une seule valeur i, on utilisera la notation [i] permettant la correspondance avec la notation classique des accès aux tableaux.

Sauf exception, nous utiliserons généralement la convention « fermé à gauche, ouvert à droite » pour dénoter les intervalles d’indice et les tranches de tableau lorsqu’il ne s’agit pas de single-tons.