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 commewiden_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).