HAL Id: tel-02367673
https://hal.archives-ouvertes.fr/tel-02367673
Submitted on 18 Nov 2019HAL is a multi-disciplinary open access archive for the deposit and dissemination of sci-entific research documents, whether they are pub-lished or not. The documents may come from teaching and research institutions in France or abroad, or from public or private research centers.
L’archive ouverte pluridisciplinaire HAL, est destinée au dépôt et à la diffusion de documents scientifiques de niveau recherche, publiés ou non, émanant des établissements d’enseignement et de recherche français ou étrangers, des laboratoires publics ou privés.
et application à la robotique
David Come
To cite this version:
David Come. Analyse de la qualité de code via une approche logique et application à la robotique. Sciences de l’ingénieur [physics]. UNIVERSITE DE TOULOUSE, 2019. Français. �tel-02367673�
THÈSE
En vue de l’obtention du
DOCTORAT DE L’UNIVERSITÉ DE TOULOUSE
Délivré par l'Université Toulouse 3 - Paul Sabatier
Présentée et soutenue par
David COME
Le 25 janvier 2019
Analyse de la qualité de code via une approche logique et
application à la robotique
Ecole doctorale : EDMITT - Ecole Doctorale Mathématiques, Informatique et
Télécommunications de Toulouse
Spécialité : Informatique et Télécommunications
Unité de recherche :
ISAE-ONERA MOIS MOdélisation et Ingénierie des Systèmes
Thèse dirigée par
Virginie WIELS
Jury
Mme Julia LAWALL,M. Jacques MALENFANT, Rapporteur M. David DOOSE, Examinateur M. Julien BRUNEL, Examinateur
Mme Karen GODARY-DEJEAN, Examinateur Mme Virginie WIELS,
Rapportrice Président Directrice de thèse Examinateur M. Jean-Paul BODEVEIX, M. Alcino CUNHA,
THÈSE
THÈSE
En vue de l’obtention duDOCTORAT DE L’UNIVERSITÉ DE TOULOUSE
Délivré par : l’Université Toulouse 3 Paul Sabatier (UT3 Paul Sabatier)
Présentée et soutenue le 25/01/2019 par :
David COME
Analyse de la qualité de code via une approche logique et application à la robotique
JURY
JULIALAWALL Rapportrice
JACQUESMALENFANT Rapporteur
VIRGINIEWIELS Directrice de thèse
JULIENBRUNEL Encadrant
DAVIDDOOSE Encadrant
JEAN-PAULBODEVEIX Président
KARENGODARY-DEJEAN Examinatrice
ALCINOCUNHA Examinateur
École doctorale et spécialité :
MITT : Domaine STIC : Réseaux, Télécoms, Systèmes et Architecture
Unité de Recherche :
Office national d’études et de recherches aérospatiales (ONERA)
Directeur(s) de Thèse :
Virginie Wiels, Julien Brunel (Encadrant) et David Doose (Encadrant)
Rapporteurs :
Table des matières
Table des matières iii
Liste des figures v
Liste des tableaux vii
I Introduction 3
1 Introduction 5
1.1 Contexte de la thèse . . . 6
1.2 Positionnement de la thèse et études . . . 16
1.3 Bilan et démarche. . . 20 2 Fondations logiques 21 2.1 Logiques formelles . . . 22 2.2 Model checking . . . 32 2.3 Combinaison de logique . . . 38 II Contributions 41 3 Problématique et exigences 43 3.1 Exigences. . . 44
3.2 Démarche et réponses aux exigences . . . 45
4 Définition de FO++ 47 4.1 Présentation globale . . . 48 4.2 Syntaxe . . . 48 4.3 Sémantique . . . 51 4.4 Résumé . . . 53 5 Model-checking pour FO++ 55 5.1 Model-checking de FO++ . . . 56 5.2 Algorithme de model-checking . . . 56
5.3 Correction et terminaison de l’algorithme . . . 59
5.4 Complexité. . . 61
5.5 Résumé . . . 61
6 FO++pour C++ 63 6.1 Sortes . . . 64
6.2 Fonctions et prédicats non temporels . . . 64
6.3 Extraction d’une structure d’interprétation . . . 64
6.4 Résumé . . . 67 iii
7 Pangolin 69
7.1 Vue d’ensemble . . . 70
7.2 Point de vue de l’utilisateur . . . 70
7.3 Éléments de conception . . . 80
7.4 Résumé . . . 83
8 Applications 85 8.1 Analyse de la qualité de paquets ROS . . . 87
8.2 Le design pattern ROSApplication . . . 92
8.3 Vérification de règles venant de standards . . . 95
8.4 Autres règles de bonne pratique de programmation pour le C++ . . . 97
8.5 Applications sur Pangolin lui même . . . 102
8.6 Résumé . . . 103 III Conclusion 105 9 Conclusion 107 9.1 Langage de spécification. . . 108 9.2 Outil d’analyse. . . 109 9.3 Applications . . . 110 IV Annexes 111 A Annexes I
A.1 Walkthrough du code. . . I
A.2 Transformation de IMU Bias remover . . . XV
A.3 Grammaire du langage utilisateur . . . XXI
A.4 Certificat de correction. . . XXVIII
A.5 Exemples de PREDEKS . . . XXXII
A.6 Liste complète des fonctions et prédicats de FO++pour C++ . . . XXXII
B Publications et résumé XXXVII
B.1 Résumé . . . XXXVIII
Liste des figures
1.1 Armag et Don, robots 2017 de l’équipe A.I.G.R.I.S, vice-champions de la coupe de France
de robotique, 3emeen coupe d’Europe. . . 6
1.2 Modèle de compilation pour le langage C++ . . . 14
1.3 Illustration de l’AST. . . 17
1.4 Illustration du CFG . . . 17
2.1 Sémantique des opérateurs futurs de LTL . . . 28
2.2 Sémantique des opérateurs de CTL . . . 30
2.3 Structure de Kripke sur laquelle F G q est vraie mais pas AFAGp. . . . 32
3.1 Démarche globale suivie dans ces travaux . . . 45
5.1 Illustration de la procédure d’évaluation des prédicats temporels . . . 57
5.2 Algorithm de réduction illustré . . . 58
5.3 Illustration de la stratégie d’évaluation. . . 59
6.1 DomaineD pour le code 6.1. Les différents éléments sonttypeseted, les sortes en ma-juscules et les partitions vides ne sont pas affichées. . . 65
6.2 Génération d’un EKR à partir d’un CFG . . . 66
7.1 Vue globale de Pangolin . . . 70
7.2 Arbre des résultats pour la propriété 7.1 sur le code 7.16 . . . 77
7.3 Début du certificat de correction : deux premières étapes de réécriture . . . 78
7.4 Vue interne de Pangolin . . . 81
7.5 Organisation en package de Pangolin. . . 82
8.1 Source inconnue . . . 86
A.1 Hiérarchie de classes pour la représentation en mémoire des expressions de FO++ . . III
A.2 Hiérarchie de classes pour les termes temporels . . . IV
A.3 Diagramme UML des classes pertinentes pour la représentation des ensembles . . . . V
A.4 Illustration schématique de la mémoire avant/après réécriture . . . V
A.5 Représentation schématique de la structure en mémoire . . . VI
A.6 Intégralité du certificat de correction . . . XXXI
Liste des tableaux
1.1 Ensemble des critères désirés pour la qualité du produit selon la norme ISO 25030 . . 9
3.1 Liste des exigences attendues pour les travaux de thèse . . . 44
6.1 Notation au sein des formules des différentes sortes . . . 64
6.2 Sous ensemble partiel des prédicats du premier ordre pour l’instanciation de FO++pour le C++ . . . 64
7.1 Liste des dépendances externes de Pangolin . . . 81
7.2 Liste des modules de Pangolin . . . 82
8.1 Tableau synthétique de la mesure de la qualité des paquets ROS . . . 91
8.2 Temps pris pour les mesures en fonction du mode de recherche . . . 92
A.1 Liste des ensembles prédéfinis . . . XXXIII
A.2 Liste des fonctions . . . XXXIII
A.3 Liste des prédicats . . . XXXIV
Remerciements
Le présent manuscrit est le résultat de trois ans de travail et de nombreuses personnes ont contri-bué à ces travaux d’une manière ou d’une autre et je tiens à les remercier.
Je tiens tout d’abord à remercier mes deux encadrants, Julien Brunel et David Doose. Ils ont su me guider et m’orienter dans cette aventure, et sans eux, ce travail n’existerait tout simplement pas. Je voudrais aussi remercier mes rapporteurs, Julia Lawall et Jacques Malenfant, d’avoir accepté de rap-porter cette thèse. Je tiens aussi à remercier les autres membres du jury, Jean-Paul Bodeveix qui a ac-cepté de présider ma soutenance, ainsi que Karen Godary-Dejean et Alcino Cunha qui ont acac-cepté de participer. Je souhaite remercier Christophe Garion et Tanguy Perennou pour m’avoir permis de dé-couvrir le rôle de (vrai) professeur dans l’enseignement supérieur. Je tiens aussi à remercier plusieurs personne au DTIS et en particulier Charles Lesire, Claire Pagetti et Rémi Delmas pour leurs conseils et remarques en diverses occasions. Enfin, Camille Coti et Fabien Andre pour nos conversations sur le déroulement d’une thèse et le monde de la recherche.
Sur un plan plus personnel, je tiens à remercier tout particulièrement Hélène, pour son sou-tien indéfectible et ses encouragements durant ces trois années. Sans elle, ce manuscrit n’aurait pas vu le jour. Un grand merci aux (ex-)doctorants du DTIM et stagiaires, en particulier Kevin, Hugo, Guillaume, Matthieu, Emanuele, César, Jeanne, Nathanel, Paul et Nadir. Nos discussions animées sur les séries ainsi que les défuntes parties de cartes ont particulièrement contribué à rendre l’aventure supportable dans les moments difficiles. Je tiens à remercier les AIGRIS (anciens et actuels) : Bap-tiste, Thomas, Benoit, Anatole, Pierre-Louis, Nicolas, Antoine, Jean, Raphaël et Pierre. Je suis content d’avoir travaillé avec vous et fier de nos résultats. Nos robots, bien que chronophages et pas toujours entièrement opérationnels, étaient une vraie bouffée d’oxygène. Je tiens aussi à remercier mes coloca-taires, Fabien, Laurent et Guillaume. Vous avez chacun contribué à rendre ces trois années spéciales et inoubliables et j’ai énormément appris et beaucoup ri avec vous. Ensuite, les membres de la LUDI Toulouse, qui sont trop nombreux pour être cités individuellement, mais qui m’ont offert de magni-fiques moments dans ma dernière année. Finalement, je remercie ma famille (et en particulier ma mère et ma grand-mère) pour leur soutient et leurs encouragements.
Première partie
Introduction
Chapitre 1
Introduction
« Mon frère, il peut pas aller à l’école. Quand on lui explique un machin technique, il s’évanouit. »
Jean-Christophe Hembert, Kaamelott, Livre II, O’Brother, écrit par Alexandre Astier
Sommaire
1.1 Contexte de la thèse . . . . 6
1.1.1 Robotique . . . 6
1.1.2 Problématique de la qualité du code. . . 6
1.1.3 Spécificité du code robotique. . . 12
1.1.4 Difficultées posées par le C++. . . 13
1.2 Positionnement de la thèse et études . . . . 16
1.2.1 Positionnement de la thèse . . . 16
1.2.2 Outils et formalismes de spécification existants . . . 16
1.3 Bilan et démarche. . . . 20
1.1 Contexte de la thèse
1.1.1 RobotiqueLa première utilisation du mot robot date de 1921 par Karel Capek (dramaturge tchèque) dans une pièce de théâtre R.U.R. : Rossums Universal Robots. Le mot a alors un sens de travail ou servage. L’usine est d’ailleurs l’un des premiers endroits où apparaissent les robots. Dès 1961, un premier robot appelé Unimate est employé par General Motors dans son usine d’Ewing Township pour retirer des pièces d’une cellule de fonderie sous pression. Ces premiers robots opèrent dans un environnement contrôlé et effectuent des tâches répétitives et/ou dangereuses préprogrammées. Ils nont donc pas besoin de capacités de décision évoluées. La définition moderne de robot apparaît à la fin des années 70, début des années 80.
Définition 1.1 (Robot). Un robot est un système mécatronique multifonctionnel, programmable, ca-pable de réaliser un éventail de tâches de manière autonome en s’adaptant à son environnement et ses variations.
FIGURE 1.1 – Armag et Don, robots 2017 de l’équipe A.I.G.R.I.S, vice-champions de la coupe de France de
robotique, 3emeen coupe d’Europe
La robotique moderne a pendant longtemps été confinée dans les laboratoires. Mais c’est aujourd’hui un secteur en pleine expansion qui trouve des applications dans de nombreux domaines tels que l’agriculture, le médical, le militaire, le transport ou encore la science et le monde éducatif (tel qu’illustré sur la figure 1.1). Les missions envisagées (ou déjà confiées) vont de la surveillance et manipulation de patients au désherbage et à la récolte des cultures en passant par le transport de biens et de personnes ou encore l’exploration de zones dangereuses. Le secteur robotique dis-pose d’enjeux industriels et sociétaux forts. En 2013, le poids économique mondial du secteur de la robotique était estimé à 29 milliards de dollars.
Les robots sont des systèmes complexes transdisciplinaires faisant appel à un large éventail de disciplines (mécanique, électronique, automatique et contrôle, traitement d’image, informatique). La qualité globale du système est directement impactée par la qualité de son code informatique de par sa prépondérance en son sein.
1.1.2 Problématique de la qualité du code
La problématique de la qualité du code est une problématique générale et touche n’importe quel développement informatique.
1.1.2.1 Motivation d’un code de qualité
1.1. CONTEXTE DE LA THÈSE 7
La première est la gestion des coûts et des délais. Il est essentiel dans un cadre industriel de dé-velopper les fonctionnalités demandées dans les coûts et délais prévus. Or un logiciel mal écrit va impacter négativement les coûts et délais de réalisation : le développement risque d’être plus long et/ou plus coûteux. En effet, les durées d’analyse, de conception, de réalisation et de vérification sont toutes rallongées par rapport à une situation où le logiciel serait mieux écrit.
La seconde facette est la gestion des risques du système final. En fonction du type de logiciel, un dysfonctionnement n’a pas les mêmes conséquences. Pour un logiciel de bureautique, une er-reur ou un plantage peut être gênant pour l’utilisateur, mais n’a, en général, pas de conséquences graves. À l’inverse, les logiciels embarqués sont souvent critiques. Un dysfonctionnement peut en-traîner une perte de vies humaines ou avoir des conséquences (financières, scientifiques, environ-nementales . . . ) graves. On peut citer par exemple le crash en 1992 du premier Raptor F-22 sur la base aérienne d’Edwards en Californie dû à d’une erreur dans les commandes de vol sur la pré-vention du pompage piloté ?. Un autre exemple est la panne de courant généralisée sur le Nord est américain en 2003 causée par une race condition dans le système de surveillance XA/21 de la division énergie de General Electric ?. On estime que cette coupure a impacté 55 millions de personnes au Canada et aux États-Unis et a engendré la mort d’au moins 100 personnes dans divers accidents.
1.1.2.2 Définition de la qualité du code
Il existe plusieurs définitions possibles pour la qualité, mais nous nous concentrons dans ces tra-vaux sur deux définitions tirées des normes IEEE ? et ISO/IEC 25030 ?.
Définition 1.2 (Qualité logicielle selon IEEE). La qualité d’un logiciel est définie comme l’ensemble des propriétés et caractéristiques d’un logiciel qui impacte sa capacité à répondre à des exigences données.
La norme ISO/IEC 25030 (qui fait partie de la série de normes 250Xn, diteSQuaRE) porte sur les exigences de qualité et définit la qualité de la manière suivante :
Définition 1.3 (Qualité logicielle selon ISO/IEC 25030). La qualité d’un logiciel est définie comme l’ensemble des caractéristiques qui affectent la capacité du produit final à répondre à des exigences, quelles soient implicites ou explicites.
La norme ISO/IEC 25010 définit deux modèles de qualité : un modèle de la qualité d’usage et un modèle de qualité du produit. Les caractéristiques définies par ces deux modèles sont génériques et fournissent une terminologie cohérente pour spécifier, mesurer et évaluer la qualité du système et du logiciel.
Qualité d’usage Le modèle définit cinq caractéristiques qui peuvent être vues comme des axes d’évaluations ou de mesure quant à l’utilisation du système final dans un contexte donné. Les cri-tères retenus pour la qualité d’usage sont :
— Efficacité : dans quelle mesure les résultats sont-ils justes et complets ?
— Performance : quelle est la quantité de ressources utilisées pour arriver au résultat et est-elle acceptable ?
— Satisfaction : dans quelle mesure l’utilisateur est-il satisfait par l’utilisation du logiciel ? Cela comprend en particulier l’utilité du logiciel, la confiance accordée au résultat ou le confort d’utilisation.
— Absence de risque : dans quelle mesure le logiciel va-t-il atténuer les potentiels risques économiques, environnementaux ou humains ?
— Adéquation au contexte : dans quelle mesure le logiciel est-il adapté à son contexte d’utilisation et adaptable à d’autres contextes ?
Qualité du produit Ce second modèle définit huit grandes caractéristiques désirées qui sont la per-formance, l’adéquation fonctionnelle, la compatibilité, l’utilisabilité, la fiabilité, la sécurité, la porta-bilité et la maintenaporta-bilité. Ces caractéristiques sont ensuite raffinées en sous-caractéristiques mesu-rables. Par exemple, la fiabilité est raffinée en quatre sous-caractéristiques : la maturité, la disponi-bilité, la résistance aux fautes et la recouvrabilité. L’ensemble des caractéristiques et sous caractéris-tiques ainsi que leurs définitions est donné le tableau1.1
1.1.2.3 Validation et vérification
Assurer la qualité du code passe par la mise en oeuvre d’un processus de validation et vérification. Validation Le processus de validation du logiciel consiste à s’assurer que la qualité d’usage du pro-duit est suffisante. La phase de validation consiste à se demander : A-t-on construit le bon propro-duit ? Vérification L’aspect vérification du logiciel consiste à vérifier que la qualité du produit est la plus élevée possible, en s’assurant que le logiciel est dépourvu d’erreur et conçu le mieux possible. La phase de vérification cherche à répondre à la question : A-t-on bien construit le produit ?
1.1.2.4 Mise en oeuvre de la validation et vérification
La mise en oeuvre de la validation et vérification passe par un ensemble de techniques. Les différentes méthodes se séparent en deux catégories. Il existe d’un côté les méthodes dites
dyna-miques, qui vont réaliser des observations et expérimentations à l’aide du système réel qu’on exécute.
Les tests sont les principaux représentants des méthodes dynamiques. De l’autre côté, il existe aussi toute une gamme de méthodes, dites statiques, qui vont chercher à analyser le système sans avoir à l’exécuter. Cette gamme de méthodes recouvre, entre autres, l’analyse sémantique et la revue de code. On va, dans la suite de cette section, détailler les différentes méthodes.
Tests La notion de tests est une notion intuitive : « on vérifie si ça marche ». Cependant, derrière se trouve une grande variété de pratiques qui diffèrent sur le « on » et le « ça » utilisés et sur le sens de « marche ». Ce paragraphe cherche donc à clarifier cette notion.
Définition et objectifs des tests
Définition 1.4 (Test). Processus qui consiste à exercer ou évaluer un système ou un des composants, manuellement ou automatiquement, afin de vérifier la conformité avec les exigences ou établir des différences entre les résultats et les attentes.
Un test vise à mettre en exergue d’éventuels problèmes de l’objet. Mais il ne peut pas prouver l’absence de problème dans le cas général1. Cependant, on essaye empiriquement de s’assurer que si une erreur est présente, alors les tests présents la mettent en évidence via une bonne couverture de
tests. Par ailleurs, un test échoué ne permet pas ni d’identifier la cause de l’erreur ni de la corriger.
Classification des tests On peut classer les différents tests existants selon trois aspects : l’objet testé, l’approche prise par le test et enfin l’aspect de l’objet qui est testé.
Tout d’abord, il est possible de réaliser des tests à quatre niveaux différents : — au niveau d’un composant, d’une fonction (test unitaire).
— au niveau d’un assemblage de composants, d’une partie d’un programme (test d’intégration). — au niveau système côté fournisseur (test système).
— au niveau système côté client (test d’acceptation). Le client procède à des tests en conditions réelles du système.
1.1. CONTEXTE DE LA THÈSE 9 C ar act ér istique S ous-car a ctér istique Définition A déquation fonctionn elle F onc tionn alités corr ect es Les fonct ionnalités répondent el les aux tâches demandées ? F onc tionn alités complètes Les fonct ionnalités répondent -elles à toutes les tâches demandées ? F onc tionn alités appr opr iées Les fonct ionnalités facilitent el les l’exécution des tâches demandées ? P er for mance T emps T emps pr is pour répondr e aux tâch es demandées . R essour c e A utr e ress our c es pr ises pour ré pondr e au x tâch es demandées . C ompat ibilité C oexistenc e C apac ité à répondr e aux tâches demandées dans un envir on nemen t par tagé sans impact sur les autr es ? In ter op ér abilité C apac ité à échang er des inf or mations . U tilisabili té A déquation C apac ité des util isateurs à reconn aîtr e si le pr oduit convient à leur s besoi ns . C ourbe d ’app ren tissag e C apac ité d ’un sy stème à ce qu e ses utili sateurs le manipulent av ec efficacité. F acilité d ’utilisation Le pr oduit est-il facile à pr end re en main, à u tiliser et opér er ? P rotect ion u tilisateur Le sy stème pr otège-il l’utili sateur de ses er reurs ? Esthét iqu e L’int er face est-ell e plaisante et agréable ? A ccess ibili té Le sy stème est-il utilisable par tous pour att eindr e un objectif donné ? F iabilité M at ur ité C apac ité à répondr e aux exi genc es de fiabili té en situation nominale . D isponibilité Le sy stème est-il utilisable q uand demandé ? Résista nce aux fautes O pér abilité en présence de fautes matér ielle s o u lo gicielles . R ecouvr a bilité Récupér abilité des données et l’éta t v oulu e n cas de panne ou d ’err eur . Sécur ité C onfiden tialité C apac ité à li miter l’acc ès au x personnes autor isées . In tégr ité In ter d iction de l’accès et/ou modifications aux personnes non autor isées . N on -répudiation C apac ité à pr ouv er qu e des faits ont eu lieu . R esponsa bilité C apac ité à li er les actions à une personne uniq ue . A uthen ticité C apac ité à pr ouv er l’ident ité d ’un su jet ou d ’une ressour ce . M aint enab ilité M odular ité Le sy stème est-il composé de sous-éléments inter ag issant peu ? Réutilisabilité M esur e à réutil iser tout ou par tie du système dans un nouv eau. Analysibilité C apac ité à compr en dr e le sy stème . E v olutivi té C apac ité à fair e év oluer le système sans intr oduir e défauts e t régr essions . T esta bilité C apac ité à établir des cr itèr es de test et à le s mesu rer. P or tabilité A dapt abilité Le sy stème peut-il êtr e u tilisé des conditions différ ent es ? F acilité d ’inst allation Le sy stème peut-il êtr e déplo yé/r et iré de son e nvir onn ement facilement ? R emplacibilité Le sy stème peut-il remplacer u n pr oduit av ec le s mêmes fonct ionnalités à iso-e nvir onn ement ? T A B L E A U 1.1 – E n semble des cr itèr es désirés pou r la qu alité du pr oduit selon la nor me ISO 2 50 30
Ensuite, il existe deux approches principales pour les tests : une approche dite boite blanche et une approche boite noire. Dans une approche boite blanche, la structure interne de l’objet est connue et utilisée au sein du test. À l’inverse, dans une approche boite noire, l’objet testé est considéré comme opaque et le test se concentre sur la relation entre les données en entrée et celles en sortie. Enfin, de nombreux aspects de la qualité sont testables : qualité fonctionnelle, performance, robustesse, sécurité . . . .
Tests unitaires Les tests les plus couramment utilisés sont des tests fonctionnels au niveau com-posant ou système. En effet, ils sont relativement simples à mettre en place : il existe beaucoup de bibliothèques pour mettre en place des tests unitaires dans différents langages de programmation. On peut citer par exemple JUnit pour Java, Google Test ou CppUnit pour le C++ ou encore ScalaTest pour Scala. Leur écriture peut être manuelle ou partiellement automatique à partir de modèles
(mo-del based testing). Ils sont déterministes2, facilement automatisables et intégrables dans le processus de développement (il est facile de les exécuter après chaque build ou chaque commit). Ils offrent une certaine forme de documentation automatique en spécifiant le comportement attendu du code. En-fin, ils ont également un coût marginal faible : une fois la plateforme mise en place, développer un nouveau test n’est pas coûteux.
Cependant, ces tests représentent du code supplémentaire qu’il faut maintenir et faire évoluer en même temps que les composants testés. Ainsi, si un test ne passe plus après une évolution du code, il est nécessaire de déterminer si le code est faux ou si le test n’est plus adéquat, même si des tentatives de résolutions automatiques existent ?.
Analyse sémantique La notion d’analyse sémantique (ou encore analyse statique, sémantiques ou
méthodes formelles) décrit un ensemble de méthodes qui cherchent à déterminer des propriétés et
in-formations sur des programmes informatiques sans les exécuter. Le point commun de ces méthodes est d’avoir une fondation mathématique solide et se baser sur le sens des programmes. Elles vont chercher à s’assurer que certaines classes d’erreurs (comme des accès invalides à la mé-moire ou des calculs erronés) soient absentes du programme.
Méthodes d’analyse sémantique L’interprétation abstraite ? est une technique constructive qui vise à approximer la sémantique des programmes par des points fixes de fonctions monotones sur des
treillis complets. Le théorème de Tarski permet, avec les
hypothèses de fonctions monotones et de treillis complets, de garantir l’existence d’un point fixe. La sémantique précise du programme est projetée dans un domaine abstrait, plus simple à manipu-ler que la sémantique concrète, mais qui possède assez d’informations pour répondre à des questions sur le programme. Par exemple, pour s’assurer qu’on ne divise jamais par zéro, il n’est pas nécessaire de connaître la valeur exacte de chaque dénominateur au sein du programme, savoir s’il est nul ou non suffit. En pratique, les domaines abstraits sont conçus en prenant en compte tant le programme à analyser que le type de propriétés à vérifier dessus. L’interprétation abstraite a été utilisée avec succès dans plusieurs projets : on peut citer Airbus qui utilise ASTREÉ ? pour vérifier le code C utilisé dans les commandes de vol des avions, ou encore la NASA et son outil IKOS ?, utilisé sur les contrôleurs dans des projets tels Mars Pathfinder ou Deep Space One.
Le model-checking est une technique d’analyse des systèmes qui consiste à s’assurer qu’un modèle du système respecte certaines propriétés. Par exemple, on peut souhaiter vérifier qu’il est toujours possible de revenir à l’état initial du système ou qu’il ne rentre jamais dans certains états problématiques. En général, les propriétés sont décrites dans une logique temporelle, un formalisme qui permet de parler de l’écoulement du temps ainsi que du séquencement d’événements à travers le temps. Le model-checking a été utilisé avec succès sur des programmes Scade, sur du code C ? ou en-core Java ?. Il existe de nombreux outils pour réaliser du model-checking, on peut citer, entre autres, Alloy ?, uPPAAL ?, spin ? ou encore nuSMV/nuXmv ?. Les aspects théoriques du model-checking sont détaillés dans la section2.2.
1.1. CONTEXTE DE LA THÈSE 11
L’exécution symbolique ? est une technique d’analyse de code qui consiste à attribuer aux
diffé-rentes expressions présentes dans le programme non pas des valeurs concrètes (comme 5 ou ’a’), mais des valeurs abstraites. Les différents chemins d’exécution au sein du programme ajoutent des contraintes sur les valeurs que les variables peuvent prendre à chaque instant. À l’aide des valeurs abstraites et des contraintes, il est possible de faire de la génération de tests (on sait comment at-teindre chaque point du programme), de la recherche de code mort (les contraintes récoltées le long d’un chemin sont incohérentes) ou encore la détection de bugs et vulnérabilités (les contraintes violent une propriété). Les principaux moteurs d’exécution symboliques sont KLEE pour le bitecode LLVM ? et SAGE ?. L’exécution concolique est une extension de la méthode qui consiste à intégrer des exécutions classiques du programme au sein de l’analyse. L’idée directrice est que l’exécution clas-sique va diriger l’exécution symbolique (cela sert d’heuristique sur les chemins à explorer en pre-mier).
Les méthodes déductives, avec la logique de Hoare, vont chercher à prouver que des assertions sur l’état du programme sont valides après son exécution (les post-conditions), étant donné certaines hypothèses avant son exécution (pré-condition). La méthode génère en partant des post-conditions du programme et en remontant ses instructions, dites obligations de preuves. Si on arrive à prouver ces obligations de preuves, alors le programme est correct. Cette vérification est généralement assu-rée par des prouveurs automatiques, bien qu’elle puisse être faite à la main. Frama-C avec son plugin WP ? est un exemple d’analyseur statique basé sur cette méthode pour le langage C. La logique de séparation est une extension de la logique de Hoare, qui permet de facilement raisonner sur les mo-difications successives de la mémoire. Infer.SL par Facebook ? est un outil d’analyse statique multi langages qui se base sur cette logique.
Avantages et inconvénients De manière générale, de part le théorème de Rice, il est impossible de prouver automatiquement de façon juste (i.e. sound3et complete4) toute propriété non triviale sur la sémantique d’un programme arbitraire. Il faut alors relâcher une hypothèse. On peut tout d’abord chercher à relâcher la terminaison automatique de deux manières possibles. Il est possible de soit requérir une intervention humaine à un certain moment, soit de rendre la méthode semi-décidable : la méthode peut ne jamais terminer sur certaines instances du problème. Relâcher la justesse rendra la méthode soit unsound soit incomplete. Dans le premier cas, il existe des faux négatifs : la méthode laisse passer des erreurs et elle accepte des programmes non valides. Dans le second cas, il existe des faux positifs : certaines erreurs trouvées ne sont pas de vraies erreurs et la méthode rejette des programmes valides. Finalement, relâcher la dernière hypothèse revient à restreindre l’ensemble des programmes sur lesquels la méthode peut s’appliquer.
Les différentes méthodes mentionnées ci-dessus sont automatiques une fois que l’ensemble des
éléments nécessaires à la méthode sont disponibles. Mais pour y arriver, elles nécessitent toutes une
phase manuelle de conception et spécification, ce qui les rend coûteuses et difficilement accessibles à tous. Dans le cadre de l’interprétation abstraite, il s’agit de concevoir le domaine abstrait et les opéra-tions associées. Pour le model-checking, les propriétés à vérifier sont définies et spécifiées manuellement, de même que le modèle dans certains cas. Enfin, en ce qui concerne les méthodes déductives, il est nécessaire de spécifier les pré et post conditions voulues, ainsi que les invariants de boucle.
Par ailleurs, pour se conformer au théorème de Rice, les différentes méthodes relâchent différentes hypothèses. L’interprétation abstraite et le model-checking sont incomplete : elles peuvent donc générer des faux positifs. En effet, le premier calcule une sur-approximation des états qui sont atteignables alors que le second utilise un modèle qui est une approximation du programme réel. Dès lors, une erreur au sein du modèle peut ne pas correspondre à une réelle erreur du programme. Les méthodes déductives peuvent générer de fausses alarmes si les prouveurs automatiques échouent à prouver une obligation de preuve. Les méthodes d’exécution symbolique se heurtent aux boucles non bornées. Elles peuvent alors soit se limiter à des programmes dont les boucles sont bornées soit définir une borne arbitraire (par exemple 4), mais deviennent alors unsound.
3. la méthode accepte tous les programmes valides
Preuve de programme Les assistants de preuve sont des environnements informatiques de gestion de preuves formelles. Ils permettent de définir des objets mathématiques, leurs propriétés ainsi que des théorèmes et assurent la correction des différentes définitions et preuves.
Ils peuvent être utilisés pour la vérification du programme de plusieurs manières. On peut tout d’abord exprimer la propriété le programme p est correct en tant qu’énoncé mathématique et ten-ter de le prouver. Si on arrive à écrire une preuve de cet énoncé, alors on sait que le programme est correct. On peut aussi les utiliser pour développer des analyseurs de programme (model-checker, in-terpréteur abstrait. . . ) qui sont alors garantis d’être corrects. On peut enfin représenter le programme
p par un terme t dans l’assistant et la spécification du programme par un type T tel que le terme t ait
le type T (i.e.t : T) ce qui implique que p soit correct. L’utilisation des assistants de preuve, bien que très puissante, est cependant coûteuse, car elle demande une expertise forte.
Il existe plusieurs assistants de preuve tels que Coq ?, Isabelle/HOL ? ou encore PVS ?. Coq a par
exemple permis le
développement de CompCert C ?, un compilateur C certifié écrit et prouvé avec Coq.
Développement par raffinements Une méthode possible pour s’assurer de la correction du programme vis-à-vis de ces spécifications est l’utilisation d’une méthode de raffinements successifs. La méthode consiste à écrire une spécification initiale, puis à la raffiner successivement jusqu’à arri-ver à du code informatique. La spécification va préciser les différentes variables d’état du système, ses invariants ainsi que ses opérations. La relation de raffinement va raffiner l’état du système et va refor-muler chaque propriété et opération avec ces nouvelles variables. La méthode B ? (et son évolution l’Event-B ?) est la méthode de développement par raffinement la plus connue. Une de ses premières utilisations industrielles est le système METEOR pour le métro parisien ?.
Revue de code La revue de code (code review) consiste en une analyse du code source par une per-sonne autre que l’auteur initial du code. Du point de vue de la qualité du code, la revue va chercher à détecter de potentiels défauts tels que le non-respect des standards et conventions, de potentielles erreurs, ou encore des problèmes de lisibilité ou de complexité. L’analyse peut être manuelle et faite par simple relecture du code ou être outillée à l’aide d’un logiciel d’analyse. Ces logiciels peuvent cal-culer des métriques de code, assurer le respect de conventions syntaxiques (nom, indentation), mais aussi rechercher du code qui ne respecte pas certaines propriétés, éventuellement fournies par l’uti-lisateur. On peut par exemple citer Crucible par Atlassian, Gerrit par Google ou encore LGTM pour les
pull requests de GitHub et Bitbucket (logiciel de recherche de vulnérabilité et d’analyse de la qualité
du code).
1.1.3 Spécificité du code robotique
Le développement d’une application robotique est un processus complexe, car il nécessite de disposer de compétences métiers (automatique, traitement d’image . . . ), mais aussi spécifiques à l’informatique à cause des contraintes de la plateforme cible (langage utilisé, taille, performance du code, modularité, communication, manipulation d’éléments à plusieurs niveaux d’abstraction . . . ). En raison de ces contraintes informatiques, le C++ est un des principaux langages utilisés dans la robotique5. Pour permettre au développeur de se concentrer sur le développement des compo-sants qui ont une valeur ajoutée, on utilise des middlewares robotiques. Ces middleware ont des caractéristiques propres et des spécificités qui doivent être prises en compte et qui sont détaillées dans cette section.
1.1.3.1 Middlewares robotiques
Les middlewares robotiques répondent à plusieurs problématiques. Ils augmentent la portabilité de l’application robotique en offrant une interface unique qui s’abstrait du système d’exploitation et 5. Plus globalement, ce langage pose des problèmes intrinsèques pour son analyse qui sont détaillées dans la section
1.1. CONTEXTE DE LA THÈSE 13
du matériel utilisé. Ils améliorent la fiabilité globale du système, car le middleware est testé de fa-çon indépendante par nombreux projets dans des configurations variées. Ils permettent de réduire la complexité du processus en offrant un certain nombre d’outils pour la conception, le développe-ment, et l’exécution qui masquent les aspects bas-niveau. Enfin, ils encouragent à la modularité des applications, ce qui améliore la maintenabilité, la réutilisation du code et l’isolation des erreurs. Il
existe plusieurs middlewares tels qu’OROCOS ?, ROS ?,
YARP ?, ORCA ? , BRICS ?, OpenRave ?, Urbi ? ou encore LCM ?. 1.1.3.2 Cas de ROS
ROS dispose d’un succès indéniable dans la communauté robotique6. Bien qu’il soit utilisé de-puis plus de 10 ans avec succès dans de nombreux projets tels que des drones ? ou des robots auto-nomes agricoles ?, il est peu employé dans le domaine de la robotique industrielle. L’une des critiques les plus courantes est que le logiciel libre n’est pas fiable et présente un risque inacceptable pour les
marchés matures dans lesquels les erreurs ne
doivent pas se produire.
L’initiative ROS-Industrial ? vise à combler le fossé qu’il existe entre ROS et la robotique indus-trielle. Elle cherche en particulier à fédérer une communauté de chercheurs et industriels afin dé-velopper des logiciels robustes et fiables qui répondent aux besoins des applications industrielles. L’objectif est de combiner les forces respectives de ROS (fonctionnalités haut-niveaux à la pointe de la recherche, abstraction matérielle) et des applications industrielles existantes (fiabilité, sécurité, documentation). Dans cette démarche, la qualité du code ROS joue un rôle essentiel, car elle doit se conformer aux normes industrielles. Un des objectifs est donc de créer des outils et pratiques portant sur la qualité des composants ROS. Cela passe entre autres par :
1. La mise en avant de la qualité des paquets ROS : elle doit être mesurable, affichée, sous la res-ponsabilité d’une personne identifiée ;
2. Le développement d’une méthode et des outils d’analyse de la qualité du code des paquets ROS ;
3. L’amélioration de l’intégration continue et du processus de revue de code, en la rendant plus systématique ;
4. La génération automatique de tests ;
5. Le développement d’une culture de la qualité (site internet, événements, . . . ). 1.1.3.3 Études dédiées à ROS
Il existe un certain nombre de travaux qui portent sur l’analyse de code robotique et de ROS en particulier. HAROS ? est un framework d’analyse et de reporting pour du code ROS. Il vise à exploi-ter les spécificités du code robotique et ROS en particulier pour approfondir les analyses. Dans ?, les auteurs cherchent à découvrir les pratiques et patterns les plus courants au sein des paquets ROS via une analyse automatique, avec l’idée que ces résultats servent à de futurs travaux de standardisations et de développement d’outils spécifiques à ROS. Dans ?, les auteurs cherchent à détecter, par l’utilisa-tion de l’analyse dimensionnelle, des inconsistances dans la manipulal’utilisa-tion des messages ROS et des unités physiques associées. Dans ?, les auteurs utilisent les automates temporisés pour modéliser un système utilisant ROS et la Timed Computation Tree Logic pour spécifier des propriétés dessus, tel le non-dépassement des buffers de communication.
1.1.4 Difficultées posées par le C++
Le langage C++ est un langage de programmation générique. Multi-paradigme, disposant à la fois de capacités d’abstraction et de possibilité de contrôler finement la mémoire et les performances, il est un des langages de programmation les plus utilisés au monde. Le C++ reste cependant un langage 6. Bien qu’il n’existe pas d’études à ce sujet, on peut regarder le succès croissant de la ROSCon, la conférence annuelle sur ROS
complexe et orienté expert7et où il est facile de faire des erreurs. C’est donc un langage qui va gran-dement bénéficier des méthodes et outils d’analyse statique. Beaucoup de ces outils vont chercher à s’assurer d’une correction fonctionnelle partielle (non utilisation de pointeurs nuls, aucun accès hors borne, absence de race condition. . . ). Il existe de nombreux outils commerciaux comme Cove-rity, Klocwork Static Code Analysis, PVS, PRQA QA C++. Il existe aussi des nombreux outils libres et/ou universitaires comme Clang Static Analyzer, Infer ?, KLEE ?, LLBMC ?, Divine ?, ou encore SeeHorn ?. Cependant, l’analyse du code C++ reste notoirement difficile. Une partie de la complexité de l’analyse est intrinsèque au langage en raison de sa richesse et de sa complexité8. Mais des difficultés addition-nelles proviennent du modèle de compilation utilisé par le langage. Le reste de cette section détaille ces difficultés ainsi que différents frameworks pour les amoindrir et faciliter l’écriture d’analyseur pour le C++.
1.1.4.1 Modèle de compilation
pre processeur compilateur generateur de code machine
executable
edition des liens fichier 1
pre processeur compilateur generateur de code machine fichier N
pre processeur compilateur generateur decode machine fichier 2
code source pre processé
representation
intermediaire fichier objet
FIGURE1.2 – Modèle de compilation pour le langage C++
Le modèle de compilation du C++ est hérité du C. Il est composé de 3 phases par unité de compilation : il y a une phase de preprocessing, une phase de compilation (parsing + analyse sémantique) et une phase génération du code machine. Une fois toutes les unités de compilation traitées, il y a une phase globale d’édition des liens pour produire le fichier exécutable final. Il est résumé sur la figure1.2.
La phase de preprocessing prend en entrée le fichier source tel qu’il est écrit par le développeur et produit un nouveau document textuel. Pour produire ce document, le préprocesseur procède à un ensemble de substitutions textuelles : les fichiers inclus sont récursivement injectés et les macros sont substituées par leurs définitions, le tout de manière conditionnelle. Avant la phase de preprocessing, du point de vue de la phase de compilation, le code n’est pas valide. Ensuite, la phase de compilation génère à partir du code pré-processé une représentation intermédiaire du code sous forme d’arbre de syntaxe abstrait et de graphe de flot de contrôle. Cette phase est complexe car le langage ne peut être décrit avec une grammaire LL ou LR dotée d’un lookahead arbitraire. Il est nécessaire de prendre en compte des éléments sémantiques afin de pouvoir procéder à une désambiguïsation syntaxique ?. Après, la phase de génération du code machine génère des fichiers objets qui contiennent un code exécutable. Cette phase est une des plus critiques car c’est là que se produisent beaucoup d’optimi-sations de la part des compilateurs. Enfin, une fois tous les fichiers objets générés, le compilateur assemble les différents fichiers objets pour créer l’exécutable.
1.1.4.2 Difficultés qui en émanent
Ce modèle pose de nombreux soucis pour l’analyse de code source. Tout d’abord, il est difficile de faire quoi que ce soit avant d’avoir une représentation intermédiaire du code. Celle-ci est produite
7. Le dernier standard fait plus de 1500 pages 8. Analyser quelque chose de complexe est complexe
1.1. CONTEXTE DE LA THÈSE 15
à partir de ce qui est vu lors de la phase de compilation. Hors c’est potentiellement radicalement différent de ce que le développeur a écrit à cause de l’utilisation des macros. A titre d’exemple, le code1.1contient plus de 28000 lignes après la phase de preprocessing.
1 # i n c l u d e < io str eam > 2 int mai n () {
3 std :: cou t << " H e l l o w o r l d " < < std :: e ndl ; 4 }
Listing 1.1 – Hello word en C++
Sur la phase de preprocessing en elle même, les définitions des macros peuvent êtres intrin-sèques aux fichiers ou données par le programmeur lors de l’invocation du compilateur. Un même fichier peut amener à des représentations intermédiaires radicalement différentes en fonction des fichiers et des macros données. De plus, pour la phase de compilation, les changements faits lors du preprocessing sont souvent intégralement perdus. Les macros posent des soucis dans le cadre du re-factoring de programme ? même s’il existe des travaux pour en tenir compte, dans des programmes C, lors des refactoring ( ?, ?).
Enfin, on compile un unique fichier à la fois sans connaissance des autres fichiers qui com-posent le projet ni de leur contenu. Obtenir une représentation intermédiaire à partir de plusieurs unités de compilation est difficile. En effet, il faut s’assurer de la cohérence des différents éléments au sein des représentations intermédiaires, cohérence qui est grandement impactée par les macros. Une méthode longtemps adoptée pour cela consiste simplement à mettre l’intégralité des sources dans un unique fichier. En plus de poser des soucis de performance évident, cette approche est potentiellement incorrecte : un programme correct peut ne plus compiler ou ne plus avoir la même sémantique en raison des fonctions statiques ou définies dans des namespaces anonymes. En effet, ces fonctions ne sont visibles que dans l’unité de compilation dans laquelle elles sont définies. Des lors que les fichiers sont regroupés, un appel peut éventuellement appeler la fonction qui était à la base cachée au sein d’une autre unité de compilation.
1.1.4.3 Frameworks pour créer des analyseurs
La complexité du C++ est telle qu’il est préférable, lorsqu’on souhaite créer un analyseur de programmes C++, de se baser sur un framework existant pour obtenir des représentations intermédiaires adaptées afin de mieux se concentrer sur l’analyse en elle-même9.
Il y a plusieurs approches possibles. Une première est de s’intégrer dans un framework d’aide à la conception d’analyseur tel que ROSE ? ou DMS Software Reengineering Toolkit ?. ROSE est un fra-mework pour la création d’outils d’analyse et de transformation de code-source à code-source, déve-loppé au Lawrence Livermore National Laboratory. Son objectif principal est de permettre à des
per-sonnes non expertes dans les compilateurs
de construire leur propre logiciel d’analyse et d’optimisation. Il comprend de multiples front-ends (C,C++, Fortran), un middle-end qui permet d’opérer sur les représentations intermédiaires du code et un back-end de regénération du code. A l’inverse de ROSE, DMS est un framework fermé à but commercial. Là encore, il offre un accès aux représentations intermédiaires, des facilités pour écrire des analyses et un mécanisme de transformation du code source.
Une seconde approche est de se baser uniquement sur un front-end pour C++ afin d’avoir accès aux représentations intermédiaires. Souvent plus simple à mettre en place et plus flexible, cette ap-proche demande en contrepartie plus de travail. Il y a plusieurs front-ends possibles. Elsa ? est un par-ser C++ qui a été construit pour démontrer l’efficacité du générateur de parseur Elkhound. Elsa est ca-pable de parser la quasi-totalité de la grammaire C++ (à l’exception des arguments template de
tem-plate). Malheureusement, Elsa ne fait qu’une analyse sémantique partielle du
programme, ce qui fait qu’il accepte des programmes invalides. En outre, sa communauté d’utili-sateurs est plutôt petite et les développements majeurs sur le projet ont cessé il y a quelques années. A l’inverse d’Elsa, EDG ? est un front-end complet pour le C++. Il réalise une analyse syntaxique et
sémantique du fichier et supporte toutes les versions du langage. Il est cependant fermé et à but com-mercial même si les projets académiques peuvent avoir une licence au cas par cas. Enfin, les compila-teurs C++ disposent d’un front-end. Les deux plus grands compilacompila-teurs pour le C++ sont g++ et clang. Malheureusement, le code de g++ est difficile d’accès, peu modulaire et a encore de grande parties écrites en C10?. A l’inverse, Clang dispose d’une base de code moderne écrite en C++11, construit un AST proche du code et y offre facilement accès.
1.2 Positionnement de la thèse et études
1.2.1 Positionnement de la thèseCette thèse se place dans l’axe numéro 2 du projet de ROS-Industrial tel que définie dans la sec-tion1.1.3.2. on cherche à développer une méthode et les outils associés pour analyser la qualité des paquets ROS existants. Il y a plusieurs aspects à prendre en compte. La première est que les paquets ROS sont majoritairement écrits en C++, qui est un langage de programmation complexe. La seconde est qu’il existe beaucoup de paquets qui fonctionnent expérimentalement. On ne cherche pas à reva-lider cet aspect, mais au contraire, à vérifier qu’ils sont écrits de manière satisfaisante.
La définition exacte de cette notion doit être laissée à l’utilisateur. En effet, même s’il existe des pratiques communément admises (ou interdites) au sein de la communauté C++, chaque projet a des conventions, normes et spécificités qui doivent être prises en compte dans la cadre d’une telle démarche. Il est donc essentiel que les utilisateurs puissent préciser et spécifier au sein d’une requête ce qu’ils veulent trouver (ou au contraire, ne pas voir) au sein des paquets ROS. Pour ce faire, il est donc impératif de disposer d’un formalisme de spécification.
1.2.2 Outils et formalismes de spécification existants
Principales représentations du code Différentes représentations du code ont été développées au cours du temps, principalement dans un but d’analyse du code et de conception des compilateurs. Les deux principales représentations du code sont les Abstract Syntax Tree (AST) et le Control Flow Graph (CFG) pour les fonctions. A noter qu’il existe d’autres représentations comme par exemple les
Codes Property Graphs ?.
AST Les AST sont des arbres qui représentent la manière dont les déclarations et expressions constitutives du programmes sont imbriquées et reliées. Au contraire des parse tree, ils sont dégagés de tout élément de la syntaxe concrète du langage tels que les parenthèses ou les virgules. Les diffé-rents noeuds d’un AST représentent des opérations, tandis que les feuilles de ces noeuds représentent les arguments de ces opérations. La figure1.3illustre une partie de l’AST pour le listing1.2.
Listing 1.2 – Code d’illustration pour les représentations AST/CFG
1 c l a s s A {}; 2 c l a s s B { 3 p u b l i c:
4 void ser A (int n ) { 5 a += n ; 6 s t o r e () ; 7 } 8 void ser B ( s t r i n g s ) { 9 s t o r e () ; 10 if( s == " r e s e t " ) { 11 b = " " ; 12 } 13 } 14 p r i v a t e: 15 voi d s t o r e () { 16 l o g g e r . log ( a ) ; 17 l o g g e r . log ( b ) ; 18 } 19 int a ; s t r i n g b ; 20 Log l o g g e r ; 21 }
1.2. POSITIONNEMENT DE LA THÈSE ET ÉTUDES 17 ClassDecl name: B inheritance: none AttributDecl type: Logger name: logger FctDecl ReturnType: void name: serviceB ArgDecl type: string name: s BlockDecl CallExpr callee: store IfStmt CallExpr callee: operator== AssignExpr VarAccess to: b Litteral "" VarAcces to: s Litteral "reset"
FIGURE1.3 – Illustration de l’AST
CFG Un CFG d’une fonction est un graphe, construit à partir de son AST, qui représente de façon explicite l’ensemble de ses exécutions possibles. Chaque chemin au sein de ce graphe est une exécu-tion possible du programme. Chaque noeud du graphe contient un ensemble d’instrucexécu-tions qui sont exécutées séquentiellement (basic block en anglais) et les différents basic blocks sont reliés entre eux par des arêtes. Ces arêtes matérialisent les instructions qui modifient le flot de contrôle (exemple : if,
while, for, . . . ) et portent les éventuelles conditions qui doivent être remplies pour suivre l’arête. Dans
les langages impératifs, le CFG d’une fonction représente la construction mentale la plus utilisée par le programmeur. En effet, on pense une fonction en termes de flot d’exécution et de branches. La figure1.4illustre le CFG de la fonctionserBdu listing1.2.
s == "reset" t store() t b = "" empty empty exit node s1 s2 s3 s4 FIGURE1.4 – Illustration du CFG
Méchanismes de spécification existants Il existe de nombreux formalismes de spécifications basés sur ces deux représentations.
Une première approche est de considérer le code comme un simple texte et d’effectuer des recherches textuelles dessus à l’aide d’outils comme grep ou ack. Rudimentaire, elle reste cependante extrèmement utilisée. Des méthodes plus évoluées de spécification existent. Elles se basent sur une représentation du code source pour gagner en précision, pertinence et capacité expressive.
? introduit un outil de recherche dans le code source, srcQL. Il permet de rechercher du code en utilisant la syntaxe du langage cible. Il se base sur srcML pour avoir une représentation XML du code source. Les requêtes sont assurées par une utilisation conjointe de XPath, d’expressions régulières et patterns unifiés syntaxiquement avec le code en cours d’analyse. Il montre comment l’utiliser pour trouver des points intérêt au sein du programme tels que tous les appels à l’opérateur new. Une li-mitation de la méthode est l’incapacité à combiner ou imbriquer différentes requêtes. Par ailleurs,
l’implémentation actuelle présente des problèmes de stabilité ainsi que de passage à l’échelle. ASTLOG ? est un projet qui vise à examiner directement l’AST d’un programme C/C++ à l’aide d’un langage basé sur Prolog. Le langage développé évite de devoir traduire manuellement le pro-gramme en une base de connaissance Prolog et l’interpréteur du langage permet de rechercher au sein des programmes des erreurs ou constructions hasardeuses. Avec, ils ont réussi à trouver des erreurs au sein de Microsoft SQL server et Microsoft Word. Cependant, cette approche examine
di-rectement l’AST du programme, ce qui exige de l’utilisateur une bonne connaissance des subtilités
de parsing du C, et en particulier du C++. Par ailleurs, l’AST d’un programme n’est pas défini par les normes du C ou du C++, ce qui rend cette approche dépendante de l’outil utilisé pour parser le code source.
Dans ? les auteurs investiguent l’utilisation de méta-modèles pour les langages textuels couplés à l’utilisation de Object Constraint Language ? pour le développement d’analyses statiques. Pour chaque langage cible qu’ils souhaitent analyser, ils décrivent avec Reuseware un méta-modèle qui sert à géné-rer un modèle du programme à partir de son code source, modèle qu’ils interrogent avec OCL. Géné-rer un méta-modèle par langage leur permet de mieux prendre en compte les spécificités de chaque langage cible. Ils montrent comment cette méthode peut être utilisée pour l’analyse de programmes écrits en Java, SQL ou dans un langage dédié aux machines à états. Cependant, cette approche se base uniquement sur l’AST des programmes pour reconstruire les méta-modèles et se limite aux possibi-lités d’OCL pour la formulation de requêtes.
HERCULES/PL ? est un langage de spécification de pattern pour les programmes C et Fortran construit au-dessus du framework HERCULES ?. Le formalisme de spécification se base sur l’uti-lisation du langage cible comme spécification et d’annotations propres au framework HERCULES (comme pragma en C) pour manipuler le code trouvé. Avec, ils recherchent des patterns au sein de boucles dans des noyaux de calcul Fortran. Cependant, leur approche n’opère que sur l’AST de
pro-gramme écrit en C ou en Fortran et est
dépendante du framework HERCULES pour comprendre les annotations spécifiques du code. Dans ?, les auteurs proposent d’utiliser les mécanismes de requête sur des graphes pour effectuer du reverse engineering et recouvrer de l’information. Ils introduisent un langage de requête pour les graphes GReQL basé sur la théorie des ensembles et des prédicats logiques, en particulier les regular
path expressions. Ces requêtes sont exécutées sur des TGraph, des graphes qui modélisent l’intégralité
de l’AST d’un programme. Avec, ils montrent comment calculer des métriques liées à la complexité ou comment établir le graphe d’appel d’un programme.
QL ? propose d’utiliser une base de données relationnelle qui contient une représentation du pro-gramme. Celle-ci est obtenue par un extracteur spécifique à chaque langage source qui va parcourir l’AST du programme AST. On peut alors exprimer des requêtes sur cette base avec un langage de pro-grammation similaire à SQL. Ces requêtes sont compilées dans Datalog11pour être exécutées. Avec, ils ont ré-implémenté le logiciel Error-Prone, un logiciel de vérification pour les programmes en Java avec un gain d’un facteur 5 sur la taille du programme.
? introduit la suite d’outils SOUL pour effectuer de la recherche de code en symbiose avec Eclipse. La suite comprend le langage de requête SOUL, une bibliothèque CAVA de prédicats sur les works-paces Eclipse ainsi qu’un plugin BARISTA pour le lancement et la visualisation des requêtes. Le lan-gage SOUL est un lanlan-gage logique proche de Prolog qui opère sur l’AST du programme. Il dispose néanmoins d’extensions telles qu’une procédure d’unification libre, et matching de snippet de code qui lui permettent de décrire des requêtes via des exemples proches du code final. Avec cette suite d’outils, ils implémentent un outil pour assurer la co-évolution du code source et des annotations spécifiques à SOUL.
Dans ?, les auteurs présentent ARABICA un plugin pour Eclipse pour rechercher du code dans un
programme Java. L’outil utilise des diagrammes de classes et de
séquences UML comme spécification. Ces diagrammes sont, par la suite, traduits en requêtes lo-giques exprimées en SOUL pour être exécutées. Ils se servent d’ARABICA pour analyser le framework JHotDraw, afin de valider les invariants rechercher les différents design patterns utilisés dedans. Mais l’outil, dépendant d’Eclipse, ne fonctionne que pour les programmes écrits en Java.
1.2. POSITIONNEMENT DE LA THÈSE ET ÉTUDES 19
Contrairement à toutes les autres méthodes précédentes, Coccinelle ? ? se concentre sur le CFG d’une fonction C au lieu de son AST.
C’est un programme d’analyse par pattern-matching avancé et de transformation de code source dont le mantra est « find once, fix everywhere » L’idée directrice de Coccinelle est de réaliser du pattern-matching sur le graphe de flot de contrôle des fonctions. Le langage utilisateur de spécification est le Semantic Patch Language (SmPL), dont la syntaxe est basée sur celle l’outil patch. Le but d’un tel langage était de le rendre accessible aux programmeurs C (cible principale de l’outil). Il permet de décrire de façon concise un pattern de code et les transformations à y apporter tout en ignorant de nombreux détails stylistiques tels que l’indentation et les espaces, les commentaires. L’outil prend en compte aussi des variations de style dans la manière de réaliser certaines tâches (comme la vérifica-tion de pointeurs NULL) en utilisant des isomorphismes.
Un exemple classique de Coccinelle est l’ajout de parenthèses lors de l’utilisation d’un ET-binaire et d’une négation d’une constante. Le problème est que dans une expression de la forme!E&C, l’opé-rateur de négation logique (!) se lie plus étroitement que l’opérateur ET-bit à bit (&) ce qui amène à un test dénué de sens (on combine un booléen, c’est à dire 0 ou 1 avec une constante). Le listing1.3
montre un exemple de fichier de spécification qui vise à rechercher et corriger ce problème. Au sein du patch,EetCsont des metavariables, c’est à dire des variables qui peuvent prendre comme valeur n’importe quelle construction du langage C tant que celle ci est cohérente avec le type de la métava-riable. Le patch spécifie les lignes à retirer sont précédées de-tandis que celle à insérer le sont d’un+ et les lignes à faire remonter à l’utilisateur sont précédées de*. Appliqué au listing1.4, il le transforme en celui du listing1.5, avecEqui dénoteblock->flagsetCla constanteACPI_WMI_METHOD.
1 @@ 2 e x p r e s s i o n E ; 3 c o n s t a n t C ; 4 @@ 5 - ! E & C 6 + !( E & C )
Listing 1.3 – fichier SmPL pour détecter et corriger l’absence de parenthèses lors de l’utilisation d’un ET-binaire en C
1 if (! block - > f l a g s & A C P I _ W M I _ M E T H O D ) Listing 1.4 – code C erroné
1 if (!( block - > f l a g s & A C P I _ W M I _ M E T H O D ) ) Listing 1.5 – Code C corrigé
Une autre exemple classique, montré sur le listing1.6concerne, au sein du noyau Linux, le rem-placement d’une allocation viakmalloc()suivi d’un appel àmemset()sur le résultat pour le rem-placer par un unique appel àkzalloc().
1 @@ 2 e x p r e s s i o n x ; 3 e x p r e s s i o n E1 , E2 , E3 ; 4 i d e n t i f i e r f ; 5 typ e T ; 6 @@ 7 - x = k m a l l o c ( E1 , E2 ) ; 8 + x = k z a l l o c ( E1 , E2 ) ; 9 ... w hen != E3 = ( T ) x 10 whe n != ( <+... x ...+ >) = E3 11 whe n != f (... , x , . . . ) 12 - m e m s e t ( x , 0 , E1 ) ;
Listing 1.6 – Fichier SmPl pour corriger introduire kzalloc
En detail, ce patch recheche donc une expressionxa laquelle on affecte le résultat dekmalloc (dont on souhaite récupérer les 2 arguments, d’où l’utilisation des meta variables E1 et E2). La
construction...permet d’ignorer des séquences arbitraires d’instructions non pertinentes et per-met de donc de dire qu’on recherche quelque chose plus loin au sein de la fonction. Ici, il s’agit un appel àmemset, avec comme troisième argument de memset la même expressionE1que celle utilisée pour l’appel àkmalloc. Pour ne pas introduire des changements éronnées, on contraint en plus les chemins en ignorant respectivement ceux où
— xest casté
— xest affectée au moins une fois
— xest passé comme argument d’une fonction
Cet exemple illustre bien l’importance d’être capable de raisonner sur les chemins au du CFG avec finesse. L’outil repose sur des fondations logiques solides. En son coeur, Coccinelle repose sur l’utili-sation de la logique temporelle CTL-VW (CTL avec variables et témoins) et de son model-checking. La logique CTL-VW est une variante proche de la logique FO-CTL, qui permet
— de quantifier via des méta-variables sur les expressions utilisées au sein du code, de sorte que leur valeur soit dépendante du chemin du CFG considéré (partie variables) ;
— de se souvenir des éléments qui satisfont les quantifications sur les variables afin d’identifier les états dans lesquels les transformations doivent avoir lieu.
Un fichier SmPL est traduit en une formule de logique temporelle CTL-VW. Coccinelle traite ensuite le problème de model-checking de cette formule sur le CFG des différentes fonctions du fichier en cours d’analyse. Bien qu’il soit limité à une seule fonction C à la fois, il est une source d’inspiration importante pour ces travaux de thèse.
Dans ?, les auteurs recherchent des design patterns décrits à l’aide d’un formalisme qui est basé sur une combinaison de logique du premier ordre et de logique temporelle intervallaire d’Allen. Le formalisme complet est ensuite traduit en Prolog pour rechercher le design pattern au sein du code source. Avec, ils ont construit un outil PRAssistor et analysé deux projets open source à la recherche de différents design patterns. Cependant, le modèle sémantique du formalisme n’est pas donné (en particulier la façon dont les fonctions sont traitées).
1.3 Bilan et démarche
La possibilité d’effectuer des requêtes sur du code source est un moyen puissant pour en amélio-rer la qualité et il est important de pouvoir spécifier les éléments recherchés. Les deux représentations principales du code source que sont l’AST et les CFG offrent des informations complémentaires. Les CFG fournissent une sur-approximation des exécutions possibles d’une fonction (étant donné que certains chemins peuvent ne jamais être pris). Inversement, l’AST permet de trouver d’autres pro-priétés (structurelles) qui ne sont pas présentes dans le CFG. Ces propro-priétés structurelles peuvent porter soit directement la fonction (son nom, le type de retour déclaré, son appartenance possible à une classe, . . . ) soit sur les objets utilisés dans le cadre d’une fonction, mais définis hors de sa portée (attributs de classe, variables globales, . . . ).
Chaque représentation dispose d’une gamme de formalisme de spécification de requêtes. D’un côté, la logique du premier ordre (sous diverses variations) est largement utilisée pour spécifier des propriétés sur l’AST des programmes ou sur des représentations extraites de l’AST. De l’autre côté, la logique temporelle est adaptée à la définition de propriétés sur les CFG de fonction.
Cependant, il n’existe aucun formalisme qui soit capable d’exprimer des propriétés simultané-ment sur le CFG de plusieurs fonctions tout en exploitant des informations de l’AST. C’est ce qui justifie cette étude. Elle consiste à développer une méthode de vérification automatique de la confor-mité du code source d’applications robotiques (et plus largement C++) vis-à-vis de règles utilisateurs qui peuvent utiliser simultanément et efficacement plusieurs représentations du code.
Chapitre 2
Fondations logiques
« Lorsque vous voulez faire quelque chose de différent du reste du monde, c’est une bonne idée de vérifier si le reste du monde sait quelque chose que vous ignorez. »
Inconnu Ce chapitre vise à donner les bases des différents éléments théoriques et pratiques qui sont utiles dans la suite des travaux. Les aspects théoriques comprennent une introduction à la logique formelle et au model-checking (détaillée pour la logique du premier ordre (FO), de CTL et de LTL) ainsi qu’une présentation des mécanismes existants pour la combinaison de logiques.
Sommaire
2.1 Logiques formelles . . . . 22 2.1.1 Définition générale d’une logique formelle. . . 22
2.1.2 Logique du premier ordre . . . 23
2.1.3 Logiques temporelles . . . 26
2.1.4 Comparaison de LTL et CTL. . . 31
2.2 Model checking . . . . 32 2.2.1 Model-checking la logique du premier ordre . . . 32
2.2.2 Model checking CTL . . . 32
2.2.3 Model-checking LTL . . . 36
2.3 Combinaison de logique . . . . 38 2.3.1 Motivation . . . 38
2.3.2 Méthodes pour la combinaison de logique modales . . . 39
2.3.3 Combinaison de la logique du premier ordre et de logiques temporelles . . . 39