Die Unterstützung für Visual Studio 2019 in PVS-Studio betraf sofort mehrere verschiedene Komponenten: das IDE-Plug-In selbst, die Befehlszeilenanalyseanwendung, C ++ - und C # -Analysatoren sowie mehrere Dienstprogramme. Ich werde kurz darauf eingehen, auf welche Probleme wir bei der Unterstützung der neuen Version der IDE gestoßen sind und wie sie gelöst werden können.
Bevor Sie beginnen, möchte ich noch einmal auf die Geschichte der Unterstützung früherer Versionen von Visual Studio zurückblicken, um ein besseres Verständnis unserer Vision der Aufgabe und der in bestimmten Situationen getroffenen Entscheidungen zu erhalten.
Beginnend mit der ersten Version des PVS-Studio-Analysators, in der das Plug-In für die Visual Studio-Umgebung erschien (damals war es die Version von Visual Studio 2005), war die Unterstützung neuer Versionen von Visual Studio für uns eine recht einfache Aufgabe - im Grunde ging es darum, die Plug-In-Projektdatei und zu aktualisieren Abhängigkeiten verschiedener Visual Studio-Erweiterungs-APIs. Manchmal war es notwendig, zusätzlich neue Funktionen der C ++ - Sprache zu unterstützen, die der Visual C ++ - Compiler nach und nach lernte, aber dies verursachte normalerweise auch keine Probleme unmittelbar vor der Veröffentlichung der nächsten Ausgabe von Visual Studio. Und damals gab es in PVS-Studio nur einen Analysator - für die Sprachen C und C ++.
Mit der Veröffentlichung von Visual Studio 2017 hat sich alles geändert. Zusätzlich zu der Tatsache, dass sich viele der Erweiterungs-APIs für diese IDE in dieser Version erheblich geändert haben, hatten wir nach dem Update Probleme, die Abwärtskompatibilität der Arbeit des neuen C # -Analysators sicherzustellen, der zu diesem Zeitpunkt erschienen war (sowie unserer neuen C ++ - Schicht Analyzer für MSBuild-Projekte) mit älteren Versionen von MSBuild \ Visual Studio.
Bevor Sie diesen Artikel lesen, empfehle ich Ihnen daher dringend, den entsprechenden Artikel über die Unterstützung von Visual Studio 2017 zu lesen: "Unterstützung von
Visual Studio 2017 und Roslyn 2.0 in PVS-Studio: Manchmal ist die Verwendung vorgefertigter Lösungen nicht so einfach, wie es auf den ersten Blick scheint ." Der oben erwähnte Artikel beschreibt die Probleme, auf die wir beim letzten Mal gestoßen sind, sowie die Interaktionsschemata verschiedener Komponenten (z. B. PVS-Studio, MSBuild, Roslyn). Das Verständnis dieser Interaktion ist hilfreich, wenn Sie diesen Artikel lesen.
Letztendlich brachte die Lösung dieser Probleme erhebliche Änderungen an unserem Analysator mit sich, und wie wir gehofft hatten, werden die neuen Ansätze, die wir dann angewendet haben, es ermöglichen, aktualisierte Versionen von Visual Studio \ MSBuild in Zukunft viel einfacher und schneller zu unterstützen. Zum Teil wurde diese Annahme bereits durch die Veröffentlichung zahlreicher Updates für Visual Studio 2017 bestätigt. Hat dieser neue Ansatz bei der Unterstützung von Visual Studio 2019 geholfen? Darüber unten.
PVS-Studio Plugin für Visual Studio 2019
Alles begann, wie es scheint, nicht schlecht. Es war einfach genug, das Plug-In auf Visual Studio 2019 zu portieren, wo es gestartet wurde und einwandfrei funktionierte. Trotzdem wurden sofort 2 Probleme aufgedeckt, die zukünftige Probleme versprachen.
Die erste ist die
IVsSolutionWorkspaceService- Schnittstelle, die zur Unterstützung des Lightweight Solution Load-Modus verwendet wird, der übrigens in einem der vorherigen Updates in Visual Studio 2017 deaktiviert wurde und mit dem Attribut "
Veraltet" versehen wurde , das nur eine Warnung während der Montage war, aber in Zukunft mehr versprach Probleme. Microsoft hat diesen Modus schnell eingeführt und aufgegeben ... Wir haben dieses Problem ganz einfach gelöst und uns geweigert, die entsprechende Benutzeroberfläche zu verwenden.
Zweitens: Beim Laden von Visual Studio mit dem Plugin wurde die folgende Meldung angezeigt:
Visual Studio hat eine oder mehrere Erweiterungen erkannt, die gefährdet sind oder in einem Feature-VS-Update nicht funktionieren.Beim Anzeigen der Visual Studio-Startprotokolle (ActivityLog-Datei) wurde schließlich das 'i' gepunktet:
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 eines: Ändern der Art und Weise, wie das Plug-In in den asynchronen Modus geladen wird. Ich hoffe, Sie werden nicht verärgert sein, wenn ich Sie nicht mit Details zur Interaktion mit COM-Schnittstellen von Visual Studio überlade, und ich werde die Änderungen kurz genug durchgehen.
Microsoft hat einen Artikel zum Erstellen asynchron geladener Plugins veröffentlicht: "
Gewusst wie: Verwenden von AsyncPackage zum Laden von VSPackages im Hintergrund ". Gleichzeitig war allen klar, dass sich die Angelegenheit nicht auf diese Änderungen beschränken würde.
Eine der wichtigsten Änderungen ist die Methode zum Laden bzw. Initialisieren. Zuvor erfolgte die erforderliche Initialisierung in zwei Methoden - der überschriebenen
Initialisierungsmethode unserer
Paketvererbungsklasse und der
OnShellPropertyChange- Methode. Die Notwendigkeit, einen Teil der Logik in die
OnShellPropertyChange- Methode zu übertragen, beruht auf der Tatsache, dass Visual Studio beim synchronen Laden des Plug-
Ins möglicherweise noch nicht vollständig geladen und initialisiert ist und daher in der Phase der Plug-In-Initialisierung möglicherweise nicht alle erforderlichen Aktionen ausgeführt werden konnten. Eine Option zur Lösung dieses Problems besteht darin, darauf zu warten, dass Visual Studio den Status "Zombie" beendet und diese Aktionen verzögert. Dies ist die Logik und wurde in
OnShellPropertyChange mit einer Überprüfung des Zombie-Status gerendert.
In der abstrakten
AsyncPackage- Klasse, von der asynchron geladene Plugins geerbt werden, verfügt die
Initialize- Methode über einen
versiegelten Modifikator. Daher muss die
Initialisierung in der überschriebenen
InitializeAsync- Methode erfolgen, die durchgeführt wurde. Wir mussten auch die Logik ändern, indem wir den 'Zombie'-Status von Visual Studio verfolgten, da wir diese Informationen nicht mehr im Plugin erhielten. Eine Reihe von Aktionen, die nach der Initialisierung des Plugins ausgeführt werden mussten, gingen jedoch nicht verloren. Die
Lösung bestand darin, die
OnPackageLoaded- Methode der
IVsPackageLoadEvents- Schnittstelle zu verwenden, mit der Aktionen ausgeführt wurden, die eine verzögerte Ausführung erforderten.
Ein weiteres Problem, das sich logischerweise aus dem asynchronen Laden des Plugins ergibt, ist das Fehlen von PVS-Studio-Plugin-Befehlen zum Zeitpunkt des Starts von Visual Studio. Wenn Sie das Analyseprotokoll durch Doppelklicken im Dateimanager öffnen (falls Sie es über Visual Studio öffnen müssen), wurde die erforderliche Version von devenv.exe mit dem Befehl zum Öffnen des Analyseberichts gestartet. 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 einen in Visual Studio registrierten Befehl aufzurufen. Dieser Ansatz funktionierte nun nicht mehr, da die Befehle erst verfügbar waren, als das Plug-In heruntergeladen wurde. Infolgedessen musste ich nach dem Laden des Plugins und bei einer Zeichenfolgendarstellung des Befehls zum Öffnen des Protokolls auf einer „Krücke“ anhalten, um die Startzeile von devenv.exe zu analysieren und das Protokoll tatsächlich zu laden. In diesem Fall war es daher möglich, die erforderliche Funktionalität beizubehalten, indem das Laden des Protokolls verzögert wurde, bis das Plug-In vollständig geladen ist, nachdem es sich geweigert hatte, die „richtige“ Schnittstelle für die Arbeit mit Befehlen zu verwenden.
Fuh, es scheint geklärt zu sein und alles funktioniert - alles wird richtig geladen und geöffnet, es gibt keine Warnungen - endlich.
Und dann passiert das Unerwartete - Pavel (Hallo!) Installiert ein Plug-In, woraufhin er fragt, warum wir nicht asynchron geladen haben?
Zu sagen, dass wir überrascht waren - ganz zu schweigen - wie? Nein, wirklich - hier ist die neue Version des Plugins installiert, hier ist die Meldung, dass das Paket synchron heruntergeladen werden kann. Wir installieren mit Alexander (und hallo auch Ihnen) die gleiche Version des Plugins auf unseren Maschinen - alles ist in Ordnung. Nichts ist klar - wir haben uns entschlossen zu sehen, welche Versionen der PVS-Studio-Bibliotheken in Visual Studio geladen wurden. Und plötzlich stellt sich heraus, dass die Versionen der PVS-Studio-Bibliotheken für Visual Studio 2017 verwendet werden, obwohl die richtige Version der Bibliotheken im VSIX-Paket enthalten ist - für Visual Studio 2019.
Nachdem ich an VSIXInstaller herumgebastelt hatte, gelang es mir, die Ursache des Problems zu finden - den Paket-Cache. Die Theorie wurde auch durch die Tatsache bestätigt, dass VSIXInstaller beim Einschränken der Zugriffsrechte auf das Paket im Cache (C: \ ProgramData \ Microsoft \ VisualStudio \ Packages) Fehlerinformationen in das Protokoll schrieb. Wenn kein Fehler vorliegt, werden überraschenderweise keine Informationen darüber, dass das Paket aus dem Cache installiert wurde, nicht in das Protokoll geschrieben.
Hinweis Als er das Verhalten von VSIXInstaller und verwandten Bibliotheken untersuchte, stellte er fest, dass es sehr cool ist, dass Roslyn und MSBuild Open Source-Code haben, der das Lesen, Debuggen und Verfolgen der Arbeitslogik erleichtert.
Infolgedessen geschah Folgendes: Bei der Installation des Plugins stellte VSIXInstaller fest, dass sich das entsprechende Paket bereits im Cache befand (es gab ein .vsix-Paket für Visual Studio 2017), und verwendete es während der Installation anstelle des tatsächlich installierten Pakets. Warum dies die in .vsixmanifest beschriebenen Einschränkungen / Anforderungen nicht berücksichtigt (z. B. die Version von Visual Studio, für die Sie die Erweiterung installieren können), ist eine offene Frage. Aus diesem Grund stellte sich heraus, dass das für Visual Studio 2017 entwickelte Plugin auf Visual Studio 2019 installiert wurde, obwohl .vsixmanifest die erforderlichen Einschränkungen enthielt.
Das Schlimmste ist, dass eine solche Installation das Abhängigkeitsdiagramm von Visual Studio gebrochen hat, und obwohl es äußerlich sogar so aussieht, als ob die Entwicklungsumgebung einwandfrei funktioniert, war tatsächlich alles sehr schlecht. Es war unmöglich, Erweiterungen zu installieren und zu deinstallieren, Updates vorzunehmen usw. Der Prozess der "Genesung" war auch ziemlich unangenehm, weil Es war erforderlich, die Erweiterung (die entsprechenden Dateien) zu löschen und die Konfigurationsdateien, in denen Informationen zum installierten Paket gespeichert sind, manuell zu bearbeiten. Im Allgemeinen ist es nicht angenehm genug.
Um dieses Problem zu lösen und ähnliche Situationen in Zukunft zu vermeiden, wurde beschlossen, eine GUID für das neue Paket zu erstellen, um die Pakete Visual Studio 2017 und Visual Studio 2019 genau zu trennen (bei älteren Paketen gibt es kein solches Problem, und sie verwendeten immer eine gemeinsame GUID).
Und da es sich um unangenehme Überraschungen handelte, möchte ich noch eines erwähnen: Nach dem Update auf Vorschau 2 wurde der Menüpunkt unter der Registerkarte "Erweiterungen" verschoben. Es scheint in Ordnung zu sein, aber der Zugriff auf die Funktionen des Plugins ist weniger bequem geworden. In nachfolgenden Versionen von Visual Studio 2019, einschließlich der Release-Version, wurde dieses Verhalten beibehalten. Ich habe zum Zeitpunkt der Veröffentlichung in der Dokumentation oder im Blog keine Erwähnung dieses "Features" gefunden.
Nun scheint alles zu funktionieren und mit der Plug-In-Unterstützung für Visual Studio 2019 ist fertig. Am Tag nach der Veröffentlichung von PVS-Studio 7.02 mit Unterstützung für Visual Studio 2019 stellte sich heraus, dass dies nicht der Fall war - ein weiteres Problem mit dem asynchronen Plug-In wurde gefunden. Für den Benutzer könnte dies folgendermaßen aussehen: Beim Öffnen eines Fensters mit den Ergebnissen der Analyse (oder beim Starten der Analyse) wurde unser Fenster manchmal als „leer“ angezeigt - es enthielt keinen Inhalt: Schaltflächen, eine Tabelle mit Warnungen des Analysators usw.
Tatsächlich wurde dieses Problem im Laufe der Arbeit manchmal wiederholt. Es wurde jedoch nur auf einem Computer wiederholt und erst nach dem Update von Visual Studio in einer der ersten Versionen von 'Preview' angezeigt. Es bestand der Verdacht, dass während der Installation / Aktualisierung ein Fehler aufgetreten war. Im Laufe der Zeit wiederholte sich das Problem jedoch auch auf dieser Maschine nicht mehr, und wir entschieden, dass es "von selbst repariert" wurde. Es stellte sich heraus, dass nein - nur so viel Glück. Genauer gesagt, kein Glück.
Es stellte sich heraus, dass die Angelegenheit in der Reihenfolge der Initialisierung des Umgebungsfensters selbst (der Nachkomme der
ToolWindowPane- Klasse) und seines Inhalts (in der Tat unserer Steuerung mit dem Raster und den Schaltflächen) lag. Unter bestimmten Bedingungen erfolgte die Initialisierung des Steuerelements vor der Initialisierung des Bereichs. Trotz der Tatsache, dass alles fehlerfrei funktionierte,
funktionierte die
FindToolWindowAsync- Methode (Erstellen eines Fensters beim ersten Aufruf) ordnungsgemäß, das Steuerelement blieb jedoch unsichtbar. Wir haben dies behoben, indem wir dem Fensterfüllcode eine verzögerte Initialisierung für unser Steuerelement hinzugefügt haben.
Unterstützt C # 8.0
Die Verwendung von Roslyn als Basis für den Analysator hat einen erheblichen Vorteil: Es ist nicht erforderlich, neue Sprachkonstrukte manuell zu verwalten. All dies wird im Rahmen der Microsoft.CodeAnalysis-Bibliotheken unterstützt und implementiert - wir verwenden vorgefertigte Ergebnisse. Daher wird die Unterstützung für die neue Syntax durch Aktualisieren der Bibliotheken implementiert.
In Bezug auf die statische Analyse müssen Sie hier natürlich bereits alles selbst tun, insbesondere um neue Sprachkonstrukte zu verarbeiten. Ja, wir erhalten den neuen Syntaxbaum automatisch mithilfe der neueren Version von Roslyn, aber wir müssen dem Analysator beibringen, wie neue / geänderte Knoten des Baums wahrgenommen und verarbeitet werden.
Ich denke, die am meisten diskutierte Innovation in C # 8 sind die nullbaren Referenztypen. Ich werde hier nicht darüber schreiben - dies ist ein ziemlich großes Thema, das eines separaten Artikels würdig ist (der bereits geschrieben wird). Im Allgemeinen haben wir uns bisher darauf festgelegt, nullfähige Anmerkungen in unserem Datenflussmechanismus zu ignorieren (d. H. Wir verstehen, analysieren und überspringen sie). Tatsache ist, dass Sie trotz des nicht nullbaren Referenztyps der Variablen ganz einfach (oder aus Versehen)
null darauf schreiben können, was zu NRE führen kann, wenn der entsprechende Link dereferenziert wird. In diesem Fall kann unser Analysator trotz des nicht nullbaren Referenztyps der Variablen einen ähnlichen Fehler erkennen und eine Warnung zur Verwendung einer potenziell Nullreferenz geben (natürlich, wenn eine solche Zuordnung im Code angezeigt wird).
Ich möchte darauf hinweisen, dass die Verwendung von nullbaren Referenztypen und der zugehörigen Syntax die Möglichkeit eröffnet, sehr interessanten Code zu schreiben. Für uns selbst nannten wir dies die "emotionale Syntax". Der folgende Code lässt sich recht gut kompilieren:
obj.Calculate(); obj?.Calculate(); obj.Calculate(); obj!?.Calculate(); obj!!!.Calculate();
Übrigens habe ich im Laufe meiner Arbeit einige Möglichkeiten gefunden, Visual Studio mit der neuen Syntax zu "füllen". Tatsache ist, dass Sie die Anzahl der Zeichen nicht auf eins beschränken können, wenn Sie '!' Geben. Das heißt, Sie können nicht nur einen Code des Formulars schreiben:
object temp = null!
aber auch:
object temp = null!!!;
Sie können pervers sein, weitermachen und so schreiben:
object temp = null!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;
Dieser Code wird erfolgreich kompiliert. Wenn Sie jedoch mithilfe des Syntax Visualizer vom .NET Compiler Platform SDK Informationen zum Syntaxbaum anfordern, stürzt Visual Studio ab.
Informationen zum Problem erhalten Sie in der Ereignisanzeige:
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 weiter gehen und die Anzahl der Ausrufezeichen mehrmals erhöhen, fällt Visual Studio von selbst aus - die Hilfe von Syntax Visualizer wird nicht mehr benötigt. Die Microsoft.CodeAnalysis-Bibliotheken und der csc.exe-Compiler verarbeiten diesen Code ebenfalls nicht.
Natürlich sind dies synthetische Beispiele, aber diese Tatsache kam mir trotzdem komisch vor.
Toolset
Hinweis Ich stehe erneut vor dem Problem, das Wort "Bewertung" im Rahmen eines Gesprächs über MSBuild-Projekte zu übersetzen. Die Übersetzung, die in ihrer Bedeutung am nächsten zu sein schien und gleichzeitig normal klang, lautete „ein Projektmodell erstellen“. Wenn Sie alternative Übersetzungsoptionen haben - Sie können mir schreiben, es wird interessant sein zu lesen.
Es war offensichtlich, dass das Aktualisieren des Toolset die zeitaufwändigste Aufgabe sein würde. Genauer gesagt schien es von Anfang an so, aber jetzt neige ich dazu zu glauben, dass die Plugin-Unterstützung am problematischsten war. Dies war insbesondere auf das bereits vorhandene Toolset und den Mechanismus zum Erstellen des MSBuild-Projektmodells zurückzuführen, das jetzt erfolgreich funktionierte, obwohl es erweitert werden musste. Keine Notwendigkeit, Algorithmen von Grund auf neu zu schreiben, vereinfachte die Aufgabe erheblich. Unsere Wette auf „unser“ Toolset, die zum Zeitpunkt der Unterstützung von Visual Studio 2017 abgeschlossen wurde, war erneut gerechtfertigt.
Traditionell beginnt alles mit der Aktualisierung von NuGet-Paketen. Auf der Registerkarte NuGet-Paketverwaltung für Lösungen befindet sich eine Schaltfläche "Aktualisieren", die nicht funktioniert. Beim Aktualisieren aller Pakete traten Konflikte mit mehreren Versionen auf, und die Lösung aller Pakete schien nicht sehr korrekt zu sein. Eine schmerzhaftere, aber anscheinend zuverlässigere Methode besteht darin, die Microsoft.Build / Microsoft.CodeAnalysis-Zielpakete Stück für Stück zu aktualisieren.
Einer der Unterschiede wurde sofort durch Tests von Diagnoseregeln festgestellt - die Struktur des Syntaxbaums für einen bereits vorhandenen Knoten hat sich geändert. Es ist okay, schnell korrigiert.
Ich möchte Sie daran erinnern, dass wir während der Arbeit Analysegeräte (C #, C ++, Java) an Open Source-Projekten testen. Auf diese Weise können Sie Diagnoseregeln gut testen - beispielsweise falsch positive Ergebnisse finden oder sich ein Bild davon machen, welche anderen Fälle nicht berücksichtigt wurden (reduzieren Sie die Anzahl falsch negativer Ergebnisse). Diese Tests helfen auch dabei, mögliche Regressionen in der Anfangsphase der Aktualisierung von Bibliotheken / Toolset zu verfolgen. Und diesmal war dies keine Ausnahme, da eine Reihe von Problemen auftauchten.
Ein Problem war die Verschlechterung des Verhaltens in den CodeAnalysis-Bibliotheken. Insbesondere bei einer Reihe von Projekten im Bibliothekscode traten bei verschiedenen Vorgängen Ausnahmen auf - Abrufen semantischer Informationen, Öffnen von Projekten usw.
Aufmerksame Leser des Artikels über die Unterstützung von Visual Studio 2017 denken daran, dass unser Distributionskit einen Stub enthält - die Datei MSBuild.exe hat eine Größe von 0 Byte.
Diesmal musste ich noch weiter gehen - jetzt enthält das Distribution Kit auch leere Compiler-Stubs - csc.exe, vbc.exe, VBCSCompiler.exe. Warum? Der Weg dahin begann mit der Analyse eines der Projekte in der Testbasis, bei denen die „Unterschiede“ der Berichte auftraten - bei Verwendung der neuen Version des Analysators fehlten einige Warnungen.
Das Problem stellte sich als bedingte Kompilierungssymbole heraus. Bei der Analyse eines Projekts mit der neuen Version des Analysators wurden einige der Symbole falsch extrahiert. Um besser zu verstehen, was dieses Problem verursachte, musste ich in die Roslyn-Bibliotheken eintauchen.
Verwenden Sie zum
Analysieren von bedingten Kompilierungszeichen die
GetDefineConstantsSwitch- Methode der
Csc- Klasse aus der
Microsoft.Build.Tasks.CodeAnalysis- Bibliothek. Das Parsen wird mit der
String.Split- Methode für eine Reihe von Trennzeichen durchgeführt:
string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' });
Diese Analysemethode funktioniert einwandfrei. Alle erforderlichen bedingten Kompilierungssymbole werden erfolgreich extrahiert. Weiter graben.
Der nächste wichtige Punkt ist der Aufruf der
ComputePathToTool- Methode der
ToolTask- Klasse. Diese Methode erstellt den Pfad zur ausführbaren Datei (
csc.exe ) und überprüft deren Vorhandensein. Wenn die Datei vorhanden ist, wird der Pfad zu ihr zurückgegeben, andernfalls wird
null zurückgegeben.
Anrufercode:
.... string pathToTool = ComputePathToTool(); if (pathToTool == null) {
Da es keine
csc.exe- Datei gibt (es scheint - warum brauchen wir sie?),
Ist PathToTool zu diesem Zeitpunkt
null und die aktuelle Methode (
ToolTask.Execute ) schließt ihre Ausführung mit dem Ergebnis
false ab . Infolgedessen werden die Ergebnisse der Aufgabe, einschließlich der resultierenden bedingten Kompilierungssymbole, ignoriert.
Mal sehen, was passiert, wenn Sie die Datei
csc.exe am erwarteten Speicherort
ablegen .
In diesem Fall gibt
pathToTool den tatsächlichen Speicherort der vorhandenen Datei an und die Ausführung der
ToolTask.Execute- Methode wird fortgesetzt. Der nächste wichtige Punkt ist der Aufruf der
ManagedCompiler.ExecuteTool- Methode. Und es beginnt wie folgt:
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 kompilieren wir nicht wirklich). Infolgedessen überprüft die aufrufende Methode (bereits erwähnte
ToolTask.Execute ), ob der Rückkehrcode der
ExecuteTool- Methode 0 ist, und schließt in diesem
Fall ihre Ausführung mit dem Wert
true ab . Was Sie hinter
csc.exe hatten, war da - der echte Compiler oder Leo Tolstois 'Krieg und Frieden' in Textform spielt keine Rolle.
Infolgedessen ergibt sich das Hauptproblem aus der Tatsache, dass die Reihenfolge der Schritte in der folgenden Reihenfolge definiert ist:
- Überprüfen Sie die Existenz des Compilers.
- Überprüfen Sie, ob der Compiler gestartet werden muss.
nicht umgekehrt. Compiler-Stubs lösen dieses Problem erfolgreich.
Wie sind die Zeichen einer erfolgreichen Kompilierung entstanden, wenn die Datei csc.exe nicht erkannt wurde (und das Ergebnis der Aufgabe ignoriert wurde)?
Für diesen Fall gibt es eine Methode:
CSharpCommandLineParser.ParseConditionalCompilationSymbols aus der
Microsoft.CodeAnalysis.CSharp- Bibliothek. Das Parsen wird auch von der
String.Split- Methode mit einer Reihe von Trennzeichen durchgeführt:
string[] values = value.Split(new char[] { ';', ',' } );
Beachten Sie den Unterschied zu den Trennzeichen der
Csc.GetDefineConstantsSwitch- Methode? In diesem Fall ist das Leerzeichen kein Trennzeichen. Wenn bedingte Kompilierungszeichen mit einem Leerzeichen geschrieben wurden, werden sie durch diese Methode falsch analysiert.
Diese Situation trat bei problematischen Projekten auf - bedingte Kompilierungszeichen wurden mit einem Leerzeichen in sie geschrieben und erfolgreich mit
GetDefineConstantsSwitch , jedoch nicht mit
ParseConditionalCompilationSymbols analysiert .
Ein weiteres Problem, das sich nach der Aktualisierung der Bibliotheken zeigte, war die Verschlechterung des Verhaltens in einer Reihe von Fällen, insbesondere bei Projekten, die nicht erfasst wurden. Probleme traten in den Microsoft.CodeAnalysis-Bibliotheken auf und wurden in Form verschiedener Ausnahmen an uns zurückgegeben -
ArgumentNullException (einige interne Logger wurden nicht initialisiert),
NullReferenceException und andere.
Ich möchte unten auf eines dieser Probleme eingehen - es schien mir ziemlich interessant zu sein.
Dieses Problem ist beim Überprüfen der neuesten Version des Roslyn-Projekts
aufgetreten. Aus dem Code einer der Bibliotheken wurde eine
NullReferenceException ausgelöst. Aufgrund ausreichend detaillierter Informationen zum Speicherort des Problems haben wir den Problemcode schnell gefunden und uns aus Gründen des Interesses entschlossen, zu prüfen, ob das Problem bei der Arbeit in Visual Studio erneut auftritt.
Nun - es war möglich, es in Visual Studio zu reproduzieren (das Experiment wurde mit Visual Studio 16.0.3 durchgeführt). Dazu benötigen wir eine Klassendefinition der folgenden Form:
class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } }
Wir benötigen außerdem Syntax Visualizer (Teil des .NET Compiler Platform SDK). Es ist erforderlich,
TypeSymbol (Menüpunkt "View TypeSymbol (falls vorhanden)") vom Knoten des Syntaxbaums vom Typ
ConstantPatternSyntax (
null ) anzufordern. Danach wird Visual Studio neu gestartet und in der Ereignisanzeige können Sie Informationen zum Problem anzeigen, insbesondere den Stack-Trace:
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, liegt die Ursache des Problems in der Dereferenzierung der Nullreferenz.
Wie bereits erwähnt, ist beim Testen des Analysators das gleiche Problem aufgetreten. Wenn Sie die Microsoft.CodeAnalysis-Debugbibliotheken zum Erstellen des Analysators verwenden, können Sie durch Debuggen an die genaue Stelle gelangen, indem Sie
TypeSymbol vom gewünschten Knoten im Syntaxbaum anfordern.
Als Ergebnis gelangen wir zu der
ClassifyImplicitBuiltInConversionSlow- Methode, 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; }
Das Problem ist, dass der Zielparameter in diesem Fall
null ist. Dementsprechend wird beim Aufrufen von
destination.SpecialType eine NullReferenceException ausgelöst .
Ja, Debug.Assert ist höher als die Dereferenzierung , aber dies reicht nicht aus, da es tatsächlich vor nichts schützt - es hilft nur, das Problem in Debug-Versionen von Bibliotheken zu identifizieren. Oder hilft nicht.Änderungen beim Erstellen eines Modells von C ++ - Projekten
Hier passierte nichts besonders Interessantes - die alten Algorithmen erforderten keine wesentlichen Modifikationen, über die es interessant wäre zu sprechen. Es gab vielleicht zwei Punkte, über die es sinnvoll ist, nachzudenken.Zuerst mussten wir die Algorithmen ändern, die auf dem Wert von ToolsVersion beruhen, um im numerischen Format geschrieben zu werden. Ohne auf Details einzugehen - es gibt mehrere Fälle, in denen Sie Toolsets vergleichen und beispielsweise eine aktuellere neue Version auswählen müssen. Diese Version hatte jeweils einen höheren numerischen Wert. Es gab eine Berechnung, dass ToolsVersion, das der neuen Version von MSBuild / Visual Studio entspricht, 16.0 sein würde. Wie auch immer ... Aus Gründen des Interesses zitiere ich eine Tabelle darüber, wie sich die Werte verschiedener Eigenschaften in verschiedenen Versionen von Visual Studio geändert haben:Der Witz ist natürlich veraltet, aber Sie müssen sich an das Ändern von Windows- und Xbox-Versionen erinnern, um zu verstehen, dass die Vorhersage zukünftiger Werte (unabhängig von Name und Version) im Fall von Microsoft eine wackelige Sache ist. :) :)
Die Lösung war einfach genug: Einführung der Priorisierung von Toolsets (Zuweisung einer separaten Prioritätseinheit).Der zweite Punkt sind Probleme beim Arbeiten in Visual Studio 2017 oder in einer angrenzenden Umgebung (z. B. das Vorhandensein der Umgebungsvariablen VisualStudioVersion ). Tatsache ist, dass die Berechnung der zum Erstellen eines Modells eines C ++ - Projekts erforderlichen Parameter viel komplizierter ist als das Erstellen eines Modells eines .NET-Projekts. Im Fall von .NET verwenden wir unser eigenes Toolset und den entsprechenden Wert von ToolsVersion. Im Fall von C ++ können wir sowohl auf unseren eigenen als auch auf vorhandenen Toolsets im System aufbauen. Beginnend mit Build - Tool als Teil von Visual Studio 2017 toolset'y in der Datei vorgeschriebenen MSBuild.exe.config, nicht in der Registrierung. Dementsprechend können wir sie nicht aus der allgemeinen Liste der Toolsets abrufen (z. B. über Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets ), im Gegensatz zu den in der Registrierung aufgezeichneten Toolsets (entsprechend <= Visual Studio 2015). .Infolge des Vorstehenden funktioniert es nicht, ein Projektmodell mit ToolsVersion 15.0 zu erstellen , da das System das erforderliche Toolset nicht sieht. Das aktuellste Toolset - Aktuell- Es wird zur gleichen Zeit verfügbar sein, da dies unser eigenes Toolset ist. Daher gibt es für Visual Studio 2019 kein solches Problem. Die Lösung erwies sich als einfach und ermöglichte es, das Problem zu lösen, ohne die vorhandenen Algorithmen für die Erstellung des Projektmodells zu ändern. Zusätzlich zu Current wurde der Liste der eigenen Toolsets ein weiteres hinzugefügt - 15.0 .Änderungen beim Erstellen eines Modells von C # .NET Core-Projekten
Im Rahmen dieser Aufgabe wurden 2 Probleme gleichzeitig gelöst, da sich herausstellte, dass sie miteinander zusammenhängen:- Nach dem Hinzufügen des Toolset "Aktuell" funktionierte die Analyse von .NET Core-Projekten für Visual Studio 2017 nicht mehr.
- Die Analyse von .NET Core-Projekten auf einem System, auf dem mindestens eine Version von Visual Studio nicht installiert war, funktionierte nicht.
Das Problem war in beiden Fällen das gleiche - einige der grundlegenden .targets / .props-Dateien wurden falsch durchsucht. Dies führte dazu, dass mit unserem Toolset kein Projektmodell erstellt werden konnte.In Abwesenheit von Visual Studio konnte ein solcher Fehler angezeigt werden (mit der vorherigen Version von toolset'a - 15.0 ): The imported project "C:\Windows\Microsoft.NET\Framework64\ 15.0\Microsoft.Common.props" was not found.
Beim Erstellen des C # .NET Core-Modells des Projekts in Visual Studio 2017 kann das folgende Problem auftreten (mit der aktuellen Version des Toolset " Aktuell" ): The imported project "C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\MSBuild\Current\Microsoft.Common.props" was not found. ....
Da die Probleme ähnlich sind (aber es sieht so aus), können Sie versuchen, zwei Fliegen mit einer Klappe zu schlagen.Im Folgenden beschreibe ich, wie dieses Problem gelöst wurde, ohne auf technische Details einzugehen. Genau diese Details (zum Erstellen von Modellen für C # .NET Core-Projekte sowie zum Ändern der Konstruktion von Modellen in unserem Toolset) warten in einem unserer zukünftigen Artikel. Übrigens, wenn Sie den obigen Text sorgfältig lesen, werden Sie möglicherweise feststellen, dass dies der zweite Verweis auf zukünftige Artikel ist. :) :)
Wie haben wir dieses Problem gelöst? Die Lösung bestand darin, unser eigenes Toolset auf Kosten der wichtigsten .targets / .props-Dateien aus dem .NET Core SDK ( Sdk.props , Sdk.targets ) zu erweitern. Dies ermöglichte uns mehr Kontrolle über die Situation, mehr Flexibilität bei der Verwaltung von Importen sowie bei der Erstellung eines Modells für .NET Core-Projekte im Allgemeinen. Ja, unser Toolset ist wieder etwas gewachsen, und wir mussten auch einige Logik hinzufügen, um die für die Erstellung des .NET Core-Modells erforderlichen Umgebungsprojekte einzurichten, aber es hat sich anscheinend gelohnt.Bisher war das Arbeitsprinzip beim Erstellen eines Modells von .NET Core-Projekten wie folgt: Wir haben diese Konstruktion einfach angefordert, und dann hat alles auf Kosten von MSBuild funktioniert.Wenn wir nun mehr Kontrolle selbst in die Hand genommen haben, sieht es etwas anders aus:- Vorbereitung der Umgebung, die zum Erstellen eines Modells von .NET Core-Projekten erforderlich ist;
- Modellbau:
- Baubeginn mit .targets / .props-Dateien aus unserem Toolset'a;
- Fortsetzung der Konstruktion mit externen Dateien.
Aus den oben beschriebenen Schritten geht hervor, dass das Einstellen der erforderlichen Umgebung zwei Hauptziele hat:- Initiieren Sie die Modellbildung mit .targets / .props-Dateien aus Ihrem eigenen Toolset.
- Leiten Sie weitere Vorgänge in externe .targets / .props-Dateien um.
Zum Suchen nach .targets / .props-Dateien, die zum Erstellen eines Modells von .NET Core-Projekten erforderlich sind, wird eine spezielle Bibliothek verwendet - Microsoft.DotNet.MSBuildSdkResolver. Die Initiierung des Erstellens mithilfe von Dateien aus unserem Toolset wurde mithilfe einer speziellen Umgebungsvariablen gelöst, die von dieser Bibliothek verwendet wird. Wir empfehlen, die erforderlichen Dateien (aus unserem Toolset) zu importieren. Da die Bibliothek Teil unserer Distribution ist, gibt es keine Befürchtungen, dass sich die Logik plötzlich ändert und nicht mehr funktioniert.Jetzt werden Sdk-Dateien zuerst aus unserem Toolset importiert. Da wir sie leicht ändern können, liegt die Kontrolle über die weitere Logik zum Erstellen des Modells in unseren Händen. Daher können wir selbst bestimmen, welche Dateien von wo importiert werden müssen. Dies gilt auch für die oben genannten Microsoft.Common.props. Wir importieren diese und andere Basisdateien aus unserem eigenen Toolset und vertrauen auf deren Verfügbarkeit und Inhalt.Nachdem wir die erforderlichen Importe abgeschlossen und eine Reihe von Eigenschaften festgelegt haben, übertragen wir die weitere Steuerung der Modellbildung auf das eigentliche .NET Core SDK, wo die restlichen erforderlichen Aktionen ausgeführt werden.Fazit
Im Allgemeinen war die Unterstützung für Visual Studio 2019 einfacher als die Unterstützung für Visual Studio 2017, was meines Erachtens auf mehrere Faktoren zurückzuführen ist. Erstens hat Microsoft nicht so viele Dinge geändert wie zwischen Visual Studio 2015 und Visual Studio 2017. Ja, wir haben das Haupt-Toolset geändert und begonnen, Plug-Ins für Visual Studio auf Asynchronität auszurichten, aber dennoch. Die zweite - wir hatten bereits eine Lösung mit unserem eigenen Toolset und dem Erstellen von Projektmodellen - bestand darin, dass nicht alles neu erfunden werden musste. Es reichte aus, nur die vorhandene Lösung zu erweitern. Die relativ einfache Unterstützung für die Analyse von .NET Core-Projekten auf neue Bedingungen (sowie für Analysefälle auf einem Computer, auf dem keine Visual Studio-Instanzen installiert sind) aufgrund der Erweiterung unseres Systems zur Erstellung von Projektmodellen lässt auch hoffen, dass wir die richtige Wahl getroffen haben.Nachdem Sie beschlossen haben, die Kontrolle über sich selbst zu übernehmen.Trotzdem möchte ich noch einmal einen Gedanken aus dem vorherigen Artikel wiederholen - manchmal ist die Verwendung von vorgefertigten Lösungen nicht so einfach, wie es auf den ersten Blick scheint.
Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Sergey Vasiliev. Unterstützung von Visual Studio 2019 in PVS-Studio