• Aucun résultat trouvé

Synthèse de tableaux et de structures de données

Nous nous intéressons dans cette section aux travaux dont l’objet était de proposer un mé-canisme permettant d’utiliser les domaines abstraits classiques dans le cadre d’une analyse du contenu des tableaux ou des structures de données.

9.1.1 Expansion et écrasement de tableau.

Les premières publications sur la synthèse sont liées au développement de l’analyseur statique Astr´ee[BCC+02]. Celui-ci utilise deux techniques pour traiter les tableaux.

L’expansion1 d’un tableau, qui consiste à introduire autant de variables qu’il y a de cellules dans le tableau. Elle ne peut être appliquée que si la taille du tableau est constante. Elle a l’avan-tage de la précision mais coûte cher si le tableau est grand. Pour déterminer un effet précis des boucles sur un tableau expansé, il sera souvent intéressant de dérouler complètement ces boucles. On peut ainsi éviter des affectations faibles, si les accès au tableau utilisent des indices constants après déroulement.

L’écrasement du tableau à l’inverse, associe à l’ensemble d’un tableau une unique variable. On donne à cette variable les propriétés vraies de toutes les cellules du tableau. On gagne en efficacité ce qu’on perd en précision. On introduit beaucoup moins de variables, et il est possible d’utiliser cette technique sur les tableaux dont la taille n’est pas connue statiquement. En contre-partie, toutes les affectations à un tableau écrasé est traité comme une affectation faible. Ainsi, on ne pourra qu’affaiblir les propriétés initiales de ces tableaux.

9.1.2 Partitionnement de tableau.

Les auteurs [GDD+04] proposent un compromis entre ces deux méthodes. Il s’agit de dé-couper les tableaux en un ensemble de tranches formant une partition de celui-ci. On introduit autant de variables de synthèse que de tranches. Chaque cellule du tableau est associée à une et

une seule variable de synthèse par une surjection qu’on appelle lafonction de synthèse. Formel-lement, les cellules ayant la même image pour la fonction de synthèse sont regroupées dans le même fragment.

Le concept de fonction de synthèse est très proche de la définition que nous utilisons au chapitre 3. Nos choix de représentants correspondent à des inverses de leur fonction de synthèse. Notre définition est néanmoins plus générale en plusieurs points :

• elle permet d’avoir des fragments se chevauchant,

• elle autorise à ignorer une partie des cellules de tableau qu’on ne souhaiterait pas considérer, • elle autorise des fragments vides, et enfin

• elle permet de choisir des cellules ayant des relations particulières, des relations entre leurs indices.

Les trois premiers points relâchent respectivement les conditions de disjonction, de recouvre-ment, et de non vacuité contraignant une partition. On peut donc les résumer en disant sim-plement que notre définition autorise les fragmentations à ne plus être des partitions. Cette contrainte de partitionnement est impliquée par la fonction de synthèse. Puisqu’il s’agit d’une application, chaque cellule a une seule image par la fonction de synthèse et il donc impossible qu’une cellule soit dans plusieurs fragments. Et puisque la fonction est totale, toute cellule ap-partient au moins à un fragment. Les auteurs nomment « projection » nos choix de représentants. Dans notre cas, ils n’en sont pas toujours : les choix de représentants peuvent associer plusieurs fois la même variable à une cellule, dans le cas où des fragments se chevauchent.

Les transformations de la fragmentations y sont décrites comme l’utilisation de quatre opé-rateurs,fold,expand, add etdrop. Le premier permet de combiner deux fragments adjacents en un seul. Elle correspond à notre composition, à ceci près que les fragments composés par l’opérationfoldsont ensuite retirés. La seconde réalise l’opération inverse et correspond à notre décomposition. Encore une fois, on ne conserve que le produit de l’opérationexpand, retirant effectivement le fragment décomposé. Les deux dernières ajoutent et retirent un fragment res-pectivement.

La description de ces quatre opérations est un peu plus facile que la définition de nos trans-formations élémentaires. Elles conservent à tout moment une partition du tableau. Tandis que les nôtres conservent à la fois les fragments source et cible de ces opérations et qu’il faut donc porter son attention sur les relations que l’on exprime entre eux. Leurs opérateursfoldetexpand

ne servent qu’à faire évoluer la fragmentation durant l’analyse, tandis que les nôtres servent éga-lement à donner ou rappeler au domaine abstrait les singularités des fragmentations. Lorsque la fragmentation décrit une partition du tableau, aucune fission ni fusion ne peut avoir lieu puis-qu’on ne peut avoir deux fragments identiques.

9.1.3 Synthèse par abstraction de fonction.

Ces résultats sont généralisés dans [JGR05]. Les tableaux et les structures de données en général sont modélisés comme des fonctions des cellules vers leurs valeurs et on cherche des abstractions de ces fonctions. Le partitionnement de tableau devient un partitionnement de l’en-semble de définition de la fonction. La construction d’un domaine abstrait pour ces fonctions repose sur la définition de deux domaines abstraits : l’un décrivant le partitionnement, l’autre décrivant les contenus. Le domaine abstrait décrivant le partitionnement est un treillis générale-ment plat dont les élégénérale-ments sont les fraggénérale-ments et leurs concrétisations les ensembles de cellules qu’ils représentent.

On part d’un état concret, fonction des cellules vers leurs valeurs. On commence par collecter pour chaque variable de synthèse les valeurs des cellules associées à cette variable. On construit ainsi des états qui aux variables de synthèse associent des ensembles de valeurs. C’est la pre-mière étape de l’abstraction. La seconde consiste à distribuer ces valeurs. On construit ainsi des états de synthèse en choisissant une valeur dans chaque ensemble. D’autre part, les résultats sont généralisés au cas où la fragmentation n’est pas une partition.

Comparé à notre méthode de synthèse, celle-ci ne permet toujours pas de désigner des cellules liées par des relations d’indice. Les fragmentations considérées ne sont pas symboliques, et ne peuvent contenir de fragment vide.

9.1.4 Synthèse de relations point à point

Dans [HP08], une toute autre manière de synthétiser les propriétés de tableau est utilisée. Le principe du partitionnement de tableau est conservé mais son usage est différent.

Le but est de pouvoir exprimer des propriété point à point de la forme : ∀`, ϕ(`)⇒ψ(A1[`+c1], . . . ,Ak[`+ck])

Ceci inclut par exemple le fait que deux tableaux soient égaux ou le fait qu’un tableau soit trié : ∀`, 1≤`≤i⇒A[`]≤A[`+1]

L’analyse tentera de trouver une telle implication pour chaque fragment de la partition. La for-muleϕà gauche de l’implication correspond donc à la définition du fragment dans la partition. À droite de l’implication, on a une propriétéψqui implique des cellulesAi[`+ci] qui sont donc des translations du fragment original.

La propriétéψest exprimée par une valeur abstraite. L’utilisateur peut choisir n’importe quel domaine abstrait pertinent pour l’expression de propriétés des contenus des tableaux. On reste bien dans le cadre de la synthèse de propriétés de structures de données. Les valeurs abstraites feront intervenir entre autre des variables de synthèse pour chacun desAi[`+ci]. Elles pourront également introduire des fragments scalaires ou des variables du programme. En revanche, on s’interdit d’exprimer des propriétés à propos de fragments (non-scalaires) qui ne seraient pas des translations l’un de l’autre.

L’intérêt de ne chercher des relations qu’entre des tranches qui sont des translations l’une de l’autre c’est que lorsqu’une tranche est vide, ses translations le sont aussi. Ainsi, dans une valeur abstraite, on ne peut pas simultanément avoir des fragments vides et des fragments non vides. En pratique,ϕ(`) est insatisfiable, les tranches translatées sont vides, et on prendra⊥comme valeur abstraite pour la partie droite de l’implication. Si au contraire ϕ(`) est satisfiable, toutes les variables de synthèse de la valeur abstraite sont super-scalaires et le comportement classique du domaine abstrait peut s’appliquer. Il n’est donc pas nécessaire de modifier les domaines abstraits comme nous l’avons fait dans cette thèse.

Introduire des variables du programme ou des fragments scalaires dans les valeurs abstraites permet de désigner d’éventuelles relations qu’ils auraient avec les tranches. La valeur abstraite peut également exprimer des propriétés liant les variables du programme entre elles. Mais cela ne nous apprend rien sur ces variables en général : ces propriétés ne sont valables que dans le cas où la partie gauche de l’implication est vérifiée. Par conséquent, si on veut découvrir des propriétés sur les variables du programme, il est de toute façon nécessaire d’avoir une valeur abstraite qui ne fait intervenir que des fragments super-scalaires.

Les auteurs décrivent trois transformations des fragmentations : le raffinement, la simplifica-tion et la progression d’indice. La première est utilisée lorsque l’on rentre dans une boucle itérant sur un tableau. On raffine la partition en séparant les cellules suivant qu’elles sont inférieures, égales, ou supérieures à l’indice de la boucle. Dans notre formalisme, celle-ci s’exprime comme une ou plusieurs décompositions. La simplification est l’opération inverse utilisée lorsque l’on sort d’une boucle, les fragments que l’on avait distingués à l’intérieur de la boucle doivent être rassemblés en un seul et on peut donc l’exprimer par des compositions. La progression d’indice intervient lors de la phase d’incrément de la boucle. On cherche à distinguer la cellule d’indice égal à l’indice de boucle : quand cet indice est incrémenté, on doit changer la partition en consé-quence. Cela revient à décomposer le fragment dont on extrait la nouvelle cellule et à composer l’ancien singleton avec la tranche adjacente.

Il est donc possible de traduire toutes ces transformations en termes de compositions et dé-compositions. Celles-ci s’appliquent à toutes les translations du fragment original.

Dans le cadre défini par les auteurs, les compositions ont l’avantage d’être simples. Si on veut composer la tranche dont les indices vérifientϕi et les contenus vérifientψiavec la tranche dont les indices vérifientϕjet les contenus vérifientψjon donnera à cette tranche les propriétés

ψij.

Si cette analyse repose sur un partitionnement du tableau, les translations de tranches intro-duisent des chevauchements. La translation d’une tranche peut chevaucher la translation d’une tranche adjacente. Par conséquent, les règles de réduction s’appliquent. Lorsqu’une translation de tranche recouvre une autre translation de tranche on peut renforcer les propriétés de la se-conde par les propriétés de la première.

Ces travaux incluent également tout un travail sur le choix de la fragmentation. Nous aborde-rons ce sujet dans la section 9.2.2.

9.1.5 Synthèse de propriétés sur les mots

Les auteurs de [BDE+10] abordent le problème de la synthèse de propriétés sur les listes simplement chaînées. La suite des données associée à chacun des maillons de la liste forme un mot. A partir d’un découpage des listes en fragments, on peut exprimer des propriétés de chacun de ces fragments ainsi que des relations entre ces fragments. Ils traitent aussi bien le cas des propriétés d’agrégation (la somme, la taille et les multi-ensembles) que le cas des propriétés vérifiées cellules par cellules. Dans ce dernier cas les relations entre les fragments de listes peuvent être précisées par des patrons choisis par l’utilisateur. Il est ainsi possible de désigner des relations entre deux cellules d’un mot telles que la première est située avant la seconde. La classe de relations qu’il est ainsi possible de rechercher est plus large que les simples relations de translation.

On retrouvera des opérateurs pour la manipulation des fragments similaires à ceux des précé-dents travaux :

• l’opérateursplitqui permet de décomposer un fragment de liste en un singleton tête et le fragment queue,

• l’opérateurconcatqui permet de composer deux fragments adjacents,

• l’opérateur sgltqui permet d’ajouter un nouveau fragment singleton (après l’allocation dynamique d’un nœud de la liste) et

Le concept de mot utilisé dans ces travaux pourrait très bien modéliser le contenu des ta-bleaux. Si on prend la séquence de valeurs d’un fragment de tableau, on pourra utiliser les même moyens pour associer des propriétés à ces fragments. La particularité des mots par rapport aux formalisations précédentes est de conserver une notion d’ordre entre les éléments d’un fragment. La formalisation proposée en revanche n’autorise pas les fragments à être vides. Il est néces-saire d’énumérer les configurations possibles des listes de sorte que dans chacune d’elle, chaque fragment de liste contienne toujours au moins un élément.

9.1.6 Conclusion

Tous ces travaux à l’exception du dernier, sont antérieurs aux notres et, à l’exception du dernier, ont fortement inspiré l’élaboration de notre formalisation. D’un coté, dans [GDD+04, JGR05] on a une méthode qui permet d’exprimer des relations entre toutes les cellules de tous les fragments. De l’autre, dans [HP08], on a une méthode qui permet l’expression de relations particulières entre des fragments, à condition que quand l’un d’entre eux est vide, l’autre l’est également. Notre formalisation cherche à embrasser l’ensemble de ces possibilités : autoriser l’expression de relations aussi bien générales que particulières sur des fragments qui peuvent se chevaucher et être vide indépendamment les uns des autres. La contrepartie c’est que notre formalisme nécessite que les domaines abstraits soient adaptés en conséquence.

Notre manière de synthétiser les propriétés n’est d’ailleurs qu’une généralisation de la mé-thode originale de [GDD+04]. Si on impose des contraintes sur notre processus de synthèse, on retrouvera la même correspondance de Galois. D’une part, il faut interdire les fragments vides ou se chevauchant. D’autre part, il faut inclure dans la fragmentation tous les choix de représen-tant associant à chaque variable de synthèse n’importe laquelle des cellules représentée. Notre manière de présenter la correspondance de Galois diffère également : notre définition est cen-trée sur les choix de représentants. Ceux-ci n’apparaissent dans l’article précédemment cité que comme inverses de la fonction de synthèse.

Enfin, on peut noter une dernière différence avec le formalisme de [GDD+04] : notre définition fait apparaître le caractère symbolique de la fragmentation. La construction d’états de synthèse à partir d’un état concret repose sur la fragmentation en cet état concret. Cela peut paraître anodin, car on peut proposer une généralisation symbolique directe de leur formalisation. Mais cette généralisation directe n’autorise pas l’introduction de fragments potentiellement vides dont la vacuité dépend de la valeur des variables du programme. Pour introduire un tel fragment, il va falloir partitionner l’ensemble d’états et conduire deux analyses en parallèle. Pour les états où le fragment n’est pas vide, on peut sans problème introduire le fragment. Pour les états où le fragment est vide, on conduira simplement l’analyse en oubliant ce fragment. L’inconvénient, c’est que dans le pire cas, on doit conduire un nombre d’analyses parallèles qui est exponentiel en le nombre de fragments.

La différence de traitement des fragments vides par les méthodes de synthèse résume très bien leur caractéristique.

• Les méthodes présentées dans [GDD+04, JGR05] interdisent les fragments vides et il faudra donc énumérer les cas de vacuité et en payer le coût.

• La méthode introduite dans [HP08] autorise les fragments à être vides, et n’a donc pas ce problème. En contrepartie, il faut une valeur abstraite distincte pour chaque relation entre des fragments.

• Notre méthode autorise des fragments vides, et permet de regrouper toute l’information dans une seule valeur abstraite. En contrepartie, elle nécessite une adaptation du domaine

abstrait.