Automated verification of termination certificates
Vérification automatique de certificats de terminaison
Résumé
Making sure that a computer program behaves as expected, especially in
critical applications (health, transport, energy, communications, etc.), is more
and more important, all the more so since computer programs become more
and more ubiquitous and essential to the functioning of modern societies. But
how to check that a program behaves as expected, in particular when the range
of its inputs is very large or potentially infinite? To express with exactness
what is the expected behavior of a program, one first needs to use some formal
logical language. However, as shown by Gödel, in any formal system rich
enough for doing arithmetic, there are valid formulas that cannot be proved.
Therefore, there is no program that can decide whether any property is true or
not. However, we can have a program that decides whether any proof is correct
or not, and the present work will use in an essential way such a program, namely
Coq, to formally prove the correctness of some particular program.
An important program property, especially in systems with strong time constraints,
is termination: will the program always provide an answer? (Un)fortunately,
as shown by Turing, termination is not decidable: there is no
Turing-machine that, for any pair (p; i) made of a finite program description
p and a finite input i for p, can tell in a finite amount of time whether p terminates
on i or not. This led to the development of many different heuristics
and tools (e.g. AProVE, TTT2, . . . ) for trying to prove the termination
of programs. In particular, term rewriting theory, introduced by
Knuth as a tool for deciding algebraic equational theories, provides a general
framework for studying program termination with applications to concrete
programming languages like Prolog, Haskell, Java, . . . This
is the framework that we will consider in this work.
But, in turn, how to guarantee that a program implementing such a heuristic
is correct? One way to break this vicious circle relies on the fact that, for a given
problem, checking that a solution is correct is usually easier than finding such
a solution, because the latter involves some back-tracking mechanism while the
former only requires blind computations. Hence, we can imagine the following
scenario: modify the tool so that it does not only answer YES or NO, but also
outputs some data, called certificate, that can be used to verify the correctness
of the answer; and design these certificates in such a way that their verification
is amenable to a complete formalization and correctness proof. Althought it
seems to only move the problem from one program to another, the certificate
verifier, there is in fact a gain in complexity. For instance, finding a boolean
assignment satisfying some boolean formula (SAT problem) is (in the worst case)
exponential in the number of boolean variables, while verifying the correctness
of a given assignment (the certificate) is linear in the size of the formula. This
is the approach that various automated provers for the termination of firstorder
term rewrite systems started to adopt in 2007. A common formal
language, CPF (Certification Problem Format), has then been designed
for representing termination certificates, and certificate verifiers started to be
developed: Rainbow, CiME3 and CeTA.
In this work, we explain the development of a new, faster and formally
proved version of Rainbow based on the extraction mechanism of Coq. The
previous version of Rainbow verified a CPF file in two steps (see Figure 1.1).
First, it used a non-certified OCaml program to translate a CPF file into a
Coq script, using the Coq libraries on rewriting theory and termination CoLoR
and Coccinelle. Second, it called Coq to check the correctness
of the script. This approach is interesting for it provides a way to reuse in
Coq termination proofs generated by external tools. This is also the approach
followed by CiME3. However, it suffers from a number of deficiencies. First,
because in Coq functions are interpreted, computation is much slower than with
programs written in a standard programming language and compiled into binary
code. Second, because the translation from CPF to Coq is not certified, it may
contain errors and either lead to the rejection of valid certificates, or to the
acceptance of wrong certificates. Moreover, the data type used for representing
certificates internally and the parsing function used to create a value of this data
type from a text file are written by hand. This is a possible source of errors
and is time-consuming. To solve the latter problem, one needs to define and
formally prove the correctness of a function checking whether a certificate is
valid or not. To avoid the problem of parser, one can develop a simple compiler
for generating a Coq data type definition for representing XML Schema data
types, and an XML parser for CPF. To solve the former problem,
one needs to compile this function to binary code. The present work shows how
to solve these two problems by using the proof assistant Coq and its extraction
mechanism to the programming language OCaml. Indeed, data structures
and functions defined in Coq can be translated to OCaml and then compiled to
binary code by using the OCaml compiler. A similar approach was first initiated
in CeTA using the Isabelle proof assistant.
S’assurer qu’un programme informatique se comporte bien, surtout dans des applications
critiques (santé, transport, énergie, communications, etc.) est de plus en plus
important car les ordinateurs et programmes informatiques sont de plus en plus omniprésents,
voire essentiel au bon fonctionnement de la société. Mais comment vérifier
qu’un programme se comporte comme prévu, quand les informations qu’il prend en
entrée sont de très grande taille, voire de taille non bornée a priori ? Pour exprimer
avec exactitude ce qu’est le comportement d’un programme, il est d’abord nécessaire
d’utiliser un langage logique formel. Cependant, comme l’a montré Gödel,
dans tout système formel suffisamment riche pour faire de l’arithmétique, il y a des
formules valides qui ne peuvent pas être prouvées. Donc il n’y a pas de programme qui
puisse décider si toute propriété est vrai ou fausse. Cependant, il est possible d’écrire
un programme qui peut vérifier la correction d’une preuve. Ce travail utilisera justement
un tel programme, Coq, pour formellement vérifier la correction d’un certain
programme.
Une propriété importante, en particulier dans les systèmes informatiques avec des
contraintes temporelles fortes, est la terminaison : le programme va-t-il me fournir
une réponse ? Malheureusement, comme l’a montré Turing, la terminaison n’est
pas décidable en général : il n’y a pas de machine de Turing qui, pour toute paire
(p; i) où p est un programme et i une donnée pour p, puisse dire en un temps fini si p
termine sur i ou non. Cela a conduit au développement de nombreuses heuristiques et
d’outils les implémentant (e.g. AProVE, TTT2, . . . ) pour essayer de montrer la
terminaison de programmes informatiques. En particulier, la théorie de la réécriture, introduite par Knuth comme un outil pour décider des théories algébriques, fournit un cadre général pour étudier la terminaison des programmes avec des
applications à des langages de programmation concrets tels que Prolog, Haskell ou Java. C’est dans ce cadre théorique que nous avons conduit notre travail.
Nous avons vu qu’il n’est pas possible d’avoir un programme qui puisse nous dire
si tout programme est correct ou non. Alors, comment s’assurer que les outils de terminaison
implémentant une certaine heuristique sont corrects ? Une manière de briser
ce cercle vicieux repose sur le fait que, pour un problème donné, il est généralement
plus facile de vérifier qu’une solution est correcte que de trouver une telle solution, car
trouver une solution nécessite souvent d’en essayer plusieurs avant d’en trouver une
qui marche (back-tracking), tandis que vérifier si une solution est correcte ne nécessite
souvent que de faire un calcul. Ainsi, on peut imaginer le scénario suivant. Premièrement,
modifier l’outil de façon à ce qu’il ne réponde pas seulement OUI ou NON, mais
fournisse également un ensemble de données, que nous appellerons certificat, qui peut
être utilisé pour vérifier la correction de la réponse. Deuxièmement, définir ces certificats
de façon à ce que leur vérification peut être complètement formalisée et prouvée.
C’est l’approche qu’ont prise plusieurs prouveurs automatiques de terminaison pour
les systèmes de réécriture du premier ordre à partir de 2007. Un langage pour
les certificats de terminaison, CPF, a ainsi été développé et des vérificateurs de
certificats ont commencé à être développés : Rainbow, CiME3 et CeTA.
Dans cette thèse, nous expliquons le développement d’une nouvelle version de
Rainbow, plus rapide et plus sûre, basée sur le mécanisme d’extraction de Coq. La
version précédente de Rainbow vérifiait un certificat en deux étapes. Premièrement, elle
utilisait un programme OCaml non certifié pour traduire un fichier CPF en un script
Coq, en utilisant les bibliothèques Coq sur la théorie de la réécriture et la terminaison
CoLoR et Coccinelle. Deuxièmement, elle appelait Coq pour vérifier
la correction du script ainsi généré. Cette approche est intéressante car elle fournit
un moyen de réutiliser dans Coq des preuves de terminaison générées par des outils
extérieurs à Coq. C’est également l’approche suivie par CiME3. Mais cette approche
a aussi plusieurs désavantages. Premièrement, comme dans Coq les fonctions sont
interprétées, les calculs sont beaucoup plus lents qu’avec un langage où les programmes
sont compilés vers du code binaire exécutable. Deuxièmement, la traduction de CPF
dans Coq peut être erronée et conduire au rejet de certificats valides ou à l’acceptation
de certificats invalides. Pour résoudre ce deuxième problème, il est nécessaire de définir
et prouver formellement la correction de la fonction vérifiant si un certificat est valide
ou non. Et pour résoudre le premier problème, il est nécessaire de compiler cette
fonction vers du code binaire exécutable.
Cette thèse montre comment résoudre ces deux problèmes en utilisant l’assistant
à la preuve Coq et son mécanisme d’extraction vers le langage de programmation
OCaml. En effet, les structures de données et fonctions définies dans Coq peuvent
être traduites dans OCaml et compilées en code binaire exécutable par le compilateur
OCaml. Une approche similaire est suivie par CeTA en utilisant l’assistant à la
preuve Isabelle et le langage Haskell.
Loading...