En effet, ce fameux bug n'est nullement spécifique à PHP et peut impacter d'autres langages, voir même des programmes qui n'ont rien à voir avec un langage de programmation.
Il trouve en effet son origine au sein de l'unité de calcul en virgule flottante x87 des processeurs x86 32 bits qui est, assez paradoxalement, trop précise, puisqu'elle utilise 80 bits pour effectuer ses calculs.
De ce fait, cette unité, encore présente dans les micro-processeurs d'aujourd'hui pour des raisons de compatibilité, se révèle incapable de gérer correctement le nombre en question, ce qui provoque la boucle infinie au sein du Zend Engine.
Heureusement, elle a été remplacé au sein des processeurs x86 par le module SSE il y a un peu plus de 10 ans, qui lui est moins précis puisqu'il gère les nombres à virgule flottante sur 64 bits, suivant en cela la norme IEE 754.
Il devrait donc être utilisé, via les compilateurs, à la place de x87.
Sauf que GCC, le compilateur utilisé traditionnellement pour produire le binaire de PHP, n'est pas capable, par défaut, d'utiliser le module SSE à la place de x87, et cela malgré que le problème ait été rapporté en... juin 2000 !
Or, la gestion des nombres en virgule flottante est une chose complexe en informatique, puisque de part son fonctionnement binaire, un ordinateur ne peut manipuler que des approximations de ces nombres.
Les développeurs du monde entier ont donc été très content, quand, en 1991, un certain David M. Gay a publié un peu plus de 2500 lignes de code écrit en C permettant de les gérer efficacement, à tel point que son code a été repris et intégré dans énormément de programme informatique, dont PHP.
En résumé, tout programme manipulant des nombres en virgule flottante à la façon de David M. Gay et compilé à l'aide de GCC ou d'un compilateur faisant appel à l'unité de calcul en virgule flottante x87 contient potentiellement le même style de bug .
C'est assez effrayant, non ?
J'en vois déjà me dire que ce n'est pas possible et que je fabule complètement.
Et pourtant, le même style de problème vient d'être découvert dans Java !
Le problème a été rapporté à Oracle il y a maintenant trois semaines, mais pour le moment, l'auteur du rapport de bug n'a reçu aucune réponse et aucun correctif n'a été apporté.
Je suis assez curieux de voir combien de temps cela va prendre pour que le problème soit corrigé, sachant qu'il a fallu 8 jours aux développeurs de PHP pour trouver l'origine du problème et apporter une solution, qui tient en un mot.
11 réactions
1 De Eric - 01/02/2011, 14:25
> Et encore plus évidemment, beaucoup de gens ont encore dit que, décidément, PHP était vraiment un langage de merde
Non ? Pas pour ça quand même !
2 De MathRobin - 01/02/2011, 14:37
Merci d'avoir éclairci la question de ce bug. J'avais pas eu le temps de me renseigner, en cinq minutes, t'as tout expliqué d'un bout à l'autre. Montrant au passage que ce n'est pas PHP qui est en cause.
PHP impacté, Java impacté, de très nombreux (si ce n'est la quasi totalité) des programmes en C ou C++ ayant utilisé GCC sont concernés... ça fait un peu bug de l'an 2000 quand même ça.
3 De Dorian - 02/02/2011, 11:11
Super article, comme d'hab ! Merci
4 De jeremyFreeAgent - 02/02/2011, 17:15
Merci pour les infos ! Comme toujours c'est bien expliqué !
5 De Nicolas Laforêt - 03/02/2011, 08:27
Vu que je n'y connais absolument rien en C (oui oui j'ai eu des cours il y a quelques années mais bon...) je suis étonné qu'un tel bug soit résolut en passant la variable en "volatile" ... mais pour ne pas rester ignorant, te serait-il possible d'expliquer en quoi la mettre en volatile solutionne le bug ?
6 De mageekguy - 03/02/2011, 09:31
@Nicolas Laforêt : Pour l'explication très technique, pleine de 0 et de 1 et d'instruction
gdb
, c'est ici.Mais en résumé, l'utilisation du mot-clef
volatile
force la variable à être stockée dans un registre 64 bits à la lecture et l'écriture, et non plus dans dans un buffer de 80 bits, ce qui permet de contourner le problème.Le problème d'arrondi à l'origine de la boucle infinie est en effet toujours présent, mais grâce au passage sur 64 bits rendu obligatoire par l'instruction
volatile
, il n'a plus d'impact.7 De SpaceFox - 08/02/2011, 11:58
La grande différence entre un langage comme PHP et un autre comme Java, c'est que PHP va essayer de convertir n'importe quel "2.2250738585072011e-308" en entrée en double (et donc planter) alors qu'un langage comme Java ne va planter que si cette entrée est réellement un double.
Or il y a assez peu de doubles "exposés" en paramètres.
Ce qui n'enlève rien au fait qu'il est honteux qu'Oracle n'aie pas corrigé ce bug très très vite.
8 De haypo - 11/02/2011, 11:57
"En effet, ce fameux bug n'est nullement spécifique à PHP et peut impacter d'autres langages, voir même des programmes qui n'ont rien à voir avec un langage de programmation."
C'est possible, mais Python n'a pas ce bug. J'ai recompilé Python en forçant GCC à utiliser x87 plutôt que SSE (en 64 bits, GCC utilise SSE par défaut), et je n'ai pas réussi à avoir le bug.
Pour les autres langages, je ne sais pas.
Au passage, l'utilisation de volatile pour contourner le bug me semble être un hack sale qui impacte beaucoup les performance. Il aurait mieux valu isoler l'instruction qui pose problème et faire un cast explicite uniquement sur cette instruction.
9 De mageekguy - 11/02/2011, 17:00
@haypo : Comme indiqué dans le billet, il faut être en 32 bits, et que le programme utilise le code de M. David M. Gay pour gérer les arrondies sur les flottants.
Quand à
volatile
, son utilisation force le cast sur 64 bits dont tu parles...10 De haypo - 18/02/2011, 11:17
Le bug est reproductible en 64 bits si le compilateur utilise x87 plutôt que SSE (x87 offre une meilleure précision, mais est un plus lent).
Par contre j'ai vérifié : Python n'utilise pas le même code que PHP pour convertir une chaîne en nombre flottant, ce qui explique que même si Python utilise x87 sur AMD64, il n'ait pas le même bug que PHP.
Par contre, depuis les versions 2.7 et 3.1, Python utilise le code de David Gay pour l'opération inverse (formatter un nombre flottant sous forme d'une chaîne de caractères) : http://bugs.python.org/issue7117.
Ceci permet une représentation plus compacte des nombres flottants.
Python a bénéficié ces derniers mois (dernières années ?) d'un important travail de notre expert en calcul numérique : Mark Dickinson.
Il a d'ailleurs trouvé un bug dans le code de David Gay (toujours dans le sens nombre => chaîne) et l'a contourné (au lieu de le remonter upstream à David, rooh) : http://www.exploringbinary.com/inco...
En fait, je pensais qu'il existait une manière plus élégante d'indiquer au compilateur qu'on voulait que le résultat d'une opération soit stockée en 64 bits plutôt qu'en 80 bits, mais il semble que non.
Je retire ce que j'ai dit.
11 De mageekguy - 18/02/2011, 13:06
@haypo : Je me suis permis d'éditer ton commentaire au niveau des liens que tu y avais mis, car ils étaient déjà tous présents, soit dans le billet, soit dans les commentaires précédents.