La prise en charge de Visual Studio 2019 dans PVS-Studio a immédiatement affecté plusieurs composants différents: le plug-in IDE lui-même, l'application d'analyse de ligne de commande, les analyseurs C ++ et C #, ainsi que plusieurs utilitaires. Je vais brièvement parler des problèmes que nous avons rencontrés lors de la prise en charge de la nouvelle version de l'IDE et comment les résoudre.
Avant de commencer, je voudrais revenir un peu en arrière pour retracer l'historique de prise en charge des versions précédentes de Visual Studio, ce qui permettra de mieux comprendre notre vision de la tâche et les décisions prises dans certaines situations.
À partir de la première version de l'analyseur PVS-Studio, dans laquelle un plug-in pour l'environnement Visual Studio est apparu (il s'agissait également d'une version de Visual Studio 2005), la prise en charge de nouvelles versions de Visual Studio était une tâche assez simple pour nous - cela se résumait essentiellement à la mise à jour du fichier de projet de plug-in dépendances de diverses API d'extension Visual Studio. Parfois, il était nécessaire de prendre en charge en outre de nouvelles fonctionnalités du langage C ++, que le compilateur Visual C ++ apprenait progressivement, mais cela ne causait généralement pas de problèmes immédiatement avant la sortie de la prochaine édition de Visual Studio. Et il n'y avait alors qu'un seul analyseur dans PVS-Studio - pour les langages C et C ++.
Tout a changé pour la sortie de Visual Studio 2017. En plus du fait que de nombreuses API d'extension pour cet IDE ont changé de manière très significative dans cette version, après la mise à jour, nous avons rencontré des problèmes pour assurer la compatibilité descendante du travail du nouvel analyseur C # qui était apparu à ce moment-là (ainsi que notre nouvelle couche C ++ analyseur pour les projets MSBuild) avec les anciennes versions de MSBuild \ Visual Studio.
Par conséquent, avant de lire cet article, je vous recommande fortement de lire l'article connexe sur la prise en charge de Visual Studio 2017: "Prise en charge de
Visual Studio 2017 et Roslyn 2.0 dans PVS-Studio: parfois, l'utilisation de solutions prêtes à l'emploi n'est pas aussi simple qu'il y paraît à première vue ." L'article mentionné ci-dessus décrit les problèmes que nous avons rencontrés la dernière fois, ainsi que les schémas d'interaction de divers composants (par exemple, PVS-Studio, MSBuild, Roslyn). Comprendre cette interaction sera utile lors de la lecture de cet article.
En fin de compte, la solution à ces problèmes a apporté des changements importants à notre analyseur et, comme nous l'espérions, les nouvelles approches que nous avons ensuite appliquées permettront de prendre en charge les versions mises à jour de Visual Studio \ MSBuild beaucoup plus facilement et plus rapidement à l'avenir. En partie, cette hypothèse a déjà été confirmée par la publication de nombreuses mises à jour de Visual Studio 2017. Cette nouvelle approche a-t-elle aidé à prendre en charge Visual Studio 2019? À ce sujet ci-dessous.
Plugin PVS-Studio pour Visual Studio 2019
Tout a commencé, semble-t-il, pas mal. Il était assez facile de porter le plug-in vers Visual Studio 2019, où il a démarré et a bien fonctionné. Malgré cela, 2 problèmes ont été immédiatement révélés, ce qui promettait de futurs problèmes.
La première est l'interface
IVsSolutionWorkspaceService , utilisée pour prendre en charge le mode Lightweight Solution Load, qui, soit dit en passant, a été désactivé dans l'une des mises à jour précédentes de Visual Studio 2017, a été décoré avec l'attribut
obsolète , qui n'était qu'un avertissement lors de l'assemblage, mais il a promis plus à l'avenir problèmes. Microsoft a rapidement introduit ce mode et l'a abandonné ... Nous avons traité ce problème tout simplement - refusé d'utiliser l'interface appropriée.
La seconde - lors du chargement de Visual Studio avec le plug-in, le message suivant est apparu:
Visual Studio a détecté une ou plusieurs extensions à risque ou ne fonctionnant pas dans une mise à jour VS de fonctionnalité.La visualisation des journaux de démarrage de Visual Studio (fichier ActivityLog) a finalement pointé le «i»:
Avertissement: l'extension «PVS-Studio» utilise la fonction «chargement automatique synchrone» de Visual Studio. Cette fonctionnalité ne sera plus prise en charge dans une future mise à jour de Visual Studio 2019, auquel cas cette extension ne fonctionnera pas. Veuillez contacter le fournisseur d'extensions pour obtenir une mise à jour.Pour nous, cela signifiait une chose - changer la façon dont le plug-in est chargé en mode asynchrone. J'espère que vous ne serez pas contrarié si je ne vous surcharge pas de détails sur l'interaction avec les interfaces COM de Visual Studio, et je passerai en revue les modifications assez brièvement.
Microsoft a un article sur la création de plugins chargés de manière asynchrone: "
Comment: utiliser AsyncPackage pour charger des VSPackages en arrière-plan ". Dans le même temps, il était évident pour tout le monde que la question ne se limiterait pas à ces changements.
L'un des principaux changements est la méthode de chargement, ou plutôt d'initialisation. Auparavant, l'initialisation nécessaire avait lieu dans deux méthodes - la méthode
Initialize substituée de notre classe
Package et la méthode
OnShellPropertyChange . La nécessité de transférer une partie de la logique vers la méthode
OnShellPropertyChange est due au fait que lorsque le plug-in est chargé de manière synchrone, Visual Studio peut ne pas encore être entièrement chargé et initialisé, et par conséquent, toutes les actions nécessaires n'ont pas pu être effectuées à l'étape d'initialisation du plug-in. Une option pour résoudre ce problème consiste à attendre que Visual Studio quitte l'état «zombie» et à retarder ces actions. C'est la logique et a été rendue dans
OnShellPropertyChange avec une vérification de l'état «zombie».
Dans la classe abstraite
AsyncPackage , dont les plug-ins chargés de manière asynchrone sont hérités, la méthode
Initialize a un modificateur
scellé , donc l'initialisation doit être effectuée dans la méthode
InitializeAsync redéfinie, qui a été effectuée. Nous avons également dû changer la logique du suivi de l'état «zombie» de Visual Studio, car nous avons cessé de recevoir ces informations dans le plugin. Cependant, un certain nombre d'actions qui devaient être effectuées après l'initialisation du plugin n'ont pas disparu. La
solution a été d'utiliser la méthode
OnPackageLoaded de l'interface
IVsPackageLoadEvents , où les actions nécessitant une exécution différée ont été effectuées.
Un autre problème qui découle logiquement du fait du chargement asynchrone du plug-in est le manque de commandes de plug-in PVS-Studio au moment du démarrage de Visual Studio. Lorsque vous ouvrez le journal de l'analyseur en double-cliquant dans le gestionnaire de fichiers (si vous devez l'ouvrir via Visual Studio), la version nécessaire de devenv.exe a été lancée avec la commande pour ouvrir le rapport de l'analyseur. La commande de lancement ressemblait à ceci:
"C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\Common7\IDE\devenv.exe" /command "PVSStudio.OpenAnalysisReport C:\Users\vasiliev\source\repos\ConsoleApp\ConsoleApp.plog"
L'indicateur "/ command" ici est utilisé pour appeler une commande enregistrée dans Visual Studio. Cette approche ne fonctionnait plus, car les commandes n'étaient pas disponibles avant le téléchargement du plug-in. En conséquence, j'ai dû m'arrêter sur la «béquille» avec l'analyse de la ligne de lancement de devenv.exe après le chargement du plugin, et s'il y a une représentation sous forme de chaîne de la commande pour ouvrir le journal - en fait, le chargement du journal. Ainsi, dans ce cas, ayant refusé d'utiliser l'interface «correcte» pour travailler avec des commandes, il a été possible de maintenir les fonctionnalités nécessaires en retardant le chargement du journal jusqu'à ce que le plug-in soit complètement chargé.
Fuh, il semble être réglé et tout fonctionne - tout se charge et s'ouvre correctement, il n'y a pas d'avertissement - enfin.
Et puis l'inattendu se produit - Pavel (bonjour!) Installe un plug-in, après quoi il demande pourquoi nous n'avons pas fait de chargement asynchrone?
Dire que nous avons été surpris - ne rien dire - comment cela? Non, vraiment - voici la nouvelle version du plugin installé, voici le message que le package est téléchargeable de façon synchrone. Nous installons avec Alexander (et bonjour à vous aussi) la même version du plugin sur nos machines - tout va bien. Rien n'est clair - nous avons décidé de voir quelles versions des bibliothèques PVS-Studio étaient chargées dans Visual Studio. Et soudain, il s'avère que les versions des bibliothèques PVS-Studio pour Visual Studio 2017 sont utilisées, malgré le fait que la version correcte des bibliothèques se trouve dans le package VSIX - pour Visual Studio 2019.
Après avoir bricolé avec VSIXInstaller, j'ai réussi à trouver la cause du problème - le cache du package. La théorie a également été confirmée par le fait que lors de la restriction des droits d'accès au package dans le cache (C: \ ProgramData \ Microsoft \ VisualStudio \ Packages) VSIXInstaller a écrit des informations d'erreur dans le journal. Étonnamment, s'il n'y a pas d'erreur, aucune information sur le fait que le package est installé à partir du cache n'est écrite dans le journal.
Remarque En étudiant le comportement de VSIXInstaller et des bibliothèques associées, il s'est dit qu'il est très cool que Roslyn et MSBuild aient un code open source qui facilite la lecture, le débogage et le suivi de la logique de travail.
En conséquence, les événements suivants se sont produits - lors de l'installation du plug-in, VSIXInstaller a vu que le package correspondant était déjà dans le cache (il y avait un package .vsix pour Visual Studio 2017) et l'a utilisé à la place du package installé lors de l'installation. Pourquoi cela ne prend pas en compte les restrictions / exigences décrites dans .vsixmanifest (par exemple, la version de Visual Studio pour laquelle vous pouvez installer l'extension) est une question ouverte. Pour cette raison, il s'est avéré que bien que .vsixmanifest contienne les restrictions nécessaires, le plug-in conçu pour Visual Studio 2017 a été installé sur Visual Studio 2019.
Le pire, c'est qu'une telle installation a brisé le graphique des dépendances de Visual Studio, et même si extérieurement il peut même sembler que l'environnement de développement fonctionnait bien, en fait tout était très mauvais. Il était impossible d'installer et de désinstaller des extensions, d'effectuer des mises à jour, etc. Le processus de «récupération» a également été assez désagréable, car il était nécessaire de supprimer l'extension (les fichiers correspondants), ainsi que de modifier manuellement les fichiers de configuration qui stockent des informations sur le package installé. En général, ce n'est pas assez agréable.
Pour résoudre ce problème et éviter des situations similaires à l'avenir, il a été décidé de créer un GUID pour le nouveau package afin de séparer exactement les packages Visual Studio 2017 et Visual Studio 2019 (il n'y a pas un tel problème avec les anciens packages, et ils ont toujours utilisé un GUID commun).
Et comme nous parlions de surprises désagréables, je mentionnerai encore une chose: après la mise à jour vers l'aperçu 2, l'élément de menu a été "déplacé" sous l'onglet "Extensions". Il semblerait que ce soit correct, mais l'accès aux fonctions du plugin est devenu moins pratique. Sur les versions ultérieures de Visual Studio 2019, y compris la version finale, ce comportement a été conservé. Je n'ai trouvé aucune mention de cette «fonctionnalité» au moment de sa sortie dans la documentation ou le blog.
Maintenant, il semblerait que tout fonctionne et que la prise en charge du plug-in pour Visual Studio 2019 est terminée. Le lendemain de la sortie de PVS-Studio 7.02 avec prise en charge de Visual Studio 2019, il s'est avéré que ce n'était pas le cas - un autre problème avec le plug-in asynchrone a été trouvé. Pour l'utilisateur, cela pourrait ressembler à ceci: lors de l'ouverture d'une fenêtre avec les résultats de l'analyse (ou du démarrage de l'analyse), notre fenêtre était parfois affichée «vide» - elle ne contenait aucun contenu: boutons, tableau avec avertissements de l'analyseur, etc.
En fait, ce problème s'est parfois répété au cours du travail. Cependant, il n'a été répété que sur une seule machine et n'a commencé à apparaître qu'après la mise à jour de Visual Studio dans l'une des premières versions de 'Preview' - il y avait des soupçons que quelque chose s'était cassé pendant l'installation / la mise à jour. Au fil du temps, cependant, le problème a cessé de se répéter même sur cette machine, et nous avons décidé qu'elle "se réparait d'elle-même". Il s'est avéré que non - juste si chanceux. Plus précisément, pas de chance.
La question s'est avérée être dans l'ordre d'initialisation de la fenêtre d'environnement elle-même (le descendant de la classe
ToolWindowPane ) et de son contenu (en fait, notre contrôle avec la grille et les boutons). Dans certaines conditions, l'initialisation du contrôle a eu lieu avant l'initialisation du volet et malgré le fait que tout fonctionnait sans erreur, la méthode
FindToolWindowAsync (création d'une fenêtre lors du premier appel) a fonctionné correctement, mais le contrôle est resté invisible. Nous avons corrigé cela en ajoutant une initialisation paresseuse pour notre contrôle au code de remplissage du volet.
Prise en charge C # 8.0
L'utilisation de Roslyn comme base pour l'analyseur présente un avantage significatif - il n'est pas nécessaire de maintenir manuellement de nouvelles constructions de langage. Tout cela est pris en charge et implémenté dans le cadre des bibliothèques Microsoft.CodeAnalysis - nous utilisons des résultats prêts à l'emploi. Ainsi, la prise en charge de la nouvelle syntaxe est implémentée en mettant à jour les bibliothèques.
Bien sûr, en ce qui concerne l'analyse statique, ici, vous devez déjà tout faire vous-même, en particulier, pour traiter de nouvelles constructions de langage. Oui, nous obtenons le nouvel arbre de syntaxe automatiquement en utilisant la version la plus récente de Roslyn, mais nous devons enseigner à l'analyseur comment percevoir et traiter les nœuds nouveaux / modifiés de l'arbre.
Je pense que l'innovation la plus parlée en C # 8 est les types de référence nullables. Je n'écrirai pas à leur sujet ici - c'est un sujet assez vaste qui mérite un article séparé (qui est déjà en cours d'écriture). En général, nous avons jusqu'à présent décidé d'ignorer les annotations annulables dans notre mécanisme de flux de données (c'est-à-dire que nous les comprenons, les analysons et les ignorons). Le fait est que malgré le type de référence non nullable de la variable, vous pouvez toujours y écrire
null tout simplement (ou par erreur), ce qui peut conduire à NRE lors du déréférencement du lien correspondant. Dans ce cas, notre analyseur peut voir une erreur similaire et donner un avertissement sur l'utilisation d'une référence potentiellement nulle (bien sûr, s'il voit une telle affectation dans le code) malgré le type de référence non nul de la variable.
Je veux noter que l'utilisation de types de référence nullables et la syntaxe qui l'accompagne ouvre la possibilité d'écrire du code très intéressant. Pour nous, nous avons appelé cela la «syntaxe émotionnelle». Le code ci-dessous se compile assez bien:
obj.Calculate(); obj?.Calculate(); obj.Calculate(); obj!?.Calculate(); obj!!!.Calculate();
Soit dit en passant, au cours de mon travail, j'ai trouvé quelques façons de `` remplir '' Visual Studio en utilisant la nouvelle syntaxe. Le fait est que vous ne pouvez pas limiter le nombre de caractères à un lorsque vous mettez "!". Autrement dit, vous pouvez écrire non seulement un code du formulaire:
object temp = null!
mais aussi:
object temp = null!!!;
Vous pouvez pervertir, continuer et écrire comme ceci:
object temp = null!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;
Ce code se compile avec succès. Mais si vous demandez des informations sur l'arborescence de syntaxe à l'aide du visualiseur de syntaxe à partir du SDK de la plateforme du compilateur .NET, Visual Studio se bloque.
Vous pouvez obtenir des informations sur le problème à partir de l'Observateur d'événements:
Faulting application name: devenv.exe, version: 16.0.28803.352, time stamp: 0x5cc37012 Faulting module name: WindowsBase.ni.dll, version: 4.8.3745.0, time stamp: 0x5c5bab63 Exception code: 0xc00000fd Fault offset: 0x000c9af4 Faulting process id: 0x3274 Faulting application start time: 0x01d5095e7259362e Faulting application path: C:\Program Files (x86)\ Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.exe Faulting module path: C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\ WindowsBase\4480dfedf0d7b4329838f4bbf953027d\WindowsBase.ni.dll Report Id: 66d41eb2-c658-486d-b417-02961d9c3e4f Faulting package full name: Faulting package-relative application ID:
Si vous allez plus loin et augmentez le nombre de points d'exclamation plusieurs fois, Visual Studio tombera de lui-même - l'aide de Syntax Visualizer n'est plus nécessaire. Les bibliothèques Microsoft.CodeAnalysis et le compilateur csc.exe ne digèrent pas non plus ce code.
Bien sûr, ce sont des exemples synthétiques, mais ce fait m'a quand même semblé drôle.
Jeu d'outils
Remarque Une fois de plus, je suis confronté au problème de traduire le mot «évaluation» dans le contexte d'une conversation sur les projets MSBuild. La traduction, qui semblait la plus proche en termes de sens et qui semblait en même temps normale, était «construire un modèle de projet». Si vous avez d'autres options de traduction - vous pouvez m'écrire, ce sera intéressant à lire.
Il était évident que la mise à jour de l'ensemble d'outils serait la tâche la plus longue. Plus précisément, cela semblait le cas dès le début, mais maintenant je suis porté à croire que le plus problématique était le support du plugin. En particulier, cela était dû à la panoplie d'outils déjà existante et au mécanisme de construction du modèle de projet MSBuild, qui a fonctionné avec succès maintenant, bien qu'il ait dû être étendu. Pas besoin d'écrire des algorithmes à partir de zéro a considérablement simplifié la tâche. Notre pari sur «notre» jeu d'outils, réalisé au stade de la prise en charge de Visual Studio 2017, était une fois de plus justifié.
Traditionnellement, tout commence par la mise à jour des packages NuGet. Sur l'onglet de gestion des packages NuGet pour les solutions, il y a un bouton «Mettre à jour» ... qui ne fonctionne pas. Lors de la mise à jour de tous les packages, plusieurs conflits de versions sont survenus et leur résolution ne semblait pas très correcte. Un moyen plus pénible, mais, semble-t-il, plus fiable, consiste à mettre à jour «morceau par morceau» les packages Microsoft.Build / Microsoft.CodeAnalysis cibles.
L'une des différences a été immédiatement identifiée par des tests de règles de diagnostic - la structure de l'arbre de syntaxe d'un nœud déjà existant a changé. C'est bon, corrigé rapidement.
Permettez-moi de vous rappeler que pendant le travail, nous testons des analyseurs (C #, C ++, Java) sur des projets open source. Cela vous permet de bien tester les règles de diagnostic - trouver, par exemple, des faux positifs, ou avoir une idée des autres cas qui n'ont pas été pris en compte (réduire le nombre de faux négatifs). Ces tests permettent également de suivre une éventuelle régression au stade initial de la mise à jour des bibliothèques / outils. Et cette fois n'a pas fait exception, car un certain nombre de problèmes sont apparus.
Un problème était la détérioration du comportement dans les bibliothèques CodeAnalysis. Plus précisément, sur un certain nombre de projets dans le code de bibliothèque, des exceptions se sont produites lors de diverses opérations - obtention d'informations sémantiques, ouverture de projets, etc.
Les lecteurs attentifs de l'article sur la prise en charge de Visual Studio 2017 se souviennent que notre kit de distribution a un stub - le fichier MSBuild.exe a une taille de 0 octet.
Cette fois, j'ai dû aller plus loin - maintenant le kit de distribution contient également des talons de compilateur vides - csc.exe, vbc.exe, VBCSCompiler.exe. Pourquoi? La voie à suivre a commencé avec l'analyse de l'un des projets de la base de test, sur lequel les «différences» de rapports sont apparues - un certain nombre d'avertissements étaient absents lors de l'utilisation de la nouvelle version de l'analyseur.
Le problème s'est avéré être des symboles de compilation conditionnelle - lors de l'analyse d'un projet à l'aide de la nouvelle version de l'analyseur, certains symboles ont été extraits incorrectement. Pour mieux comprendre ce qui a causé ce problème, j'ai dû plonger dans les bibliothèques Roslyn.
Pour analyser les caractères de compilation conditionnelle, utilisez la méthode
GetDefineConstantsSwitch de la classe
Csc de la bibliothèque
Microsoft.Build.Tasks.CodeAnalysis . L'analyse est effectuée à l'aide de la méthode
String.Split sur un certain nombre de délimiteurs:
string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' });
Cette méthode d'analyse fonctionne correctement, tous les symboles de compilation conditionnelle nécessaires sont extraits avec succès. Creuser plus loin.
Le point clé suivant est l'appel à la méthode
ComputePathToTool de la classe
ToolTask . Cette méthode crée le chemin d'accès au fichier exécutable (
csc.exe ) et vérifie sa présence. Si le fichier existe, son chemin d'accès est renvoyé, sinon
null est renvoyé.
Code de l'appelant:
.... string pathToTool = ComputePathToTool(); if (pathToTool == null) {
Puisqu'il n'y a pas de fichier
csc.exe (il semblerait - pourquoi en avons-nous besoin?),
PathToTool à ce stade est
nul et la méthode actuelle (
ToolTask.Execute ) termine son exécution avec le résultat
faux . Par conséquent, les résultats de la tâche, y compris les symboles de compilation conditionnelle résultants, sont ignorés.
Voyons ce qui se passe si vous placez le fichier
csc.exe à l'emplacement attendu.
Dans ce cas,
pathToTool indique l'emplacement réel du fichier existant et l'exécution de la méthode
ToolTask.Execute continue. Le point clé suivant est l'appel à la méthode
ManagedCompiler.ExecuteTool . Et cela commence comme suit:
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { if (ProvideCommandLineArgs) { CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands) .Select(arg => new TaskItem(arg)).ToArray(); } if (SkipCompilerExecution) { return 0; } .... }
La propriété
SkipCompilerExecution est
vraie (logiquement, nous ne
compilons pas réellement). Par conséquent, la méthode appelante (déjà mentionnée
ToolTask.Execute ) vérifie que le code retour de la méthode
ExecuteTool est 0 et, si tel est le cas, termine son exécution avec la valeur
true . Ce que vous aviez derrière
csc.exe était là - le vrai compilateur ou «Guerre et paix» de Leo Tolstoï sous forme textuelle n'a pas d'importance.
Par conséquent, le problème principal provient du fait que la séquence des étapes est définie dans l'ordre suivant:
- vérifier l'existence du compilateur;
- vérifier si le compilateur doit être démarré;
pas l'inverse. Les stubs du compilateur ont réussi à résoudre ce problème.
Eh bien, comment les caractères d'une compilation réussie se sont-ils produits si le fichier csc.exe n'a pas été détecté (et le résultat de la tâche a été ignoré)?
Il existe une méthode pour ce cas -
CSharpCommandLineParser.ParseConditionalCompilationSymbols à partir de la bibliothèque
Microsoft.CodeAnalysis.CSharp . L'analyse est également effectuée par la méthode
String.Split avec un certain nombre de délimiteurs:
string[] values = value.Split(new char[] { ';', ',' } );
Remarquez la différence avec l'ensemble des délimiteurs de la méthode
Csc.GetDefineConstantsSwitch ? Dans ce cas, l'espace blanc n'est pas un séparateur. Ainsi, si des caractères de compilation conditionnelle ont été écrits avec un espace, cette méthode ne les analysera pas correctement.
Cette situation s'est produite sur des projets problématiques - des caractères de compilation conditionnelle y ont été écrits avec un espace et ont été analysés avec succès à l'aide de
GetDefineConstantsSwitch , mais pas
ParseConditionalCompilationSymbols .
Un autre problème qui s'est révélé après la mise à jour des bibliothèques a été la détérioration du comportement dans un certain nombre de cas, en particulier sur des projets qui n'ont pas été collectés. Des problèmes sont survenus dans les bibliothèques Microsoft.CodeAnalysis et nous sont retournés sous la forme de diverses exceptions -
ArgumentNullException (certains enregistreurs internes n'ont pas été initialisés),
NullReferenceException et d'autres.
Je veux parler d'un de ces problèmes ci-dessous - il m'a semblé assez intéressant.
Nous avons rencontré ce problème lors de la vérification de la dernière version du projet Roslyn - une
exception NullReferenceException a été levée à partir du code de l'une des bibliothèques. En raison de suffisamment d'informations détaillées sur l'emplacement du problème, nous avons rapidement trouvé le code du problème et, pour des raisons d'intérêt, nous avons décidé d'essayer de voir si le problème se reproduit lorsque vous travaillez à partir de Visual Studio.
Eh bien - il était possible de le reproduire dans Visual Studio (l'expérience a été menée sur Visual Studio 16.0.3). Pour ce faire, nous avons besoin d'une définition de classe de la forme suivante:
class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } }
Nous aurons également besoin du visualiseur de syntaxe (qui fait partie du SDK de la plateforme du compilateur .NET). Il est nécessaire de demander
TypeSymbol (élément de menu «Afficher TypeSymbol (le cas échéant)») à partir du nœud de l'arborescence de syntaxe de type
ConstantPatternSyntax (
null ). Après cela, Visual Studio va redémarrer et dans l'Observateur d'événements, vous pouvez voir des informations sur le problème, en particulier, trouver la trace de la pile:
Application: devenv.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.NullReferenceException at Microsoft.CodeAnalysis.CSharp.ConversionsBase. ClassifyImplicitBuiltInConversionSlow( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.ConversionsBase.ClassifyBuiltInConversion( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoForNode( Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode) at Microsoft.CodeAnalysis.CSharp.MemberSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.SyntaxTreeSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfo( Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoFromNode( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoCore( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) ....
Comme vous pouvez le voir, la cause du problème est le déréférencement de la référence nulle.
Comme je l'ai mentionné précédemment, nous avons rencontré le même problème lors des tests de l'analyseur. Si vous utilisez les bibliothèques de débogage Microsoft.CodeAnalysis pour créer l'analyseur, vous pouvez arriver à l'endroit exact en
déboguant en demandant
TypeSymbol au nœud souhaité dans l'arborescence de syntaxe.
En conséquence, nous arrivons à la méthode
ClassifyImplicitBuiltInConversionSlow mentionnée dans la trace de pile
ci -
dessus :
private Conversion ClassifyImplicitBuiltInConversionSlow( TypeSymbol source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); if (source.SpecialType == SpecialType.System_Void || destination.SpecialType == SpecialType.System_Void) { return Conversion.NoConversion; } Conversion conversion = ClassifyStandardImplicitConversion(source, destination, ref useSiteDiagnostics); if (conversion.Exists) { return conversion; } return Conversion.NoConversion; }
Le problème est que le paramètre de
destination est
nul dans ce cas. Par conséquent, lors de l'appel de
destination.SpecialType, une exception NullReferenceException est levée .
Oui, Debug.Assert est plus élevé que le déréférencement , mais ce n'est pas suffisant, car en fait il ne protège pas contre quoi que ce soit - il aide seulement à identifier le problème dans les versions de débogage des bibliothèques. Ou n'aide pas.Changements dans la construction d'un modèle de projets C ++
Rien de particulièrement intéressant ne s'est produit ici - les anciens algorithmes ne nécessitaient aucune modification importante, ce qui serait intéressant à discuter. Il y avait peut-être deux points sur lesquels il est logique de s'attarder.Tout d'abord, nous avons dû modifier les algorithmes qui s'appuient sur la valeur de ToolsVersion pour être écrits au format numérique. Sans entrer dans les détails - il existe plusieurs cas où vous devez comparer des jeux d'outils et choisir, par exemple, une nouvelle version plus récente. Cette version, respectivement, avait une valeur numérique plus élevée. Il y a eu un calcul que ToolsVersion, correspondant à la nouvelle version de MSBuild / Visual Studio, sera égal à 16.0. Quel que soit le cas ... Par souci d'intérêt, je cite un tableau sur la façon dont les valeurs des différentes propriétés ont changé dans différentes versions de Visual Studio:La blague, bien sûr, est obsolète, mais vous ne pouvez pas vous empêcher de vous souvenir de changer les versions de Windows et Xbox afin de comprendre que la prédiction des valeurs futures (quels que soient le nom et la version), dans le cas de Microsoft, est une chose fragile. :)
La solution était assez simple - introduisant la hiérarchisation des ensembles d'outils (attribution d'une entité prioritaire distincte).Le deuxième point concerne les problèmes lors du travail dans Visual Studio 2017 ou dans un environnement adjacent (par exemple, la présence de la variable d'environnement VisualStudioVersion ). Le fait est que le calcul des paramètres nécessaires pour construire un modèle de projet C ++ est beaucoup plus compliqué que de construire un modèle de projet .NET. Dans le cas de .NET, nous utilisons notre propre ensemble d'outils et la valeur correspondante de ToolsVersion. Dans le cas de C ++, nous pouvons construire à la fois sur nos propres outils et sur les ensembles d'outils existants dans le système. À partir de Build Tools dans Visual Studio 2017, les jeux d'outils sont enregistrés dans le fichier MSBuild.exe.config, pas dans le registre. Par conséquent, nous ne pouvons pas les obtenir à partir de la liste générale des jeux d'outils (par exemple, via Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets ), contrairement aux jeux d'outils qui sont enregistrés dans le Registre (correspondant à <= Visual Studio 2015) .En conséquence de ce qui précède, il ne fonctionnera pas pour construire un modèle de projet à l'aide de ToolsVersion 15.0 , car le système ne verra pas le jeu d'outils nécessaire. Ensemble d'outils le plus récent - Actuel- il sera disponible en même temps, car il s'agit de notre propre jeu d'outils, par conséquent, il n'y a pas un tel problème pour Visual Studio 2019. La solution s'est avérée simple et a permis de résoudre le problème sans changer les algorithmes existants pour la construction du modèle de projet - en ajoutant un autre à la liste de vos propres jeux d'outils, Current , 15.0 .Changements dans la construction d'un modèle de projets C # .NET Core
Dans le cadre de cette tâche, 2 problèmes ont été résolus à la fois, car ils se sont avérés liés:- après l'ajout du jeu d'outils 'Actuel', l'analyse des projets .NET Core pour Visual Studio 2017 a cessé de fonctionner;
- L'analyse des projets .NET Core sur un système où au moins une version de Visual Studio n'était pas installée n'a pas fonctionné.
Le problème dans les deux cas était le même - certains fichiers de base .targets / .props ont été recherchés de manière incorrecte. Cela a conduit au fait qu'il n'était pas possible de construire un modèle de projet à l'aide de notre ensemble d'outils.En l'absence de Visual Studio, vous pouvez voir une telle erreur (avec la version précédente de toolset'a - 15.0 ): The imported project "C:\Windows\Microsoft.NET\Framework64\ 15.0\Microsoft.Common.props" was not found.
Lors de la génération du modèle C # .NET Core du projet dans Visual Studio 2017, vous pouviez voir le problème suivant (avec la version actuelle du jeu d'outils, Current ): The imported project "C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\MSBuild\Current\Microsoft.Common.props" was not found. ....
Comme les problèmes sont similaires (mais ça ressemble à ça), vous pouvez essayer de tuer deux oiseaux avec une pierre.Ci-dessous, je décris comment ce problème a été résolu sans entrer dans les détails techniques. Ces détails (sur la construction de modèles de projets C # .NET Core, ainsi que sur la modification de la construction des modèles dans notre ensemble d'outils) attendent dans l'un de nos futurs articles. À propos, si vous lisez attentivement le texte ci-dessus, vous remarquerez peut-être qu'il s'agit de la deuxième référence à de futurs articles. :)
Alors, comment avons-nous résolu ce problème? La solution consistait à étendre notre propre ensemble d'outils au détriment des principaux fichiers .targets / .props du SDK .NET Core ( Sdk.props , Sdk.targets ). Cela nous a permis d'avoir plus de contrôle sur la situation, plus de flexibilité dans la gestion des importations, ainsi que dans la construction d'un modèle de projets .NET Core en général. Oui, notre ensemble d'outils a un peu augmenté, et nous avons également dû ajouter de la logique pour configurer les projets d'environnement nécessaires à la construction du modèle .NET Core, mais il semble que cela en valait la peine.Auparavant, le principe de travail lors de la construction d'un modèle de projets .NET Core était le suivant: nous avons simplement demandé cette construction, puis tout a fonctionné aux dépens de MSBuild.Maintenant, lorsque nous avons pris plus de contrôle entre nos mains, cela semble un peu différent:- préparation de l'environnement nécessaire à la construction d'un modèle de projets .NET Core;
- construction de modèles:
- début de la construction en utilisant les fichiers .targets / .props de notre ensemble d'outils'a;
- poursuite de la construction à l'aide de fichiers externes.
À partir des étapes décrites ci-dessus, il est évident que la mise en place de l'environnement nécessaire a deux objectifs principaux:- lancer la construction de modèles à l'aide de fichiers .targets / .props à partir de votre propre jeu d'outils;
- rediriger d'autres opérations vers des fichiers externes .targets / .props.
Pour rechercher les fichiers .targets / .props nécessaires à la construction d'un modèle de projets .NET Core, une bibliothèque spéciale est utilisée - Microsoft.DotNet.MSBuildSdkResolver. L'initiation de la construction à l'aide de fichiers de notre ensemble d'outils a été résolue en utilisant une variable d'environnement spéciale utilisée par cette bibliothèque - nous suggérons où importer les fichiers nécessaires (à partir de notre ensemble d'outils). Puisque la bibliothèque fait partie de notre distribution, il n'y a aucune crainte que la logique change soudainement et cesse de fonctionner.Maintenant, les fichiers Sdk sont d'abord importés de notre ensemble d'outils, et comme nous pouvons facilement les modifier, le contrôle de la logique supplémentaire de construction du modèle passe entre nos mains. Par conséquent, nous pouvons déterminer nous-mêmes quels fichiers doivent être importés et d'où. Cela s'applique également aux Microsoft.Common.props mentionnés ci-dessus. Nous importons ce fichier et d'autres fichiers de base de notre propre ensemble d'outils en toute confiance dans leur disponibilité et leur contenu.Après cela, après avoir effectué les importations nécessaires et défini un certain nombre de propriétés, nous transférons le contrôle supplémentaire de la création de modèles vers le SDK .NET Core réel, où le reste des actions nécessaires ont lieu.Conclusion
En général, la prise en charge de Visual Studio 2019 est devenue plus facile que la prise en charge de Visual Studio 2017, ce qui, à mon avis, est dû à plusieurs facteurs. Tout d'abord, Microsoft n'a pas changé autant de choses qu'entre Visual Studio 2015 et Visual Studio 2017. Oui, nous avons changé le jeu d'outils principal, avons commencé à orienter les plug-ins pour Visual Studio sur l'asynchronie, mais néanmoins. La seconde - nous avions déjà une solution prête avec nos propres outils et modèles de projet de construction - il n'était pas nécessaire de tout inventer à nouveau, il suffisait simplement d'étendre la solution existante. La prise en charge relativement simple de l'analyse des projets .NET Core pour de nouvelles conditions (ainsi que pour les cas d'analyse sur une machine où il n'y a pas d'instances Visual Studio installées) en raison de l'expansion de notre système de construction de modèle de projet donne également l'espoir que nous avons fait le bon choix.Ayant décidé de prendre le contrôle de vous-même.Mais tout de même, je voudrais répéter une pensée qui figurait à nouveau dans l'article précédent - parfois, utiliser des solutions toutes faites n'est pas aussi simple qu'il y paraît à première vue.
Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Sergey Vasiliev. Prise en charge de Visual Studio 2019 dans PVS-Studio