AST (Abstract Syntax Tree)

L’AST (Abstract Syntax Tree) est une représentation graphique du code source principalement utilisée par les compilateurs pour lire le code et générer les binaires cibles.

Par exemple, l’AST de cet échantillon de code :

while b ≠ 0
if a > b
a := a − b
else
b := b − a
return a

Va ressembler à ceci :

La transformation du code source en un AST, est un schéma très courant lors du traitement de tout type de données structurées. Le flux de travail typique est basé sur « la création d’un analyseur syntaxique qui convertit les données brutes en un format basé sur les graphes, qui peut ensuite être consommé par un moteur de rendu ».

C’est essentiellement le processus de conversion des données brutes en objets en mémoire fortement typés qui peuvent être manipulés par programme.

Voici un autre exemple provenant de l’outil en ligne vraiment cool astexplorer.net:

Notez comment dans l’image ci-dessus le texte brut du langage DOT a été converti en un arbre d’objets (qui peut également être consommé/sauvegardé comme un fichier json).

En tant que développeur, si vous êtes capable de « voir le code ou les données brutes sous forme de graphique », vous aurez effectué un changement de paradigme étonnant qui vous aidera énormément à travers votre soignant.

Par exemple, j’ai utilisé des AST et des parsers personnalisés pour :

  • écrire des tests qui vérifient ce qui se passe réellement dans le code (facile lorsque vous avez accès à un modèle objet du code)
  • consommer des données de logs que les analyseurs normaux basés sur les ‘regex’ avaient du mal à comprendre
  • réaliser une analyse statique sur du code écrit dans des langages personnalisés
  • transformer des vidages de données en objets in-.mémoire (qui peuvent ensuite être facilement transformés et filtrés)
  • créer des fichiers transformés avec des tranches de plusieurs fichiers de code source (que j’ai appelés MethodStreams et CodeStreams) – Voir l’exemple ci-dessous pour voir à quoi cela ressemble en pratique
  • réaliser un refactoring de code personnalisé (par exemple pour corriger automatiquement les problèmes de sécurité dans le code) – Voir l’exemple ci-dessous pour voir à quoi cela ressemble en pratique

Lors de la construction d’un analyseur syntaxique pour une source de données particulière, un jalon clé est la capacité d’aller-retour, de pouvoir passer de l’AST, au texte original (sans perte de données).

C’est exactement comme cela que fonctionne le refactoring de code dans les IDEs (par exemple lorsque vous renommez une variable et que toutes les instances de cette variable sont renommées).

Voici comment fonctionne cet aller-retour :

  1. débuter avec le fichier A (c’est-à-dire . le code source)
  2. créer l’AST du fichier A
  3. créer le fichier B comme transformation de l’AST
  4. le fichier A est égal au fichier B (octet par octet)

Quand cela est possible, il devient facile de modifier le code de manière programmatique, puisque nous manipulerons des objets fortement typés sans nous soucier de la création d’un code source syntaxiquement correct (qui est maintenant une transformation de l’AST).

Écrire des tests en utilisant des objets AST

Une fois que vous commencez à voir votre code source (ou les données que vous consommez) comme « seulement un parseur AST loin d’être des objets que vous pouvez manipuler », tout un mot d’opportunités et de capacités s’ouvrira pour vous.

Un bon exemple est comment détecter un motif particulier dans le code source que vous voulez vous assurer qu’il se produit dans un grand nombre de fichiers, disons par exemple que vous voulez : « vous assurer qu’une méthode d’autorisation (ou de validation des données) particulière est appelée sur chaque méthode de services web exposée » ?

Ce n’est pas un problème trivial, car à moins que vous ne soyez capable d’écrire programmatiquement un test qui vérifie cet appel, vos seules options sont :

  1. écrire un ‘document standard/page wiki’ qui définit cette exigence, et s’assurer que tous les développeurs le lisent, le comprennent, et plus important encore, le suivent
  2. vérifier manuellement si cette norme/exigence a été correctement mise en œuvre (sur les revues de code Pull Requests)
  3. essayer d’utiliser l’automatisation avec des outils basés sur le ‘regex’ (commerciaux ou open source), et réaliser qu’il est vraiment difficile d’en obtenir de bons résultats
  4. se rabattre sur les tests manuels d’AQ (et les revues de sécurité) pour repérer les points aveugles

Mais, lorsque vous avez la capacité d’écrire des tests qui vérifient cette exigence, voici ce qui se passe :

  1. écrire des tests qui consomment l’AST du code pour pouvoir vérifier très explicitement si la norme/exigence a été correctement implémentée/codée
  2. via des commentaires dans le fichier de test, la documentation peut être générée à partir du code de test (i.c’est-à-dire aucune étape supplémentaire nécessaire pour créer une documentation pour cette norme/exigence)
  3. exécuter ces tests dans le cadre de la construction locale et dans le cadre du pipeline principal de CI
  4. en ayant un test échoué, les développeurs sauront ASAP une fois qu’un problème a été découvert, et pourront le corriger très rapidement

C’est un exemple parfait de la façon d’échelonner l’architecture et les exigences de sécurité, d’une manière qui est intégrée dans le cycle de vie du développement logiciel.

Nous avons besoin d’AST pour les environnements patrimoniaux et en nuage

Plus vous pénétrez dans les AST, plus vous réalisez qu’elles sont des couches d’abstractions entre différentes couches ou dimensions. Plus important encore, ils permettent la manipulation des données d’une couche particulière d’une manière programmatique.

Mais quand vous regardez les environnements legacy et cloud actuels (la partie que nous appelons ‘Infrastructure as code’), ce que vous verrez, ce sont de grandes parties de ces écosystèmes qui, aujourd’hui, n’ont pas d’analyseurs AST pour convertir leur réalité en objets programmables.

C’est un grand domaine de recherche, où vous vous concentrerez sur la création de DSL (Domain Specific Languages) soit pour les systèmes hérités, soit pour les applications cloud (choisissez-en un puisque chacun aura des ensembles de matériaux sources complètement différents). Un exemple du type de DSL dont nous avons besoin est un langage pour décrire et codifier le comportement des fonctions Lambda (à savoir les ressources dont elles ont besoin pour s’exécuter, et quel est le comportement attendu de la fonction Lambda)

MethodStreams

L’un des exemples les plus puissants de manipulation AST que j’ai vu, est la fonctionnalité MethodStreams que j’ai ajoutée à la plateforme O2.

Avec cette fonctionnalité, j’ai pu créer de manière programmatique un fichier basé sur l’arbre d’appel d’une méthode particulière. Ce fichier contenait tout le code source pertinent à cette méthode originale (généré à partir de plusieurs fichiers), et faisait une différence massive lors des revues de code.

Pour comprendre pourquoi j’ai fait cela, commençons par le problème que j’avais.

En 2010, je faisais une revue de code d’une application .Net qui avait un million de lignes de code. Mais je ne regardais que les méthodes WebServices, qui ne couvraient qu’une petite partie de cette base de code (ce qui était logique puisque c’étaient les méthodes exposées à internet). Je savais comment trouver ces méthodes exposées à l’internet, mais pour comprendre comment elles fonctionnaient, je devais regarder des centaines de fichiers, qui étaient les fichiers qui contenaient du code dans le chemin d’exécution de ces méthodes.

Puisque dans la plateforme O2 je disposais déjà d’un parseur C# très fort et d’un support de refactoring de code (implémenté pour la fonctionnalité REPL), j’ai pu écrire rapidement un nouveau module qui :

  1. en partant de la méthode X du service web
  2. calculait toutes les méthodes appelées depuis cette méthode X
  3. calculait toutes les méthodes appelées par 2. (de manière récursive)
  4. capturer les objets AST de toutes les méthodes identifiées par les étapes précédentes
  5. créer un nouveau fichier avec tous les objets de 4.

Ce nouveau fichier était étonnant, car il contenait SEULEMENT le code que je dois lire pendant ma revue de sécurité.

Mais l’événement s’est amélioré, puisque dans cette situation, j’ai pu ajouter les RegEx de validation (appliquées à toutes les méthodes WebServices) en haut du fichier, et ajouter le code source des procédures stockées concernées en bas du fichier.

Certains des fichiers générés avaient 3k+ lignes de code, ce qui était une simplification massive des 20+ fichiers qui les contenaient (qui avaient probablement 50k+ lignes de code).

Voici un bon exemple de ma capacité à faire un meilleur travail, en ayant accès à un large ensemble de capacités et de techniques (dans ce cas, la capacité à manipuler programmatiquement le code source)

Ce type de manipulation AST est un domaine de recherche sur lequel je vous recommande fortement de vous concentrer (ce qui vous donnera également une boîte à outils massive pour vos activités de codage quotidiennes). Si vous suivez cette voie, consultez également les CodeStreams de la plate-forme O2, qui sont une évolution de la technologie MethodStreams. CodeStreams vous donnera un flux de toutes les variables qui sont touchées par une variable source particulière (ce qui dans l’analyse statique est appelé Taint flow analysis et Taint Checking)

Fixer le code en temps réel (ou au moment de la compilation)

Un autre exemple vraiment cool de la puissance de la manipulation AST est le PoC que j’ai écrit en 2011 sur Fixing/Encoding .NET code en temps réel (dans ce cas Response.Write), où je montre comment ajouter programmatiquement un correctif de sécurité à une méthode vulnérable par conception.

Voici à quoi ressemblait l’interface utilisateur, où le code à gauche, a été transformé programmatiquement en code à droite (en ajoutant la méthode wrapper AntiXSS.HtmlEncode supplémentaire)

Voici le code source qui effectue la transformation et la correction du code (notez l’aller-retour du code) :

En 2018, la façon de mettre en œuvre ce flux de travail de manière conviviale pour les développeurs, est de créer automatiquement une Pull Request avec ces changements supplémentaires.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.