AStat/rapport.md
2024-06-09 18:11:53 +02:00

4.1 KiB

Analyseur statique

Le projet reprend et complète la base de code fournie. Nous avons implémenté l'analyse disjonctive par chemin ainsi que le domaine de Karr.

Les tests de l'analyseur sont fournis avec les tests préexistants et selon leurs conventions. Ils sont intégrés à scripts/test.sh. Figurent des tests quand aux support des fonctions, des congruences, de l'analyse disjonctive.

Considérations sur les domaines

Des domaines nus

Que ce soit dans les opérations en avant ou en arrière, le comportement de "Bot" (et dans une moindre mesure de "Top") dans un VALUE_DOMAIN ne dépend pas du domaine implémenté. Afin d'aiser l'implémentation des divers VALUE_DOMAIN (constantes,intervalles, congruences,signes) on a fait le choix d'implémenter un type de domaine NAKED_VALUE_DOMAIN, dont la sémantique est celle d'un VALUE_DOMAIN à ceci près que l'on interdit "Bot" et "Top" en entrée des fonctions (ce qui amène à changer un peu la signature, cf. domains/naked.ml). Un foncteur est alors défini, AddTopBot, qui complète un NAKED_VALUE_DOMAIN en VALUE_DOMAIN.

Un problème qui apparait est qu'un NAKED_VALUE_DOMAIN peut avoir besoin d'exprimer le concept de Top ou Bot, notamment dans les opérations à l'envers. Pour gérer cela, on utilise deux exceptions, NeedTop et Absurd.

Une signature plus expressive

Afin de faciliter l'écriture du domaine de Karr, la signature de DOMAIN change le type de init en un int -> t; cela permet d'indiquer au domaine le nombre de variables prévu.

L'itérateur

Terminaison

Dans notre projet, lors de l'exécution, certains noeuds du CFG sont annotés comme des widen_target : l'accumulation d'état abstrait se fait sur eux par des widen et non des join. La terminaison est alors assurée dès lors que chaque boucle apparaissant dans le CFG contient au moins une widen_target. Pour ce faire, on procède de deux manières :

  • lors de la conversion de l'AST en CFG, les noeuds "en tête" de boucle (ceux suivant immédiatement la décision de refaire un tour) sont annotés comme des widen_target;
  • cela ne suffisant pas pour les boucles "implicites" formées via des goto, une passe de recherche de cycle par BFS est ensuite effectuée sur chaque fonction; dès qu'elle trouve un cycle, elle note la tête du cycle comme widen_target.

Ce deuxième mécanisme n'apportant aucune garantie sur la pertinence de la cible choisie, un avertissement est émi lorsqu'il annote un noeud.

Itérer dans des fonctions

Nous avons fait le choix, lors de l'analyse d'une fonction, de remettre à zéro les états des noeuds internes à la fonction (excepté l'entrée). Cela permet à des appels différents à la même fonction de ne pas créer d'imprécision à cause de join fortuits. Une telle approche pourrait se généraliser aux fonctions récursives, mais il faudrait du support vis-à-vis du passage de paramètre, de valeur de retour et la terminaison semble plus subtile à garantir.

Les iterables

Initialement, l'algorithme de worklist a été implémenté directement à l'aide des domaines. Toutefois, adapter une telle structure à l'analyse disjonctive aurait nécessité de changer considérablement la signature des domaines pour l'annoter des informations de branchement.

Ces subtilités ont été encapsulées dans un type de domaine ITERABLE (iterator/iterable.ml), dont les instances sont en toute généralité des objets permettant d'effectuer l'algorithme de worklist. Deux fonctions des domaines vers les itérables sont alors fournis,SimpleIterable fournissant une analyse naïve (disponible via l'option -fno-disjunction) dont les résultats sont plus lisibles et DisjunctiveIterable fournissant une analyse disjonctive.

Dans la sortie correspondant à une analyse disjonctive, chaque noeud est associé à une fonction partielle de listes de paires entier booléen vers des éléments du domaine. Une liste de paire (entier,booléen) correspond aux exécutions ayant rencontré exactement les conditionnelles indiquées par leur node_id et ayant pris la branche indiquée par le booléen (au dernier passage).