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) {
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[] { ';', ',' } );
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:
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.