Unterstützung von Visual Studio 2019 in PVS-Studio


Die Unterstützung von Visual Studio 2019 in PVS-Studio betraf eine Reihe von Komponenten: das Plugin selbst, den Befehlszeilenanalysator, die Kerne der C ++ - und C # -Analysatoren sowie einige Dienstprogramme. In diesem Artikel werde ich kurz erklären, auf welche Probleme wir bei der Implementierung der Unterstützung der IDE gestoßen sind und wie wir sie behoben haben.

Bevor wir beginnen, möchte ich einen Blick auf die Geschichte der Unterstützung der vorherigen Versionen von Visual Studio in PVS-Studio werfen, damit Sie unsere Vision der Aufgabe und der Lösungen, die wir in jeder einzelnen Situation entwickelt haben, besser verstehen.

Seit der ersten Version von PVS-Studio, die mit einem Plugin für Visual Studio ausgeliefert wurde (damals war es Visual Studio 2005), war die Unterstützung neuer Versionen dieser IDE für uns eine ziemlich triviale Aufgabe, die im Wesentlichen darauf hinauslief, das Projekt des Plugins zu aktualisieren Datei und Abhängigkeiten der verschiedenen API-Erweiterungen von Visual Studio. Hin und wieder mussten wir Unterstützung für neue Funktionen von C ++ hinzufügen, mit denen der Visual C ++ - Compiler allmählich zu arbeiten lernte, aber es war im Allgemeinen auch keine schwierige Aufgabe und konnte direkt vor einer neuen Visual Studio-Version problemlos ausgeführt werden . Außerdem hatte PVS-Studio damals nur einen Analysator - für C / C ++.

Mit der Veröffentlichung von Visual Studio 2017 haben sich die Dinge geändert. Zusätzlich zu den großen Änderungen an vielen API-Erweiterungen der IDE ist auch ein Problem mit der Aufrechterhaltung der Abwärtskompatibilität des kurz zuvor hinzugefügten neuen C # -Analysators (sowie der neuen Analysatorschicht für C ++ zur Arbeit mit MSBuild-Projekten) mit dem aufgetreten neue Versionen von MSBuild \ Visual Studio.

In Anbetracht all dessen empfehle ich dringend, dass Sie einen verwandten Artikel über die Unterstützung von Visual Studio 2017 lesen: " Unterstützung von Visual Studio 2017 und Roslyn 2.0 in PVS-Studio: Manchmal ist es nicht so einfach, vorgefertigte Lösungen zu verwenden, wie es scheint ", bevor Sie weiterlesen. Dieser Artikel beschreibt die Probleme, mit denen wir das letzte Mal konfrontiert waren, und das Modell der Interaktion zwischen verschiedenen Komponenten (wie PVS-Studio, MSBuild und Roslyn). Wenn Sie diese Details kennen, können Sie den aktuellen Artikel besser verstehen.

Die Lösung dieser Probleme führte letztendlich zu erheblichen Änderungen am Analysegerät. Wir hatten gehofft, dass die dann angewandten neuen Ansätze uns helfen würden, zukünftige Versionen von Visual Studio \ MSBuild viel einfacher und schneller zu unterstützen. Diese Hoffnung erwies sich bereits als realistisch, als die zahlreichen Updates von Visual Studio 2017 veröffentlicht wurden. Hat uns der neue Ansatz bei der Unterstützung von Visual Studio 2019 geholfen? Lesen Sie weiter, um es herauszufinden.

PVS-Studio Plugin für Visual Studio 2019


Der Start schien vielversprechend. Wir haben uns nicht viel Mühe gegeben, das Plugin auf Visual Studio 2019 zu portieren und es gut starten und laufen zu lassen. Wir sind jedoch bereits auf zwei Probleme gleichzeitig gestoßen, die später weitere Probleme verursachen könnten.

Das erste hatte mit der IVsSolutionWorkspaceService- Schnittstelle zu tun, die zur Unterstützung des Lightweight Solution Load-Modus verwendet wurde (der übrigens in einem der früheren Updates in Visual Studio 2017 deaktiviert worden war). Es wurde mit dem Attribut " Veraltet" dekoriert, das derzeit nur zum Zeitpunkt der Erstellung eine Warnung auslöste, aber in Zukunft zu einem großen Problem werden würde. Dieser Modus hielt in der Tat nicht lange an ... Das war leicht zu beheben - wir haben einfach aufgehört, diese Schnittstelle zu verwenden.

Das zweite Problem war die folgende Meldung, die beim Laden von Visual Studio mit aktiviertem Plugin immer wieder angezeigt wurde: Visual Studio hat eine oder mehrere Erweiterungen erkannt, die gefährdet sind oder in einem Feature-VS-Update nicht funktionieren.

Die Protokolle der Visual Studio-Starts (die ActivityLog-Datei) halfen beim Aufräumen:

Warnung: Die Erweiterung 'PVS-Studio' verwendet die Funktion 'Synchrones automatisches Laden' von Visual Studio. Diese Funktion wird in einem zukünftigen Visual Studio 2019-Update nicht mehr unterstützt. Ab diesem Zeitpunkt funktioniert diese Erweiterung nicht mehr. Bitte wenden Sie sich an den Anbieter der Erweiterung, um ein Update zu erhalten.

Für uns bedeutete dies, dass wir vom synchronen in den asynchronen Lademodus wechseln mussten. Ich hoffe, es macht Ihnen nichts aus, wenn ich Ihnen die Details der Interaktion mit den COM-Schnittstellen von Visual Studio erspare und die Änderungen nur kurz skizziere.

Es gibt einen Artikel von Microsoft über das asynchrone Laden von Plugins: " Gewusst wie: Verwenden von AsyncPackage zum Laden von VSPackages im Hintergrund ". Es war jedoch bereits klar, dass weitere Änderungen folgen würden.

Eine der größten Änderungen war der Lademodus bzw. der Initialisierungsmodus. In früheren Versionen wurde die gesamte erforderliche Initialisierung mit zwei Methoden durchgeführt: Initialisierung unserer von Package geerbten Klasse und OnShellPropertyChange . Letzteres musste hinzugefügt werden, da Visual Studio beim synchronen Laden möglicherweise noch geladen und initialisiert wird und daher einige der erforderlichen Aktionen während der Initialisierung des Plugins nicht ausgeführt werden konnten. Eine Möglichkeit, dies zu beheben, bestand darin, die Ausführung dieser Aktionen zu verzögern, bis Visual Studio den Status "Zombie" beendet. Es war dieser Teil der Logik, den wir in die OnShellPropertyChange- Methode mit einer Überprüfung des ' Zombie' -Status herausgegriffen haben.

Die Initialize- Methode der abstrakten Klasse AsyncPackage , von der asynchron geladene Plugins geerbt werden , ist versiegelt. Daher muss die Initialisierung in der überschriebenen Methode InitializeAsync erfolgen , genau wie wir es getan haben. Die 'Zombie'-Check-Logik musste ebenfalls geändert werden, da die Statusinformationen für unser Plugin nicht mehr verfügbar waren. Außerdem mussten wir noch die Aktionen ausführen, die nach der Plugin-Initialisierung ausgeführt werden mussten. Wir haben das gelöst, indem wir die OnPackageLoaded- Methode der IVsPackageLoadEvents- Schnittstelle verwendet haben, in der diese verzögerten Aktionen ausgeführt wurden.

Ein weiteres Problem beim asynchronen Laden war, dass die Befehle des Plugins erst nach dem Laden von Visual Studio verwendet werden konnten. Das Öffnen des Analyseprotokolls durch Doppelklicken im Dateimanager (falls Sie es in Visual Studio öffnen mussten) führte zum Starten der entsprechenden Version von devenv.exe mit einem Befehl zum Öffnen des Protokolls. Der Startbefehl sah ungefähr so ​​aus:

"C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\Common7\IDE\devenv.exe" /command "PVSStudio.OpenAnalysisReport C:\Users\vasiliev\source\repos\ConsoleApp\ConsoleApp.plog" 

Das Flag "/ command" wird hier verwendet, um den in Visual Studio registrierten Befehl auszuführen. Dieser Ansatz funktionierte nicht mehr, da Befehle erst nach dem Laden des Plugins verfügbar waren. Die Problemumgehung bestand darin, den Startbefehl devenv.exe nach dem Laden des Plugins zu analysieren und den Befehl log open auszuführen, wenn er im Startbefehl gefunden wurde. Das Verwerfen der Idee, die "geeignete" Schnittstelle für die Arbeit mit Befehlen zu verwenden, ermöglichte es uns, die erforderliche Funktionalität beizubehalten und das Protokoll verzögert zu öffnen, nachdem das Plugin vollständig geladen wurde.

Puh, sieht so aus, als hätten wir es endlich geschafft. Das Plugin wird wie erwartet ohne Warnungen geladen und geöffnet.

Und hier laufen die Dinge schief. Paul (Hi Paul!) Installiert das Plugin auf seinem Computer und fragt, warum wir immer noch nicht auf asynchrones Laden umgestellt haben.

Zu sagen, dass wir schockiert waren, wäre eine Untertreibung. Das konnte nicht sein! Aber es ist real: Hier ist die neue Version des Plugins und hier ist eine Meldung, dass das Paket synchron geladen wird. Alexander (Hallo Alexander!) Und ich versuche die gleiche Version auf unseren jeweiligen Computern - es funktioniert gut. Wie ist das möglich? Dann fällt es uns ein, die Versionen der in Visual Studio geladenen PVS-Studio-Bibliotheken zu überprüfen - und wir stellen fest, dass dies die Bibliotheken für Visual Studio 2017 sind, während das VSIX-Paket die neuen Versionen enthält, d. H. Für Visual Studio 2019.

Nachdem wir eine Weile an VSIXInstaller herumgebastelt hatten, konnten wir feststellen, dass das Problem mit dem Paket-Cache zu tun hatte. Diese Theorie wurde auch durch die Tatsache unterstützt, dass die Einschränkung des Zugriffs auf das zwischengespeicherte Paket (C: \ ProgramData \ Microsoft \ VisualStudio \ Packages) dazu führte, dass VSIXInstaller eine Fehlermeldung im Protokoll ausgab. Seltsamerweise wurden die Informationen zur Installation zwischengespeicherter Pakete nicht angezeigt, wenn der Fehler nicht auftrat.

Randnotiz . Während ich das Verhalten von VSIX Installer und den zugehörigen Bibliotheken studierte, dachte ich, wie cool es ist, dass Roslyn und MSBuild Open Source sind, mit denen Sie ihren Code bequem lesen und debuggen und seine Arbeitslogik verfolgen können.

Folgendes ist also passiert: Bei der Installation des Plugins hat VSIX Installer festgestellt, dass das entsprechende Paket bereits zwischengespeichert wurde (es war tatsächlich das .vsix-Paket für Visual Studio 2017), und dieses Paket anstelle des neuen installiert. Warum die in der .vsixmanifest-Datei definierten Einschränkungen / Anforderungen (die unter anderem die Installation von Erweiterungen auf eine bestimmte Version von Visual Studio einschränkten) ignoriert wurden, müssen noch beantwortet werden. Infolgedessen wurde das für Visual Studio 2017 entwickelte Plugin in Visual Studio 2019 installiert - trotz der in der .vsixmanifest-Datei angegebenen Einschränkungen.

Am schlimmsten war jedoch, dass diese Installation das Abhängigkeitsdiagramm von Visual Studio durchbrach, und obwohl die IDE gut zu laufen schien, waren die Dinge tatsächlich schrecklich. Sie konnten keine Erweiterungen, Updates usw. installieren oder löschen. Der "Wiederherstellungs" -Prozess war ebenfalls schmerzhaft, da wir die Erweiterung (dh die Dateien, die sie enthalten) manuell löschen und - auch manuell - die Konfigurationsdateien bearbeiten mussten, in denen die Informationen über das installierte Paket gespeichert waren. Mit anderen Worten, es hat überhaupt keinen Spaß gemacht.

Um dies zu beheben und sicherzustellen, dass wir in Zukunft nicht auf solche Situationen stoßen, haben wir beschlossen, eine eigene GUID für das neue Paket zu erstellen, damit die Pakete für Visual Studio 2017 und Visual Studio 2019 sicher voneinander isoliert sind ( Die älteren Pakete waren in Ordnung (sie hatten immer eine gemeinsame GUID verwendet).

Da wir über unangenehme Überraschungen gesprochen haben, ist hier noch eines: Nach dem Update auf Vorschau 2 wurde das PVS-Studio-Menü auf die Registerkarte "Erweiterungen" "verschoben". Keine große Sache, aber es machte den Zugriff auf die Funktionalität des Plugins weniger bequem. Dieses Verhalten blieb auch in den nächsten Visual Studio 2019-Versionen, einschließlich der Version, bestehen. Ich habe Erwähnungen dieser "Funktion" weder in der Dokumentation noch im Blog gefunden.

Okay, jetzt sah es gut aus und wir schienen endlich mit der Unterstützung von Visual Studio 2019 fertig zu sein. Dies erwies sich am nächsten Tag nach der Veröffentlichung von PVS-Studio 7.02 als falsch. Es war wieder der asynchrone Lademodus. Beim Öffnen des Analyseergebnisfensters (oder Starten der Analyse) erscheint das Analysefenster dem Benutzer "leer" - keine Schaltflächen, kein Raster, überhaupt nichts.

Dieses Problem trat tatsächlich hin und wieder während der Analyse auf. Es betraf jedoch nur einen Computer und wurde erst angezeigt, als Visual Studio auf eine der ersten Iterationen von 'Vorschau' aktualisiert wurde. Wir vermuteten, dass während der Installation oder des Updates etwas kaputt gegangen war. Das Problem verschwand jedoch einige Zeit später und trat selbst auf diesem bestimmten Computer nicht auf. Daher dachten wir, dass es "von selbst behoben" wurde. Aber nein - wir hatten einfach Glück. Oder Pech.

Wie wir festgestellt haben, war es die Reihenfolge, in der das IDE-Fenster selbst (die von ToolWindowPane abgeleitete Klasse ) und sein Inhalt (unser Steuerelement mit dem Raster und den Schaltflächen) initialisiert wurden. Unter bestimmten Bedingungen wurde das Steuerelement vor dem Fenster initialisiert, und obwohl die Dinge gut liefen und die FindToolWindowAsync- Methode (die das Fenster beim ersten Zugriff erstellt) ihre Aufgabe gut erledigte, blieb das Steuerelement unsichtbar. Wir haben dies behoben, indem wir dem Code zum Füllen von Fenstern eine verzögerte Initialisierung für unsere Steuerung hinzugefügt haben.

Unterstützung von C # 8.0


Die Verwendung von Roslyn als Basis für den Analysator bietet einen großen Vorteil: Sie müssen die Unterstützung für neue Sprachkonstrukte nicht manuell hinzufügen - dies erfolgt automatisch über die Microsoft. Code Analysis-Bibliotheken, und wir verwenden nur die vorgefertigten Lösungen. Dies bedeutet, dass eine neue Syntax durch einfaches Aktualisieren der Bibliotheken unterstützt wird.

Was die Analyse selbst angeht, mussten wir natürlich die Dinge selbst optimieren - insbesondere neue Sprachkonstruktionen handhaben. Sicher, wir hatten den neuen Syntaxbaum automatisch durch einfaches Aktualisieren von Roslyn generiert, aber wir mussten dem Analysator trotzdem beibringen, wie neue oder geänderte Syntaxbaumknoten genau zu interpretieren und zu verarbeiten sind.

Die nullbaren Referenztypen sind möglicherweise die am häufigsten diskutierte neue Funktion von C # 8. Ich werde jetzt nicht darüber sprechen, da ein so großes Thema einen separaten Artikel wert ist (der gerade geschrieben wird). Im Moment haben wir uns entschlossen, nullfähige Annotationen in unserem Datenflussmechanismus zu ignorieren (dh wir verstehen, analysieren und überspringen sie). Die Idee ist, dass einer Variablen, selbst von einem nicht nullbaren Referenztyp, immer noch ziemlich einfach (oder versehentlich) der Wert null zugewiesen werden kann, was zu einem NRE führt, wenn versucht wird, ihn zu dereferenzieren. Unser Analysator kann solche Fehler erkennen und eine mögliche Null-Dereferenzierung melden (wenn er eine solche Zuordnung natürlich im Code findet), selbst wenn die Variable vom Typ einer nicht nullbaren Referenz ist.

Durch die Verwendung von nullbaren Referenztypen und der zugehörigen Syntax können Sie ziemlich interessanten Code schreiben. Wir haben es "emotionale Syntax" genannt. Dieses Snippet ist perfekt kompilierbar:

 obj.Calculate(); obj?.Calculate(); obj.Calculate(); obj!?.Calculate(); obj!!!.Calculate(); 

Übrigens haben mich meine Experimente dazu gebracht, einige Tricks zu entdecken, mit denen Sie Visual Studio mithilfe der neuen Syntax "zum Absturz bringen" können. Sie basieren auf der Tatsache, dass Sie so viele '!' Zeichen, wie Sie möchten. Es bedeutet, dass Sie nicht nur Code wie diesen schreiben können:

 object temp = null! 

aber auch so:

 object temp = null!!!; 

Und wenn Sie es noch weiter vorantreiben, könnten Sie verrückte Dinge wie diese schreiben:

 object temp = null!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!; 

Dieser Code ist kompilierbar. Wenn Sie jedoch versuchen, den Syntaxbaum in Syntax Visualizer über das .NET Compiler Platform SDK anzuzeigen, stürzt Visual Studio ab.

Der Fehlerbericht kann aus der Ereignisanzeige abgerufen werden:

 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: 

Wenn Sie noch verrückter werden und mehrmals mehr Ausrufezeichen hinzufügen, stürzt Visual Studio ohne Hilfe von Syntax Visualizer von selbst ab. Die Microsoft.CodeAnalysis-Bibliotheken und der csc.exe-Compiler können mit einem solchen Code ebenfalls nicht umgehen.

Diese Beispiele sind natürlich erfunden, aber ich fand diesen Trick lustig.

Toolset


Es war offensichtlich, dass die Aktualisierung des Toolset der schwierigste Teil sein würde. Zumindest sah es am Anfang so aus, aber jetzt denke ich, dass die Unterstützung des Plugins der schwierigste Teil war. Zum einen hatten wir bereits ein Toolset und einen Mechanismus zur Bewertung von MSBuild-Projekten, was gut war, obwohl es noch erweitert werden musste. Die Tatsache, dass wir die Algorithmen nicht von Grund auf neu schreiben mussten, machte es viel einfacher. Die Strategie, sich auf "unser" Toolset zu verlassen, an das wir uns bei der Unterstützung von Visual Studio 2017 lieber gehalten haben, hat sich erneut als richtig erwiesen.

Traditionell beginnt der Prozess mit der Aktualisierung von NuGet-Paketen. Die Registerkarte zum Verwalten von NuGet-Paketen für die aktuelle Lösung enthält die Schaltfläche "Aktualisieren" ... dies hilft jedoch nicht. Das gleichzeitige Aktualisieren aller Pakete verursachte Konflikte mit mehreren Versionen, und der Versuch, alle zu lösen, schien keine gute Idee zu sein. Eine schmerzhaftere, aber vermutlich sicherere Möglichkeit bestand darin, Zielpakete von Microsoft.Build / Microsoft.CodeAnalysis selektiv zu aktualisieren.

Beim Testen der Diagnose wurde sofort ein Unterschied festgestellt: Die Struktur des Syntaxbaums wurde auf einem vorhandenen Knoten geändert. Keine große Sache; das haben wir schnell behoben.

Ich möchte Sie daran erinnern, dass wir unsere Analysegeräte (für C #, C ++, Java) an Open-Source-Projekten testen. Auf diese Weise können wir die Diagnose gründlich testen. Überprüfen Sie sie beispielsweise auf falsch positive Ergebnisse oder prüfen Sie, ob wir Fälle verpasst haben (um die Anzahl der falsch negativen Ergebnisse zu verringern). Diese Tests helfen uns auch dabei, mögliche Regressionen im ersten Schritt der Aktualisierung der Bibliotheken / Toolsets zu verfolgen. Diesmal hatten sie auch eine Reihe von Problemen.

Eines war, dass sich das Verhalten in CodeAnalysis-Bibliotheken verschlechterte. Insbesondere bei der Überprüfung bestimmter Projekte wurden Ausnahmen vom Code der Bibliotheken für verschiedene Vorgänge wie das Abrufen semantischer Informationen, das Öffnen von Projekten usw. festgestellt.

Diejenigen unter Ihnen, die den Artikel über die Unterstützung von Visual Studio 2017 sorgfältig gelesen haben, erinnern sich, dass unsere Distribution mit einem Dummy geliefert wird - der Datei MSBuild.exe mit 0 Byte.

Jetzt mussten wir diese Praxis noch weiter vorantreiben und leere Dummies für die Compiler csc.exe, vbc.exe und VBCSCompiler.exe einfügen. Warum? Wir haben diese Lösung gefunden, nachdem wir eines der Projekte aus unserer Testbasis analysiert und Diff-Berichte erhalten hatten: Die neue Version des Analysators gab einige der erwarteten Warnungen nicht aus.

Wir haben festgestellt, dass es sich um bedingte Kompilierungssymbole handelt, von denen einige bei Verwendung der neuen Version des Analysators nicht ordnungsgemäß extrahiert wurden. Um dem Problem auf den Grund zu gehen, mussten wir uns eingehender mit dem Code von Roslyns Bibliotheken befassen.

Bedingte Kompilierungssymbole werden mit der GetDefineConstantsSwitch- Methode der Klasse Csc aus der Bibliothek Microsoft.Build.Tasks.CodeAnalysis analysiert. Die Analyse erfolgt mit der String.Split- Methode für eine Reihe von Trennzeichen:

 string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' }); 

Dieser Parsing-Mechanismus funktioniert perfekt. Alle bedingten Kompilierungssymbole werden korrekt extrahiert. Okay, lass uns weiter graben.

Der nächste wichtige Punkt war der Aufruf der ComputePathToTool- Methode der Klasse ToolTask . Diese Methode berechnet den Pfad zur ausführbaren Datei ( csc.exe ) und prüft, ob sie vorhanden ist. In diesem Fall gibt die Methode den Pfad zurück oder andernfalls null .

Der aufrufende Code:

 .... string pathToTool = ComputePathToTool(); if (pathToTool == null) { // An appropriate error should have been logged already. return false; } .... 

Da es keine csc.exe- Datei gibt (warum haben wir sie benötigt?), Wird PathToTool an dieser Stelle der Wert null zugewiesen, und die aktuelle Methode ( ToolTask.Execute ) gibt false zurück . Die Ergebnisse der Ausführung der Aufgabe, einschließlich der extrahierten bedingten Kompilierungssymbole, werden ignoriert.

Okay, mal sehen, was passiert, wenn wir die Datei csc.exe dort ablegen , wo sie erwartet wird.

Jetzt speichert pathToTool den tatsächlichen Pfad zur jetzt vorhandenen Datei, und ToolTask.Execute wird weiterhin ausgeführt. Der nächste wichtige Punkt ist der Aufruf der ManagedCompiler.ExecuteTool- Methode:

 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; } .... } 

Die SkipCompilerExecution- Eigenschaft ist true (logischerweise, da wir nicht für real kompilieren). Die aufrufende Methode (die bereits erwähnte ToolTask.Execute ) prüft, ob der Rückgabewert für ExecuteTool 0 ist, und gibt in diesem Fall true zurück . Ob Ihre csc.exe ein tatsächlicher Compiler oder "War and Peace" von Leo Tolstoy war, spielt überhaupt keine Rolle.

Das Problem hat also mit der Reihenfolge zu tun, in der die Schritte definiert wurden:

  • auf Compiler prüfen;
  • Überprüfen Sie, ob der Compiler gestartet werden soll.

Und wir würden eine umgekehrte Reihenfolge erwarten. Um dies zu beheben, wurden die Dummies für die Compiler hinzugefügt.

Okay, aber wie haben wir es geschafft, überhaupt Kompilierungssymbole zu erhalten, ohne dass die Datei csc.exe vorhanden war (und die Aufgabenergebnisse ignoriert wurden)?

Nun, auch für diesen Fall gibt es eine Methode: CSharpCommandLineParser.ParseConditionalCompilationSymbols aus der Bibliothek Microsoft.CodeAnalysis.CSharp . Es wird auch analysiert, indem die String.Split- Methode für eine Reihe von Trennzeichen aufgerufen wird:

 string[] values = value.Split(new char[] { ';', ',' } /*, StringSplitOptions.RemoveEmptyEntries*/); 

Sehen Sie, wie sich dieser Satz von Trennzeichen von dem unterscheidet, der von der Csc.GetDefineConstantsSwitch- Methode behandelt wird? Hier ist ein Leerzeichen kein Trennzeichen. Dies bedeutet, dass durch Leerzeichen getrennte bedingte Kompilierungssymbole mit dieser Methode nicht ordnungsgemäß analysiert werden.

Dies geschah, als wir die Problemprojekte überprüften: Sie verwendeten durch Leerzeichen getrennte bedingte Kompilierungssymbole und wurden daher erfolgreich von der GetDefineConstantsSwitch- Methode analysiert, nicht jedoch von der ParseConditionalCompilationSymbols- Methode.

Ein weiteres Problem, das nach dem Aktualisieren der Bibliotheken auftrat, war in bestimmten Fällen ein fehlerhaftes Verhalten - insbesondere bei Projekten, die nicht erstellt wurden. Es wirkte sich auf die Microsoft. Code Analysis- Bibliotheken aus und manifestierte sich als Ausnahmen aller Art: ArgumentNullException (fehlgeschlagene Initialisierung eines internen Loggers), NullReferenceException usw.

Ich möchte Ihnen von einem bestimmten Fehler erzählen, den ich ziemlich interessant fand.

Wir haben es beim Überprüfen der neuen Version des Roslyn-Projekts festgestellt : Eine der Bibliotheken hat eine NullReferenceException ausgelöst . Dank detaillierter Informationen zu seiner Quelle haben wir den Quellcode des Problems schnell gefunden und - aus Neugier - beschlossen, zu prüfen, ob der Fehler bei der Arbeit in Visual Studio weiterhin besteht.

Wir haben es geschafft, es in Visual Studio (Version 16.0.3) zu reproduzieren. Dazu benötigen Sie eine Klassendefinition wie folgt:

 class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } } 

Sie benötigen außerdem Syntax Visualizer (im Lieferumfang des .NET Compiler Platform SDK enthalten). Suchen Sie das TypeSymbol (indem Sie auf den Menüpunkt "View TypeSymbol (falls vorhanden)" klicken) des Syntaxbaumknotens vom Typ ConstantPatternSyntax ( null ). Visual Studio wird neu gestartet und die Ausnahmeinformationen - insbesondere die Stapelverfolgung - werden in der Ereignisanzeige verfügbar:

 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) .... 

Wie Sie sehen können, wird das Problem durch eine Nullreferenz-Dereferenzierung verursacht.

Wie bereits erwähnt, ist beim Testen des Analysators ein ähnliches Problem aufgetreten. Wenn Sie es mit Debug-Bibliotheken von Microsoft Code Analysis erstellen, können Sie direkt zum Problempunkt gelangen, indem Sie das TypeSymbol des entsprechenden Syntaxbaumknotens nachschlagen .

Es wird uns schließlich zur ClassifyImplicitBuiltInConversionSlow- Methode führen, die im obigen Stack-Trace erwähnt wird:

 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; } 

Hier ist der Zielparameter null , sodass beim Aufrufen von destination.SpecialType eine NullReferenceException ausgelöst wird . Ja, der Dereferenzierungsoperation geht Debug.Assert voraus, aber es hilft nicht, weil es tatsächlich vor nichts schützt - es ermöglicht Ihnen einfach, das Problem in den Debug-Versionen der Bibliotheken zu erkennen. Oder nicht.

Änderungen am Mechanismus zur Bewertung von C ++ - Projekten


In diesem Teil gab es nicht viel Interessantes: Die vorhandenen Algorithmen erforderten keine großen erwähnenswerten Änderungen, aber Sie möchten vielleicht zwei kleinere Probleme kennen.

Das erste war, dass wir die Algorithmen ändern mussten, die auf dem numerischen Wert von ToolsVersion beruhten. In bestimmten Fällen müssen Sie Toolsets vergleichen und beispielsweise die neueste Version auswählen, ohne auf Details einzugehen. Die neue Version hat natürlich einen größeren Wert. Wir haben erwartet, dass ToolsVersion für das neue MSBuild / Visual Studio den Wert 16.0 hat. Ja sicher! Die folgende Tabelle zeigt, wie sich die Werte verschiedener Eigenschaften im Verlauf des Visual Studio-Entwicklungsverlaufs geändert haben:
Visual Studio Produktname
Versionsnummer von Visual Studio
Tools-Version
PlatformToolset-Version
Visual Studio 2010
10.0
4.0
100
Visual Studio 2012
11.0
4.0
110
Visual Studio 2013
12.0
12.0
120
Visual Studio 2015
14.0
14.0
140
Visual Studio 2017
15.0
15.0
141
Visual Studio 2019
16.0
Strom
142

Ich weiß, dass der Witz über die durcheinandergebrachten Versionsnummern von Windows und Xbox ein alter ist, aber er beweist, dass Sie keine verlässlichen Vorhersagen über die Werte (ob im Namen oder in der Version) zukünftiger Microsoft-Produkte treffen können. :) :)

Wir haben das einfach gelöst, indem wir Prioritäten für Toolsets hinzugefügt haben (dh die Priorität als separate Einheit herausgreifen).

Das zweite Problem betraf Probleme beim Arbeiten in Visual Studio 2017 oder einer verwandten Umgebung (z. B. wenn die Umgebungsvariable VisualStudioVersion festgelegt ist). Dies liegt daran, dass die Berechnung der zur Bewertung eines C ++ - Projekts erforderlichen Parameter eine viel schwierigere Aufgabe ist als die Bewertung eines .NET-Projekts. Für .NET verwenden wir unser eigenes Toolset und den entsprechenden Wert von ToolsVersion. Für C ++ können wir sowohl unser eigenes Toolset als auch die vom System bereitgestellten verwenden. Ab Build Tools für Visual Studio 2017 werden Toolsets in der Datei MSBuild.exe.config anstelle der Registrierung definiert. Aus diesem Grund konnten wir sie nicht mehr aus der globalen Liste der Toolsets abrufen (z. B. mit Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets ), anders als in der Registrierung definiert (z. B. für Visual Studio 2015 und früher).

All dies hindert uns daran, ein Projekt mit ToolsVersion 15.0 zu evaluieren, da das System das erforderliche Toolset nicht sieht. Das neueste Toolset, Current , wird weiterhin verfügbar sein, da es unser eigenes Toolset ist. Daher gibt es in Visual Studio 2019 kein solches Problem. Die Lösung war recht einfach und ermöglichte es uns, dies zu beheben, ohne die vorhandenen Bewertungsalgorithmen zu ändern: Wir nur musste zusätzlich zu Current ein weiteres Toolset, 15.0 , in die Liste unserer eigenen Toolsets aufnehmen.

Änderungen am Mechanismus zur Bewertung von C # .NET Core-Projekten


Diese Aufgabe umfasste zwei miteinander verbundene Probleme:

  • Durch Hinzufügen des Toolsets "Aktuell" wurde die Analyse von .NET Core-Projekten in Visual Studio 2017 unterbrochen.
  • Die Analyse würde für .NET Core-Projekte auf Systemen ohne mindestens eine installierte Kopie von Visual Studio nicht funktionieren.

Beide Probleme stammten aus derselben Quelle: Einige der Basisdateien .targets / .props wurden auf falschen Pfaden nachgeschlagen. Dies hat uns daran gehindert, ein Projekt mit unserem Toolset zu bewerten.

Wenn Sie keine Visual Studio-Instanz installiert haben, wird der folgende Fehler angezeigt (mit der vorherigen Toolset-Version 15.0 ):

 The imported project "C:\Windows\Microsoft.NET\Framework64\ 15.0\Microsoft.Common.props" was not found. 

Bei der Evaluierung eines C # .NET Core-Projekts in Visual Studio 2017 wird der folgende Fehler angezeigt (mit der aktuellen Toolset-Version " Aktuell" ):

 The imported project "C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\MSBuild\Current\Microsoft.Common.props" was not found. .... 

Da diese Probleme ähnlich sind (was sie zu sein scheinen), könnten wir versuchen, zwei Fliegen mit einer Klappe zu schlagen.

In den nächsten Abschnitten werde ich erklären, wie wir das erreicht haben, ohne auf Details einzugehen. Diese Details (Informationen zur Bewertung von C # .NET Core-Projekten sowie Änderungen am Bewertungsmechanismus in unserem Toolset) werden in einem unserer zukünftigen Artikel behandelt. Übrigens, wenn Sie diesen Artikel sorgfältig gelesen haben, haben Sie wahrscheinlich bemerkt, dass dies der zweite Verweis auf unsere zukünftigen Artikel ist. :) :)

Wie haben wir dieses Problem gelöst? Wir haben unser eigenes Toolset um die Basisdateien .targets / .props aus dem .NET Core SDK ( Sdk.props , Sdk.targets ) erweitert. Dies gab uns mehr Kontrolle über die Situation und mehr Flexibilität beim Importmanagement sowie bei der Bewertung von .NET Core-Projekten im Allgemeinen. Ja, unser Toolset wurde wieder etwas größer, und wir mussten auch Logik zum Einrichten der Umgebung hinzufügen, die für die Evaluierung von .NET Core-Projekten erforderlich ist, aber es scheint sich zu lohnen.

Bis dahin hatten wir .NET Core-Projekte evaluiert, indem wir einfach die Evaluierung angefordert und uns bei der Ausführung auf MSBuild verlassen hatten.

Jetzt, da wir mehr Kontrolle über die Situation hatten, änderte sich der Mechanismus ein wenig:

  • Richten Sie die für die Evaluierung von .NET Core-Projekten erforderliche Umgebung ein.
  • Bewertung:
    • Starten Sie die Evaluierung mit .targets / .props-Dateien aus unserem Toolset.
    • Setzen Sie die Auswertung mit externen Dateien fort.

Diese Sequenz legt nahe, dass die Einrichtung der Umgebung zwei Hauptziele verfolgt:

  • Initiieren Sie die Evaluierung mithilfe von .targets / .props-Dateien aus unserem Toolset.
  • Leiten Sie alle nachfolgenden Vorgänge in externe .targets / .props-Dateien um.

Eine spezielle Bibliothek Microsoft.DotNet.MSBuildSdkResolver wird verwendet, um die erforderlichen .targets / .props-Dateien nachzuschlagen. Um die Einrichtung der Umgebung mithilfe von Dateien aus unserem Toolset zu initiieren, haben wir eine spezielle Umgebungsvariable verwendet, die von dieser Bibliothek verwendet wird, damit wir auf die Quelle verweisen können, aus der die erforderlichen Dateien importiert werden sollen (dh unser Toolset). Da die Bibliothek in unserer Distribution enthalten ist, besteht kein Risiko eines plötzlichen Logikfehlers.

Jetzt haben wir zuerst die Sdk-Dateien aus unserem Toolset importiert, und da wir sie jetzt leicht ändern können, steuern wir den Rest der Auswertungslogik vollständig. Das heißt, wir können jetzt entscheiden, welche Dateien und von welchem ​​Speicherort importiert werden sollen. Gleiches gilt für die oben genannten Microsoft.Common.props. Wir importieren diese und andere Basisdateien aus unserem Toolset, damit wir uns nicht um deren Existenz oder Inhalt kümmern müssen.

Sobald alle erforderlichen Importe abgeschlossen und die Eigenschaften festgelegt sind, übergeben wir die Kontrolle über den Evaluierungsprozess an das eigentliche .NET Core SDK, wo alle verbleibenden erforderlichen Vorgänge ausgeführt werden.

Fazit


Die Unterstützung von Visual Studio 2019 war aus mehreren Gründen im Allgemeinen einfacher als die Unterstützung von Visual Studio 2017. Erstens hat Microsoft nicht so viele Dinge geändert wie beim Aktualisieren von Visual Studio 2015 auf Visual Studio 2017. Ja, sie haben das Basistoolset geändert und Visual Studio-Plugins gezwungen, in den asynchronen Lademodus zu wechseln, aber diese Änderung war nicht der Fall das drastisch. Zweitens hatten wir bereits eine vorgefertigte Lösung mit unserem eigenen Toolset und Projektbewertungsmechanismus, und wir mussten einfach nicht alles von Grund auf neu aufarbeiten - sondern nur auf dem aufbauen, was wir bereits hatten. Der relativ schmerzlose Prozess der Unterstützung der Analyse von .NET Core-Projekten unter neuen Bedingungen (und auf Computern ohne installierte Visual Studio-Kopien) durch Erweiterung unseres Projektevaluierungssystems gibt uns auch Hoffnung, dass wir die richtige Wahl getroffen haben, indem wir einen Teil der Kontrolle übernommen haben unsere Hände.

Aber ich möchte die im vorherigen Artikel kommunizierte Idee wiederholen: Manchmal ist die Verwendung von vorgefertigten Lösungen nicht so einfach, wie es scheint.

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


All Articles