Apprentissage automatique dans l'analyse statique du code source du programme

Apprentissage automatique dans l'analyse statique du code source du programme

L'apprentissage automatique est solidement ancré dans une variété de domaines humains, de la reconnaissance vocale au diagnostic médical. La popularité de cette approche est si grande que les gens essaient de l'utiliser partout où ils le peuvent. Certaines tentatives pour remplacer les approches classiques par des réseaux de neurones échouent. Cette fois, nous considérerons l'apprentissage automatique en termes de création d'analyseurs de code statique efficaces pour trouver des bogues et des vulnérabilités potentielles.

On demande souvent à l'équipe PVS-Studio si nous voulons commencer à utiliser l'apprentissage automatique pour trouver des bogues dans le code source du logiciel. La réponse courte est oui, mais dans une mesure limitée. Nous pensons qu'avec l'apprentissage automatique, de nombreux pièges se cachent dans les tâches d'analyse de code. Dans la deuxième partie de l'article, nous en parlerons. Commençons par un examen des nouvelles solutions et idées.

De nouvelles approches


De nos jours, il existe de nombreux analyseurs statiques basés sur ou utilisant l'apprentissage automatique, y compris l'apprentissage en profondeur et la PNL pour la détection des erreurs. Non seulement les amateurs ont doublé le potentiel d'apprentissage automatique, mais aussi les grandes entreprises, par exemple, Facebook, Amazon ou Mozilla. Certains projets ne sont pas des analyseurs statiques à part entière, car ils ne trouvent que certaines erreurs dans les validations.

Fait intéressant, presque tous sont positionnés comme des produits révolutionnaires qui feront une percée dans le processus de développement grâce à l'intelligence artificielle.



Regardons quelques-uns des exemples bien connus:

  1. Deepcode
  2. Infer, Sapienz, SapFix
  3. Enhardir
  4. Source {d}
  5. Clever-Commit, Commit Assistant
  6. CodeGuru

Deepcode


Deep Code est un outil de recherche de vulnérabilités pour le code logiciel Java, JavaScript, TypeScript et Python qui comprend l'apprentissage automatique en tant que composant. Selon Boris Paskalev, plus de 250 000 règles sont déjà en place. Cet outil apprend des modifications apportées par les développeurs dans le code source des projets open source (un million de référentiels). La société elle-même dit que leur projet est une sorte de grammaire pour les développeurs.



En fait, cet analyseur compare votre solution avec sa base de projets et vous offre la meilleure solution prévue à partir de l'expérience d'autres développeurs.

En mai 2018, les développeurs ont déclaré que la prise en charge de C ++ était en cours, mais jusqu'à présent, ce langage n'est pas pris en charge. Bien que, comme indiqué sur le site, le nouveau support de langue puisse être ajouté en quelques semaines en raison du fait que la langue ne dépend que d'une étape, qui est l'analyse.





Une série d'articles sur les méthodes de base de l'analyseur est également disponible sur le site.

Inférer


Facebook est assez zélé dans ses tentatives d'introduire de nouvelles approches complètes dans ses produits. L'apprentissage automatique n'est pas resté à l'écart non plus. En 2013, ils ont acheté une startup qui a développé un analyseur statique basé sur l'apprentissage automatique. Et en 2015, le code source du projet est devenu ouvert .

Infer est un analyseur statique pour les projets en Java, C, C ++ et Objective-C, développé par Facebook. Selon le site, il est également utilisé dans Amazon Web Services, Oculus, Uber et d'autres projets populaires.

Actuellement, Infer est capable de trouver des erreurs liées au déréférencement de pointeur nul et aux fuites de mémoire. Infer est basé sur la logique de Hoare, la logique de séparation et la bi-abduction, ainsi que sur la théorie de l'interprétation abstraite. L'utilisation de ces approches permet à l'analyseur de diviser le programme en morceaux et de les analyser indépendamment.

Vous pouvez essayer d'utiliser Infer sur vos projets, mais les développeurs avertissent que même si les projets Facebook génèrent environ 80% des avertissements utiles, un faible nombre de faux positifs n'est pas garanti sur les autres projets. Voici quelques erreurs qu'Infer ne peut pas détecter jusqu'à présent, mais les développeurs travaillent sur l'implémentation de ces avertissements:

  • index de tableau hors limites;
  • exceptions de transtypage de type;
  • fuites de données non vérifiées;
  • condition de course.

Sapfix


SapFix est un outil d'édition automatisé. Il reçoit des informations de Sapienz, un outil d'automatisation des tests et de l'analyseur statique Infer. Sur la base des modifications et des messages récents, Infer sélectionne l'une des nombreuses stratégies pour corriger les bogues.



Dans certains cas, SapFix annule toutes les modifications ou parties de celles-ci. Dans d'autres cas, il essaie de résoudre le problème en générant un correctif à partir de son ensemble de modèles de correction. Cet ensemble est formé de modèles de correctifs collectés par les programmeurs eux-mêmes à partir d'un ensemble de correctifs déjà effectués. Si un tel modèle ne corrige pas une erreur, SapFix essaie de l'ajuster à la situation en faisant de petites modifications dans un arbre de syntaxe abstraite jusqu'à ce que la solution potentielle soit trouvée.

Mais une solution potentielle ne suffit pas, donc SapFix recueille plusieurs solutions sur la base de quelques points: s'il y a des erreurs de compilation, s'il se bloque, s'il introduit de nouveaux plantages. Une fois les modifications entièrement testées, les correctifs sont examinés par un programmeur, qui décidera laquelle des modifications résout le mieux le problème.

Enhardir


Embold est une plateforme de démarrage pour l'analyse statique du code source du logiciel qui s'appelait Gamma avant le changement de nom. L'analyseur statique fonctionne sur la base des propres diagnostics de l'outil, ainsi qu'en utilisant des analyseurs intégrés, tels que Cppcheck, SpotBugs, SQL Check et autres.



En plus des diagnostics eux-mêmes, la plate-forme se concentre sur des infographies vives sur la charge de la base de code et la visualisation pratique des erreurs trouvées, ainsi que sur la recherche d'une éventuelle refactorisation. En outre, cet analyseur possède un ensemble d'anti-modèles qui vous permet de détecter des problèmes dans la structure du code au niveau de la classe et de la méthode, et diverses mesures pour calculer la qualité d'un système.



L'un des principaux avantages est le système intelligent proposant des solutions et des modifications qui, en plus des diagnostics conventionnels, vérifie les modifications sur la base des informations sur les modifications précédentes.



Avec NLP, Embold brise le code et recherche les interconnexions et les dépendances entre les fonctions et les méthodes, ce qui fait gagner du temps au refactoring.



De cette façon, Embold offre essentiellement une visualisation pratique de vos résultats d'analyse de code source par divers analyseurs, ainsi que par ses propres diagnostics, dont certains sont basés sur l'apprentissage automatique.

Source {d}


La source {d} est l'outil le plus ouvert en termes de modes de mise en œuvre par rapport aux analyseurs que nous avons examinés. C'est également une solution de code open source . Sur leur site Web, en échange de votre adresse e-mail, vous pouvez obtenir une brochure produit décrivant les technologies qu'ils utilisent. En outre, le site Web donne un lien vers la base de données des publications liées à l'utilisation de l'apprentissage automatique pour l'analyse de code, ainsi que le référentiel avec l'ensemble de données pour l'apprentissage basé sur le code. Le produit lui-même est une plate-forme entière pour analyser le code source et le produit logiciel, et n'est pas axé sur les développeurs, mais plutôt sur les gestionnaires. Parmi ses capacités figurent le calcul de la taille de la dette technique, les goulets d'étranglement dans le processus de développement et d'autres statistiques mondiales sur le projet.



Leur approche de l'analyse de code par apprentissage automatique est basée sur l'hypothèse naturelle, comme indiqué dans l'article " Sur le caractère naturel du logiciel ".

"Les langages de programmation, en théorie, sont complexes, flexibles et puissants, mais les programmes que les vraies personnes écrivent sont en fait simples et plutôt répétitifs, et ils ont donc des propriétés statistiques prévisibles utiles qui peuvent être capturées dans des modèles de langage statistique et exploitées pour l'ingénierie logicielle. tâches. "

Sur la base de cette hypothèse, plus la base de code est grande, plus les propriétés statistiques sont grandes et plus les métriques, obtenues grâce à l'apprentissage, seront précises.

Pour analyser le code dans la source {d}, le service Babelfish est utilisé, qui peut analyser le fichier de code dans n'importe quelle langue disponible, obtenir un arbre de syntaxe abstrait et le convertir en un arbre de syntaxe universel.



Cependant, la source {d} ne recherche pas d'erreurs dans le code. Sur la base de l'arborescence utilisant ML sur l'ensemble du projet, la source {d} détecte la mise en forme du code, le style appliqué dans le projet et dans une validation. Si le nouveau code ne correspond pas au style de code du projet, il effectue quelques modifications.





L'apprentissage se concentre sur plusieurs éléments de base: espaces, tabulation, sauts de ligne, etc.



En savoir plus à ce sujet dans leur publication: " STYLE-ANALYZER: correction des incohérences de style de code avec des algorithmes interprétables non supervisés ".

Dans l'ensemble, source {d} est une large plate-forme pour collecter diverses statistiques sur le code source et le processus de développement de projet: des calculs d'efficacité des développeurs aux coûts de temps pour la révision du code.

Engagement intelligent


Clever-Commit est un analyseur créé par Mozilla en collaboration avec Ubisoft. Il est basé sur une étude CLEVER (Combining Levels of Bug Prevention and Resolution Techniques) d'Ubisoft et de son produit enfant Commit Assistant, qui détecte les validations suspectes susceptibles de contenir une erreur. Étant donné que CLEVER est basé sur une comparaison de code, il peut à la fois pointer du code dangereux et faire des suggestions de modifications possibles. Selon la description, dans 60 à 70% des cas, Clever-Commit trouve les endroits problématiques et propose des modifications correctes avec la même probabilité. En général, il y a peu d'informations sur ce projet et sur les erreurs qu'il peut trouver.

CodeGuru


Récemment, CodeGuru, qui est un produit d'Amazon, s'est aligné sur les analyseurs utilisant l'apprentissage automatique. Il s'agit d'un service d'apprentissage automatique qui vous permet de trouver des erreurs dans le code, ainsi que d'identifier des zones coûteuses. Jusqu'à présent, l'analyse n'est disponible que pour le code Java, mais les auteurs promettent de prendre en charge d'autres langages à l'avenir. Bien qu'il ait été annoncé assez récemment, Andy Jassy, ​​PDG d'AWS (Amazon Web Services) dit qu'il est utilisé depuis longtemps sur Amazon.

Le site Web indique que CodeGuru apprenait sur la base de code Amazon, ainsi que sur plus de 10 000 projets open source.

Fondamentalement, le service est divisé en deux parties: CodeGuru Reviewer, enseigné à l'aide de la recherche de règles associatives et à la recherche d'erreurs dans le code, et CodeGuru Profiler, surveillant les performances des applications.



En général, il n'y a pas beaucoup d'informations disponibles sur ce projet. Comme l'indique le site Web, le réviseur analyse les bases de code Amazon et recherche les demandes d'extraction, contenant les appels d'API AWS afin d'apprendre à détecter les écarts par rapport aux "meilleures pratiques". Ensuite, il examine les modifications apportées et les compare aux données de la documentation, qui sont analysées en même temps. Le résultat est un modèle de «meilleures pratiques».

Il est également dit que les recommandations pour le code de l'utilisateur ont tendance à s'améliorer après avoir reçu des commentaires à leur sujet.

La liste des erreurs auxquelles Reviewer répond est assez floue, car aucune documentation d'erreur spécifique n'a été publiée:

  • Meilleures pratiques AWS
  • Accès simultané
  • Fuites de ressources
  • Fuite d'informations confidentielles
  • "Meilleures pratiques" générales de codage

Notre scepticisme


Examinons maintenant la recherche d'erreurs du point de vue de notre équipe, qui développe des analyseurs statiques depuis de nombreuses années. Nous voyons un certain nombre de problèmes de haut niveau d'application des méthodes d'apprentissage, que nous aimerions aborder. Pour commencer, nous diviserons toutes les approches ML en deux types:

  1. Ceux qui apprennent manuellement à un analyseur statique à rechercher divers problèmes, en utilisant des exemples de code synthétique et réel;
  2. Celles qui enseignent les algorithmes sur un grand nombre de codes open source et d'historique de révision (GitHub), après quoi l'analyseur commencera à détecter les bugs et même à proposer des modifications.

Nous parlerons de chaque direction séparément, car elles présentent des inconvénients différents. Après cela, je pense que les lecteurs comprendront pourquoi nous ne nions pas les possibilités de l'apprentissage automatique, mais ne partageons toujours pas l'enthousiasme.

Remarque Nous regardons du point de vue du développement d'un analyseur universel statique universel. Nous nous concentrons sur le développement de l'analyseur, que toute équipe pourra utiliser, pas celui axé sur une base de code spécifique.

Apprentissage manuel d'un analyseur statique


Disons que nous voulons utiliser ML pour commencer à rechercher les types de défauts suivants dans le code:

if (A == A) 

Il est étrange de comparer une variable avec elle-même. Nous pouvons écrire de nombreux exemples de code correct et incorrect et apprendre à l'analyseur à rechercher de telles erreurs. De plus, vous pouvez ajouter de vrais exemples de bogues déjà trouvés aux tests. Eh bien, la question est de savoir où trouver de tels exemples. Ok, supposons que c'est possible. Par exemple, nous avons un certain nombre d'exemples de telles erreurs: V501 , V3001 , V6001 .

Est-il donc possible d'identifier de tels défauts de code en utilisant les algorithmes ML? Oui, ça l'est. La chose est - pourquoi en avons-nous besoin?

Voyez, pour enseigner l'analyseur, nous devrons consacrer beaucoup d'efforts à la préparation des exemples pour l'enseignement. Une autre option consiste à marquer le code des applications réelles, en indiquant les fragments où l'analyseur doit émettre un avertissement. Dans tous les cas, beaucoup de travail devra être fait, car il devrait y avoir des milliers d'exemples d'apprentissage. Ou des dizaines de milliers.

Après tout, nous voulons détecter non seulement les cas (A == A), mais aussi:

  • si (X && A == A)
  • si (A + 1 == A + 1)
  • si (A [i] == A [i])
  • si ((A) == (A))
  • et ainsi de suite.


Examinons l'implémentation potentielle d'un diagnostic aussi simple dans PVS-Studio:

 void RulePrototype_V501(VivaWalker &walker, const Ptree *left, const Ptree *right, const Ptree *operation) { if (SafeEq(operation, "==") && SafeEqual(left, right)) { walker.AddError("Oh boy! Holy cow!", left, 501, Level_1, "CWE-571"); } } 

Et c'est tout! Vous n'avez besoin d'aucune base d'exemples pour ML!

À l'avenir, le diagnostic devra apprendre à prendre en compte un certain nombre d'exceptions et à émettre des avertissements pour (A [0] == A [1-1]). Comme nous le savons, il peut être facilement programmé. Au contraire, dans ce cas, les choses vont mal avec la base d'exemples.

Notez que dans les deux cas, nous aurons besoin d'un système de test, de documentation, etc. Quant à la contribution du travail à la création d'un nouveau diagnostic, l'approche classique, où la règle est rigoureusement programmée dans le code, prend les devants.

Ok, il est temps pour une autre règle. Par exemple, celui où le résultat de certaines fonctions doit être utilisé. Il est inutile de les appeler et de ne pas utiliser leur résultat. Voici certaines de ces fonctions:

  • malloc
  • memcmp
  • chaîne :: vide

C'est ce que fait le diagnostic PVS-Studio V530 .

Donc, ce que nous voulons, c'est détecter les appels à ces fonctions, dont le résultat n'est pas utilisé. Pour ce faire, vous pouvez générer de nombreux tests. Et nous pensons que tout fonctionnera bien. Mais encore une fois, il n'est pas clair pourquoi cela est nécessaire.

L'implémentation du diagnostic V530 avec toutes les exceptions a pris 258 lignes de code dans l'analyseur PVS-Studio, dont 64 sont des commentaires. Il y a aussi un tableau avec des annotations de fonctions, où il est à noter que leur résultat doit être utilisé. Il est beaucoup plus facile de recharger ce tableau que de créer des exemples synthétiques.

Les choses vont encore empirer avec les diagnostics qui utilisent l'analyse du flux de données. Par exemple, l'analyseur PVS-Studio peut suivre la valeur des pointeurs, ce qui vous permet de trouver une telle fuite de mémoire:

 uint32_t* BnNew() { uint32_t* result = new uint32_t[kBigIntSize]; memset(result, 0, kBigIntSize * sizeof(uint32_t)); return result; } std::string AndroidRSAPublicKey(crypto::RSAPrivateKey* key) { .... uint32_t* n = BnNew(); .... RSAPublicKey pkey; pkey.len = kRSANumWords; pkey.exponent = 65537; // Fixed public exponent pkey.n0inv = 0 - ModInverse(n0, 0x100000000LL); if (pkey.n0inv == 0) return kDummyRSAPublicKey; // <= .... } 

L'exemple est tiré de l'article " Chrome: fuites de mémoire ". Si la condition (pkey.n0inv == 0) est vraie, la fonction se termine sans libérer le tampon, dont le pointeur est stocké dans la variable n .

Du point de vue du PVS-Studio, il n'y a rien de compliqué ici. L'analyseur a étudié la fonction BnNew et se souvient qu'il a renvoyé un pointeur sur le bloc de mémoire alloué. Dans une autre fonction, il a remarqué que le tampon peut ne pas se libérer et que le pointeur vers celui-ci est perdu au moment de quitter la fonction.

C'est un algorithme courant de suivi des valeurs qui fonctionne. Peu importe comment le code est écrit. Peu importe ce qu'il y a d'autre dans la fonction qui ne se rapporte pas au travail du pointeur. L'algorithme est universel et le diagnostic V773 trouve beaucoup d'erreurs dans divers projets. Voyez à quel point les fragments de code avec des erreurs détectées sont différents!

Nous ne sommes pas des experts en ML, mais nous avons le sentiment que de gros problèmes sont à nos portes ici. Il existe un nombre incroyable de façons d'écrire du code avec des fuites de mémoire. Même si la machine a bien appris à suivre les valeurs des variables, elle devrait comprendre qu'il existe également des appels aux fonctions.

Nous pensons qu'il faudrait tellement d'exemples d'apprentissage pour que la tâche devienne insaisissable. Nous ne disons pas que c'est irréaliste. Nous doutons que le coût de création de l'analyseur soit payant.

Analogie Ce qui me vient à l'esprit, c'est l'analogie avec une calculatrice, où au lieu de diagnostics, il faut programmer des actions arithmétiques. Nous sommes sûrs que vous pouvez apprendre à une calculatrice basée sur ML à bien résumer les nombres en l'alimentant des résultats des opérations 1 + 1 = 2, 1 + 2 = 3, 2 + 1 = 3, 100 + 200 = 300 et ainsi de suite . Comme vous le comprenez, la faisabilité de développer une telle calculatrice est une grande question (sauf si une subvention lui est allouée :). Une calculatrice beaucoup plus simple, plus rapide, plus précise et plus fiable peut être écrite en utilisant l'opération simple "+" dans le code.

Conclusion Eh bien, cette façon fonctionnera. Mais à notre avis, son utilisation n'a pas de sens pratique. Le développement prendra plus de temps, mais le résultat - moins fiable et précis, en particulier lorsqu'il s'agit de mettre en œuvre des diagnostics complexes basés sur l'analyse du flux de données.

Apprendre sur une grande quantité de code Open Source


D'accord, nous avons trié avec des exemples synthétiques manuels, mais il y a aussi GitHub. Vous pouvez suivre l'historique des validations et en déduire les modèles de modification / correction de code. Ensuite, vous pouvez non seulement pointer des fragments de code suspect, mais même suggérer un moyen de corriger le code.

Si vous vous arrêtez à ce niveau de détail, tout semble bien. Le diable, comme toujours, est dans les détails. Parlons donc bien de ces détails.

La première nuance. Source de données.

Les modifications GitHub sont assez aléatoires et diverses. Les gens sont souvent paresseux pour effectuer des validations atomiques et effectuer plusieurs modifications dans le code en même temps. Vous savez comment cela se produit: vous corrigeriez le bug, et en même temps le remanieriez un peu ("Et ici je vais ajouter la gestion d'un tel cas ..."). Même une personne peut alors être incompréhensible, que ces problèmes soient liés les uns aux autres ou non.

Le défi est de savoir comment distinguer les erreurs réelles de l'ajout de nouvelles fonctionnalités ou autre chose. Vous pouvez, bien sûr, obtenir 1000 personnes qui marqueront manuellement les commits. Les gens devront signaler: ici une erreur a été corrigée, voici la refactorisation, voici quelques nouvelles fonctionnalités, ici les exigences ont changé et ainsi de suite.

Un tel balisage est-il possible? Ouais! Mais remarquez à quelle vitesse l'usurpation se produit. Au lieu de "l'algorithme apprend par lui-même sur la base de GitHub", nous discutons déjà de la façon de dérouter des centaines de personnes pendant longtemps. Le travail et le coût de création de l'outil augmentent considérablement.

Vous pouvez essayer d'identifier automatiquement où les bugs ont été corrigés. Pour ce faire, vous devez analyser les commentaires des commits, faire attention aux petites modifications locales, qui sont très probablement ces corrections de bugs. Il est difficile de dire dans quelle mesure vous pouvez rechercher automatiquement les correctifs d'erreurs. Dans tous les cas, c'est une tâche importante qui nécessite une recherche et une programmation distinctes.

Donc, nous n'avons même pas encore appris, et il y a déjà des nuances :).

La deuxième nuance. Un retard de développement.

Les analyseurs qui apprendront sur la base de telles plateformes, comme GitHub, seront toujours soumis à un tel syndrome, comme «retard de retard mental». En effet, les langages de programmation changent avec le temps.

Depuis C # 8.0, il existe des types de référence Nullable, qui aident à lutter contre les exceptions de référence Null (NRE). Dans JDK 12, un nouvel opérateur de commutateur ( JEP 325 ) est apparu. En C ++ 17, il est possible d'effectuer des constructions conditionnelles à la compilation ( constexpr if ). Et ainsi de suite.

Les langages de programmation évoluent. De plus, ceux, comme C ++, se développent très rapidement. De nouvelles constructions apparaissent, de nouvelles fonctions standard sont ajoutées, etc. Parallèlement aux nouvelles fonctionnalités, il existe de nouveaux modèles d'erreur que nous aimerions également identifier avec l'analyse de code statique.

À ce stade, la méthode ML fait face à un problème: le motif d'erreur est déjà clair, nous aimerions le détecter, mais il n'y a pas de base de code pour l'apprentissage.

Examinons ce problème à l'aide d'un exemple particulier. La boucle for basée sur une plage est apparue en C ++ 11. Vous pouvez écrire le code suivant en parcourant tous les éléments du conteneur:

 std::vector<int> numbers; .... for (int num : numbers) foo(num); 

La nouvelle boucle a apporté le nouveau modèle d'erreur avec elle. Si nous modifions le conteneur à l'intérieur de la boucle, cela entraînera l'invalidation des itérateurs "fantômes".

Jetons un coup d'œil au code incorrect suivant:

 for (int num : numbers) { numbers.push_back(num * 2); } 

Le compilateur le transformera en quelque chose comme ceci:

 for (auto __begin = begin(numbers), __end = end(numbers); __begin != __end; ++__begin) { int num = *__begin; numbers.push_back(num * 2); } 

Pendant push_back , les itérateurs __begin et __end peuvent être invalidés, si la mémoire est déplacée à l'intérieur du vecteur. Le résultat sera le comportement indéfini du programme.

Par conséquent, le modèle d'erreur est connu et décrit depuis longtemps dans la littérature. L'analyseur PVS-Studio le diagnostique avec le diagnostic V789 et a déjà trouvé de vraies erreurs dans les projets open source.

Dans combien de temps GitHub obtiendra-t-il suffisamment de nouveau code pour remarquer ce modèle? Bonne question ... Il est important de garder à l'esprit que s'il existe une boucle for basée sur une plage, cela ne signifie pas que tous les programmeurs commenceront immédiatement à l'utiliser en même temps. Cela peut prendre des années avant que beaucoup de code n'utilise la nouvelle boucle. De plus, de nombreuses erreurs doivent être faites, puis elles doivent être corrigées pour que l'algorithme puisse remarquer le motif dans les modifications.

Combien d'années cela prendra-t-il? Cinq? Dix?

Dix, c'est trop ou est-ce une prédiction pessimiste? Loin de là. Au moment où l'article a été écrit, cela faisait huit ans qu'une boucle for basée sur une plage était apparue en C ++ 11. Mais jusqu'à présent dans notre base de données, il n'y a que trois cas d'une telle erreur. Trois erreurs, ce n'est pas beaucoup et pas peu. Il ne faut tirer aucune conclusion de ce chiffre. L'essentiel est de confirmer qu'un tel modèle d'erreur est réel et qu'il est logique de le détecter.

Comparez maintenant ce nombre, par exemple, avec ce modèle d'erreur: le pointeur est déréférencé avant la vérification . Au total, nous avons déjà identifié 1 716 de ces cas lors de la vérification de projets open source.

Peut-être que nous ne devrions pas du tout rechercher des erreurs dans les boucles basées sur la plage? Non. C'est juste que les programmeurs sont inertiels, et cet opérateur devient très populaire très lentement. Progressivement, il y aura respectivement plus de code et d'erreurs.

Cela ne devrait se produire que 10 à 15 ans après l'apparition du C ++ 11. Cela conduit à une question philosophique. Supposons que nous connaissions déjà le modèle d'erreur, nous attendrons simplement de nombreuses années jusqu'à ce que nous ayons de nombreuses erreurs dans les projets open source. En sera-t-il ainsi?

Si «oui», il est sûr de diagnostiquer un «retard de développement mental» pour tous les analyseurs basés sur ML.

Si "non", que devons-nous faire? Il n'y a pas d'exemples. Les écrire manuellement? Mais de cette façon, nous revenons au chapitre précédent, où nous avons donné une description détaillée de l'option lorsque les gens écriraient un ensemble complet d'exemples d'apprentissage.

Cela peut être fait, mais la question de l'opportunité se pose à nouveau. L'implémentation du diagnostic V789 avec toutes les exceptions dans l'analyseur PVS-Studio ne prend que 118 lignes de code, dont 13 lignes sont des commentaires. Autrement dit, il s'agit d'un diagnostic très simple, qui peut être facilement programmé de manière classique.

La situation sera similaire à toutes les autres innovations qui apparaissent dans toutes les autres langues. Comme on dit, il y a quelque chose à penser.

La troisième nuance. La documentation

Un élément important de tout analyseur statique est la documentation décrivant chaque diagnostic. Sans lui, il sera extrêmement difficile, voire impossible, d'utiliser l'analyseur. Dans la documentation de PVS-Studio, nous avons une description de chaque diagnostic, qui donne un exemple de code erroné et comment le corriger. Nous donnons également le lien vers CWE , où l'on peut lire une description alternative du problème. Et pourtant, parfois, les utilisateurs ne comprennent pas quelque chose, et ils nous posent des questions de clarification.

Dans le cas des analyseurs statiques basés sur ML, le problème de documentation est en quelque sorte étouffé. Il est supposé que l'analyseur pointera simplement vers un endroit qui lui semble suspect et peut même suggérer comment le corriger. La décision de faire un montage ou non appartient à la personne. C'est là que le problème commence ... Il n'est pas facile de prendre une décision sans pouvoir lire, ce qui fait que l'analyseur semble suspect d'un endroit particulier dans le code.

Bien sûr, dans certains cas, tout sera évident. Supposons que l'analyseur pointe vers ce code:

 char *p = (char *)malloc(strlen(src + 1)); strcpy(p, src); 

Et nous suggérons de le remplacer par:

 char *p = (char *)malloc(strlen(src) + 1); strcpy(p, src); 

Il est immédiatement clair que le programmeur a fait une faute de frappe et a ajouté 1 au mauvais endroit. Par conséquent, moins de mémoire sera allouée que nécessaire.

Ici, tout est clair même sans documentation. Mais ce ne sera pas toujours le cas.

Imaginez que l'analyseur pointe "silencieusement" vers ce code:

 char check(const uint8 *hash_stage2) { .... return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE); } 

Et suggère que nous modifions le type char de la valeur de retour pour int:

 int check(const uint8 *hash_stage2) { .... return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE); } 

Il n'y a aucune documentation pour l'avertissement. Apparemment, il n'y aura pas non plus de texte dans le message d'avertissement, si nous parlons d'un analyseur complètement indépendant.

Que ferons-nous? Quelle est la différence? Vaut-il la peine de faire un tel remplacement?

En fait, je pourrais tenter ma chance et accepter de corriger le code. Bien qu'accepter des correctifs sans les comprendre est une pratique grossière ... :) Vous pouvez regarder la description de la fonction memcmp et découvrir que la fonction retourne vraiment des valeurs comme int : 0, plus de zéro et moins de zéro. Mais il est toujours difficile de savoir pourquoi effectuer des modifications, si le code fonctionne déjà bien.

Maintenant, si vous ne savez pas en quoi consiste la modification, consultez la description du diagnostic V642 . Il devient immédiatement clair qu'il s'agit d'un véritable bug. De plus, cela peut provoquer une vulnérabilité.

Peut-être que l'exemple ne semblait pas convaincant. Après tout, l'analyseur a suggéré un code susceptible d'être meilleur. Ok Regardons un autre exemple de pseudocode, cette fois, pour un changement, en Java.

 ObjectOutputStream out = new ObjectOutputStream(....); SerializedObject obj = new SerializedObject(); obj.state = 100; out.writeObject(obj); obj.state = 200; out.writeObject(obj); out.close(); 

Il y a un objet. C'est la sérialisation. Ensuite, l'état de l'objet change et il re-sérialise. Ça a l'air bien. Imaginez maintenant que, tout d'un coup, l'analyseur n'aime pas le code et qu'il veuille le remplacer par ce qui suit:

 ObjectOutputStream out = new ObjectOutputStream(....); SerializedObject obj = new SerializedObject(); obj.state = 100; out.writeObject(obj); obj = new SerializedObject(); // The line is added obj.state = 200; out.writeObject(obj); out.close(); 

Au lieu de modifier l'objet et de le réécrire, un nouvel objet est créé et il sera sérialisé.

Il n'y a pas de description du problème. Pas de documentation. Le code est devenu plus long. Pour une raison quelconque, un nouvel objet est créé. Êtes-vous prêt à effectuer une telle modification dans votre code?

Vous direz que ce n'est pas clair. En effet, c'est incompréhensible. Et il en sera toujours ainsi. Travailler avec un tel analyseur "silencieux" sera une étude sans fin pour tenter de comprendre pourquoi l'analyseur n'aime rien.

S'il y a de la documentation, tout devient transparent. La classe java.io.ObjectOuputStream utilisée pour la sérialisation met en cache les objets écrits. Cela signifie que le même objet ne sera pas sérialisé deux fois. La classe sérialise l'objet une fois, et la deuxième fois écrit simplement dans le flux une référence au même premier objet. En savoir plus: V6076 - La sérialisation récurrente utilisera l'état d'objet mis en cache à partir de la première sérialisation.

Nous espérons avoir réussi à expliquer l'importance de la documentation. Voici la question. Comment la documentation de l'analyseur basé sur ML apparaîtra-t-elle?

Lorsqu'un analyseur de code classique est développé, tout est simple et clair. Il y a un schéma d'erreurs. Nous le décrivons dans la documentation et implémentons le diagnostic.

Dans le cas du ML, le processus est inverse. Oui, l'analyseur peut remarquer une anomalie dans le code et la signaler. Mais il ne sait rien de l'essence du défaut. Il ne comprend pas et ne vous dira pas pourquoi vous ne pouvez pas écrire du code comme ça. Ce sont des abstractions de trop haut niveau. De cette façon, l'analyseur doit également apprendre à lire et à comprendre la documentation des fonctions.

Comme je l'ai dit, comme le problème de la documentation est évité dans les articles sur l'apprentissage automatique, nous ne sommes pas prêts à nous y attarder davantage. Juste une autre nuance importante dont nous avons parlé.

Remarque Vous pourriez faire valoir que la documentation est facultative. L'analyseur peut se référer à de nombreux exemples de correctifs sur GitHub et la personne, en parcourant les commits et les commentaires, comprendra ce qui est quoi. Oui, c'est vrai. Mais l'idée n'a pas l'air attrayante. Ici, l'analyseur est le mauvais gars, qui va plutôt intriguer un programmeur que l'aider.

Quatrième nuance. Langues hautement spécialisées.

L'approche décrite n'est pas applicable aux langages hautement spécialisés, pour lesquels l'analyse statique peut également être extrêmement utile. La raison en est que GitHub et d'autres sources n'ont tout simplement pas une base de code source suffisamment grande pour fournir un apprentissage efficace.

Voyons cela à l'aide d'un exemple concret. Tout d'abord, allons sur GitHub et recherchons des référentiels pour le langage Java populaire.

Résultat: langue: "Java": 3 128 884 résultats de référentiel disponibles

Prenons maintenant le langage spécialisé «1C Enterprise» utilisé dans les applications comptables produites par la société russe 1C .

Résultat: langue: «1C Enterprise»: 551 résultats de référentiel disponibles

Peut-être que les analyseurs ne sont pas nécessaires pour ce langage? Non, ils le sont. Il existe un besoin pratique d'analyser de tels programmes et il existe déjà des analyseurs appropriés. Par exemple, il existe le plug-in SonarQube 1C (BSL), produit par la société " Silver Bullet ".

Je pense qu'aucune explication spécifique n'est nécessaire pour expliquer pourquoi l'approche ML sera difficile pour les langues spécialisées.

La cinquième nuance. C, C ++, #include .

Les articles sur l'analyse de code statique basée sur ML concernent principalement des langages tels que Java, JavaScript et Python. Cela s'explique par leur extrême popularité. Quant à C et C ++, ils sont en quelque sorte ignorés, même si vous ne pouvez pas les appeler impopulaires.

Nous suggérons qu'il ne s'agit pas de leur popularité / perspectives prometteuses, mais des problèmes avec les langages C et C ++. Et maintenant, nous allons mettre en lumière un problème inconfortable.

Un fichier c / cpp abstrait peut être très difficile à compiler. Au moins, vous ne pouvez pas charger un projet à partir de GitHub, choisissez un fichier cpp aléatoire et compilez-le simplement. Nous allons maintenant expliquer ce que tout cela a à voir avec le ML.

Nous voulons donc enseigner l'analyseur. Nous avons téléchargé un projet depuis GitHub. Nous connaissons le correctif et supposons qu'il corrige le bogue. Nous voulons que cette modification soit un exemple d'apprentissage. En d'autres termes, nous avons un fichier .cpp avant et après l'édition.

C'est là que le problème commence. Il ne suffit pas d'étudier les correctifs. Un contexte complet est également requis. Vous devez connaître la déclaration des classes utilisées, vous devez connaître les prototypes des fonctions utilisées, vous devez savoir comment les macros se développent et ainsi de suite. Et pour ce faire, vous devez effectuer un prétraitement complet des fichiers.

Regardons l'exemple. Au début, le code ressemblait à ceci:

 bool Class::IsMagicWord() { return m_name == "ML"; } 

Il a été fixé de cette façon:

 bool Class::IsMagicWord() { return strcmp(m_name, "ML") == 0; } 

L'analyseur doit-il commencer à apprendre afin de proposer (x == "y") un remplacement pour strcmp (x, "y")?

Vous ne pouvez pas répondre à cette question sans savoir comment le membre m_name est déclaré dans la classe. Il pourrait y avoir, par exemple, de telles options:

 class Class { .... char *m_name; }; class Class { .... std::string m_name; }; 

Des modifications seront apportées au cas où nous parlons d'un pointeur ordinaire. Si nous ne prenons pas en compte le type de variable, l'analyseur peut apprendre à émettre des avertissements bons et mauvais (pour le cas avec std :: string ).

Les déclarations de classe se trouvent généralement dans les fichiers d'en-tête. Ici, il était nécessaire d'effectuer un prétraitement pour disposer de toutes les informations nécessaires. C'est extrêmement important pour C et C ++.

Si quelqu'un dit qu'il est possible de se passer de prétraitement, c'est soit une fraude, soit il ne connaît pas les langages C ou C ++.

Pour rassembler toutes les informations nécessaires, vous avez besoin d'un prétraitement correct. Pour ce faire, vous devez savoir où et quels fichiers d'en-tête se trouvent, quelles macros sont définies pendant le processus de génération. Vous devez également savoir comment un fichier cpp particulier est compilé.

Voilà le problème. On ne compile pas simplement le fichier (ou, plutôt, spécifiez la clé du compilateur pour qu'il génère un fichier de prétraitement). Nous devons comprendre comment ce fichier est compilé. Ces informations se trouvent dans les scripts de construction, mais la question est de savoir comment les obtenir à partir de là. En général, la tâche est compliquée.



De plus, de nombreux projets sur GitHub sont un gâchis. Si vous prenez un projet abstrait à partir de là, vous devez souvent bricoler pour le compiler. Un jour, il vous manque une bibliothèque et vous devez la trouver et la télécharger manuellement. Un autre jour, une sorte de système de construction auto-écrit est utilisé, qui doit être traité. Ça pourrait être n'importe quoi. Parfois, le projet téléchargé refuse simplement de se construire et il doit être en quelque sorte modifié. Vous ne pouvez pas simplement prendre et obtenir automatiquement une représentation prétraitée (.i) pour les fichiers .cpp. Cela peut être délicat même lorsque vous le faites manuellement.

Nous pouvons dire, eh bien, le problème des projets non liés à la construction est compréhensible, mais pas crucial. Ne travaillons qu'avec des projets qui peuvent être construits. Il reste la tâche de prétraiter un fichier particulier. Sans parler des cas où nous avons affaire à certains compilateurs spécialisés, par exemple pour les systèmes embarqués.

Après tout, le problème décrit n'est pas insurmontable. Cependant, tout cela est très difficile et demande beaucoup de main-d'œuvre. Dans le cas de C et C ++, le code source situé sur GitHub ne fait rien. Il y a beaucoup de travail à faire pour apprendre à exécuter automatiquement les compilateurs.

Remarque Si le lecteur ne comprend toujours pas la profondeur du problème, nous vous invitons à participer à l'expérience suivante. Prenez dix projets aléatoires de taille moyenne de GitHub et essayez de les compiler, puis obtenez leur version prétraitée pour les fichiers .cpp. Après cela, la question de la pénibilité de cette tâche disparaîtra :).

Il peut y avoir des problèmes similaires avec d'autres langages, mais ils sont particulièrement évidents en C et C ++.

Sixième nuance. Le prix de l'élimination des faux positifs.

Les analyseurs statiques sont susceptibles de générer des faux positifs et nous devons constamment affiner les diagnostics pour réduire le nombre de faux avertissements.

Nous allons maintenant revenir au diagnostic V789 précédemment considéré, détectant les changements de conteneur à l'intérieur de la boucle for basée sur la plage. Disons que nous n'avons pas fait assez attention lors de sa rédaction et que le client rapporte un faux positif. Il écrit que l'analyseur ne prend pas en compte le scénario où la boucle se termine après le changement du conteneur, et donc il n'y a pas de problème. Il donne ensuite l'exemple de code suivant où l'analyseur donne un faux positif:

 std::vector<int> numbers; .... for (int num : numbers) { if (num < 5) { numbers.push_back(0); break; // or, for example, return } } 

Oui, c'est un défaut. Dans un analyseur classique, son élimination est extrêmement rapide et bon marché. Dans PVS-Studio, l'implémentation de cette exception se compose de 26 lignes de code.

Cette faille peut également être corrigée lorsque l'analyseur est basé sur des algorithmes d'apprentissage. Bien sûr, il peut être enseigné en collectant des dizaines ou des centaines d'exemples de code qui doivent être considérés comme corrects.

Encore une fois, la question n'est pas de faisabilité, mais d'approche pratique. Nous pensons que lutter contre des faux positifs spécifiques, qui dérangent les clients, est beaucoup plus coûteux en cas de blanchiment d'argent. Autrement dit, le support client en termes d'élimination des faux positifs coûtera plus cher.

Septième nuance. Caractéristiques rarement utilisées et longue queue.

Auparavant, nous nous sommes attaqués au problème des langages hautement spécialisés, pour lesquels le code source n'est peut-être pas suffisant pour l'apprentissage. Un problème similaire se produit avec les fonctions rarement utilisées (celles du système, WinAPI, des bibliothèques populaires, etc.).

Si nous parlons de telles fonctions du langage C, comme strcmp , alors il y a en fait une base d'apprentissage. GitHub, résultats de code disponibles:

  • strcmp - 40,462,158
  • strictmp - 1,256,053

Oui, il existe de nombreux exemples d'utilisation. Peut-être que l'analyseur apprendra à remarquer, par exemple, les modèles suivants:

  • C'est étrange si la chaîne est comparée à elle-même. Il se corrige.
  • C'est étrange si l'un des pointeurs est NULL. Il se corrige.
  • Il est étrange que le résultat de cette fonction ne soit pas utilisé. Il se corrige.
  • Et ainsi de suite.

N'est-ce pas cool? Non. Ici, nous sommes confrontés au problème de la "longue queue". Très brièvement le point de la "longue queue" dans ce qui suit. Il est impossible de vendre uniquement le Top50 des livres les plus populaires et les plus lus dans une librairie. Oui, chacun de ces livres sera acheté, disons, 100 fois plus souvent que les livres ne figurant pas sur cette liste. Cependant, la plupart des recettes seront constituées d'autres livres qui, comme on dit, trouvent leur lecteur. Par exemple, une boutique en ligne Amazon.com reçoit plus de la moitié des bénéfices de ce qui ne fait pas partie des 130 000 «articles les plus populaires».

Il existe des fonctions populaires et peu nombreuses. Il y a des impopulaires, mais il y en a beaucoup. Par exemple, il existe les variantes suivantes de la fonction de comparaison de chaînes:

  • g_ascii_strncasecmp - 35,695
  • lstrcmpiA - 27,512
  • _wcsicmp_l - 5 737
  • _strnicmp_l - 5,848
  • _mbscmp_l - 2,458
  • et d'autres.

Comme vous pouvez le voir, ils sont utilisés beaucoup moins fréquemment, mais lorsque vous les utilisez, vous pouvez faire les mêmes erreurs. Il y a trop peu d'exemples pour identifier les modèles. Cependant, ces fonctions ne peuvent pas être ignorées. Individuellement, ils sont rarement utilisés, mais beaucoup de code est écrit avec leur utilisation, ce qui vaut mieux être vérifié. C'est là que la "longue queue" se montre.

Chez PVS-Studio, nous annotons manuellement les fonctionnalités. Par exemple, à l'heure actuelle, environ 7 200 fonctions avaient été annotées pour C et C ++. Voici ce que nous marquons:

  • Winapi
  • Bibliothèque C standard,
  • Bibliothèque de modèles standard (STL),
  • glibc (bibliothèque GNU C)
  • Qt
  • Mfc
  • zlib
  • libpng
  • Openssl
  • et d'autres.

D'une part, cela semble être une voie sans issue. Vous ne pouvez pas tout annoter. D'un autre côté, cela fonctionne.

Voici maintenant la question. Quels avantages le ML peut-il avoir? Les avantages significatifs ne sont pas si évidents, mais vous pouvez voir la complexité.

Vous pourriez faire valoir que les algorithmes construits sur ML eux-mêmes trouveront des modèles avec des fonctions fréquemment utilisées et qu'ils n'ont pas besoin d'être annotés. Oui c'est vrai. Cependant, il n'y a aucun problème à annoter indépendamment des fonctions populaires telles que strcmp ou malloc .

Néanmoins, la longue queue pose des problèmes. Vous pouvez enseigner en faisant des exemples synthétiques. Cependant, nous revenons ici à la partie article, où nous disions qu'il était plus facile et plus rapide d'écrire des diagnostics classiques, plutôt que de générer de nombreux exemples.

Prenons par exemple une fonction, telle que _fread_nolock . Bien sûr, il est utilisé moins fréquemment que le fread . Mais lorsque vous l'utilisez, vous pouvez faire les mêmes erreurs. Par exemple, le tampon doit être suffisamment grand. Cette taille ne doit pas être inférieure au résultat de la multiplication des deuxième et troisième arguments. Autrement dit, vous voulez trouver un tel code incorrect:

 int buffer[10]; size_t n = _fread_nolock(buffer, size_of(int), 100, stream); 

Voici à quoi ressemble l'annotation de cette fonction dans PVS-Studio:

 C_"size_t _fread_nolock" "(void * _DstBuf, size_t _ElementSize, size_t _Count, FILE * _File);" ADD(HAVE_STATE | RET_SKIP | F_MODIFY_PTR_1, nullptr, nullptr, "_fread_nolock", POINTER_1, BYTE_COUNT, COUNT, POINTER_2). Add_Read(from_2_3, to_return, buf_1). Add_DataSafetyStatusRelations(0, 3); 

À première vue, une telle annotation peut sembler difficile, mais en fait, lorsque vous commencez à les écrire, cela devient simple. De plus, c'est du code en écriture seule. A écrit et oublié. Les annotations changent rarement.

Parlons maintenant de cette fonction du point de vue de ML. GitHub ne nous aidera pas. Il y a environ 15 000 mentions de cette fonction. Il y a encore moins de bon code. Une partie importante des résultats de recherche reprend les éléments suivants:

 #define fread_unlocked _fread_nolock 

Quelles sont les options?
  1. Ne fais rien. C'est un chemin vers nulle part.
  2. Imaginez, enseignez l'analyseur en écrivant des centaines d'exemples juste pour une fonction afin que l'analyseur comprenne l'interconnexion entre le tampon et les autres arguments. Oui, vous pouvez le faire, mais c'est économiquement irrationnel. C'est une rue sans issue.
  3. Vous pouvez trouver un moyen similaire au nôtre lorsque les annotations des fonctions seront définies manuellement. C'est une bonne façon sensée. C'est juste ML, ce qui n'a rien à voir avec ça :). C'est un retour à la manière classique d'écrire des analyseurs statiques.

Comme vous pouvez le voir, ML et la longue queue des fonctionnalités rarement utilisées ne vont pas ensemble.

À ce stade, des personnes liées au ML se sont opposées et ont déclaré que nous n'avions pas pris en compte l'option lorsque l'analyseur apprendrait toutes les fonctions et tirerait des conclusions de ce qu'elles faisaient. Ici, apparemment, soit nous ne comprenons pas les experts, soit ils ne comprennent pas notre point de vue.

Les corps de fonctions peuvent être inconnus. Par exemple, il peut s'agir d'une fonction liée à WinAPI. S'il s'agit d'une fonction rarement utilisée, comment l'analyseur comprendra-t-il ce qu'il fait? On peut imaginer que l'analyseur utilisera Google lui-même, trouvera une description de la fonction, la lira et la comprendra . En outre, il devra tirer des conclusions de haut niveau de la documentation. La description _fread_nolock ne dit rien sur l'interconnexion entre le tampon, le deuxième et le troisième argument. Cette comparaison doit être déduite de l'intelligence artificielle seule, basée sur une compréhension des principes généraux de la programmation et du fonctionnement du langage C ++. Je pense que nous devrions réfléchir sérieusement à tout cela dans 20 ans.

Des corps de fonctions peuvent être disponibles, mais cela peut ne pas être utile. Examinons une fonction, telle que memmove . Il est souvent implémenté dans quelque chose comme ceci:

 void *memmove (void *dest, const void *src, size_t len) { return __builtin___memmove_chk(dest, src, len, __builtin_object_size(dest, 0)); } 

Qu'est-ce que __builtin___memmove_chk ? Il s'agit d'une fonction intrinsèque que le compilateur lui-même implémente déjà. Cette fonction n'a pas le code source.

Ou memmove pourrait ressembler à ceci: la première version d'assemblage . Vous pouvez apprendre à l'analyseur à comprendre les différentes options d'assemblage, mais une telle approche semble incorrecte.

Ok, parfois des corps de fonctions sont vraiment connus. De plus, nous connaissons également des corps de fonctions dans le code de l'utilisateur. Il semblerait que dans ce cas, ML obtienne d'énormes avantages en lisant et en comprenant ce que font toutes ces fonctions.

Cependant, même dans ce cas, nous sommes pleins de pessimisme. Cette tâche est trop complexe. C'est compliqué même pour un humain. Pensez à quel point il est difficile pour vous de comprendre le code que vous n'avez pas écrit. Si c'est difficile pour une personne, pourquoi cette tâche devrait-elle être facile pour une IA? En fait, l'IA a un gros problème pour comprendre les concepts de haut niveau.Si nous parlons de comprendre le code, nous ne pouvons pas nous passer de la possibilité de faire abstraction des détails de l'implémentation et de considérer l'algorithme à un niveau élevé. Il semble que cette discussion puisse également être reportée de 20 ans.

Autres nuances

Il y a d'autres points qui devraient également être pris en compte, mais nous ne les avons pas approfondis. Soit dit en passant, l'article s'avère assez long. Par conséquent, nous énumérerons brièvement quelques autres nuances, en les laissant à la réflexion du lecteur.

  • Outdated recommendations. As mentioned, languages change, and recommendations for their use change, respectively. If the analyzer learns on old source code, it might start issuing outdated recommendations at some point. Example. Formerly, C++ programmers have been recommended using auto_ptr instead of half-done pointers. This smart pointer is now considered obsolete and it is recommended that you use unique_ptr .
  • Data models. At the very least, C and C++ languages have such a thing as a data model . This means that data types have different number of bits across platforms. If you don't take this into account, you can incorrectly teach the analyzer. For example, in Windows 32/64 the long type always has 32 bits. But in Linux, its size will vary and take 32/64 bits depending on the platform's number of bits. Without taking all this into account, the analyzer can learn to miscalculate the size of the types and structures it forms. But the types also align in different ways. All this, of course, can be taken into account. You can teach the analyzer to know about the size of the types, their alignment and mark the projects (indicate how they are building). However, all this is an additional complexity, which is not mentioned in the research articles.
  • Behavioral unambiguousness. Since we're talking about ML, the analysis result is more likely to have probabilistic nature. That is, sometimes the erroneous pattern will be recognized, and sometimes not, depending on how the code is written. From our experience, we know that the user is extremely irritated by the ambiguity of the analyzer's behavior. He wants to know exactly which pattern will be considered erroneous and which will not, and why. In the case of the classical analyzer developing approach, this problem is poorly expressed. Only sometimes we need to explain our clients why there is a/there is no analyzer warning and how the algorithm works, what exceptions are handled in it. Algorithms are clear and everything can always be easily explained. An example of this kind of communication: " False Positives in PVS-Studio: How Deep the Rabbit Hole Goes ". It's not clear how the described problem will be solved in the analyzers built on ML.

Conclusions


Nous ne nions pas les perspectives de la direction ML, y compris son application en termes d'analyse de code statique. ML peut être potentiellement utilisé dans les tâches de recherche de fautes de frappe, lors du filtrage des faux positifs, lors de la recherche de nouveaux modèles d'erreur (non encore décrits), etc. Cependant, nous ne partageons pas l'optimisme qui imprègne les articles consacrés au ML en termes d'analyse de code.

Dans cet article, nous avons décrit quelques problèmes sur lesquels il faudra travailler s'il veut utiliser ML. Les nuances décrites nient en grande partie les avantages de la nouvelle approche. De plus, les anciennes approches classiques de mise en œuvre des analyseurs sont plus rentables et plus économiquement réalisables.

Fait intéressant, les articles des adhérents de la méthodologie ML ne mentionnent pas ces pièges. Eh bien, rien de nouveau. ML provoque un certain battage médiatique et nous ne devrions probablement pas nous attendre à une évaluation équilibrée de ses apologistes concernant l'applicabilité du ML dans les tâches d'analyse de code statique.

De notre point de vue, l'apprentissage automatique remplira une niche dans les technologies, utilisées dans les analyseurs statiques avec l'analyse des flux de contrôle, les exécutions symboliques et autres.

La méthodologie de l'analyse statique peut bénéficier de l'introduction du ML, mais n'exagérez pas les possibilités de cette technologie.

PS


Étant donné que l'article est généralement critique, certains pourraient penser que nous craignons le nouveau et que les Luddites se sont retournés contre ML de peur de perdre le marché des outils d'analyse statique.

Luddites


Non, nous n'avons pas peur. Nous ne voyons tout simplement pas l'intérêt de dépenser de l'argent pour des approches inefficaces dans le développement de l'analyseur de code PVS-Studio. Sous une forme ou une autre, nous adopterons le ML. De plus, certains diagnostics contiennent déjà des éléments d'algorithmes d'auto-apprentissage. Cependant, nous serons certainement très conservateurs et ne prendrons que ce qui aura clairement un plus grand effet que les approches classiques, construites sur des boucles et des ifs :). Après tout, nous devons créer un outil efficace, pas travailler sur une subvention :).

L'article est écrit parce que de plus en plus de questions sont posées sur le sujet et nous voulions avoir un article explicatif qui met tout à sa place.

Merci de votre attention. Nous vous invitons à lire l'article "Pourquoi devriez-vous choisir l'analyseur statique PVS-Studio pour l'intégrer dans votre processus de développement . "

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


All Articles