• Aucun résultat trouvé

Traduction des expressions d’adresses en chemins d’accès mémoire constants

Dans le document Analyse des pointeurs pour le langage C (Page 93-98)

4.3 Le schéma général de l’analyse points-to

4.3.3 Traduction des expressions d’adresses en chemins d’accès mémoire constants

constants (CP )

Une des originalités de l’analyse points-to implémentée dans PIPS est de ne pas passer par une simplification des lhs via du code trois adresses, afin de pouvoir restituer le code source aussi fidèlement que possible. Les expressions complexes sont tout fois traduites sous la forme d’une représentation plus facile à analyser. Prenons par exemple les déclarations suivantes :

struct stuff {int val; struct stuff * next;} s1;

struct stuff *q = &s1;

Prog 4.2 – Structure de données récursive

Les lhs sont traduits en chemins d’accès dans E. Ces derniers n’utilisent que la construction subscript pour convertir les notations usuelles de C de la manière suivante :

Pour pouvoir calculer de manière unique les ensembles de base au niveau des expressions d’adresse, on a besoin de les traduire en des chemins constants. Prenons cette fois-ci comme exemple l’expression **p, qui est d’abord traduite en une pseudo référence p[0][0] par norma-

68 Chapitre 4. L’analyse intraprocédurale simplifiée

q->next -> q[0][2] // next est le second champ

(*q).next -> q[0][2]

q->next->next -> q[0][2][0][2]

Prog 4.3 – Traduction des chemins constants

lisation ; puis chaque déréférencement est remplacé par un ensemble contenant les emplace- ments mémoire qu’il représente en utilisant l’information points-to disponible.

La traduction d’une expression d’adresse en un ensemble de chemin d’accès constants s’ef- fectue en trois étapes :

1. normaliser l’expression d’adresse ; par exemple, **p en p[0][0], my_str.p en my_str[p], my_str->q en my_str[0][q].

2. évaluer les chemins en utilisant l’information points-to disponible initialement ; par exemple, p[0][0][0] est modifié en remplaçant p[0] par sa cible s’il apparaît dans un arc points-to comme (p,q) ce qui donnerait q[0][0].

3. en fonction de l’évaluation précédente construire un ensemble de chemins constants si la substitution de p[0] par sa cible est possible de multiples manières à cause de l’imprécision inévitable d’une analyse statique. Renvoyer le cas d’erreur s’il y a déréférencement d’un pointeur non initialisé.

Pour illustrer le processus de traduction considérons le programme 4.4 qui contient des poin- teurs multiples initialisés au niveau de la déclaration. Le but est d’abord de traduire l’expression ***p sous la forme d’un accès à un tableau puis de traduire ce dernier en utilisant l’information points-to.

int i = 0, *p2 = &i, **p1 = &p2, ***p = &p1 ; ***p = 2;

Prog 4.4 – Exemple d’expression complexe

La figure suivante montre le processus de traduction pour l’expression ***p du programme 4.4, avec comme pour information points-to les arcs {(p, p1), (p1, p2), (p2, i)}.

FIGURE4.4 – Traduction de ***p

D’une manière plus fonctionnelle, une première fonction eta est définie pour calculer l’en- semble des adresses L correspondant à une expression l :

4.3. Le schéma général de l’analyse points-to 69

eta prend en entrée le terme gauche d’une assignation de type pointeur, ainsi qu’un ensemble d’arcs points-to. Elle évalue l’expression lhs en utilisant la relation P dans le but de la changer en un ensemble de chemins constants pouvant apparaître comme une source d’un arc points- to. Par exemple, pour une expression comme ∗x, le graphe de la relation P est parcouru en cherchant les arcs où x apparaît comme source. La fonction aura comme résultat l’ensemble des emplacements pointés par x (c’est-à-dire les « sinks »).

Quant à la partie droite de l’assignation, l’évaluation utilise les arcs points-to courants et effectue un déréférencement de plus que pour la partie gauche. L’équation générale pour la partie droite a la même signature que celle de la partie gauche et est définie comme suit :

(R, P0) =etv([[r]], P ) (4.9)

Les définitions des fonctions eta et etv, plus générales que la simple conversion d’un chemin d’accès en un chemin d’accès constant, sont fournies sous-sections 4.3.3.3 et 4.3.3.4. Il faut noter que la relation points-to P peut être modifiée en une relation P0 par ces deux fonctions

quand le contexte formel est augmenté suite à un déréférencement.

4.3.3.1 Définition de la fonction eval

La fonction eval décrite à la Fonction 4 reçoit comme argument un chemin constant a et un ensemble d’arcs points-to P . Elle évalue une référence en utilisant l’information points-to.

Fonction 4 : eval

eval : A × PT → P(A) × PT eval(a : A, P : PT ) 7→ (A0, P0)

if a ∈ {undefined, NULL} then return (∅, ⊥) else let e = apply_pt(a, P ) if e 6= ∅ then return (e, P ) else

return eval(a, stub_cp(a, P ))

Le chemin constant est supposé être de type pointeur. En premier lieu, si un pointeur est évalué à undefined ou NULL l’évaluation retourne une liste vide de chemins d’accès ainsi qu’un ensemble d’arcs points-to qui vaut ⊥. En second lieu, l’ensemble des arcs points-to est utilisé pour chercher la ou les cibles de a. C’est le rôle de la fonction apply_pt. Si aucune cible n’est trouvée et que a est un paramètre formel ou un stub (voir section 4.3.3.2), alors P est augmenté par la nouvelle cible créée par stub_cp pour construire le contexte formel représentant un site d’appel générique sans aliasing. La fonction renvoie la cible ainsi que l’ensemble P augmenté.

La fonction est appelée principalement pour déterminer quelle variable est lue ou écrite par une instruction de déreférencement de pointeur. Le déréférencement d’un pointeur non-initialisé ou qui vaut NULL aboutit à un ensemble d’arcs non valide. Nous pouvons alors conclure que le programme comporte des erreurs.

4.3.3.2 Définition des fonctions stub_cp et new_stub

Cette fonction, présentée à la figure 5, prend en entrée un chemin d’accès constant a, ainsi qu’un ensemble P d’arcs points-to.

70 Chapitre 4. L’analyse intraprocédurale simplifiée Fonction 5 : stub_cp stub_cp : A × PT → PT stub_cp(a : A, P : PT ) if a ∈ sources(P ) then return P

else if type(a) = pointer(t) then

n =new_stub(a) type(n) = t

return stub_cp(n, P ∪ {(a, n)}) else if type(a) = struct then

P0= P

foreach f ∈ Dtype(a)do

P0=stub_cp(a.f, P0)

return P0

else if type(a) = array(t0, N )then

P0= P for j ∈ {0, . . . , N − 1} do P0=stub_cp(a[j], P0) return P0 else return P

Selon le type de a, la fonction crée un nouvelle variable dont le nom est dérivé de a et dont le type correspond au type pointé par a. Elle retourne comme résultat un nouvel ensemble qui est l’union de P et des nouveaux arcs créés. Lors de la création des arcs, la fonction teste le type de la nouvelle cible créée à partir de a, et, tant que c’est de type pointeur, elle continue à créer des cibles jusqu’à tomber sur un type basique (différent des types pointeurs et struct).

La fonction appelle à son tour la fonction new_stub, qui permet de générer un nouveau nom à partir d’un chemin constant Elle est défini comme suit :

new_stub : A → I

et la fonction sources(P ), qui renvoie les chemins d’accès qui apparaissent comme source au niveau des arcs points-to, est définie par :

sources : PT → P(A)

P 7→ {a ∈ A|∃s ∈ A, (a, s) ∈ P }

4.3.3.3 Définition de la fonction eta, expression_to_address()

La fonction eta est défini à la fonction 6. Elle prend en entrée le terme gauche d’une assigna- tion de type pointeur, ainsi qu’un ensemble d’arcs points-to. Elle évalue l’expression etdans le

but de la changer en un ensemble de chemins constants pouvant apparaître comme une source7

d’un arc points-to, c’est-à-dire comme la partie gauche d’une assignation de pointeurs.

Par exemple, pour une expression de type ∗x, l’ensemble P est parcouru en cherchant les relations où x apparaît comme source.

La fonction a comme résultat l’ensemble des emplacements pointés par x ; c’est-à-dire ses destinations(voir ci-dessous pour la définition de sti). La fonction renvoie error dans le cas où

4.3. Le schéma général de l’analyse points-to 71 Fonction 6 : eta eta : E × PT → P(A) × PT eta(e : E, P : PT ) 7→ (A, P0) switch e do case Id return ({Id}, P ) case e0.f (A0, P0) =eta(e0, P ) return (S a0∈A0{a0.f }, P0) case ∗e return etv(e, P ) case &e return ({error}, P ) case e1[e2] (A0, P0) =eta(e1, P ) return (S a0∈A0{a0[sti(e2)]}, P0) case cte return ({error}, P )

cas typage syntaxe résultat P(A) résultat PT

Id pointer p {p} P

e0.f

pointer s.f {s.f } P

pointer (*s).f {_s_1.f } P ∪ {(s,_s_1)} pointer a[1].f {a[1].f } P

pointer a[i].f {a[∗].f } P

&e int &p {error} P

∗e pointer *p {_p_1} P ∪ {(p,_p_1)}

pointer **p {_p_1_1} P ∪ {(p,_p_1), (_p_1, _p_1_1)} e1[e2] pointer a[1] {a[1]} P

pointer a[*p] {a[∗]} P

cte int 1 {error} P

72 Chapitre 4. L’analyse intraprocédurale simplifiée

l’expression ne peut apparaître comme terme gauche comme par exemple la constante NULL ou l’opérateur d’adressage &. D’autres exemples sont traités dans le tableau 4.2. Il faut noter que, lorsque l’expression comprend un paramètre formel qui n’a pas été initialisé, l’ensemble des arcs points-to est augmenté avec l’initialisation à la demande du paramètre.

4.3.3.4 Définition de la fonction etv, expression_to_values()

La fonction etv (Fonction 7) prend en entrée le terme droit d’une assignation ainsi qu’un ensemble d’arcs points-to. Elle évalue l’expression e dans le but de la changer en un ensemble de chemins constants pouvant apparaître comme une destination d’un arc points-to8. La fonction

effectue une évaluation de plus que la fonction eta ; dans le cas où e est un identificateur, un test sur le type t de e est effectué. Si t est un pointeur, alors il est évalué pour obtenir la case mémoire vers laquelle il pointe. L’évaluation de l’expression est faite par la fonction eval (définie au niveau de la section 4.3.3.1). Pour les expressions complexes qui comportent un chemin d’accès composé de sous chemins qui doivent être évalués de gauche à droite, comme par exemple (*s).p, la fonction commence par récupérer le préfixe du chemin qui correspond à la partie structure *s. Le préfixe est traduit via la fonction eta. Cette dernière renvoie une liste de chemins d’accès constants a1 sur lesquels la fonction itère en concaténant a1au suffixe de

l’expression e et en fournissant le résultat comme argument à la fonction eval. La fonction couvre tous les cas possibles en effectuant une disjonction sur tous les types d’expressions. Dans le cas où etest un élément de tableau, l’expression qui représente l’indice est passée comme argument

à la fonction sti9. La fonction teste si l’expression est dépendante de l’état

mémoire10via le prédicat sdp11.

Si ce dernier est évalué à vrai, alors l’expression est changée en ∗ qui représente n’importe quel élément du tableau12. La pseudo-expression ∗ représente un entier compris entre 0 et l’in-

dice du dernier élément du tableau. Sinon, il s’agit d’une valeur constante et l’expression est renvoyée comme résultat.

sti : E → C

e 7→ sisdp(e) alors ∗ sinon e

Le tableau 4.3 montre des exemples de calcul de destination à partir des expressions qui apparaissent en partie droite. L’ensemble des arcs P est augmenté par les nouvelles cibles des paramètres formels créées par la fonction stub_cp.

Dans le document Analyse des pointeurs pour le langage C (Page 93-98)