• Aucun résultat trouvé

Fragmentation par prédicat

9.2 Critères de fragmentation et abstractions de fragmentations

9.2.1 Fragmentation par prédicat

L’outil d’analyse statique générique TVLA [LARSW00] applique l’interprétation abstraite à la recherche de propriétés des structures de données. Ces structures de données sont décrites comme des ensembles de nœuds ayant des champs pouvant pointer d’autres nœuds. Ces nœuds sont regroupés en un nombre fini de classes. On décrit les propriétés de chaque classe de nœuds grâce à un ensemble de prédicats que l’on interprète sur une logique tri-valuée. [SRW99] Comme son nom l’indique, cette logique permet d’attribuer aux prédicats trois valeurs :

• ou bien on sait que tous les nœuds d’une classe vérifient le prédicat et on évalue le prédicat à la valeur 1,

• ou bien on sait qu’aucun des nœuds de la classe ne le vérifie et on évalue le prédicat à la valeur 0,

• dans, les autres cas, s’il y à la fois des nœuds vérifiant et ne vérifiant pas le prédicat, on évalue le prédicat à la valeur1/2.

Ce dernier cas représente aussi bien une incertitude due à l’approximation réalisée durant l’ana-lyse qu’une possible combinaison de nœuds vérifiant et ne vérifiant pas le prédicat.

On peut avoir des prédicatsn-aires qui permettent d’observer des propriétés liant plusieurs classes de cellules. De la même manière, on interprète les prédicats sur les classes en considérant tous les n-uplets de nœuds issus des classes respectives. Les trois valeurs de la logique sont organisées en demi-treillis avec>=1/2:

1v1/2, 0v1/2

0t1=1/2,xt1/2=1/2

La généricité de l’analyseur TVLA vient de ce que l’on peut lui donner n’importe quel en-semble de prédicats. Leur choix dépendra du type de structures de donnée analysées et des propriétés que l’on cherche à démontrer. On n’utilisera pas les mêmes prédicats selon que l’on considère des listes simplement chaînées, doublement chaînées, ou encore des arbres. Il sera sou-vent nécessaire d’introduire des prédicats spécifiques au programme que l’on analyse, comme par exemple le prédicat « la liste est triée. » L’ensemble des prédicats peut être généré automa-tiquement, mais doit rester fini. On ne peut pas ajouter la classe de prédicats « un nœud est à distancend’un autre dans une liste » pour toutn∈N.

Les auteurs proposent par exemple d’introduire systématiquement certains prédicats : • pour chaque pointeurxdu programme, on introduit un prédicat unairex(v) qui s’évalue à 1

si la variablexpointe surv,

• pour chaque pointeur x et chaque champn, on introduit un prédicat unaire r[x,n](v) qui s’évalue à 1 lorsquevest accessible à partir dexen utilisant uniquement le champn, et • pour chaque champn, on introduit un prédicat binairen(u,v) qui s’évalue à 1 si le champn

deupointe surv.

Ici, ce qui nous intéresse particulièrement à propos de cette théorie, c’est la manière dont les classes de nœuds sont constituées. Les auteurs de TVLA proposent de choisir un sous-ensemble de prédicats unaires et de partitionner l’ensemble des nœuds par ces prédicats. Ainsi, on regroupe les nœuds pour lesquels les prédicats sont évalués à la même valeur. Puisque il y a trois valeurs par prédicats, s’il y anprédicats, on aura au maximum 3nclasses.

Supposons qu’on veuille partitionner les nœuds d’une liste simplement chaînée avec les deux types de prédicats énoncés plus haut. Un prédicat x(v) ne peut valoir 1 que pour le seul nœud pointé, et permettra donc de distinguer ce nœud dans une classe singleton. Les prédicatsr[x,n] quant à eux permettent de séparer les cellules en fonction de leur position dans la liste relative-ment aux différents pointeurs.

Pour illustrer ce dernier exemple, supposons que nous voulions analyser un programme itérant sur une liste dont le début est pointé par une variable pet l’élément courant pointé par une va-riableq. On introduit les prédicatsp(v),q(v),r[p,n](v),r[q,n](v) etn(u,v) oùndésigne le champ pointant sur le nœud suivant dans la liste. La valuation des ces prédicats peut être représentée par un graphe, que nous tracerons avec les conventions de [LARSW00] :

p r[p,n] r[p,n] q r[p,n] q[p,n] r[p,n] q[p,n] n n n

Dans ce graphe, les sommets à bords simples représentent les classes contenant un unique nœud. Les sommets à bords doubles représentent au contraire les classes pouvant contenir plus d’un nœud. On marque à l’intérieur des sommets les prédicats vérifiés par la classe correspon-dante. Lorsque deux classes vérifient un prédicat binaire, on relie les deux sommets par un arc étiqueté avec le nom du prédicat. Lorsque ce prédicat binaire s’évalue à 1/2, on trace l’arc en pointillés.

L’analyse ne permet pas à une classe de nœuds d’être vide. Par conséquent, comme nous l’avons déjà mentionné en section 9.1.6, chaque fois qu’une classe peut être vide on devra conduire deux analyses en parallèle : l’une pour le cas où la classe est vide, l’autre pour le cas où elle contient au moins un nœud.

Une partie des auteurs de [GDD+04] ont combiné l’outil TVLA avec leur théorie permettant de synthétiser des propriétés de tableau [GRS05]. Dans leurs travaux, ils remplacent les prédi-cats automatiquement introduits sur les structures de données par des prédiprédi-cats similaires mais adaptés au partitionnement des tableaux. Pour chaque tableau et pour chaque variable d’indice utilisée dans un accès à ce tableau ils introduisent une fonction qui à chaque cellule associe une valeur selon que l’indice de la cellule est inférieure, égale, ou supérieure à l’indice. Les cellules

sont partitionnées selon cette fonction, ce qui revient à introduire les prédicats correspondant à chacune des valuations de cette fonction. On trouve le partitionnement familier des tableaux, si-milaire à celui des listes, dans lequel on sépare la cellule manipulée, les cellules déjà manipulées, et les cellules qui ne le sont pas encore.

Le processus de synthèse de [GDD+04] que nous avons décrit plus tôt n’accepte pas de frag-ments vides, une caractéristique qu’il partage avec TVLA. On décomposera encore l’analyse suivant la vacuité des différents fragments. Si cette décomposition a un coût potentiellement im-portant, elle peut avoir des effets intéressants dans l’analyse de certains programmes, notamment en présence d’alias en permettant de distinguer les différent cas d’alias et rendre ainsi l’analyse précise.

Pour illustrer la méthode de partitionnement, prenons l’exemple suivant. Supposons que nous ayons un tableau dont les indices vont de 1 ànet que nous ayons deux variablesiet jutilisées comme index et vérifiant 1 < i < n et 1 < j < n. Pour chacune de ces deux variables on introduit les prédicats qui discriminent les cellules du tableau en trois sous ensembles suivant que leurs indices sont inférieurs, égaux ou supérieurs à la variable. Nous obtenons donc 3×3=9 fragments distincts. En fonction de leur vacuité on obtient les cinq configurations suivantes :

<i, < j i >i, < j j >i, > j <i, < j j > j, <i i >i, > j

<i, < j i j >i, > j <i, < j j i >i, > j

<i, < j i= j >i, > j

Les fragments aux extrêmes gauches et droites ne sont jamais vides. On les retrouve donc dans toutes les configurations. Si au lieu d’avoir des inégalités strictes suriet jon avait les inégalités large 1 ≤ i≤ net 1≤ j≤ nle nombre de configurations augmenterait grandement : ces deux fragments pourraient toujours être vides et il faudrait multiplier le nombre de configurations par quatre. Ceci illustre assez bien le coût qu’introduit l’interdiction de considérer des fragments vides.

Ce qui peut toutefois s’avérer intéressant dans certaines analyses de programme, c’est que pour chaque position relative de i et j, on a une analyse complètement indépendante. Cette séparation est souvent nécessaire pour une opération très courante sur les tableaux, l’échange de cellules, dont nous verrons dans la section 10.2.1 qu’elle nécessite au moins la distinction des cas i = j eti , j. Ici, la séparation des cas est due au fait suivant. Lorsque i < jalors les fragmentsA[= i, > j],A[= j, < i] etA[= i,= j] sont vides tandis que sii > jce sont les fragments A[= i, < j], A[= j, > i] et A[= i,= j] et sii = jce sont les fragmentsA[= i, > j],

A[= i, < j],A[= j, < i] etA[= j, > i]. On est donc forcément dans une configuration différente de la partition.

Conclusion. Pour ces deux analyses, on peut dire que le critère de fragmentation est de nature syntaxique. Pour le premier, on cherche dans le programme les pointeurs pour introduire les prédicats correspondants et dans le second on cherche les indices utilisés dans les accès aux tableaux. Ce critère syntaxique peut être développé pour améliorer la précision de l’analyse comme nous le verrons dans la section suivante.