• Aucun résultat trouvé

3.2 Système de types avec effets de RegML

4.1.4 Le problème d’aliasing

Comme nous l’avons expliqué, les types enregistrements et les régions sont la même chose. Nous pouvons d’ailleurs reformuler le principe de Liskov en termes de régions :

Si Q(fl) est une propriété démontrable pour toute région privée fl, alors la propriété Q(flÕ) doit être vraie pour toute région Õ telle que Õ est un raffinement de la région fl.

Comme nous l’avons vu dans le chapitre précédent, le contrôle statique des alias fait partie des propriétés qui sont assurées par le typage de WhyML. Rappelons que cela signifie que pour toute paire de pointeurs qui apparaissent dans un programme bien typé, le système connaît statiquement si ces deux noms réfèrent à la même case mé-moire ou pas. Le principe de Liksov implique donc que si le code client était bien typé en premier lieu, il doit rester bien typé après le raffinement, y compris en ce qui concerne le contrôle statique des alias. Or, le raffinement d’une région peut y intro-duire des champs supplémentaires contenant eux-mêmes de nouvelles régions. Si l’on ne met aucune restriction sur la relation entre ces régions introduites et les régions qui existaient déjà dans le code client avant le raffinement, il devient possible de briser la barrière d’abstraction et de mettre le principe de Liskov en défaut. Par exemple, si l’ensemble des régions introduites par le raffinement n’est pas disjoint de l’ensemble des régions déjà connues du client, on peut facilement casser le typage du code client. En effet, supposons que l’on dispose de deux opérations newA () et newB () qui créent respectivement deux régions distinctes 1 et 2. Dans le code ci-dessous, les types et les effets seront donc :

let x = newA () : 1· (? · fl1) in let y = newB () : 2· (? · fl2) in

x

Maintenant, supposons que l’on raffine la région 2 en introduisant un champ f et que 2.f = fl1. Dans ce cas-ci, l’effet de l’appel newB () devient (? · {fl2, fl1}) où la région1 devient donc invalidée. Or, la variable x est justement de type 1 et donc ne peut pas être utilisée : le code ci-dessus devient donc rejeté par le typage, alors qu’il

était bien typé auparavant. Ainsi, on doit imposer que les régions introduites par le raffinement doivent être disjointes des régions connues auparavant.

Mais la fraîcheur des régions introduites vis-à-vis des régions existantes ne suffit pas : il y a d’autres moyens, plus subtils, d’introduire des alias inconnus du client. Considérons par exemple le code client qui définit deux ensembles mutables que l’on modifie ensuite :

let bar (x: G.node) =

let marked = MutableSet.create () in let on_stack = MutableSet.create () in

add x marked; add x on_stack;

Pour que ce code soit bien typé, il est nécessaire de supposer que les deux ensembles sont typés avec deux régions distinctes, disons 1 et2. En effet, si ces régions étaient égales, la création de l’ensemble on_stack invaliderait l’utilisation de l’ensemble marked. Imaginons maintenant que l’on a choisi l’implémentation des ensembles mutables avec les tables de hachage, comme dans le module MutableSetbyHashtbl. Notons qu’a priori, rien n’empêche les régions 1 et 2 de partager le même tableau à l’intérieur du champ buckets. Or, même si ce tableau-là appartenait à une région3 fraîche, dès lors que l’on a l’égalité 1.data = fl2.data (où les deux expressions dénotent la même région 3), le code de la fonction bar ci-dessus devient nécessairement mal typé. En effet, dans l’implémentation de la fonction add, la première ligne

if t.size = t.buckets.length then resize t;

a pour effet de restreindre l’utilisation de la région 3, puisque la fonction resize est susceptible de remplacer le tableau stocké dans t.buckets par un tableau frais. La région 3 ne sera dorénavant accessible que depuis la région 1. Par conséquent, la dernière ligne (add x on_stack;) dans le code ci-dessus devient mal typée, car nous avons supposé que 1 ”= fl2.

D’une manière encore plus subtile, si l’on raffine deux régions équivalentes (c’est-à-dire qui ont la même structure d’aliasing, voir la définition 1 © fl2 dans section 3.2.1

du chapitre précédent) par deux régions qui ne le sont pas, il devient encore possible de mettre le principle de Liskov en défaut. Illustrons ce propos sur l’exemple suivant. Considérons une interface :

module GF

type t

val f: unit æ t

val g: t æ unit

end

et le code client très simple qui l’utilise :

Maintenant, supposons que l’on raffine le type t par un type enregistrement avec deux composantes mutables :

type t = { mutable a: array int;

mutable b: array int }

A priori, rien n’empêche de construire des données du type t correspondant à des régions structurellement égales mais non équivalentes, selon que les champs a et b sont aliasés ou non. Supposons donc que l’on donne aux fonctions f et g respectivement les signatures suivantes (ignorons ci-dessous les effets qui ne sont pas pertinents pour notre propos ici)

val f: unit æ {a : fla; b : fla}r val g: {a : fla; b : flb}rÕ æ unit

où l’on suppose que les régionsa etb sont distinctes. Or, puisque la règle du typage de l’appel (voir la section 3.2.4) impose que les régions des paramètres formels et les régions des arguments soient équivalentes, l’appel g(f()) devient mal typé, alors qu’il était accepté par le typage en premier lieu.

Il est donc nécessaire de trouver des conditions suffisantes, mais pas trop restric-tives, sur la manière dont les nouvelles régions sont introduites, pour garantir que le raffinement ne mette pas en défaut le principe de Liskov. Comme nous l’avons illustré avec les trois exemples ci-dessus, trouver ce genre de conditions n’est pas trivial. Par ailleurs, cette question peut être étudiée indépendamment des autres conditions de la validité du raffinement qui sont elles plus en rapport avec la préservation des proprié-tés logiques. La contribution de ce chapitre est de proposer de telles conditions et de montrer formellement leur validité. Concrètement, nous allons adapter le formalisme du chapitre précédent aux notions de régions privées, puis définir le raffinement des régions et des signatures de fonctions. Ensuite, nous montrerons que le raffinement ainsi défini préserve la relation du typage.

Insistons encore fois sur le fait que la préservation des notions logiques telles que les invariants de types et les contrats de fonctions doivent également être prises en compte pour montrer la validité du raffinement. Comme il s’agit de notions bien étudiées dans la littérature, nous ne les formalisons pas dans ce chapitre, en nous consacrant entièrement à la problématique des alias.

4.2 Extension de RegML avec des régions privées