Projet : LANDE

previous up next contents
Précédent : Résultats nouveaux Remonter : Résultats nouveaux Suivant : Actions «amont»


Sous-sections


   
Actions «aval»

Nous présentons d'abord des résultats généraux sur l'analyse de programmes (modules 6.1.1 et 6.1.2) avant de détailler un certain nombre de recherches portant sur des analyses particulières: la vérification de politiques de sécurité (module 6.1.3) et l'analyse de programmes $ \lambda$Prolog (module 6.1.4). Nous décrivons ensuite nos travaux récents sur le le débogage dynamique (modules 6.1.5 et 6.1.6), la génération de traces (module 6.1.7), et la génération de jeux de test (module 6.1.8).

Construction systématique d'analyses génériques



Participants : Valérie Gouranton, Thomas Jensen, Daniel Le Métayer, Florimond Ployette.

Mots clés : Analyse de programmes, Sémantique naturelle, Slicing .

 

Résumé :

Certaines analyses de programmes ne sont pas restreintes à un langage de programmation particulier. Plutôt que d'en fournir une nouvelle définition pour chaque langage, nous avons montré qu'il est possible de définir une telle analyse de manière générique et de l'instancier pour obtenir des analyses particulières. Nous avons proposé pour ce faire un format de sémantique naturelle et nous avons appliqué cette technique à l'analyse de slicing. Nous avons pu ainsi dériver, par simple instanciation d'une définition générique, des analyses dynamiques et statiques pour un langage impératif, un langage fonctionnel et un langage de programmation logique.

De nombreux travaux ont été effectués sur les fondements de l'analyse sémantique, la correction et la précision des analyseurs. On peut regretter cependant le peu d'attention accordé jusqu'à présent à la conception d'outils d'analyse génériques. Il est ainsi très difficile de factoriser les efforts en matière de réalisation d'analyseurs. Nous avons abordé ce problème en considérant la construction d'analyseurs sous l'angle de la dérivation de programmes à partir de spécifications. Le programme en l'occurrence est l'analyseur lui-même et sa spécification est composée de deux parties:

1.
La sémantique opérationnelle du langage de programmation décrite sous forme de sémantique naturelle.
2.
La propriété recherchée exprimée sous forme de récurrences sur les arbres de preuves de la sémantique naturelle.
Les deux composants peuvent en fait s'exprimer sous forme de fonctions et la dérivation consiste en une série de transformations (pliage/dépliage) permettant d'obtenir un programme récursif autonome qui constitue l'analyseur [[23]].

L'intérêt de cette démarche est qu'elle permet d'exprimer dans un même cadre des analyses variées sur différents langages. Nous en avons fait la démonstration pour des analyses de programmes impératifs (durée de vie des variables), logiques (clôture des termes) et fonctionnels (nécessité, globalisation). Certaines de ces analyses sont spécifiques à un langage de programmation: on peut citer dans cette catégorie l'analyse de clôture pour la programmation logique. D'autres, comme le slicing (cf. module 3.2) ont un intérêt plus général. Plutôt que d'en fournir une nouvelle définition pour chaque langage, nous avons montré qu'il est possible de définir une telle analyse de manière générique et de l'instancier pour obtenir des analyses particulières. Nous avons proposé pour ce faire un format de sémantique naturelle et nous avons appliqué cette technique à l'analyse de slicing. Nous avons pu ainsi dériver, par simple instanciation d'une définition générique, des analyses dynamiques et statiques pour un langage impératif, un langage fonctionnel et un langage de programmation logique [[17]]. D'un point de vue plus pratique, nous travaillons maintenant à la mise au point d'un moteur d'analyse (solveur de point fixe générique) qui pourra servir de base pour le dévéloppement de nouveaux analyseurs.

Spécification et implantation d'analyses à partir de règles d'inférence



Participant : Thomas Jensen.

Mots clés : Typage non-standard, Logique de programmes, Modularité .

 

Résumé :

En nous appuyant sur des résultats récents concernant l'inférence de types d'intersection et de types polymorphes, nous avons conçu un algorithme qui permet de déduire pour chaque programme une propriété principale à partir de laquelle toute autre propriété démontrable par l'analyse peut être déterminée. Un avantage de cet algorithme est qu'il traite des fragments de programmes aussi bien que des programmes entiers. Il s'agit donc d'une démarche qui, à plus long terme, peut mener à un cadre général pour l'analyse modulaire.

Exprimer une analyse par des règles d'inférence comme un système de typage non-standard présente l'avantage de la rendre plus compréhensible mais permet également de s'appuyer sur des algorithmes de typage connus. Dans des travaux antérieurs, nous avons conçu une méthode efficace pour vérifier qu'un programme satisfait une propriété donnée. Se pose alors la question de savoir s'il est possible, étant donné seulement le programme, de trouver les propriétés satisfaites par ce programme, c'est-à-dire d'inférer les propriétés. En nous appuyant sur des résultats récents concernant l'inférence de types d'intersection et de types polymorphes, nous avons conçu un algorithme qui permet de déduire pour chaque programme une propriété principale à partir de laquelle toute autre propriété démontrable par l'analyse peut être déterminée. Un avantage de cet algorithme est qu'il traite des fragments de programmes aussi bien que des programmes entiers. Il s'agit donc d'une démarche qui, à plus long terme, peut mener à un cadre général pour l'analyse modulaire [[25]].

Vérification de politiques de sécurité



Participants : Thomas Jensen, Daniel Le Métayer, Tommy Thorn.

Mots clés : Téléchargement, Sécurité, Chargement dynamique, Java .

 

Résumé :

Nous avons proposé un cadre de définition de politiques de sécurité reposant sur une logique temporelle linéaire à deux niveaux. Nous avons montré que ce modèle est suffisamment expressif pour décrire une variété de politiques de sécurité communes et nous avons proposé une méthode automatique (et complète) de vérification de propriétés pour cette logique. L'idée de base de cette méthode est d'explorer un sous-ensemble fini de suites d'exécution dont la taille est déterminée par la complexité de la propriété à vérifier. Afin d'appliquer ce cadre à Java nous avons formalisé certains aspects de sa sémantique et nous avons montré comment la politique de sécurité du dernier environnement de développement de Java (JDK 1.2) peut s'exprimer dans notre logique.

La sécurité d'un système informatique dépend en général d'un ensemble de contrôles de nature très variée (vérification formelle, analyse statique, analyse dynamique, gestionnaire de sécurité, protocole d'authentification, contrôles d'accès, etc.). Une difficulté majeure dans ce domaine est de pouvoir déterminer la politique globale de sécurité qui est assurée par cette association de contrôles spécifiques. L'évolution récente vers des langages de programmation qui offrent des fonctions de sécurité propres (comme Java ou Telescript) permet d'espérer des progrès en matière de vérification formelle de politiques de sécurité. Beaucoup reste cependant à accomplir comme le montrent les discussions récurrentes sur la sécurité des différentes versions d'environnements de Java. Dans ce contexte, on peut décomposer le problème en trois tâches complémentaires:

Nous avons étudié ces trois aspects en fournissant un cadre général de spécification de politiques de sécurité et en décrivant son instanciation au langage Java et à son environnement de développement JDK 1.2 [[32],[24]].

Notre cadre de définition de politiques de sécurité repose sur un modèle abstrait de programmes comportant des opérations génériques de contrôle de sécurité. Leur sémantique opérationnelle est définie comme un système de transitions sur des états constitués de piles de contrôle. Les politiques de sécurité sont exprimées dans une logique temporelle linéaire à deux niveaux (les objets étant définis par des suites de piles). Nous avons montré que ce formalisme est suffisamment expressif pour décrire une variété de politiques de sécurité communes (séparation des devoirs ou ``segregation of duty'', protection de ressources, bac à sable, inspection de pile, etc.). Nous avons par ailleurs proposé une méthode automatique (et complète) de vérification de propriétés pour cette logique [[32]]. L'idée de base de cette méthode est d'explorer un sous-ensemble fini de suites d'exécution dont la taille est déterminée par la complexité de la propriété à vérifier.

L'application de ce cadre à Java est conditionnée par la formalisation du langage (tout du moins des aspects qui ont un impact sur la sécurité). L'une des particularités de Java est la possibilité de télécharger du code et de l'exécuter de manière transparente. Cette pratique soulève de sérieux problèmes en matière de sécurité des informations (confidentialité, intégrité notamment). Nous nous sommes donc attaqués à la formalisation de cet aspect en nous focalisant sur les règles de visibilité (des classes et de leurs membres) et leur évolution lors du chargement dynamique de classes [[32]]. Cette formalisation en terme de systèmes d'inférence nous a permis de décrire de manière rigoureuse l'origine d'une erreur de sécurité qui avait été découverte empiriquement par des chercheurs d' ATT.

Nous avons ensuite montré comment la politique de sécurité du dernier environnement de développement de Java (JDK 1.2) peut se formaliser dans notre logique. La technique citée plus haut permet donc de vérifier automatiquement qu'une application Java satisfait la politique affichée. Nous travaillons actuellement à lever certaines restrictions de la méthode (concernant notamment la récursivité mutuelle) et à en dériver un analyseur modulaire (mieux adapté à un environnement ouvert comme celui de Java).

Analyse statique de programmes $ \lambda$Prolog



Participant : Olivier Ridoux.

Mots clés : Analyse statique, $ \lambda$Prolog .

 

Résumé :

Nous étudions l'analyse statique de $ \lambda$Prolog par la technique de la compilation abstraite où un programme source est traduit en un autre programme dont les résultats sont interprétés comme le résultat de l'analyse du programme source. L'extension à $ \lambda$Prolog des techniques éprouvées dans le cas de Prolog nécessite entre autres le traitement des $ \lambda$-termes simplement typés.

Nous avons choisi pour l'analyse statique de $ \lambda$Prolog la technique de la compilation abstraite où un programme source est traduit en un autre programme dont les résultats sont interprétés comme le résultat de l'analyse du programme source. Cette méthode a déjà été employée pour Prolog et l'appliquer à $ \lambda$Prolog nécessite des extensions dans trois directions:

1.
La prise en compte de la structure des formules de $ \lambda$Prolog (formules de Harrop au lieu de formules de Horn).
2.
Le traitement de la structure des termes de $ \lambda$Prolog ($ \lambda$-termes simplement typés au lieu de termes de premier ordre).
3.
L'application à l'analyse de propriétés nouvelles dont l'utilisation permettrait d'optimiser l'implantation du langage (état de $ \beta$-normalisation, combinateurs, etc.).

Le domaine de calcul choisi pour les programmes abstraits est celui des booléens. Dans ce cadre, nous avons proposé une solution au deuxième problème qui consiste à coder par un vecteur booléen une fonction de transfert de la propriété à analyser et à calculer pour chaque terme d'ordre supérieur sa contribution à la propriété et sa fonction de transfert [[30]].

Un développement complémentaire et qui concerne aussi le domaine de calcul de $ \lambda$Prolog consiste à prendre en compte le type des termes pour la constitution du domaine abstrait dans lequel s'exprime la propriété analysée. Ainsi, si la propriété analysée est l'absence de variable existentielle libre (la clôture, ou groundness en anglais), l'expression (list (pair vrai faux)) sera vraie d'une liste dont le squelette ne comporte pas de variable, dont aucun élément n'est une variable, et dont tous les éléments comportent un «champ gauche» sans variable libre et un «champ droit» qui peut en comporter.

Ce domaine d'abstraction et sa combinaison avec les fonctions de transfert seront complètement développés dans la thèse de Frédéric Malésieux (co-encadrée par Olivier Ridoux et Patrice Boizumault de l'École des Mines de Nantes), dont la soutenance aura lieu au début de l'année 1999.

Explications pour les bases de données déductives



Participants : Mireille Ducassé, Sarah Mallet.

Mots clés : Débogage, Explication, Génération et Analyse de trace, Base de données déductive .

 

Résumé :

Une base de données déductive est composée de faits (de base) et de règles de déduction déclaratives permettant d'augmenter la connaissance par des faits déduits. Ces règles permettent aux concepteurs de la base de données de se concentrer sur sa logique. Toutefois, les utilisateurs des bases de données ont besoin d'explications pour leur restituer cette logique qui peut être masquée par les détails opérationnels de la mise en oeuvre. Les systèmes d'explication existants rendent à l'utilisateur des arbres de preuve qui ne correspondent pas toujours au bon profil d'exécution. Nous proposons une technique de traçage qui consiste à intégrer une trace ``relationnelle'' avec un méta-interprète instrumenté utilisant des ensembles de substitutions. Les aspects coûteux de la méta-interprétation sont réduits par l'utilisation de la trace qui évite beaucoup de calculs. La flexibilité de la méta-interprétation est conservée. Elle permet de produire facilement des traces de profils différents.

Une base de données déductive est composée de deux types de données: des faits de base stockés dans une base de données relationnelle et des données, dites virtuelles, déduites des données de la base à l'aide de règles.

La forme déclarative du langage de règles permet aux concepteurs de la base de données de se concentrer sur sa logique (cf. module 3.5). Toutefois, la mise en oeuvre des bases de données déductives fait intervenir de nombreuses optimisations afin d'obtenir des manipulations de données efficaces. De ce fait, les utilisateurs des bases de données ont besoin de facilités de débogage et d'explication pour restituer la logique des programmes qui peut être masquée par les détails opérationnels (cf. module 3.3).

Ces explications sont particulièrement nécessaires dans le contexte des bases de données déductives où les utilisateurs du logiciel doivent comprendre le déroulement des déductions pour en accepter les résultats ou mettre à jour les données, alors qu'ils ne sont pas forcément des informaticiens.

Les systèmes d'explication existants pour les bases de données déductives rendent à l'utilisateur des arbres de preuve. Ces arbres donnent une vision très détaillée de la manipulation des données. Cette présentation éclatée, qui peut être utile dans certains cas, provoque, dans le cas général, une explosion du nombre d'arbres de preuve produits. Il s'agit donc de concevoir un outil capable de présenter différents points de vue aux utilisateurs.

Nous proposons une technique de traçage qui consiste à intégrer une trace ``relationnelle'' avec un méta-interprète instrumenté utilisant des ensembles de substitutions. La trace relationnelle donne, de manière efficace, de l'information précise sur l'extraction de données de la base relationnelle. Le méta-interprète ensembliste gère des ensembles de substitutions et donne des explications sur la déduction. Les aspects coûteux de la méta-interprétation sont réduits par l'utilisation de la trace qui évite beaucoup de calculs. La flexibilité de la méta-interprétation est conservée. Elle permet de produire facilement des traces de profils différents [[28],[29]].

Ce travail fait l'objet d'une coopération avec Next Century Media. Cette société fournit Validity, un produit reposant sur la technologie des bases de données déductives, et vise les applications dans la publicité ciblée. Un prototype de recherche, développé dans LANDE, met en oeuvre la technique de traçage mentionnée ci-dessus en utilisant des traces relationnelles produites par le traceur de Validity. Des discussions sont en cours pour affiner la spécification des informations qui doivent apparaître dans les explications de haut niveau.

Analyse de trace automatisée



Participants : Mireille Ducassé, Erwan Jahier.

Mots clés : Débogage, Analyse dynamique, Traceur abstrait, Prolog, Mercury, C .

 

Résumé :

Nous avons développé un outil, OPIUM, qui analyse les traces d'exécution de programmes générées par un traceur Prolog existant. Les programmeurs peuvent spécifier de manière précise ce qu'ils veulent observer du comportement du programme à l'aide de requêtes en Prolog. À partir du langage de requêtes, nous avons bâti des analyses qui donnent des vues abstraites des exécutions selon certains critères. Il a également été possible de mettre en oeuvre à faible coût des traceurs abstraits pour des langages de haut niveau. Si le contenu de la trace utilisée et les analyses proposées sont dédiés à Prolog, les techniques de base exploitent une trace dont le seul pré-requis est d'être séquentielle. La meilleure preuve en est les deux nouveaux prototypes de recherche qui appliquent ces idées aux langages Mercury et C.

Nous avons développé un outil, OPIUM, qui analyse les traces d'exécution de programmes générées par un traceur Prolog existant (cf. module  3.3). Le programmeur peut poser des questions sur les exécutions à l'aide de Prolog et de quelques primitives dans une forme concise en s'appuyant sur la logique et les mécanismes de recherche de Prolog. Les programmeurs peuvent donc spécifier de manière précise ce qu'ils veulent voir du comportement du programme.

Ces requêtes peuvent être traitées à la volée ou a posteriori. Dans le cas d'un traitement à la volée, les performances du système permettent d'analyser plusieurs millions d'événements avec des temps de réponse supportables. L'analyse a posteriori permet de traiter un ensemble plus riche de requêtes mais nécessite de stocker les événements dans une base de données interne, ce qui prend un temps non négligeable. L'analyse proprement dite, par contre, ne prend pas plus de temps que l'analyse à la volée. Du point de vue de l'utilisateur, les requêtes se posent de la même manière dans les deux cas.

À partir du langage de requêtes, nous avons bâti des analyses qui donnent des vues abstraites des exécutions selon certains critères (par exemple, flot de contrôle ou flot de données). Il a également été possible de mettre en oeuvre à faible coût des traceurs abstraits pour des langages de haut niveau, ce qui a permis de mettre au point facilement des démonstrations d'applications sophistiquées [[15]].

Si le contenu de la trace utilisée et les analyses proposées sont dédiés à Prolog, les techniques de base pour mettre en oeuvre le langage de requêtes exploitent une trace qui peut être produite pour n'importe quel langage séquentiel. Nous mettons à profit cette généralité pour concevoir des débogueurs pour les langages Mercury (cf. module  7.1) et C [[31]].

Génération de traces



Participante : Mireille Ducassé.

 

Mots clés : Débogage, traceur, transformation de programmes, mesures de performances, Prolog .

Résumé :

Les traces d'exécution ne sont pas disponibles directement, il faut des outils pour les générer, que l'on appelle des traceurs. Les traceurs actuels modifient en général en profondeur le compilateur, ce qui rend leur portage problématique. La génération de traces d'exécution par transformation source à source de programmes permet d'éviter cet écueil. Un prototype a été réalisé pour Prolog. Des mesures montrent que cette technique, bien que donnant des performances moindres que des techniques de plus bas niveau, peut être acceptable pour construire des traceurs.

Les traces d'exécution ne sont pas disponibles directement, il faut des outils pour les générer, que l'on appelle des traceurs. Les techniques d'implémentation de ces traceurs sont dans l'ensemble mal étudiées. Ce qui nuit, bien sûr, à leur implémentation, mais aussi à leur portage et à leur maintenance. Typiquement, les traceurs modifient en profondeur le compilateur, les structures de données et les schémas d'exécution. Les problèmes avec ce type de traceurs sont de plusieurs ordres: tout d'abord, on perd souvent les optimisations qui donnent tout leur intérêt aux compilateurs; ensuite, l'information est donnée à l'utilisateur en termes de trop bas niveau, il peut même y avoir perte significative d'information; enfin, ces traceurs ne sont pas portables d'un compilateur à l'autre pour un même langage. Nous étudions actuellement la génération de traces d'exécution pour Prolog par transformation source à source de programmes. Cette technique permet d'éviter les problèmes mentionnés ci-dessus. Il se pose alors la question de son efficacité. Nous avons implémenté un prototype. Des mesures montrent que les performances des programmes tracés par transformation de programmes sont au pire deux fois moins bonnes que celles des programmes tracés par deux traceurs opérationnels ``bas niveau'' [[20]]. Cette réduction de performance est acceptable pour un traceur ``standard'' surtout lorsque l'on considére le gain significatif en termes de portabilité. Ce travail est mené en collaboration avec Jacques Noyé, de l'École des Mines de Nantes.

Génération systématique de jeux de test



Participants : Daniel Le Métayer, Valérie-Anne Nicolas, Olivier Ridoux, Lionel Van Aertryck.

 

Mots clés : Test en boîte noire, Test en boîte blanche, Test structurel, Contrainte, Casting, Jeu de test, Suite de test .

Résumé :

Nous avons proposé une méthode de génération de suites de test qui forme le noyau de l'outil CASTING développé en collaboration avec la société AQL. La méthode est indépendante du format d'entrée, ce qui la rend utilisable aussi bien dans le cas du test structurel que fonctionnel. Les suites de test engendrées dépendent de stratégies spécifiées par l'utilisateur, permettant ainsi d'atteindre la souplesse d'utilisation exigée pour un usage industriel.

Nous avons abordé le problème de la systématisation de la génération de jeux de test en tentant d'abolir la dichotomie «boîte noire/boîte blanche» (cf. module 3.4). Pour ce faire, nous décomposons le processus de production des données de test en trois étapes:

1.
L'acquisition des critères de test et la production des hypothèses de test associées, à partir de différents supports d'entrée.
2.
La décomposition des opérations en classes d'opérations et la génération d'un graphe d'accessibilité symbolique.
3.
La génération des jeux de test par parcours du graphe d'accessibilité en assurant un critère de couverture donné.
Les supports d'entrée peuvent être constitués de spécifications formelles ou informelles, de programmes sources ou de propriétés fournies directement par l'utilisateur. Les opérations peuvent être des machines abstraites dans le cas du langage B, des schémas pour le langage Z, des programmes dans le cas d'un langage de programmation, etc. Dans tous les cas, les critères de test sont implantés par des stratégies de test et se traduisent in fine par des hypothèses d'uniformité et hypothèses de régularité. Ces hypothèses permettent de préciser le sens (et les limites) des jeux de test qui seront engendrés (cf. module 3.4). D'un point de vue pratique, une stratégie de test correspond à un mode d'extraction de contraintes à partir du texte source. Ces contraintes caractérisent les jeux de données qui devront être engendrés pour chaque opération du système. Le graphe d'accessibilité symbolique indique l'ordre dans lequel les opérations peuvent être appliquées pour satisfaire toutes les contraintes. Dans le cas général en effet on ne peut faire l'hypothèse qu'une opération est toujours applicable: selon l'état du système, il peut être nécessaire d'effectuer plusieurs opérations intermédiaires avant de pouvoir appliquer une opération donnée. La dernière phase consiste à explorer ce graphe en résolvant les contraintes associées pour générer les données de test effectives.

Ces travaux ont conduit au développement de l'outil d'aide à la génération de jeux de test CASTING [*] [[13]] (cf. module 5). La version actuelle de CASTING prend en entrée des spécifications dans la notation AMN de la méthode B [Abr96] et fait appel à Ilog Solver[*] pour résoudre les contraintes engendrées. Diverses démonstrations de ce prototype ont été réalisées (notamment à la conférence B). Nous travaillons maintenant à la transposition de cette technique pour la génération de programmes C et C++ (test structurel) dans le cadre du projet européen TWO (cf. module 7.2).

Nous nous intéressons conjointement à la manière d'assurer que des programmes sont conformes à des hypothèses de test. Nous considérons dans ce but des schémas de programmes qui peuvent être vus comme une manière de définir des classes de fautes: identifier un programme de manière non ambiguë dans une classe revient à assurer que toutes les fautes ayant pour effet de produire une version de programme dans cette classe seront détectées. À tout schéma de programme est associé un jeu de test fini et robuste [[18]] (c'est à dire permettant de distinguer deux fonctions quelconques du schéma). Ce résultat permet de montrer automatiquement qu'un programme satisfait une propriété donnée (à condition que l'un et l'autre puissent être situés dans notre hiérarchie de schémas). La propriété attendue est définie comme une fonction et la propriété effective est dérivée du programme par interprétation abstraite. Il s'agit ensuite de déterminer le plus petit schéma de la hiérarchie qui les contient toutes les deux. On sait alors que le jeu de test associé à ce schéma est suffisant pour établir l'égalité des deux fonctions. Ce jeu de test peut être concrétisé (opération inverse de l'interprétation abstraite) pour être soumis au programme [[12]].



Footnotes

...[*]
Computer Assisted Software Testing
...[*]
Ilog Solver est une marque déposée par Ilog.


previous up next contents
Précédent : Résultats nouveaux Remonter : Résultats nouveaux Suivant : Actions «amont»