Aller au contenu
AFUP AFUP Day 2025 Baromètre Planète PHP PUFA
 

Comprendre le fonctionnement de l’analyse statique

Description

L’analyse statique est un domaine en pleine explosion, surtout en PHP. Relire du code à la vitesse de l’ordinateur requiert des outils théoriques puissants : contrôle de flux, dépendance des données, arbre de syntaxe abstraite, graphe de dépendances acycliques !

Si tout cela semble bien loin de PHP, vendez découvrir comment ils fonctionnent en PHP. Ce sont les outils les plus efficaces pour détecter ces mêmes erreurs que l’on retrouve en production, mais bien avant même que le code ne compile. Nous verrons comment ces différentes approches peuvent être exploitées pour construire une analyse de code destinée à traquer les erreurs qui vous gênent le plus.

Conférence donnée lors du Forum PHP 2018, ayant eu lieu les 25 et 26 octobre 2018.

Informations complémentaires

Vidéo

Le speaker

Damien SEGUY

Damien Seguy est directeur technique chez Exakat Ltd., société spécialisée dans les solutions pour la qualité du code source en PHP. Il dirige le développement du moteur d'analyse statique Exakat, qui assure la revue de code pour les migrations, la clareté et la sécurité. Avec près de vingt ans de contributions au monde PHP, son expérience l'a fait passé par la rédaction de la documentation, l'élevage d'elephants, l'animation de groupes d'utilisateurs sur trois continents. Il aime faire du gremlin, des ??? et du camembert.

Transcript

Merci à tous. C'est courageux de venir écouter quelque chose d'un peu compliqué en pleine digestion.

Surtout que j'ai quelques petites surprises en cours de route, il va falloir travailler.

J'espère que vous êtes prêts à répondre.

Enfin voilà, on va commencer, on va faire de l'analyse statique, on va d'abord commencer par voir ce que c'est, parce qu'on est pas toujours au fait de ça, mais on va voir cette fois-ci, non pas la façon avec laquelle l'analyse va vous aider dans votre travail de tous les jours, mais plutôt comment elle fonctionne.

Généralement, on est plutôt en mode : on voit un audit et puis on regarde pourquoi est ce que le code est tout pourri, là on va être plutôt en mode : comment ça fonctionne ? Comment est-ce que la machine arrive à comprendre du code que vous avez écrit, qui est déjà normalement le langage de base qu'elle-même comprend pour se comprendre elle-même ? Un peu de problèmes de...

Le serpent va se mordre un petit peu la queue.

Evidemment, l'objectif ça va être de vous donner les différents éléments dans lesquels vous allez pouvoir intervenir.

Donc il y a clairement une partie du moteur qui est automatique et qui fonctionne.

Ca, c'est la partie la plus facile pour vous ; la partie la plus difficile, c'est justement rajouter de l'information et l'adapter à vos besoins donc c'est un peu ce qu'on va essayer de voir.

Effectivement, comme le présentait Adrien, l'expérience que vous avez là, le retour je dirais, va être lié au travail que je fais chez Exakat, de direction sur le moteur d'Exakat, qui est un moteur d'analyse statique, dédié à PHP.

Cela dit, les concepts que je vous présente là, sont des concepts qu'on utilise tous dans l'industrie donc si vous en utilisez un autre...

Combien d'entre vous utilisent déjà un outil d'analyse statique ? Il faut lever la main un peu plus qu'une seconde parce que j'ai pas le temps de compter.

Ah oui quand même. 1/5e de la salle. Pas mal, bon.

De toute façon, à ce stade-là, c'est mieux que rien. Il faut savoir que c'est extrêmement excitant de travailler dans un environnement comme ça.

Parce qu'on travaille avec du code qui a énormément de champs et d'analyses qui sont amusantes à essayer donc aujourd'hui si vous pouvez en utiliser et améliorer votre code tous les jours, c'est exactement ce qu'il faut.

Alors l'analyse, c'est quoi ? Il y a plusieurs définitions.

Je dirais, si on utilise les deux, d'abord c'est de la revue de code. C'est vraiment de la revue de code aussi simple que possible.

Si ce n'est que, jusqu'à présent la revue de code c'était quoi ? C'était le chef d'équipe qui devait prendre ses développeurs, récupérer le code et puis vérifier ça avant que ça parte en production.

Chose qu'évidemment on n'a jamais eu le temps de faire, quand on était en l'an 2000 ça se faisait pas, aujourd'hui c'est toujours pas le cas.

Donc on est un petit peu embêté et le relire de manière complètement manuelle, c'est pas possible, c'est trop lent.

Ca prend trop de temps donc c'est hors de question. Donc à ce stade là, l'idée c'est de pouvoir justement faire une partie des analyses de manière automatique, et puis de garder le niveau supérieur, le niveau le plus abstrait pour l'humain. Parce que, à ce stade là, la machine ne va pas être capable de comprendre.

Pour vous donner une idée, la problématique est arrivé quand j'étais CTO en Chine.

Parce qu'effectivement avec 60 développeurs chinois qui codent avec du pinyin au lieu de l'anglais, c'est difficile de suivre ce qui se passe.

Or les problèmes justement c'est toujours à 6 heures qu'il faut faire leurs revues de code, pour partir en production à 6 heures donc il y avait un problème de vitesse, il y avait un problème de systématique.

Et donc les revues peuvent être simples mais généralement c'est souvent les plus simples qui sont les plus spectaculaires donc vous allez voir, on va s'amuser avec ça.

Alors comment ça fonctionne de l'analyse statique ? Le problème c'est que vous partez d'un matériau qui est un peu bizarre.

Que vous, vous avez appelé du code PHP, et en fait c'est du texte.

C'est du Word en moins bien, si vous voulez. Si vous voulez comprendre.

Mais en réalité, on part d'un fichier texte. Vous éditez un fichier texte.

Vous avez un super éditeur qui vous met des couleurs partout, mais derrière, c'est que du texte, PHP ne lit que du texte dès que vous lui mettez le BOM. Pas content, il le crashe partout.

Mais le premier problème, c'est d'arriver à convertir ce texte en code. En quelque chose qui a du sens pour PHP, et ne pas le confondre avec autre chose.

Ensuite, ça va être d'ajouter de l'information supplémentaire.

Parce qu'on va vite voir qu'on est assez limités, si on prend juste le code tel qu'il est.

La valeur faciale.

On va être bloqué et à partir de là, il faut l'interroger, et c'est la dernière partie, ça c'est la partie sympa.

On va construire des requêtes pour aller dans la base.

Donc l'ensemble du synopsis en images c'est celui là : on part du côté du source, on va passer entre les deux avec une partie, que j'appelle la tokenisation.

Qui est en fait un peu plus évoluée. Et entre les deux, on va le stocker dans un médium intermédiaire, qui va, lui, supporter les requêtes. La plupart du temps, dans les moteurs d'analyse statique qu'on a aujourd'hui, c'est effectivement stocké en mémoire. Vous l'exécutez sur votre code, il le prend, il récupère les infos, il les met en mémoire, il vous donne les résultats et après il crashe tout. D'accord ? Chez Exakat, on le met dans une véritable base de données entre les deux donc on peut le garder entre deux appels.

Mais c'est exactement le même principe, il y a un stockage intermédiaire, au format qui nous est agréable pour faire les requêtes.

La partie sur le côté.

Donc ça, c'est les sources des analyses. Qu'est-ce qu'on peut aller chercher et qu'est qu'on a envie d'aller trouver dans le code ? Les motifs, les patterns si vous voulez faire anglais.

Et donc, on va en voir quelques-uns. Ce qui nous intéresse moins aujourd'hui, c'est la partie audit, le résultat.

On ne va pas s'intéresser sur comment interpréter ces résultats.

Alors l'analyse statique, il faut savoir qu'il y en a un qui en fait déjà, et vous ne l'utilisez pas toujours. C'est PHP.

En gros, la différence entre faire de l'analyse statique et exécuter PHP, elle est relativement faible.

C'est à dire, qu'initialement, PHP part bien avec votre fichier texte, ok.

Il commence par le transformer en token. Et faire les tokens, c'est littéralement casser les phrases et en faire des mots.

En français c'est facile hein. On prend les espaces, et puis on trouve les mots.

On met aussi des virgules parce que sinon bon... Et les espaces et les virgules mais c'est facile. On met aussi les points.

Il ne faut pas oublier les points. Donc on met les points, les virgules, les espaces et comme ça on case une phrase en plusieurs morceaux.

Il y a les deux points aussi.

Bon, de toute façon, les romains nous ont jamais rien apporté de bon, on est d'accord.

Donc, à ce stade là, c'est PHP qui fait ce boulot là. A partir de là, une fois qu'il a les mots, il va remonter la syntaxe. Il va essayer d'organiser les mots les uns à côté des autres.

et à partir de là, il va faire un travail de syntaxe. Et là, les deux utilisations vont se séparer.

Côté PHP, qu'est ce qu'il fait ? D'abord, il va lui-même faire de l'analyse statique.

Vous avez remarqué ? Depuis PHP 7, on n'a pas le droit à mettre deux "default" dans un "switch".

C'est emmerdant quand même. D'habitude, on aimait bien ça. On en mettait 2, 3. C'était Noël. Bon maintenant, il le vérifie.

Parce qu'effectivement, à ce stade-là, il prend les tokens, il fait sa vérification et il dit : "Bah là, de toute façon, l'un des deux va mourir donc, je veux pas le prendre. J'ai mis une erreur. Et voilà, c'est corrigé." Donc il en fait un petit peu, mais il faut imaginer qu'on est en mode production, on va vouloir exécuter le code derrière donc il faut que ça aille vite, faut que ça dépote.

PHP, il en fait un tout petit peu. Le truc facile, et après il les met là-dedans, enfin, il les met dans le cache, et après, il part à l'exécution. Et à l'exécution, PHP, il va avoir un 2e avantage que nous, on n'aura pas en analyse statique, c'est qu'il a les données.

Parce qu'évidemment, on ne fait pas tourner un script vide. Il y a un moment donné, il faut qu'on lui fournisse quelque chose à manger.

Donc on lui donne les données et là, PHP marrie les deux. Il marrie notre code avec des données qui arrivent de l'extérieur.

Résultat, si vous lui mettez un While 1.

PHP ne se fatigue pas beaucoup.

Une boucle infinie, il ne sait pas faire. Qu'est-ce qu'il va faire ? "While 1, je tourne, je tourne, et 30 secondes, les gars, j'arrête. Pause." "Max execution time." En termes d'analyse statique, on est complètement capable de travailler avec l'infini.

While 1, ça se comprend.

C'est le paradoxe de Zénon. Si je cours deux fois plus vite que vous, je ne vous rattraperai jamais.

Parce qu'à chaque fois que j'aurai fait la distance, vous aurez fait la moitié de la distance devant. Donc ça va se rapprocher, mais en travaillant avec l'infini, on peut comprendre ce qui va se passer.

PHP ne le fait pas. Dès qu'il arrive une impossibilité, que la fonction n'est pas définie, "je m'arrête, fatal error." "Trouve-moi la fonction et puis je continue." Ca, ça va être notre grosse différence. Gardez-le en tête, ça va nous revenir.

Alors, on l'a dit, on attaque l'analyse. Première étape, on fait la tokenisation.

Est-ce qu'il y en a certains d'entre vous qui ont déjà utilisé le tokenizer de PHP ? 4, 5. Bon, je vais reposer la question. Est-ce qu'il y en a qui ont utilisé PHP ? Alors comme Monsieur Jourdain, vous faites de la prose sans le savoir.

Le tokenizer, c'est littéralement la partie de PHP qui prend le fichier texte, le découpe en petits morceaux et le donne à PHP pour qu'il aille un petit peu plus loin.

Dont vous avez déjà utilisé le tokenizer. Ce qui fait évidemment la blague, c'est que le tokenizer, en fait, on a accès à ces informations-là. Il faut savoir que ça revient littéralement à détruire ou à exploser votre maison de Lego en petites briques. Vous allez vous retrouver avec 8000 pièces.

Un bon million de token.

On peut dire que c'est fréquent mais que c'est courant. Que ça arrive facilement et pas de soucis pour un fichier un million de tokens.

Qu'est-ce qu'on va trouver là-dedans ? Vous avez un exemple. Vous avez une seule ligne : le tag d'ouverture, juste une définition de fonction et on retrouve plein de trucs. D'abord vous retrouvez un mélange.

Le tableau lui-même n'est pas du tout cohérent. Un coup, c'est des tableaux, un coup, c'est des chaînes. Super.

Deuxième étape. A l'intérieur, c'est absolument pas nommé. le premier, c'est un entier qui donne le nom du token. Ca c'est la deuxième fonction du tokenizer, qui vous donne le nom de la constante qui va derrière. D'accord ? 382, une chaîne vide, 319, une T_STRING. Une T_STRING, ça gère quoi ? une chaîne de caractères.

Les noms de chose, par exemple. Donc il faut vraiment interpréter à chaque fois les tokens, voir ce que ça fait.

Et on continue. Donc vous voyez, j'en ai déjà un paquet pour une ligne qui est assez simple.

Il faut savoir qu'il y a à peu près un tiers des tokens que vous générez dont PHP va se débarrasser immédiatement.

C'est effectivement les espaces, les commentaires, et même la PHPdoc. Tout ça, en termes d'exécution, ça ne l'intéresse mais alors absolument pas.

A la poubelle ! A la rigueur, les analyseurs aiment bien ce genre de choses. Parce qu'on peut récupérer des choses dans la PHPdoc.

Donc ça, c'est intéressant mais c'est tout. Deuxième chose, les délimiteurs. Les délimiteurs, c'est un peu la même chose.

Ca n'intéresse absolument pas PHP pour l'exécution.

Ca l'intéresse pour comprendre ce que vous avez écrit. On commence avec une chaîne, hop, on change le contexte.

On va pouvoir mettre des variables, on va pouvoir mettre un certain nombre de choses. Hop, on sort de la chaîne, c'est fini.

Mais les guillemets eux-mêmes, il ne s'en sert pas du tout pour l'exécution. Une fois qu'il sait ce qu'il a à faire, c'est délimité et c'est tout.

Au final, il y a à peu près deux tiers du code que vous produisez qui ne servent absolument à rien.

Je suis désolé c'est le vendredi, hein.

Mais bon les informations vont être stockées de manière complètement différente et on va passer dans ce qu'on appelle l'AST.

Alors l'AST, vous en avez probablement entendu parler, puisque les tokens c'est beaucoup plus vieux. PHP le faisait depuis PHP/FI.

L'AST c'est plus récent, on l'a depuis PHP 7.

Donc si vous avez l'occasion, c'est une extension supplémentaire à compiler.

Et PHP vous fournit directement un AST. Ce n'est pas du tout celui-là là parce qu'en fait des AST, il y en a plusieurs.

C'est juste arbres de syntaxe abstraite, c'est juste la façon avec laquelle les informations et les opérateurs sont organisés les uns par rapport aux autres.

Donc il y en a un qui est fait par PHP et il y en a un autre qui est fait par PhpStorm parce qu'ils ont leur AST interne.

Il y en aura un autre qui va être fait par Sonar.

Chacun a le sien, c'est pas un problème du moment qu'on sait naviguer à l'intérieur.

Donc là, vous avez un petit exemple : un script relativement simple, avec deux instructions en ligne, une définition de fonction et puis un petit "if".

Et vous voyez que tout ça, ça se transforme en un seul AST, qui est de l'autre côté dans lequel on va retrouver les structures.

Ce que je trouve amusant, c'est qu'on retrouve donc le côté emboîté et on retrouve aussi un certain déséquilibre parce que des choses aussi simples finalement qu'un Return comme celui-là, ça va être quasiment celui qui est le plus profond.

Il va falloir descendre extrêmement profond à l'intérieur de l'app pour avoir les informations.

Mais c'est déjà intéressant. Vous remarquez, comme je vous l'ai dit, tous les dénominateurs ont disparu, plus de parenthèses, plus d'accolades. A la rigueur, comme un rappel, pour vous dire : ça, c'est un bout de blocs, ça, c'est un bout de fonction.

Mais on en a besoin pour le support visuel, l'analyse ne s'intéresse pas à ça.

Alors première étape, qu'est-ce qu'on peut faire avec ça ? On va pouvoir extraire déjà les types d'informations ou les types d'exécution et d'opérations qui apparaissent dans l'AST. Qu'est-ce qu'on peut identifier facilement là-dedans ? Variables ? Bon. Fonctions ? Elle est là-haut. Une fonction, effectivement, qu'est-ce qu'il vous faut pour définir une fonction : son nom, ses arguments, et puis le reste du corps et on comprend bien que le reste du corps va être quelque chose de beaucoup plus gros, de beaucoup plus complexe. On a déjà les trois qui sont bien séparés, peu importe la façon avec laquelle ça a été écrit.

Donc toutes les conventions de codage, la façon avec laquelle on décide de mettre l'accolade : après le nom de la méthode, ou en dessous, trois lignes en dessous, de pas la mettre ou des choses comme ça.

Disparues. On est complètement abstrait avec ça. Donc toutes vos conventions de codage, on les revoit pas ici. Disparues.

Il y en a quelques unes qui passent. Qu'est-ce qu'on a d'autre ? Bon, l'addition est là.

Et vous remarquez qu'au passage, on retrouve la priorité de l'addition par rapport à la multiplication.

La multiplication est en dessous. C'est la première qui va être exécutée.

Les premières qui sont exécutées sont celles qui sont au plus profond et après, on remonte en dépilant. D'accord ? Donc là ici, l'addition est en dessous, les multiplications sont ici.

Techniquement, une multiplication, une division, c'est la même chose donc elles sont toutes les deux au même rang.

Le "if" qui a la spécialité évidemment d'avoir des branches conditionnelles, on pourrait le simplifier et n'avoir pas la branche "else".

Ici, c'est pour l'exemple, elle est là. Mais on pourrait avoir les 2 dont il y a des branches qui apparaissent ou qui disparaissent.

Qu'est-ce qu'il y a encore d'autre ? Les "return". Bon, il y en a plusieurs.

Et les variables.

Alors les variables, elles ont une spécialité, c'est que ce sont toutes les mêmes.

Dollar.

Suivi de quelque chose derrière. Tout ça c'est un token T variable donc PHP, sympa, nous dit que c'est une variable. Est-ce que c'est toujours une variable ? C'est là où vous commencez à réfléchir parce que je vais bientôt vous interroger. Alors essayez de réfléchir un peu.

Est-ce que c'est toujours une variable ? $b, Est-ce que c'est toujours une variable ? Qui est-ce qui veut dire oui ? Mauvaise idée ! Une variable c'est tout et n'importe quoi. Ca peut être un paramètre ou une véritable variable.

Ca peut évidemment être une propriété pour les définitions. Ca peut être une propriété statique $a:: et le nom du paramètre qui va derrière, enfin, le nom de la propriété qui va derrière.

Vous ne faites absolument pas la différence entre une variable statique et une variable globale.

Tout ça, bah c'est là.

Mais c'est stocké dans la structure de l'arbre.

Donc tous les délimiteurs qui ont disparu, en fait, nous permettraient de retrouver la structure qui est là. Donc on a des choses qui sont là et vous pouvez voir qu'effectivement grâce à l'AST, on fait la différence entre le b qui est là-haut, qui est donc un paramètre.

Définition dans une fonction.

Et puis les b qui sont là, parce que c'est des variables, lors de l'exécution de la fonction.

Donc on peut faire la différence entre un paramètre entrant et puis une variable.

Ce qui fait que, d'ores et déjà, et au lieu de passer par un grep dans le code, on peut sortir des statistiques sur le nombre de paramètres par fonction.

Je ne sais pas si vous avez vu déjà ce petit graphe que j'ai sorti il y a un mois ou deux.

Là, on a testé toutes les méthodes, fonctions, closures qu'on a pu trouver.

On a compté à chaque fois le nombre de paramètres qui était entrant. Donc vous pouvez voir que la totalité de 2000 projets utilise au moins une fonction avec un paramètre.

Le 100%, tout le monde utilise au moins des méthodes avec un seul paramètre.

Il y a des projets qui arrivent à se passer de méthode sans aucun paramètre.

Et vous pouvez voir que déjà on parle de 75. Je dirais que le cut, l'endroit où vous avez envie de mettre la limite, ça va être quelque part entre le 75% donc 5 ou 6.

5 ou 6, visiblement, c'est raisonnable.

Et là, vous commencez à sentir un petit peu ce que vous auriez en discussion avec d'autres développeurs.

A quel moment est-ce que je mets la limite ? Quel est le nombre de paramètres maximum raisonnable ? On sent déjà qu'il y a une limite qui apparaît, qui est assez simple, quelque part 5, 6, ça devrait être ça. D'accord ? Voyez notamment que, au-delà de 11, déjà, on est largement en dessous de 20%.

Mais ça existe.

Il y en a entre vous qui font ça. Donc on a dit, 11%, on est combien ? 50 ? Vous êtes 5, 6 là-dedans à faire des paramètres à des fonctions ??? Et de temps en temps, il y a un fou qui va jusqu'à 57.

Ouais. Alors bon, pour l'avoir cherché celui-là. En plus, tous les arguments sont typehintés.

Je vous laisse imaginer que c'est un petit peu long de créer cet objet-là.

Mais bon.

Ca permet quand même de se donner, c'est un feeling qu'on avait, et là, on voit une évolution.

De manière plus amusante, par exemple, ça c'est le nombre de variables locales.

Alors déjà, ça ressemble furieusement à ce qu'on vient de voir d'ailleurs.

On commence, zéro. Des méthodes qui n'utilisent absolument aucune variable locale. C'est possible ça ? Est-ce que c'est possible déjà ? Qui est-ce qui dit non ? C'est pas toujours la même réponse, méfiez-vous ! Donc c'est possible. Donc c'est complètement possible. Un relais, un appel statique, c'est correct.

Mais donc il y a quand même un maximum de gens qui utilisent au moins une variable locale.

Puis après, ça diminue progressivement, et de manière beaucoup plus, beaucoup plus stable, enfin, beaucoup plus lente. Ce qui fait que on a déjà les choses raisonnables. Mettons au-delà de 50% jusqu'à 10 variables locales, c'est C'est une stat normale. Là, vous remarquerez que je ne me suis pas arrêté à 57.

Déjà, 51 variables, c'est bien. En réalité, le résultat du graphe est quelque part derrière, dans la rue. Le maximum pour l'instant que j'ai en stock, le record du monde, c'est 293.

Le plus drôle, c'est que ce sont des variables qui sont extraites. Donc il y a un paramètre qui rentre.

ce n'est qu'un seul paramètre.

Extract. Et hop ! 293 ! Je ne sais pas comment ils font pour entretenir ce template-là mais bon.

Il y a des trucs costauds mais ça nous donne une idée.

Outre les stats, pour revenir sur l'AST, et parce que c'est quand même ça qu'on veut regarder.

Outre la nature de chacun des noeuds qu'on a mis en place.

On va surtout jouer dans l'arbre. C'est rigolo ça. On va installer une balançoire et puis on va se balader.

Donc on va chercher des motifs locaux. On peut suivre et requêter un arbre comme ça et le suivre.

Et puis, vous pouvez voir que le code que j'ai mis sur le côté, c'est la première ligne. On retrouve l'assignation en haut.

Côté gauche, c'est la variable qui va accueillir les informations.

Côté droite, c'est une autre opération qui est elle-même une addition.

avec, à chaque fois, son côté droit, son côté gauche. les additions ne freinent systématiquement que deux éléments donc si vous avez 1+2+3, c'est une addition d'une addition, on récupère les éléments et donc on aurait l'arbre qui s'étendrait vers le bas.

Ca, c'est juste un motif donc c'est une structure de code.

Qu'avant on chercherait avec un bout de regexp. Mince, je suis parti dans le mauvais sens.

C'est un motif.

C'est à dire qu'on est capable maintenant de reconnaître de manière systématique une assignation d'une variable plus 1.

Ou un autre littéral. Ce qu'on va vouloir faire, c'est passer à l'étape d'après et faire une analyse, parce que, en soi, ajouter 1 à une variable et puis mettre le tout dans une autre, c'est pas grave. Par contre, si on s'aperçoit qu'on a une variable, qu'on lui ajoute 1 et qu'on la remet effectivement dans la même variable.

Là, on a d'autres façons de l''écrire.

Et là on va se dire, ça c'est du bout de code, j'ai pas envie de le voir dans mon code.

C'est quelque chose que je ne veux pas écrire. Qu'est-ce qu'on aurait comme solution pour remplacer celui-là ? Alors, tous en même temps, non. a++, c'est bien ! ++a, c'est bien aussi. Et +=1, c'est encore bien.

Et donc là, à ce stade-là, on l'a. Bon vous imaginez bien, vu la taille de l'AST, que je ne peux pas vous mettre un truc gigantesque mais on peut vite monter sur des analyses un peu plus intéressantes. Dans lesquelles, on va fouiller toute l'addition et essayer de rechercher...

Qu'est-ce qu'on recherche là ? +b -b, voilà. Donc des choses qui finalement devraient simplifier et pourraient partir...

être égal à zéro.

Donc là, vous avez vu, on passe de 2 choses. On a d'un côté, le moyen de reconnaître un élément dans le code. De l'autre côté, on a le moyen de se dire, ça, c'est quelque chose que je ne veux pas dans mon code.

Et j'ai les moyens de le repérer.

Alors, Se balader dans l'arbre, ça va être rigolo. Mais en fait, il y a beaucoup plus intéressant encore que ça, parce qu'à ce stade-là, PHP continue. Quelle est l'étape d'après pour PHP ? C'est qu'il va utiliser un motif de développement qui est très courant, c'est qu'on a la dichotomie : définition, utilisation.

On définit à un endroit, et après, on utilise à l'intérieur du code à plein d'endroits.

Chose qu'on n'a jusqu'à présent pas vue.

On a juste vu comment le code est organisé, mais on le sait.

Ce genre de choses, c'est décliné. C'est valable pour les fonctions.

Ca, c'est l'exemple que je vous ai donné. C'est valable pour les classes, les traits, les interfaces, évidemment.

C'est valable pour les constantes. Sachant que les constantes, cette fois-ci, on a deux façons de faire les définitions.

C'est valable pour les variables aussi.

Julien est là encore pour me contredire ? Non ? C'est bon alors, je vais vous le dire.

Quand PHP fait son analyse, il commence par faire une passe dans l'AST et il repère toutes les variables intermédiaires que vous allez utiliser.

D'accord ? Et en fait il les pré-alloue, comme ça, quand il va exécuter la méthode, il sait déjà qu'il a 5 arguments, 8 variables, la mémoire va être pré-allouée. Si vous utilisez les 3 petits points, les points de suspension, ben là, il va effectivement allouer, à la volée, les éléments pour ses arguments, et il va perdre du temps.

Donc, si vous voulez un conseil de performances, évitez ce genre de stratégie.

Mais PHP les alloue, il les crée tout seul. Donc il a sa définition à un endroit, quelque part.

Alors qu'est-ce que ça veut dire dans notre AST ? Initialement on est parti avec ça. Quelque chose de relativement simple.

Et bien, on va pouvoir rajouter des liens entre les définitions.

Entre les définitions et leurs usages.

Et ça devient illisible. Alors, pour ceux qui ont un peu de mal dans le fond.

La définition de la fonction, on la voit apparaître là. Ca, c'est un élément extra.

Il y a aussi une définition des fonctions qui apparaît là, mais ça, ça va être beaucoup plus anecdotique.

Alors, si on reprend cet exemple-là, donc cette partie de graphe, et qu'on décide de la simplifier. Voilà les deux parties les plus importantes, d'accord ? Dans quel espace on vit nous ? On vit dans l'espace séquentiel.

On prend une opération, on en fait une autre, on en fait une autre, on en fait une autre.

Donc, en fait, on va suivre essentiellement cette partie-là, d'accord ? C'est le code de départ, mais un petit peu plus étendu, parce que j'avais besoin d'un "echo" derrière.

PHP, lui, va en plus identifier que le "echo", pardon, le "foo" est défini à un autre endroit, donc il va rajouter cette définition.

Il a les tables de hash qui lui permettent de savoir comment relier une partie du code avec quelque chose qui est un peu plus loin.

Donc on a maintenant cette espèce de branchement qui apparaît, que nous on a mis sous la forme "le lien entre eux, une fonction et sa définition".

La fonction elle-même, on l'a vue.

J'ai appelé cette séquence à ce stade-là mais c'est son bloc, son corps dans lequel on pourrait continuer et aller est un petit peu plus haut.

Alors sur une structure comme celle-là, quand on la simplifie et qu'on ne s'intéresse qu'à la partie définition, qui est vraiment simple. Vous êtes tous d'accord qu'une définition de fonction, son usage, on s'attend dans du code à une structure comme celle-là.

Est-ce que ça fait du sens ? J'ai une définition de fonction, je l'utilise plusieurs fois, et quand je le prends dans son contexte, un coup, c'est sur un tableau, un coup sur une fonction.

Tranquille.

Le cas le plus général va être celui-là.

Mais déjà, on va pouvoir se poser des questions. Est-ce qu'on peut tomber sur des cas particuliers comme celui-là ? Fonction, une seule fois, et utilisée une seule fois. Est-ce que c'est un problème ? C'est un peu du gâchis mais c'est pas grave.

Je vous sens un peu apathiques là...

Ca vous choque ? Ca vous choque pas ? Vous vous imposez vous-mêmes de vérifier que chacune des définitions de méthodes est utilisée au moins 2 fois, pour être sûr qu'il y a une réutilisation dans votre code ? Non hein ?! Moi non plus ! Donc voilà, on part là-dessus, on est toujours sur cette définition.

On peut tomber sur des cas un peu comme ceux-là.

C'est possible ça ? C'est pas la bonne bouteille. C'est possible ça ? Alors, qui dit oui et qui dit non ? Alors est-ce que c'est possible ? Ah c'est bien ça, ça doit être des gens qui faisaient du PHP 4 ça, comme moi.

Pourquoi c'est possible ça ? On est sur des fonctions.

Alors, les exemples que je vais vous donner là, ce sont des fonctions pour une raison simple, c'est que si on avait mis des méthodes, c'est vachement plus compliqué de les retrouver.

On se perdrait dans les objets.

On va rester sur les fonctions, vous connaissez tous. La définition va être plus simple.

Qu'est-ce que c'est ça ? Comment est-ce qu'on arrive à une structure comme celle-là ? La même fonction est définie quatre fois dans le code.

Je suis désolé, vous êtes 3 à parler en même temps. Alors levez le bras, je vais en désigner un.

"C'est pas possible".

Bon bah, moi je voulais quelqu'un qui disait que c'est possible.

Pourquoi c'est possible 4 définitions ? Vous n'avez pas compris "levez le bras" !? Vas-y...

Exactement ! La première est optimisée pour PHP 4, la deuxième pour PHP 5, La troisième pour PHP 3, et je vous épargne la PHP 6.

La dernière c'est pour PHP 7.

Qui est-ce qui programmait comme ça ? On programmait ça en PHP 4.

On n'avait pas de Namespaces, on n'avait aucun moyen. On ne faisait déjà pas de POO.

Donc on était obligé de définir des fonctions qui s'adaptaient à différentes situations.

Et comme elles pouvaient pas être toutes en même temps dans le code, elles étaient conditionnées.

je vous rappelle que contrairement à PHP, qui ne serait pas capable d'accepter 4 fonctions du même nom.

Nous, on a l'analyse statique. C'est pas un problème. On sait qu'on en n'utilisera qu'une seule.

Je dirais même qu'on est intéressé de voir que les 4 sont appelées au même endroit.

Et on voudrait justement voir si, quand on remonte dessus, les 4 sont capables de faire le même travail.

Donc ça, c'est important pour nous. Et je vous rappelle qu'on n'est pas en exécution. On n'est pas PHP.

Vous voulez le twitter ? C'est le bon moment.

Alors, puisqu'on en est là.

Puisque vous m'avez l'air frais... On va se lancer ! Un petit challenge ! Comment est-ce qu'avec l'arbre qu'on vient de voir, on est capable de détecter une fonction morte ? Une fonction qui n'a pas d'utilisation ? Je veux bien un "levez le bras" Oui "Le bloc est vide ?" Non, ça c'est une fonction vide. On veut une fonction morte, c'est une fonction qui n'est pas appelée.

Mais je suis d'accord, on rentre dans le bloc, le bloc n'a qu'un seul élément "void", tout vide, effectivement "fonction vide" ça marche.

C'est ça. Donc on n'a pas de lien vers la fonction pour l'usage.

Donc on a une fonction qui est là, qui est toute seule, qui est un peu perdue, qui est dans l'AST.

mais elle n'a pas de lien qui s'appelle définition parce qu'on n'a pas trouvé d'utilisation dans le reste du code.

Il reste 15 minutes !? Non...

Il va falloir accélérer les gars ! Je vous en pose une autre ? Fonction morte ! Plus dur ! Alors on a dit, c'est une fonction qui n'a pas de définition. Très bien.

Mais il y en a d'autres des fonctions mortes qui n'ont pas de définition.

C'est les fonctions qui sont appelées par des fonctions mortes.

J'appelle ça "la propagation linéaire de la mort".

Quand vous regardez, généralement, vous héritez ça de vos parents quand même ! Bon bref...

Plus dur ! Il y en a d'autres encore des fonctions qui sont mortes et qui ont pourtant des définitions.

Pardon ? Non, ah ouais. Ouais ? Non ! Fonction morte récursive ! C'est quoi une fonction récursive ? C'est une fonction qui s'appelle elle-même. Donc j'ai une définition de la fonction sur elle-même.

Ce qu'il faut, c'est que la fonction récursive ne soit pas appelée par l'extérieur.

Ah ça devient dur !? Là, vous êtes contents que je n'ai pas fait d'études littéraires, parce que j'ai pas de vocabulaire supplémentaire.

Encore une ?! C'est récursive. Allez on va accélérer parce que sinon, il y en a qui ne vont pas être contents. Récursive de deuxième niveau.

3e niveau.

Ca devient compliqué mais si on fait le bilan, là. Vous avez vu, on est parti de pas grand chose ? Mais parti de la définition, de ses usages et là, on est obligé d'appliquer des concepts, purement de développement et on regarde différentes étapes. Le premier, c'est plus évident. On en parle, vous vous êtes jetés dessus comme la misère sur le monde.

"Ouais, y'a pas de définition, il n'est pas appelé, parfait !" Et puis après, on va sur le terrain, et là, on s'aperçoit que des petits cas particuliers et des situations un petit peu spéciales.

J'enlève une fonction, je repasse l'audit, j'ai toujours une fonction morte. Pas la même.

Oui, ça se propage et après c'est une fonction récursive, et après...

Après, on commence à rentrer dans un truc bizarre là.

Cette espèce de cycle là. C'est à dire qu'il faut que ces 3 fonctions qui s'appelle l'une l'autre, et que à chacune des étapes des fonctions, il n'y a aucune des fonctions qui soient appelées par l'extérieur.

Alors là, ça devient chiant à faire, même pour de l'analyse statique.

Parce que si vous avez 192 fonctions, le cycle maximal, il fait 192 fonctions, ça va être long à traiter.

Mais donc, on a vraiment cette progression qui est hyper importante de dire, je commence par la définition la plus évidente, et puis, plus je progresse, plus il faut que j'aille sur le terrain pour vérifier ce que ça donne.

Et qu'à un moment, non seulement je vais m'arrêter, mais finalement je vais démarrer une analyse différente parce que repérer des fonctions qui s'appellent en cycle, ça peut être intéressant. ça peut peut-être expliquer un certain nombre de ralentissements à l'intérieur de votre code. Les fonctions se renvoient la balle jusqu'à ce qu'il y en est une qui dise "OK, on s'arrête".

Et là, il y avait un problème de conception ou quelque chose qui est apparu, d'accord ? Le dernier, on est plus en découverte de code. Les premiers, on va pouvoir l'anticiper. On va réfléchir nous-mêmes.

Le dernier, on va le découvrir et là, ça va être terrible.

Pour finir...

C'est quoi ça ? Un appel de fonction sans définition.

Facile ! J'aime bien, il y en a qui secouent la tête en espérant que ce soit la réponse.

C'est quoi ça ? Il y en a qui ont de petites voix.

Une fonction non définie que vous appelez. Ca vous arrive d'appeler des fonctions non définies ? Les fonctions internes ! Alors les fonctions internes, il y a les autres aussi. Les fonctions externes. Mais le principe effectivement là Vous avez noir de fonctions qui ne sont pas définies dans le code. Il y a celles qui sont natives.

Il y a celles qui sont natives, il y a celles qui viennent de PHP lui-même.

Enfin là, vous ne pourrez pas y échapper. strtolower vous l'avez en permanence.

Vous avez celles qui viennent des extensions supplémentaires.

Et puis vous avez tous vos frameworks. Alors là, j'ai mis 'composants", c'est le terme générique. Vous appelez ça une plateforme, vous appelez ça un framework, vous appelez ça librairie interne, bon.

Mais votre code est en sandwich, et tous ces outils-là que vous n'avez pas envie d'analyser, fournissent des éléments intéressants.

Comment est-ce qu'on va être capable de passer de votre code et d'élever le niveau d'abstraction ? On va évidemment se baser sur tous les éléments qui peuvent se définir. On vient travailler avec les fonctions, c'est un exemple.

on peut décliner les classes, les constantes, les interfaces, les traits. Tout ce que vous voulez. Il y a même des extensions suhosin Ceux qui l'ont utilisé.

Il ne faisait que des directives. Il n'y a pas de fonction, il y avait juste des directives de compilation, pas d'exécution.

Et à partir de là, on va pouvoir fouiller toutes les extensions qui existent et arriver à comprendre quelles sont celles qui sont utilisées dans le code.

Et ça, c'est un élément important dans notre élévation de niveau de jeu parce que, d'un côté, l'analyse statique travaille dans votre code. Donc elle est vraiment capable de repérer les variables et compagnie, mais elle n'a pas de vision d'ensemble.

De notre côté, quand on est ensemble sur une conférence, je ne vais pas vous donner les détails des classes. Je vais vous parler de l'extension MongoDB, l'extension Phing, que des choses comme ça.

Entre les deux, il nous manque une information. Il nous faut une bibliothèque de données qui soit capable de relier les extensions avec le code. Et là, l'analyse statique, d'un seul coup, elle fait le lien et elle ne se pose pas de questions, parce qu'elle peut travailler de manière systématique.

Exemple : Qu'est-ce que c'est ça ? Ca, ce sont les extensions.

Donc, on prend toutes les définitions de fonction, on les répartit par nom d'extension, et on se retrouve avec la répartition des extensions dans cette application. Est-ce que c'est une vraie application ça ? Est-ce que vous avez une idée de ce qu'elle fait ? Elle utilise des chaînes, elle utilise des tableaux, elle utilise PHP standard. Jusque-là, ça casse pas trois pattes à un canard.

Derrière, on a encore SPL, Date.

P***, ça veut être dur de comprendre que c'est un système de e-commerce.

Elle est où la base de données ? Elle n'est pas là, donc elle n'est pas dans les extensions.

Elle n'est pas dans les extensions, elle est dans le framework.

Alors le framework, le problème est exactement le même que pour les extensions.

Si ce n'est qu'il est démultiplié. Prenez le Zend Framework, vous êtes en 2.5.

C'est un framework monolithique, vous décidez de passer en version 3.

Au lieu d'avoir un framework, vous vous retrouvez avec 60 composants, ou 65, je ne sais plus.

Et puis, chacun a sa propre version. Barcode, il existe, personne ne l'utilise. Mais il est là, il ne bouge plus. Parfait.

Par contre, il y en a d'autres qui changent de version tout le temps.

Mais le framework fonctionne de la même façon qu'une extension. Donc on est capable de dire : voici les classes, les interfaces que le framework définit.

Et à partir de là, on peut regarder à l'intérieur qu'est-ce qui est utilisé dans l'application pour en définir les compatibilités ? Ici, on est capable de repérer, non seulement, les composants qui sont pas utilisés, mais parmi ceux qui sont utilisés, on repère les compatibilités juste en repérant la méthode et la version dans laquelle elle est définie.

D'accord ? Alors, comment ça marche ? Cette fois-ci, on parle du composant. Ca peut être aussi simple qu'un composant et ça peut être un framework complet.

Il faut qu'on décline par version, il faut qu'on décline les fonctions, les classes, les méthodes, les interfaces. Il faut que, pour chacune, on aille voir les méthodes.

A l'intérieur, on va voir les arguments avec leur typehint, leur valeur par défaut, leurs noms et leurs ordres.

Vous prenez CakePHP, vous prenez toutes les versions qu'ils ont, ça vous fait un demi giga de documentation.

Mais ce n'est pas un gros framework, il y a plus gros.

Par contre, en soi, ça fait peur. S'il faut le faire à la main, c'est horrible. Mais tout ça c'est automatisable.

De quoi est-ce qu'on a besoin pour faire une base de données comme celle-là ? On part du code source du framework. Ca tombe bien, il est en PHP cette fois-ci, donc contrairement au C, on est capable de s'intéresser à ce qu'il y a derrière.

On extrait les versions. On a même un outil qui industrialise ça, qu'on appelle le Juicer, qui extrait toutes les infos là-dessus.

qui nous le ressort et qui nous en fait une base de données interrogeable. A partir de là, l'analyse statique reprend les infos, on est capable de sortir les versions de chacun des composants les uns à côté des autres.

Et là, au pire, la discussion avec la machine ça va être : "Oh bah c'est gros, bah, file-moi un Giga de plus." C'est une machine. Là, pour le coup, elle ne se pose pas trop de questions et s'il faut le refaire demain, on le refera demain.

Donc on va terminer là-dessus. Je n'ai pas eu droit à mon petit 5 moi.

Donc pour la compatibilité, même si c'est extrêmement volumineux, même si c'est extrêmement volumineux, c'est pas grave là. C'est la machine qui travaille. Elle va mettre un peu plus de temps, mais elle est capable de vous faire le détail et de vous sauver énormément de travail.

Elle est capable aussi de travailler sur des situations beaucoup plus contextuelles.

Evitez d'utiliser un entier dans l'Error reporting, parce que dès qu'il va y avoir un changement dans les noms de variables, dans les noms de constantes, vous allez avoir un changement dans les logs qui vont être envoyés, d'accord ? Mais par contre, on peut chercher les bugs, les bugs de PHP.

Aujourd'hui, on en a bientôt 80 000 qui ont été fermés, donc c'est un bon score mais chacun des bugs dispose d'un petit script, qui explique comment ils ont découvert le bug. A partir de là, on peut récupérer une petite partie des éléments.

On se base juste sur l'API et ça nous permet de dire, si jamais votre code utilise ses fonctions-là, a priori, vous allez être impactés par une version mineure de PHP. Qui est-ce qui surveille ces versions mineures de PHP ? Ah c'est bon, merci.

Qui est-ce qui surveille ces fonctions mineures ? Non, à la rigueur des versions intermédiaires, des versions majeures, on sait que de toute façon ça va casser. Mais les mineures, on ne sait pas quel impact.

Or là, c'est relativement simple, il y a généralement quelques fonctions qui sont impactées, qui sont décrites dans le bug report.

Il suffit de les prendre, de les collecter et comme vous pouvez voir ici.

Là, le code qui est là, je ne sais plus qui est-ce que j'avais interrogé, mais il y a des impacts sur la 7.2.11, la 7.2.7 et, entre les deux, a priori, on passe à côté.

Et ça, quand vous vous apercevez que, d'un seul coup, un composant tombe en marche sans que vous sachiez pourquoi, information intéressante.

Je vais finir l'abstraction, on finit de s'élever.

Il y a des fois la source de, comment, la source des informations que vous allez vouloir utiliser, pour pouvoir chercher dans le code, ça va être encore un niveau au-dessus.

L'OWASP ne fait pas que PHP, c'est destiné au web en général.

Il faut à chaque fois comprendre la règle, la traduire en quelque chose de PHP et, à ce moment-là, on peut chercher.

D'accord ? Donc là, on a ré-appliqué le même principe.

On prend la définition telle qu'elle est présentée de manière abstraite, on la décline en PHP, et à ce moment-là, on peut compter les utilisations à l'intérieur du code.

Encore une source d'informations intéressantes, pour pouvoir rajouter de l'information, la mettre dans la machine, et à partir de là récupérer les résultats et les recherches.

Et je vais avoir droit à un petit 2 là. Pour finir, pour finir, on s'est beaucoup présenté comme étant du côté des, comment ça s'appelle, des victimes ici, d'accord ? C'est le méchant framework qui change sa version, et on voudrait le suivre. C'est le méchant PHP qui change sa version, et on voudrait savoir où est-ce que c'est.

Les informations comme vous avez là, vous êtes parfois la source des informations.

C'est vous qui lui développez un nouveau framework, une plateforme de e-commerce.

Vous voulez pouvoir le mettre à disposition de vos utilisateurs, vous connaissez l'information. Ce qui vous manque, c'est : 1 - d'accéder au code de vos utilisateurs, donc ça, la machine pourrait le faire, mais surtout vous pouvez fournir la documentation et dire "voilà, cette méthode-là n'existe plus.

Ca, c'est une question de compatibilité entre les deux versions." Analysez-le avec l'analyse statique et vous allez avoir un bilan complet, et des suggestions qui vous permettront de passer à la prochaine version.

Merci pour tous.

"Donc, on a deux minutes pour les questions".

Bonjour.

Bonjour.

Stas Trefilov, de Vestiaire collective. La question par rapport à des fonctions mortes, comme vous les avez définies, est-ce que j'ai le code qui, qui définit le nombre de fonctions dans une variable ? Alors ça, c'est de l'analyse dynamique.

De l'analyse dynamique, typiquement, c'est des choses où vous aurez la donnée au moment donné de l'exécution.

Généralement, l'analyse dynamique couvre un gros 2 à 3% de l'application elle-même, d'accord ? Donc déjà, c'est effectivement un problème puisqu'on est en analyse statique, donc on n'a pas la donnée, et donc, à ce stade-là, il va falloir qu'effectivement, on abandonne, enfin, qu'on laisse de côté, tous les appels de fonctions dynamiques parce qu'on n'a pas l'information.

Il faut savoir qu'il y a quand même des choses qu'on va pouvoir étudier, d'accord ? Typiquement, le nom de la variable peut être filtré, donc ça arrive par un GET.

Elle est filtrée, elle doit faire partie d'un ensemble assez court.

La machine est capable de reprendre cet ensemble assez court, et se dire forcément c'est une de ces fonctions qui va être appelée.

On aura un appel qui se fera sur 5 ou 6 fonctions différentes.

Ce que je te présente là, c'est de la science fiction, ça ne se fait pas encore.

Mais en tous cas, il y a moyen d'aller chercher une partie de l'information et d'aller la vérifier.

La technique que je viens de décrire là, en plus, a l'avantage de sécuriser l'ensemble. C'est à dire que tu fais une liste blanche, la liste blanche nous sert en analyse statique pour garder la navigation à l'intérieur du code, et en plus d'assurer la sécurité de l'application.

Mais en règle générale non. Dès que ça devient dynamique, dans le nom, ça ne sera pas possible, donc, il faut que tu finisses à la main, je dirais, les quelques cas qui t'impactent.

Mais ça n'est pas beaucoup souvent.

Mais ça peut être plus gros, je suis d'accord.

"Merci Damien, on ne pourra pas prendre d'autres questions." Moi, je reste là encore pendant une heure donc...

"N'hésitez pas à voir Damien et à lui fournir le feedback via le tableau de feedbacks qui est disponible comme pour toutes les autres conférences aussi." "Encore merci Damien !"