03 - Prouver les programmes : pourquoi, quand, comment ?
Gérard Berry Algorithmes, machines et langages Année 2014-2015 Prouver les programmes : pourquoi, quand, comment ? Troisième leçon : Les méthodes générales : assertions, réécriture, interprétation abstraite, logiques et assistants de preuve Il y a deux principaux types de méthodes formelles pour la preuve de programme : les méthodes générales, qui s'adressent à tous les types de programmes et seront présentées dans ce cours, et celles de la vérification de modèles (model-checking), qui s'intéressent principalement aux programmes à espaces d'états finis et aux circuits électroniques et seront traitées dans le cours no 5 du 25 mars 2015. La nécessité de traiter les programmes comme des objets mathématiques à part entière a été reconnue par A. Turing dès 1949. Il a alors introduit la notion d'assertion associant un prédicat logique à un point de contrôle du programme, ainsi que l'importance de la notion d'ordre bien fondé pour montrer la terminaison des programmes. Son approche par assertions est la première que nous traiterons dans le cours. Elle a été étendue et perfectionnée par R. Floyd, C.A.R. Hoare, E.W. Dijkstra, et bien d'autres, pour aboutir à une théorie et une pratique complètes de la définition logique des langages de programmation et de la vérification de programmes, applicables initialement aux programmes séquentiels impératifs. La notion d'assertion a été ensuite étendue en celle de contrat (assume/guarantee) à respecter par les fonctions ou modules d'un programme, applicable aussi aux langages objets et aux langages parallèles. En 1963, dans un article fondateur militant pour le traitement mathématique de la programmation et introduisant une brochette remarquable de nouveaux concepts, J. McCarthy a introduit l'idée bien différente d'utiliser la réécriture de termes comme outil applicable à la fois pour l'optimisation de programmes et leur vérification formelle. Cette approche, la seconde que nous étudierons, a eu une longue descendance dans les systèmes de vérification (Boyer&More, ACL2, PVS, Key, ProVerif, etc.), et continue d'irriguer les autres approches. Elle a eu des succès remarquables en circuits, en avionique, en sécurité, etc. La troisième approche historique est celle de la sémantique dénotationnelle des langages introduite par Scott et Strachey vers 1970. Ici, un programme est interprété comme une fonction dans un espace topologique ordonné par un ordre d'information, et toutes les fonctions sont rendues totales par l'ajout explicite d'éléments indéfinis. Une théorie générale du point fixe dans les espaces ordonnés permet d'interpréter de façon uniforme les boucles, la récursion et la programmation fonctionnelle d'ordre supérieur. Au début des années 1970, cette théorie a été à l'origine du pionnier des assistants de preuve de programmes, LCF, créé par Milner et al. Mais le traitement explicite de l'indéfini s'est révélé trop compliqué, et les assistants de preuve ont ensuite suivi un autre chemin. La sémantique dénotationnelle a cependant eu un succès considérable dans une autre approche de la vérification, l'interprétation abstraite créé par P. et R. Cousot en 1977. L'idée est ici de travailler avec des assertions portant non plus sur les valeurs exactes calculées, mais sur une abstraction de celles-ci, comme la preuve par 9 le fait pour détecter des erreurs dans la multiplication. De nombreux domaines abstraits ont été développés, ainsi que des méthodes algorithmiques générales de combinaison de ces domaines et d'accélération des calculs de points fixes. L'interprétation abstraite est maintenant développée industriellement. Elle a permis de vérifier des propriétés critiques de très gros programmes, comme l'absence d'erreurs à l'exécution dans le code de pilotage de l'Airbus A380. Elle est également utilisée pour l'évaluation du temps de calcul maximal de logiciels emb