Et s'il n'y a pas d'analyseur statique pour votre langue préférée?

Eh bien, si votre langue préférée est le russe, l'anglais, etc., c'est dans un autre hub . Et si le langage de programmation ou de balisage, alors bien sûr, écrivez l'analyseur vous-même! À première vue, c'est très difficile, mais, heureusement, il existe des outils multilingues prêts à l'emploi, qui sont relativement faciles à ajouter à la prise en charge d'une nouvelle langue. Aujourd'hui, je vais montrer comment ajouter la prise en charge du langage Modelica à l' analyseur PMD avec un temps assez court.


Soit dit en passant, savez-vous ce qui peut dégrader la qualité de la base de code obtenue à partir d'une séquence de demandes d'extraction idéales? Le fait que des programmeurs tiers aient copié des morceaux de code de projet existant dans leurs correctifs au lieu d'une abstraction compétente. Vous devez admettre que, dans une certaine mesure, il est encore plus difficile de détecter une telle banalité qu'un code de mauvaise qualité - il est de haute qualité et même déjà complètement débogué, donc la vérification locale ne suffit pas ici, vous devez garder à l'esprit l'intégralité de la base de code, mais ce n'est pas facile pour une personne ... Donc: l'ajout de la prise en charge complète de Modelica (sans créer de règles spécifiques) à l'état «peut exécuter des vérifications primitives» m'a pris environ une semaine, puis seule la prise en charge du détecteur copier-coller peut souvent être ajoutée en une journée!


Quoi d'autre est Modelica?


Modelica est, comme son nom l'indique, un langage pour écrire des modèles de systèmes physiques. En fait, non seulement physique: il est possible de décrire les processus chimiques, le comportement quantitatif des populations animales, etc. - cela est décrit par des systèmes d'équations différentielles de la forme der(X) = f(X) , où X est le vecteur d'inconnues. Des morceaux de code impératifs sont également pris en charge. Les équations différentielles partielles ne sont pas explicitement prises en charge, mais il est possible de diviser la zone d'étude en morceaux (comme nous l'aurions probablement fait dans un langage à usage général), puis d'écrire les équations pour chaque élément, réduisant le problème au précédent. L'astuce de Modelka est que la solution à ce der(X) = f(X) réside dans le compilateur: vous pouvez simplement changer le solveur dans les paramètres , l'équation n'a pas à être linéaire, etc. En bref, il y a quelques avantages (j'ai écrit la formule du manuel - et cela a fonctionné), et les inconvénients (avec plus d'abstraction, nous obtenons moins de contrôle). Une introduction à Modelika fait l'objet d'un article séparé (qui a déjà paru plusieurs fois sur Habré), et même d'un cycle entier, aujourd'hui il m'intéresse comme un open et ayant plusieurs implémentations, mais, hélas, toujours un standard humide.


De plus, Modelika, d'une part, a une typification statique (ce qui nous aidera à écrire une analyse significative plus rapidement), d'autre part, lors de l'instanciation d'un modèle, le compilateur n'est pas obligé de vérifier entièrement la bibliothèque entière (par conséquent, un analyseur statique est très utile pour capturer «dormant» bugs). Enfin, contrairement à certains C ++, pour lesquels il existe un nuage d'analyseurs statiques et de compilateurs avec de belles, et surtout détaillé, voir les modèles C ++ diagnostics d'erreurs, compilateurs Les modèles génèrent toujours périodiquement une erreur de compilation interne, ce qui signifie qu'il y a de la place pour aider l'utilisateur même avec un analyseur assez simple.


Qu'est-ce que PMD?


Je répondrai chanson à vélo. Une fois, je voulais faire une petite demande d'extraction dans l'environnement de développement pour OpenModelica. En voyant comment l'enregistrement du modèle est traité dans une autre partie du code, j'ai remarqué une petite partie intérieure peu claire de quatre lignes de code qui supportait une sorte d'invariant. Ne comprenant pas avec quel type d'éditeur interne il interagit, mais réalisant que du point de vue de ce morceau de code, ma tâche est complètement identique, je l'ai simplement mis dans une fonction afin de pouvoir le réutiliser et non le casser. Menteiner a dit, c'est merveilleux, alors seulement remplacez ce code par un appel de fonction dans les vingt endroits restants ... J'ai décidé de ne pas m'impliquer encore, et j'ai juste fait une autre copie, notant qu'alors d'une certaine façon je devrais tout peigner à la fois sans mélanger avec le patch actuel. Googler, j'ai trouvé le détecteur de copier-coller (CPD) - une partie de l'analyseur statique PMD - qui prend en charge encore plus de langues que l'analyseur lui-même. Après l'avoir défini sur la base de code OMEdit, je m'attendais à voir ces deux douzaines de morceaux de quatre lignes. Je ne les ai tout simplement pas vus (chacun d'eux n'a tout simplement pas dépassé le seuil du nombre de jetons), mais j'ai vu, par exemple, la répétition de près de cinquante lignes de code C ++. Comme je l'ai déjà dit, il est peu probable que le mentor ait simplement copié une gigantesque pièce d'un autre fichier. Mais il pourrait facilement passer à côté de PR - car le code, par définition, répondait déjà à toutes les normes du projet! Lorsque j'ai partagé l'observation avec le mentor, il a convenu qu'il serait nécessaire de nettoyer dans le cadre d'une tâche distincte.


En conséquence, le détecteur d'erreur de programme (PMD) est un analyseur statique facilement extensible. Peut-être qu'il ne calcule pas l'ensemble de valeurs qu'une variable peut prendre (bien que qui sait ...), mais pour y ajouter des règles, vous n'avez même pas besoin de connaître Java et de changer son code! Le fait est que la première chose que lui, et sans surprise, construit des fichiers AST avec des codes sources. Et à quoi ressemble l'arbre d'analyse source? Vers l'arbre d'analyse XML! Ainsi, vous pouvez décrire les règles simplement comme des demandes XPath - pour lesquelles elles correspondent, puis nous émettons un avertissement. Ils ont même un débogueur graphique pour les règles! Des règles plus complexes, bien sûr, peuvent être écrites directement en Java en tant que visiteurs de l'AST.


Conséquence : PMD peut être utilisé non seulement pour les règles strictes et universelles que les programmeurs Java sévères ont appliquées au code de l'analyseur, mais aussi pour le style de codage local - même si vous poussez votre propre jeu de règles local.xml dans chaque référentiel!


Niveau 1: trouver le copier-coller automatiquement


En principe, l'ajout d'une prise en charge d'une nouvelle langue en CPD est souvent très simple. Je ne vois aucun sens à raconter la documentation «comment faire» - elle est très claire, structurée et étape par étape. Pour raconter une telle chose - ne jouez que dans un téléphone endommagé. Je ferais mieux de décrire ce qui vous attend (TLDR: ça va) :


  • L'analyseur (PMD et CPD) est développé sur le github dans le référentiel pmd / pmd
  • Le débogueur de règles visuelles a été déplacé vers un référentiel pmd / pmd-designer distinct. Veuillez noter que le pseudonyme fini est automatiquement intégré dans la distribution binaire PMD , que Gradle collectera pour vous dans le référentiel précédent, vous n'avez pas besoin de cloner spécialement pmd-designer pour cela.
  • Le projet dispose d'une documentation développeur . Celui que j'ai lu était très détaillé. Certes, un peu dépassé, mais cela est traité par la deuxième demande de tirage :)

Je vous préviens que je développe sur Ubuntu. Sous Windows, cela devrait également fonctionner parfaitement - à la fois en termes de qualité et dans le sens d'une manière légèrement différente de lancer des outils.


Donc, pour ajouter une nouvelle langue au CPD, il vous suffit de ...


  • ATTENTION: si vous voulez une prise en charge complète de PMD avant la sortie de PMD 7, alors il vaut mieux aller directement au niveau 2, car la prise en charge normale pour le chemin facile à travers la grammaire Antlr finie apparaîtra, selon les rumeurs, dans la version 7, mais pour l'instant, vous ne passerez que du temps (bien que et un peu ...)
  • Forkez le référentiel pmd / pmd .
  • Trouvez dans antlr / grammars-v4 une grammaire prête à l'emploi pour votre langue - bien sûr, si la langue est interne, vous devez l'écrire vous-même, mais pour Modelika, par exemple, elle a été trouvée. Ici, bien sûr, vous devez vous conformer aux formalités avec les licences - je ne suis pas avocat, mais au moins je dois spécifier la source d'où j'ai copié.
  • Après cela, vous devez créer le pmd-<your language name> , l'ajouter à Gradle et y placer le fichier de grammaire. De plus, après avoir lu deux pages de documentation non stressante, refaire le script d'assemblage du module pour Go, quelques classes pour charger le module par réflexion, eh bien, il y a une petite chose ...
  • Corrigez la sortie de référence dans l'un des tests, car CPD prend désormais en charge une autre langue! Comment trouvez-vous ce test? Très facile: il veut casser la construction .
  • PROFIT! C'est vraiment simple à condition qu'il y ait une grammaire toute faite

Maintenant, étant à la racine du référentiel pmd, vous pouvez taper ./mvnw clean verify , tandis que dans pmd-dist/target vous obtiendrez, entre autres, une distribution binaire sous la forme d'une archive zip que vous devez décompresser et exécuter à l'aide de ./bin/run.sh cpd --minimum-tokens 100 --files /path/to/source/dir --language <your language name> du répertoire décompressé. En principe, vous pouvez effectuer ../mvnw clean verify partir de votre nouveau module, ce qui accélérera considérablement l'assemblage, mais vous devrez ensuite placer correctement le surnom de bocal assemblé dans la distribution binaire non compressée (par exemple, assemblé une fois après l'enregistrement d'un nouveau module).


Niveau 2: recherche d'erreurs et de violations du guide de style


Comme je l'ai dit, un support complet pour Antlr est promis dans PMD 7 . Si vous, comme moi, ne voulez pas attendre la sortie de la mer, vous devrez obtenir une description de la grammaire de la langue au format JJTree quelque part. Vous pouvez peut-être durcir vous-même le support d'un analyseur arbitraire - la documentation dit que c'est possible, mais ils ne disent pas exactement comment ... J'ai simplement pris modelica.g4 du même référentiel avec les grammaires pour Anltr comme base, et je l'ai manuellement refait dans JJTree. Naturellement, si la grammaire s'est avérée être un traitement de l'existant, indiquez à nouveau la source, vérifiez le respect des licences et. etc.


Soit dit en passant, pour une personne qui connaît bien toutes sortes de générateurs d'analyseurs, il est peu probable que cela surprenne. Avant cela, je l'ai utilisé sérieusement à moins que j'aie moi-même écrit des habitués et des combinateurs d'analyseurs sur Scala. Par conséquent, la chose évidente, en fait, m'a d'abord attristé: AST, bien sûr, je vais partir de modelica.g4 , mais cela n'a pas l'air très clair et "utilisable": il y aura des nuages ​​de nœuds supplémentaires dedans, et si vous ne regardez pas les jetons , mais seulement à nœuds, il n'est pas toujours clair où, par exemple, la branche then se termine, et else commence.


Encore une fois, je ne reverrai pas la documentation JJTree et un bon tutoriel - cette fois, cependant, non pas parce que l'original brille avec détails et clarté, mais parce que je ne l'ai pas compris moi-même, mais la documentation a été retransmise incorrectement, mais avec confiance, évidemment pire que pas de récit. Je ferais mieux de laisser un petit indice, découvert en cours de route:


  • Premièrement, le code de description de l'analyseur JavaCC suppose des insertions Java qui seront inscrites dans l'analyseur généré.
  • Ne soyez pas confus par le fait que lors de la construction d'un AST, une syntaxe comme [ Expression() ] signifie facultatif, et dans le contexte de la description des jetons - choisir un caractère, comme dans une expression régulière. Pour autant que je comprends l'explication des développeurs PMD, ce sont des constructions similaires qui ont une signification si différente - hérité, monsieur ...
  • Pour le nœud racine (dans mon cas, StoredDefinition ), vous devez spécifier son type au lieu de void (c'est-à-dire ASTStoredDefiniton )
  • En utilisant la syntaxe #void après le nom du nœud, vous pouvez le cacher de l'arborescence analysée (c'est-à-dire, cela n'affectera que ce qui est la bonne source et ce qui ne l'est pas, et comment les autres nœuds seront imbriqués)
  • En utilisant une construction de la forme void SimpleExpression() #SimpleExpression(>1) nous pouvons dire que le nœud doit être affiché dans l'AST résultant, s'il a plus d'un descendant. Ceci est très pratique pour décrire des expressions avec de nombreux opérateurs avec des priorités différentes: c'est-à-dire, du point de vue de l'analyseur, la constante solitaire 1 sera quelque chose comme LogicExpression(AdditiveExpression(MultiplicativeExperssion(Constant(1)))) - entrez tous les n niveaux de priorités de fonctionnement - mais le code de l'analyseur obtiendra juste Constant(1)
  • Le nœud a une image variable standard (voir les setImage getImage , setImage ), qui setImage généralement "l'essence" de ce nœud: par exemple, pour un nœud correspondant au nom d'une variable locale, il est logique de copier le jeton correspondant avec l'identifiant dans l' image (par défaut, tous les jetons de les arbres seront jetés, il vaut donc la peine de copier le sens qu'ils contiennent, dans tous les cas, s'il s'agit de quelque chose de variable, et pas seulement de mots clés)
  • LOOKAHEAD - eh bien, c'est une chanson distincte, même un chapitre séparé de la documentation lui est consacré
    • En gros, dans JavaCC, si vous allez sur un nœud, vous ne pouvez pas le renvoyer et essayer d'analyser différemment, mais vous pouvez regarder en avant à l'avance et décider si vous allez ou non
    • dans le cas le plus simple, en voyant un avertissement JavaCC, vous venez de dire dans l'en-tête LOOKAHEAD = n et vous obtenez de mystérieuses erreurs d'analyse, car dans le cas général, il semble que cela ne peut pas résoudre tous les problèmes (enfin, sauf qu'en définissant quelques milliards de jetons, vous obtenez en fait un aperçu de tout, mais pas le fait que cela fonctionne de cette façon ... .)
    • devant le nom du nœud intégré, vous pouvez indiquer explicitement sur la base du nombre de jetons ici, vous pouvez certainement prendre la décision finale
    • si dans le cas général il n'y a pas un tel nombre fixe de jetons, vous pouvez dire "allez ici, si précédemment, à partir de ce point, nous avons réussi à faire correspondre un tel préfixe - puis la description habituelle du sous-arbre"
    • soyez prudent: dans le cas général, JavaCC ne peut pas vérifier l'exactitude des directives LOOKAHEAD - il vous fait confiance, alors trouvez au moins la preuve mathématique pourquoi une telle anticipation est suffisante ...

Maintenant que vous avez une description de la grammaire de la langue au format JJTree, ces 14 étapes simples vous aideront à ajouter la prise en charge de la langue. La plupart d'entre eux ont la forme "créer une classe similaire à l'implémentation pour java ou vm, mais adaptée". Je ne noterai que les fonctionnalités typiques, certaines d'entre elles apparaîtront dans la documentation principale si elles acceptent ma demande d'extraction de documentation :


  • Commentant la suppression de tous les fichiers générés dans le script d'assemblage alljavacc.xml (qui se trouve dans votre nouveau module), vous pouvez les transférer vers l'arborescence source à partir target/generated-sources . Mais mieux vaut pas. Probablement, seule une petite partie sera modifiée, il est donc préférable de ne supprimer que quelques-uns: ils ont vu la nécessité de modifier l'implémentation par défaut, copiée dans l'arborescence source, ajoutée à la liste des fichiers supprimés, reconstruite - et maintenant vous gérez le fichier - en particulier ce fichier . Sinon, il sera difficile de comprendre exactement ce qui a été changé et le support peut difficilement être qualifié d'agréable.
  • maintenant que vous avez une implémentation du mode PMD «principal», vous pouvez facilement accrocher à votre analyseur JJTree une liaison pour CPD, similaire à Java ou à une autre implémentation disponible
  • N'oubliez pas d'implémenter une méthode qui renvoie le nom d'hôte pour les requêtes XPath. Dans l'implémentation par défaut, soit une récursion infinie est obtenue (le nom du nœud passe par toString et vice versa), soit autre chose, en général, à cause de cela, il n'est également pas possible de regarder l'arborescence dans PMD Designer et de déboguer la grammaire sans elle
  • une partie des enregistrements de composants se fait en ajoutant des fichiers texte des points d'entrée de nom de classe pleinement qualifiés à META-INF/services
  • ce qui peut être décrit de manière déclarative dans les règles (par exemple, une description détaillée de la vérification et des exemples d'erreurs) n'est pas décrit dans le code, mais dans la category/<language name>/<ruleset>.xml - dans tous les cas, vous devrez y enregistrer vos règles
  • ... mais lors de la mise en œuvre des tests, apparemment, certains mécanismes de découverte automatique, peut-être d'origine locale, sont activement utilisés, donc
    • si on vous dit, "ajoutez un test trivial pour chaque version du langage" - mieux vaut ne pas discuter, ils disent "je n'en ai pas besoin, ça fonctionne comme ça" - c'est peut-être le mécanisme de découverte automatique
    • si vous voyez un test pour une règle spécifique avec un corps de classe contenant uniquement un commentaire // no additional unit tests , alors ce ne sont pas des tests, ils se trouvent juste dans les ressources sous la forme d'une description XML des données d'entrée et des réactions attendues de l'analyseur, tout de suite: quelques-unes correctes et quelques exemples incorrects.

Une petite mais importante quête: a terminé le concepteur PMD


Vous pouvez peut-être tout déboguer sans visualiseur. Mais pourquoi? Tout d'abord, pour terminer, c'est très simple. Deuxièmement, cela aidera grandement vos utilisateurs qui ne sont pas familiers avec Java: ils sont faciles et simples (si cela s'applique à XPath du tout), enfin, ou du moins sans recompilation, PMD pourra décrire des modèles simples de ce qu'ils n'aiment pas (dans le cas le plus simple) - un guide de style comme «le nom d'un package de modèle commence toujours par un p minuscule»).


Contrairement à d'autres erreurs qui sont immédiatement visibles, les problèmes avec PMD Designer sont assez insidieux: il semblerait que vous ayez déjà compris que l'inscription Java sur le côté droit du menu n'est pas un bouton, mais une liste déroulante de la sélection de langue O_o, dans laquelle elle est déjà apparue Modelica, car un nouveau module avec enregistrement des points d'entrée est apparu dans le chemin de classe. Mais ici, vous choisissez votre langue, téléchargez un fichier de test et consultez AST. Et cela semble être une victoire, mais c'est en quelque sorte en noir et blanc, et le sous-arbre en surbrillance pourrait être mis en surbrillance dans le texte - bien que non, le point culminant est là, mais il est mis à jour de manière tordue - et pourtant, comment n'ont-ils pas deviné pour mettre en évidence les correspondances trouvées avec XPath ... Estimant déjà la quantité de travail, vous pensez à la prochaine demande de pull, mais vous décidez accidentellement de basculer le langage vers Java et de télécharger du code source du PMD lui-même ... Oh! Il est coloré! .. Et le surbrillance du sous-arbre fonctionne! Euh ... et il s'avère qu'il met normalement en surbrillance les correspondances trouvées et écrit des morceaux de texte dans la zone à droite de la demande ... Il semble que lorsqu'une exception se produit dans le code JavaFX pendant le rendu de l'interface, il interrompt le rendu, mais n'imprime pas sur la console ...


En général, il vous suffit d'ajouter une classe ma-a-scarlet pour mettre en évidence la syntaxe basée sur les expressions régulières. Dans mon cas, c'était net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.ModelicaSyntaxHighlighter , qui doit être enregistré dans la classe AvailableSyntaxHighlighters . Veuillez noter que ces deux modifications se produisent dans le pmd-designer , dont l'artefact d'assembly doit être placé dans votre distribution binaire.


Au final, cela ressemble à ceci (GIF extrait de README dans le référentiel PMD Designer):


PMD Designer au travail


Sous-total


Si vous avez terminé tous ces niveaux, vous avez maintenant:


  • copier le détecteur de pâte
  • moteur de règles
  • visualiseur pour déboguer AST et le mettre sous une forme pratique pour l'analyse (comme nous l'avons déjà vu, toutes les grammaires du même langage ne sont pas également utiles!)
  • le même visualiseur pour le débogage des règles XPath que vos utilisateurs peuvent écrire sans recompiler PMD et généralement aucune connaissance de Java (XPath, bien sûr, n'est pas non plus BASIC, mais c'est au moins un langage de requête standard et non local)

J'espère que vous comprenez également le fait que la grammaire est maintenant une API stable pour votre implémentation de la prise en charge des langues - ne la modifiez pas (ou plutôt la fonction de conversion de la source en AST décrite par elle) à moins que cela ne soit absolument nécessaire, et si vous avez changé, notifiez-le comme un changement de rupture, et les utilisateurs seront bouleversés: très probablement, tout le monde n'écrira pas de tests pour leurs règles, mais c'est très triste quand les règles ont vérifié le code, puis se sont arrêtées sans avertissement - presque comme une sauvegarde, qui s'est brisée soudainement, et il y a un an ...


L'histoire ne s'arrête pas là: au moins quelques règles utiles doivent être écrites.


Mais ce n'est pas tout: PMD prend en charge nativement les étendues et les déclarations. Chaque nœud AST a une portée qui lui est associée: le corps de la classe, la fonction, la boucle ... Le fichier entier, au pire! Et dans chaque domaine, il y a une liste de définitions (déclarations) qu'il contient directement. Comme dans d'autres cas, il est proposé de l'implémenter par analogie avec d'autres langages, par exemple, Modelika (mais au moment de la rédaction, la logique de ma pull request est, franchement, brute). scopes declarations visitor, - ScopeAndDeclarationFinder , — , , , - , read-only AST. , .


 public class ModelicaHandler extends AbstractLanguageVersionHandler { // ... @Override public VisitorStarter getSymbolFacade() { return new VisitorStarter() { @Override public void start(Node rootNode) { new SymbolFacade().initializeWith((ASTStoredDefinition) rootNode); } }; } } 


PMD . , «» Clang Static Analyzer , . , CPD ( ), .

Source: https://habr.com/ru/post/fr474920/


All Articles