[Atom] [Mail] [Twitter]
Liens : C · git · hacks · divers · cabale · à propos +

Génération de tests, compilateurs, et preuve formelle

Réactions
<. Sécurité et interface utilisateur

Quand il est sorti sur Lambda the Ultimate, j’ai lu avec intérêt l’article Finding and Understanding Bugs in C Compilers (PDF, 12 pages), de Xuejun Yang, Yang Chen, Eric Eide, et John Regehr, 2011. Je compte en faire un petit résumé, mentionner quelques points de l’article qui m’ont intéressé, et surtout faire une longue digression sur un sujet très légèrement abordé par l’article mais qui m’intéresse beaucoup : les rapports entre tests de fonctionnement et preuves de correction dans le développement logiciel.

Pendant la relecture, rz0 fait des remarques sur la forme et des choses à reformuler pour améliorer l’article, mais parfois il se contente de donner simplement son avis. J’ai décidé de garder ses remarques dans le texte, au lieu de me contenter de les lire pour moi et, si l’inspiration vient, d’y répondre par mail.

Le cœur de l’article

L’idée de base est de générer aléatoirement des programmes C, pour trouver des bugs dans les compilateurs. L’efficacité de la technique repose sur deux bonnes idées, une déjà connue et une nouvelle.

Concrètement, générer des programmes aléatoires permet de repérer les plantages (le compilateur lance une exception au lieu de produire un résultat), mais si le compilateur produit du code, comment savoir s’il est correct ? Alors déjà, il faut bien comprendre qu’on ne regarde pas directement le programme assembleur produit pour savoir s’il est correct ; cela demanderait de savoir raisonner sur du code assembleur quelconque, ce qui est bien trop compliqué. Non, on exécute le programme généré, et on regarde le résultat de l’exécution (donc le résultat du résultat du générateur de programmes…).

Que peut-on déduire du résultat de cette exécution ? D’habitude, on génère des tests dont on connaît le résultat (« l’exécution de ce programme va afficher 3 »), mais là on ne sait pas forcément prévoir le résultat correct à partir du programme C généré (pour cela, il faudrait dans le cas général avoir à disposition un compilateur C sans bugs…). C’est là qu’entre en jeu la première idée : on va comparer les sorties des différents compilateurs (GCC, Clang/LLVM, ICC, MSVC…), et supposer l’existence d’un bug quand ils se contredisent. Comme ça, on n’a pas besoin d’un « oracle » qui connaît la bonne réponse. C’est le principe des « tests différentiels » (differential testing).

Mais ce n’est pas aussi simple qu’il y paraît. Dans un grand nombre de cas, quand le programme en entrée n’est pas un « bon » programme, la norme ne définit pas uniquement le comportement que les compilateurs doivent adopter. Par exemple, si le programme commet une erreur grave comme une divison par 0 ou un accès hors d’un tableau, les compilateurs sont autorisés à produire un programme qui fait n’importe quoi (undefined behavior). Dans d’autres cas, il y a plusieurs choix possibles, comme par exemple pour l’ordre d’évaluation, et la norme ne tranche pas entre les différentes possibilités, elle laisse cet aspect non spécifié (unspecified behavior). Les auteurs de l’article ont recensé, dans la norme C99, 191 situations de undefined behavior et 52 cas de unspecified behavior. Si on prend un code C qui contient de tels comportements mal définis, deux compilateurs peuvent produire des résultats différents et être tout de même corrects, puisqu’ils respectent la norme.

La deuxième bonne idée de l’article est alors de vérifier que les programmes générés ne contiennent pas de comportement mal déterminé. Pour cela, ils utilisent une combinaison d’analyse statique du programme et des tests à l’exécution.

Les tests n’utilisent pas toutes les fonctionnalités du langage C, mais seulement un sous-ensemble raisonnable du langage pour lequel ils ont su mettre ces analyses au point. Ils ont sorti le grand jeu : data flow analysis pour déterminer le comportement des pointeurs, typage (qui prend en compte const et volatile), analyses d’effets… Il faut choisir des analyses qui rejettent tous les programmes mal définis ; mais elles rejettent alors nécessairement, on ne peut pas tout avoir dans la vie, certains programmes bien définis. Plus l’analyse est fine, moins elle rejette de programmes corrects, plus ils génèrent des tests compliqués et plus ils trouvent de bugs.

En pratique, ça marche bien : ils ont trouvé « des centaines de bugs » dans les compilateurs testés, dont 79 dans GCC et 202 dans LLVM ; certains des bugs trouvés sont détaillés dans l’article. Ces derniers correspondent en général à des optimisations trop aggressives des compilateurs, qui négligent certains cas et changent la sémantique, ou parfois à des erreurs d’implémentation dans le code de l’optimisation.

Quelques points de détail

Leur outil s’est révélé plus efficace quand il génère de gros programmes : « the largest number of bugs is found at a surprisingly large program size: about 81 KB ». Il n’y a pas de méthode complètement automatique pour réduire un programme-test en un programme plus petit qui préserve le bug sans introduire de comportement mal spécifié ; ils ont dû faire ça à la main. Ça pourrait s’automatiser, mais c’est sans doute un problème aussi difficile que la génération initiale de tests corrects.

Ils profitent de leur article pour critiquer gentiment certaines méthodologies de test, en particulier la métrique de couverture de code (code coverage). C’est une pratique très répandue dans l’industrie du test (et utilisée comme critère par exemple par les autorités de certification de code critique), qui consiste à utiliser un jeu de tests tel que chaque instruction du programme est exécutée au moins une fois (c’est le critère instruction coverage ; il y a d’autres critères de couverture, cf. Wikipédia) ; le terme « couverture » désigne ici la proportion du code du programme qui est exécuté au moins une fois par la batterie de tests. Ils montrent que leurs tests générés aléatoirement n’améliorent pas la couverture obtenue par les batteries de tests déjà existantes des compilateurs, c’est-à-dire qu’ils ont trouvé plein de bugs sans améliorer la couverture.

Enfin, ils ont même évalué le « retour sur investissement » de leur méthode : ils ont additionné les salaires de tous les chercheurs et étudiants qui ont travaillé sur cette méthode, le coût de l’équipement, de l’administration, etc., et obtenu un « coût » de $1000 par bug. Visiblement c’est considéré comme très rentable dans cette industrie (on imagine que les bugs dans les compilateurs peuvent être critiques et très difficiles à trouver) ; et maintenant que les outils sont développés, ils peuvent être utilisés à bien moindre coût.

Je suis quand même étonné de la quantité de bugs qui persistent dans ces compilateurs utilisés industriellement. Il ne s’agit pas de nouvelles fonctionnalités C99 qui viennent d’être implémentées (leur outil ne les prend pas en compte) mais de comportements bizarres sur un overflow dans une optimisation arithmétique, le genre de chose qu’on teste depuis des années. Pour la petite touche historique, ils mentionnent la longue histoire des tests par génération aléatoire de programme, qui a commencé en 1962 sur des compilateurs COBOL.

Remarque de rz0 :

Ça m’étonne pas, en vrai ; je pense que la plupart des programmes tendent à se cantonner à une partie plutôt bien huilée du langage et c’est ça qu’on teste bien. D’autre part, les programmes ont eux-mêmes des bugs, et en général, on n’accuse pas le compilo, du coup, il peut persister des problèmes que les gens workaround plutôt que de passer des heures à fouiller dans les normes et les forums pour savoir si c’est effectivement légal, pas légal, bien, mal défini, etc. La norme est sujette à interprétation, comme tu le sais, et sans être spécialiste, c’est très dur de savoir si ce que tu fais est légitime ou pas, et c’est nettement plus rentable de contourner le problème…

Le point le plus difficile de leur travail est la conception de ces analyses qui évitent les comportements mal définis et leur intégration dans le générateur de programmes. On peut se demander si cette difficulté est inhérente au langage C, et s’il faut prendre cet aspect en compte lors de la conception d’un langage de programmation : définir une sémantique déterministe rend le compilateur plus facile à tester. Sur la discussion de Lambda the Ultimate, un membre indique que les générateurs aléatoires de programme ont été très utiles pour les implémentations du langage Common Lisp ; est-ce lié à un plus petit nombre de comportement mal définis dans ce langage ?

Pour ma part, l’idée qu’un programme puisse se mettre à faire absolument n’importe quoi en cas d’erreur me semble désastreuse, mais je suis plutôt favorable à certains unspecified behavior, comme par exemple le fait de ne pas spécifier l’ordre d’évaluation des arguments d’une fonction, pour décourager les programmeurs d’écrire du mauvais code qui reposerait sur cet ordre. Mais des gens plus expérimentés que moi ne sont pas d’accord ; je crois que certains des concepteurs de Caml, par exemple, considèrent comme une erreur le fait de ne pas avoir fixé un ordre d’évaluation. La question n’est pas tranchée.

Remarque de rz0 :

C’est pas exactement dans le sujet, mais en fait, je pense qu’il faut mitiger cet aspect « comportement mal défini », parce qu’au final, du point de vue de l’implémenteur, il y a un choix à faire, et ce choix est rarement « on fait n’importe quoi ». Ce que je veux dire par là, c’est que l’implémenteur restreint fortement le nombre de comportements vraiment « désastreux », comme tu dis, et surspécifie le langage qu’il va prendre en charge, par rapport à la norme. Par exemple, Clang compile vers de l’assembleur intermédiaire LLVM, qui a des types entiers fixés (i8, i16, i32, etc.) avec une représentation prédéterminées (complément à deux) et des opérations arithmétiques bien définies dans tous les cas. Donc le fait que le langage C lui-même ne spécifie pas toutes ces choses-là ne devrait pas du tout affecter la qualité de l’implémentation.

CompCert, tests et preuves de correction

L’article mentionne le compilateur CompCert, un compilateur prouvé correct écrit en Coq. Je ne suis pas tout à fait d’accord avec le traitement qui en est fait, et je voudrais développer un peu ce point.

The striking thing about our CompCert results is that the middle- end bugs we found in all other compilers are absent. As of early 2011, the under-development version of CompCert is the only compiler we have tested for which Csmith cannot find wrong-code errors. This is not for lack of trying: we have devoted about six CPU-years to the task. The apparent unbreakability of CompCert supports a strong argument that developing compiler optimizations within a proof framework, where safety checks are explicit and machine-checked, has tangible benefits for compiler users.

Aucun bug trouvé dans un compilateur certifié. C’est normal, mais vous étiez quand même un peu inquiets, non ? Il se trouve qu’ils ont en fait trouvé des bugs, avant « early 2011 », dans le front-end de Coq, qui utilise CIL, une bibliothèque externe codée en OCaml qui parse le code C et lui applique quelques transformations. Cette bibliothèque non vérifiée semble effectivement contenir quelques bugs. Depuis, Xavier Leroy a réécrit cette partie de CompCert pour rétrécir la partie non-prouvée ; mais il en reste un peu, car pour l’instant on ne sait même pas vraiment comment spécifier un parseur (décrire formellement son comportement).

Les auteurs du présent article concluent en substance : bon, la vérification c’est très sympa et ça enlève plein de bugs, mais ça reste complémentaire au test, parce qu’il y a toujours des petits détails qui coincent. On se souvient de la célèbre phrase de Knuth : « Beware of bugs in the above code ; I have only proved it correct, not tried it. » Mais fondamentalement, ils ne semblent pas vraiment convaincus ; ils font aussi remarquer, à juste titre, qu’on est encore loin d’avoir un compilateur certifié pour un langage aussi délirant que C++0X (la prochaine norme du langage C++, en cours de finalisation ces mois-ci).

Cette attitude sceptique par défaut se retrouve aussi chez les autorités de certification de logiciel critique. Les gens qui travaillent là-bas sont chargés d’expertiser du logiciel pour vérifier son respect de normes de qualité très strictes, permettant ensuite l’utilisation dans les avions, voitures, centrales nucléaires, etc. Les expertises consistent essentiellement à analyser le code et les tests qui l’accompagnent mais aussi le processus de conception, la documentation produite, etc. Il y a des critères de tests divers ; l’idée n’est même pas vraiment de vérifier que les tests passent correctement (c’est une évidence), mais que leur présence montre l’existence d’une démarche qualité appropriée chez les gens qui ont écrit le code.

Ces autorités de certification n’ont pas d’expérience en vérification formelle : les outils d’analyse de programme sont déjà utilisés dans l’industrie pour trouver des erreurs, mais jusqu’à présent aucun programme n’était entièrement certifié. Les outils formels prouvaient par exemple l’absence d’overflow, mais pas un lien entre le code et une spécification. L’idée commence à germer et des questions se posent : quel degré de confiance accorder à un programme prouvé en Coq ? Une preuve remplace-t-elle la construction des jeux de tests (processus qui a sa part de coûts et de lourdeurs) ? Il y a un début de débat, mais la question est loin d’être tranchée, et ce sont des milieux évidemment assez conservateurs.

À l’opposé, j’ai rencontré des utilisateurs de Coq pour qui la réponse était claire : quand un programme est « prouvé en Coq », il ne peut évidemment pas contenir d’erreur, et il est inutile de le tester. Pour eux, le test est une méthode peu coûteuse (il est facile d’écrire un test) mais peu fiable (un test qui passe ne garantit en rien l’absence de bugs) de prendre confiance en la correction d’un logiciel, et la preuve formelle est une méthode coûteuse (il est très difficile d’écrire une preuve complète) mais absolument fiable.

Ce point de vue est malheureusement un leurre ; ce n’est pas un idéal qu’on veut atteindre un jour et pour lequel on fait des progrès, c’est tout simplement un état qui ne pourra jamais exister. La preuve formelle permet de s’assurer que tel théorème (théorème qui mentionne le programme que l’on veut vérifier) est absolument, mathématiquement juste. Mais le théorème peut être une trivialité qui dit « le programme marche, ou bien le programme ne marche pas ». Quand on a une preuve d’un théorème vérifiée par ordinateur, on est sûr que la preuve est juste, mais il faut toujours vérifier que le théorème est bien celui que l’on croit. On aurait très bien pu se tromper dans sa formulation (et c’est déjà arrivé en pratique), même si le fait d’écrire une preuve permet la plupart du temps de détecter des incohérences dans l’énoncé.

Je pense donc que l’idée de tester un programme prouvé n’est pas la bonne approche, et que les six ans de CPU n’ont pas été très utiles. On devrait plutôt essayer de tester la spécification établie par la preuve, l’énoncé du théorème de correction. Qu’est-ce que ça veut dire, tester une formule mathématique ? Je ne sais pas exactement, mais j’ai quelques idées. Par exemple, on pourrait montrer à la main qu’un système simple et faux ne vérifie pas cette spécification. On a inversé la situation : au lieu de tester le programme en disant « telle entrée est acceptée par le programme comme prévu », on teste la spécification en disant « la spécification rejette tel programme comme prévu ».

Bref, je pense que oui, il faut continuer à « tester » un programme prouvé correct, mais qu’on ne le teste pas de la même façon. En substance, prouver un programme revient à réduire sa « surface de confiance » : au lieu de devoir mettre en doute l’ensemble du programme (lire le code et se convaincre qu’il fait bien ce qu’on pense), on doit ausculter sa spécification qui est, espère-t-on, plus abstraite, plus simple et plus concise. Mais la possibilité d’erreur est toujours là, et elle ne disparaîtra jamais : il y aura toujours un gouffre entre la description mécanique du programme donnée à l’ordinateur et l’idée informelle que s’en font ses concepteurs et utilisateurs ; ce sont deux mondes différents.

Une force de la preuve formelle que je n’ai pas mentionnée est son aspect progressif. On peut commencer par ne rien montrer du tout sur un programme, puis prouver qu’il vérifie telle propriété plus ou moins simple (par exemple qu’il termine sur toutes les entrées bien formées), puis une autre propriété un peu plus subtile. Par exemple, pour un programme qui renvoie les racines d’un polynôme, on peut commencer par montrer que tous les résultats renvoyés sont bien des racines, car c’est nettement plus facile que de montrer que toutes les racines sont bien renvoyées. Petit à petit, on peut progresser vers la preuve de correction « totale » où la spécification décrit exactement ce que le concepteur aimerait que son programme fasse. Une autre façon de s’assurer d’une spécification est alors de vérifier qu’elle implique les énoncés plus faibles précédents ; par opposition aux tests précédents, c’est une forme de preuve de spécification.

De nombreux articles d’opinions ont été écrits sur la question de la vérification formelle des logiciels. Je vous recommande chaudement la lecture de Social Process and Proofs of Theorems and Programs, Richard De Millo, Richard Lipton et Alan Perlis, 1979. Cet article explique avec fermeté que la vérification formelle est un rêve qui n’existe pas en réalité, même chez les mathématiciens, et que nous n’arriverons jamais à prouver formellement de « vrais programmes ». Je ne suis pas d’accord, mais ça le rend d’autant plus intéressant à lire. Plus neutre, et moins pessimiste, j’ai apprécié le court article de vulgarisation Formal Proof – Theory and Practice, de John Harrison, 2008.

[ tag:blog.huoc.org,2009:posts/generation-aleatoire-bugs-compilateurs-C ]
Gabriel Scherer (gasche).
Le 19.04 2011 à 23:09.

[>] Commentaires[Atom]

# tcpc
20.04.11, 11:55.

Merci pour cet article. :) C'était très intéressant et j'ai appris plein de choses. C'est étrange mais je n'avais jamais pensé qu'un compilateur (ou un interpréteur) pouvait recelait des bugs (autant de bugs, même). Mais finalement, c'est plus ou moins légitime. Et j'ignorais qu'on pouvait traquer les bugs de la manière originale de générer des programmes aléatoires, les faire exécuter puis vérifier automatiquement le résultat.

Bref, intéressant tout cela ! Merci !
[ tag:blog.huoc.org,2011-04-20:comments/1303293327.19419 ]

# asmanur
24.04.11, 14:20.

Merci pour cet article très intéressant. On sent que tu veux te prononcer sur la
question derrière un tel sujet : pourquoi développe-t-on encore à l'heure
actuelle des normes d'une telle complexité, mais tu n'oses pas.

Est-ce qu'il y a une raison inhérente au langage défini (par exemple le C étant
impératif de bas niveau, la norme doit définir beaucoup de comportements) ?  Une
autre question aussi, si obtenir l'adéquation entre implémentation et
spécification est très dur à obtenir, ne vaut-il pas mieux renoncer à donner une
spécification indépendante de toute implémentation ? C'est le compromis qui a
l'air d'être de mise en OCaml et en Haskell (GHC devant un standard de fait, et
la proportion de code qui est effectivement correct vis-à-vis du
standard). C'est d'ailleurs rigolo de remarquer qu'aucun compilateur C n'a
réussi à imposer ses extensions comme standard de fait (ou je me trompe
?). J'imagine que la communauté est plus grosse, et donc il y a plus d'inertie,
mais bon.
[ tag:blog.huoc.org,2011-04-24:comments/1303647628.7772 ]

# gasche
25.04.11, 10:36.

Toi, tu as oublié le tag "%mdown" en début de post :-’ Pas grave, c’est très lisible dans mon lecteur de flux de syndication.

Je crois qu’il y a plusieurs questions mélangées qui sont très différentes. Est-ce qu’on parle

  • de la complexité du langage : est-ce que la norme est grosse, combien contient-elle de concepts différents à comprendre ?
  • de la précision de ses spécifications : Tous les cas de figure sont-ils couverts ? Combien y a-t-il de comportements explicitement non-spécifiés ?)
  • ou de la précision de sa formulation : la norme est-elle claire, ou contient-elle des ambiguïtés ?

Je n’ai pas d’avis ferme sur les deux premiers points. Je pense que si on veut un langage généraliste qui serve sur une large palette d’applications, il faut nécessairement une certaine dose de complexité dans le langage. Par exemple tout ce qui touche à la modularité, et plus encore ce qui touche à la non-séquentialité introduit de la complexité dans le langage et donc dans sa norme. On pourrait essayer de découper en plusieurs langages séparés, chacun simples à spécifier, mais il reste une complexité d’ensemble importante, liée simplement à la complexité inhérente des domaines d’application.

Pour la question des "comportement indéfinis" ou non-spécifiés, encore une fois je n’ai pas vraiment d’avis ferme. Je pense que le mieux qu’on puisse avoir, par exemple pour l’ordre d’évaluation, c’est un langage qui sait vérifier que le comportement du programme qu’on lui fournit est indépendant de l’ordre d’évaluation des arguments, et qui produit une erreur sinon. Malheureusement on en est encore très loin.

Je pense qu’une des difficultés avec la norme C, et aussi avec la norme C++, est qu’il s’agit d’une prose informelle qui peut parfois être difficile à lire et interpréter. La façon dont l’ordre d’évaluation est décrit dans la norme C est, paraît-il¹, une horreur. De même, il y a des points de détails sur le layout mémoire dans la norme C++ dont la signification ne fait en fait pas consensus au sein du comité.

¹: Ce sont des informations de seconde main, que j’ai apprises en discutant avec des gens qui travaillent sur des spécifications formelles de ces langages. Je n’ai moi-même jamais regardé la norme C++, ni cette partie de la norme C (mais j’en ai vu des extraits, et c’est vrai que c’est pas joyeux).

Le pire au niveau des normes trop informelles c’est sans doute celles qui spécifient le comportement des langages en cas de parallélisme. C’est le domaine des "modèles mémoire faibles", qui doit être géré à la fois au niveau du hardware et du software. En gros il y a des optimisations faites par le matériel ou le compilateur qui préservent la sémantique des programmes séquentiels, mais pas celle des programmes parallèles (la sémantique du résultat n’est pas forcément un des entrelacements de l’exécution des différents fils). Jusqu’à présent, ces comportements étaient décrits de façon imprécise et ambiguë.

Je n’adhère pas à ton idée de langages "spécifiés par leur implémentation". D’ailleurs je tiens à signaler que dans les deux cas que tu cites, OCaml et Haskell, il y a un cœur qui est bien spécifié et ensuite des extensions faites par l’implémentation principale qui sortent de ce cadre. Je suis d’accord sur le fait que "GHC Haskell" n’est pas spécifié (et serait sans doute trop coûteux à spécifier, je développerai ce point ensuite), mais "Haskell 98" est bien spécifié (et a connu plusieurs implémentations), et OCaml aussi contient un noyau décrit très précisément (par exemple en partie dans le livre "U3" OCaml, et partiellement spécifiée en Coq par Jacques Garrigue.

Je pense que la spécification indépendante est très importante. Je pense même que les "langages du futur", en tout cas ceux du monde académique, devront être accompagnés d’une spécification formelle mécanisée complète. Je ne suis pas le seul d’ailleurs, je suis tombé récemment sur une citation de Greg Morrisett disant exactement ça. Ça a été fait pour SML, et c’est un tour de force, mais ça devient indispensable du fait de l’augmentation en complexité des systèmes, et ça deviendra de moins en moins inabordable avec l’avancée des méthodes de preuve. Pour moi cette obligation a de nombreux avantages :

  • Elle joue un rôle simplificateur et harmonisateur sur le langage. Actuellement quand on se dit "bon allez je rajoute un cas particulier à cette définition…", on se sent un peu coupable mais il n’y a pas de conséquences immédiates. Si tu dois écrire des preuves sur le bon comportement de ton langage, tu réfléchis à deux fois avant d’ajouter des irrégularités de ce genre. Je pense que ça a un effet bénéfique sur le langage, et que des langages plus simples à spécifier seront aussi plus simples à apprendre et utiliser.

  • Elle permet de régler une fois pour toute la question de l’ambiguïté des normes. C’est difficile d’écrire une norme formelle lisible, elle sera certainement accompagnée d’une description informelle, mais en cas de doute on peut trancher toute ambiguïté de façon univoque.

  • Enfin, ça ouvre la porte à un vaste champ d’applications terriblement alléchantes, sur tout ce qu’on peut développer comme outils sur les programmes d’un langage dont on connaît la sémantique. La compilation certifiée n’est qu’un exemple parmi d’autres. La preuve de l’indépendance par rapport à l’ordre d’évaluation en est un autre. Une des applications qui m’attire le plus personnellement, c’est le traitement de la compatibilité : quand on fait un changement à une bibliothèque, au lieu de s’interdire de casser la compatibilité, on fait ce qu’on veut (dans la limite du raisonnable) du moment qu’on peut apporter un "patch sémantique" qui transforme tout programme utilisant la vieille version en un programme équivalent utilisant la nouvelle version.

Bon, en pratique on peut déjà développer ce genre de choses sans avoir une spécification absolument formelle du langage, il suffit de faire quelque chose qui marche dans 99% des cas. Mais je pense que ce genre d’outils seront quand même plus faciles à développer et à faire accepter à l’utilisateur quand l’idée qu’on peut raisonner facilement et automatiquement sur la signification des programmes sera plus répandue.

Ceci dit, il ne faut pas perdre de vue que cette spécification a un coût en temps et en effort. C’est une demande envisageable pour des langages bien stabilisés et qui évoluent peu comme C ou SML. C’est irréaliste pour les parties d’un langage que des chercheurs font évoluer par tâtonnements, pour trouver la bonne façon de faire, comme c’est le cas des fonctionnalités de GHC Haskell ou des fonctionnalités avancées de OCaml. Donc pour être tout à fait réaliste, il faut prévoir que les implémentations de langages se sépareront en plusieurs couches, un noyau qui implémente la sémantique spécifiée rigoureusement (voir qui est lui-même certifié), des couches plus jeunes dont on connaît informellement la sémantique mais pour lesquelles on n’a pas encore fait l’effort de certification formelle, et enfin des parties expérimentales dont on n’est même pas sûr qu’elles sont spécifiables car elles sont potentiellement incohérentes.

[ tag:blog.huoc.org,2011-04-25:comments/1303720579.12177 ]

# robocop
18.06.11, 13:59.

Merci, super intéressant comme article !
[ tag:blog.huoc.org,2011-06-18:comments/1308398386.1967 ]

# Cacophrène
19.06.11, 08:32.

Bonjour,
J'arrive un peu après la bataille mais j'apprécie aussi beaucoup le contenu de cet article, et surtout l'effort de pédagogie de son auteur. Merci pour le partage !
[ tag:blog.huoc.org,2011-06-19:comments/1308465151.5202 ]

# Nicolas
01.09.11, 16:10.

Une petite remarque sur la couverture de code. Il en existe plusieurs. La couverture d'instruction est la plus connu, mais n'est pas celle utilisé par les niveaux de certification aéronautique(DO178B). Il utilise la couverture dite MC/DC qui revient à tester indépendamment chaque booléen pour que le résultat d'un test change, la différence avec le test exhaustif est d'être en n+1, au lieu de 2^n. 

Concernant la certification du train, c'est un des premier processus à parler de la vérification formelle, ce qui a entrainé la mise en oeuvre de la méthode B chez matra.

Toute vérification est la comparaison d'une chose avec une autre. Le résultat d'un code avec sa spec, le résultat d'un code avec un "golden model", le respect des contrats internes (assert()). On compare toujours quelques choses avec autre chose. Cet autre chose est souvent un texte informel (la spécification). Si jamais on arrive à définir un langage de spécification formel, il sera pas si difficile que cela à terme, de le compiler directement. Et on revient à la validation par rapport à la spécification de niveau supérieur (system ?).

Le ferroviaire a introduit une méthode qui consiste à faire 2 fois le même programme par 2 équipes séparés. On part du principe que les humains feront des erreurs mais pas les mêmes. Ensuite, les 2 programmes sont tester l'un par rapport à l'autre comme pour les compilateur. Si il existait un "equivalence checker" comme il en existe en électronique numérique (VHDL), il serait possible de prouver automatiquement l'équivalence des 2 programmes et de gagner un temps fou. Cette méthode devient intéressant dés que l'on remarque que le temps de test devient supérieur au temps de développement.
[ tag:blog.huoc.org,2011-09-01:comments/1314886218.29801 ]

Nouveau commentaire