Dies ist der zweite Artikel über die Verwendung des statischen Analysators PVS-Studio in Cloud-CI-Systemen. Dieses Mal wird die Azure DevOps-Plattform betrachtet - eine Cloud-CI \ CD-Lösung von Microsoft. Betrachten Sie diesmal ShareX als analysiertes Projekt.
Wir werden drei Komponenten benötigen. Der erste ist der statische Analysator PVS-Studio. Das zweite ist Azure DevOps, in das wir den Analysator integrieren werden. Das dritte Projekt wird überprüft, um die Funktionen von PVS-Studio bei der Arbeit in der Cloud zu demonstrieren. Also fangen wir an.
PVS-Studio ist ein statischer Code-Analysator zur Suche nach Fehlern und Sicherheitsmängeln. Führt eine Code-Analyse in C, C ++, C # und Java durch.
Azure DevOps . Die Azure DevOps-Plattform enthält Tools wie Azure Pipeline, Azure Board, Azure Artifacts und andere, um das Erstellen von Software zu beschleunigen und deren Qualität zu verbessern.
ShareX ist eine kostenlose Anwendung, mit der Sie jeden Teil des Bildschirms erfassen und aufzeichnen können. Das Projekt ist in C # geschrieben und eignet sich hervorragend zur Demonstration der Ausführung des statischen Analysators. Der Quellcode für das Projekt 
ist auf GitHub verfügbar .
Die Ausgabe des Befehls cloc für das ShareX-Projekt:
Mit anderen Worten, das Projekt ist klein, aber völlig ausreichend, um die Arbeit von PVS-Studio in Verbindung mit einer Cloud-Plattform zu demonstrieren.
Lass uns einrichten
Um in Azure DevOps zu beginnen, klicken Sie auf den 
Link und dann auf die Schaltfläche "Kostenlos mit GitHub starten".
Gewähren der Microsoft-Anwendung Zugriff auf die Daten des GitHub-Kontos.
Um die Registrierung abzuschließen, muss ein Microsoft-Konto erstellt werden.
Erstellen Sie nach der Registrierung ein Projekt:
Als nächstes müssen wir zum Abschnitt "Pipelines" - "Builds" gehen und eine neue Build-Pipeline erstellen
Auf die Frage, wo sich unser Code befindet, werden wir antworten - GitHub.
Wir autorisieren die Azure Pipelines-Anwendung und wählen das Repository mit dem Projekt aus, für das wir den Start des statischen Analysators konfigurieren möchten
Wählen Sie im Vorlagenauswahlfenster "Starter-Pipeline".
Wir können eine statische Analyse des Projektcodes auf zwei Arten durchführen: mithilfe von Microsoft-gehosteten oder selbst gehosteten Agenten.
In der ersten Version werden von Microsoft gehostete Agenten verwendet. Solche Agenten sind normale virtuelle Maschinen, die beim Starten unserer Pipeline gestartet und nach dem Ende der Aufgabe gelöscht werden. Durch die Verwendung solcher Agenten können Sie keine Zeit damit verschwenden, sie zu unterstützen und zu aktualisieren. Sie unterliegen jedoch einigen Einschränkungen, z. B. der Unmöglichkeit, zusätzliche Software zu installieren, die zum Erstellen des Projekts verwendet wird.
Ersetzen Sie unsere Standardkonfiguration durch die folgende für die Verwendung von von Microsoft gehosteten Agenten:
#    #      master- trigger: - master #         # ,   Docker-, #      Windows Server 1803 pool: vmImage: 'win1803' container: microsoft/dotnet-framework:4.7.2-sdk-windowsservercore-1803 steps: #    - task: PowerShell@2 inputs: targetType: 'inline' script: 'Invoke-WebRequest -Uri https://files.viva64.com/PVS-Studio_setup.exe -OutFile PVS-Studio_setup.exe' - task: CmdLine@2 inputs: workingDirectory: $(System.DefaultWorkingDirectory) script: | #      nuget restore .\ShareX.sln #  ,        md .\PVSTestResults #   PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /COMPONENTS=Core #        "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" credentials -u $(PVS_USERNAME) -n $(PVS_KEY) #        html. "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" -t .\ShareX.sln -o .\PVSTestResults\ShareX.plog "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" -t html -o .\PVSTestResults\ .\PVSTestResults\ShareX.plog #    - task: PublishBuildArtifacts@1 inputs: pathToPublish: PVSTestResults artifactName: PVSTestResults 
Hinweis: Gemäß der 
Dokumentation muss der verwendete Container im Image der virtuellen Maschine zwischengespeichert werden. Zum Zeitpunkt des Schreibens funktioniert dieser Artikel jedoch nicht und der Container wird bei jedem Start der Aufgabe heruntergeladen, was sich negativ auf die Ausführungszeit auswirkt.
Speichern Sie die Pipeline und erstellen Sie die Variablen, die zum Erstellen der Lizenzdatei verwendet werden. Öffnen Sie dazu das Pipeline-Bearbeitungsfenster und klicken Sie in der oberen rechten Ecke auf die Schaltfläche „Variablen“.
Fügen Sie zwei Variablen hinzu - 
PVS_USERNAME und 
PVS_KEY , die den Benutzernamen bzw. den Lizenzschlüssel enthalten. Vergessen Sie beim Erstellen der Variablen 
PVS_KEY nicht, das 
Element "Diesen Wert geheim halten" zu 
aktivieren , 
um den Variablenwert mit einem 2048-Bit-RSA-Schlüssel zu verschlüsseln und die Ausgabe des Variablenwerts an das 
Taskausführungsprotokoll zu unterdrücken.
Wir speichern die Variablen und starten die Pipeline mit der Schaltfläche "Ausführen".
Die zweite Option zum Ausführen der Analyse ist die Verwendung eines selbst gehosteten Agenten. Selbst gehostete Agenten sind Agenten, die wir selbst konfigurieren und verwalten. Solche Agenten bieten mehr Möglichkeiten für die Installation von Software, die für die Montage und das Testen unseres Softwareprodukts erforderlich ist.
Vor der Verwendung solcher Agenten müssen sie gemäß den 
Anweisungen konfiguriert und ein statischer Analysator installiert und 
konfiguriert werden .
Um die Aufgabe auf einem selbst gehosteten Agenten zu starten, ersetzen wir die vorgeschlagene Standardkonfiguration durch die folgende:
 #    #    master- trigger: - master #    self-hosted    'MyPool' pool: 'MyPool' steps: - task: CmdLine@2 inputs: workingDirectory: $(System.DefaultWorkingDirectory) script: | #      nuget restore .\ShareX.sln #  ,        md .\PVSTestResults #        html. "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" -t .\ShareX.sln -o .\PVSTestResults\ShareX.plog "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" -t html -o .\PVSTestResults\ .\PVSTestResults\ShareX.plog #    - task: PublishBuildArtifacts@1 inputs: pathToPublish: PVSTestResults artifactName: PVSTestResults 
Nach Abschluss der Aufgabe kann das Archiv mit den Analyseberichten auf der Registerkarte "Zusammenfassung" heruntergeladen werden, oder wir können die Erweiterung "E-Mail 
senden" verwenden , mit der Sie das Senden von E-Mails konfigurieren oder auf dem 
Marktplatz nach einem für uns bequemeren Tool suchen können.
Über Analyseergebnisse
Schauen wir uns nun einige der Fehler an, die im überprüften Projekt gefunden wurden - ShareX.
Redundante SchecksBeginnen wir zum Aufwärmen mit einfachen Fehlern im Code, nämlich mit redundanten Überprüfungen:
 private void PbThumbnail_MouseMove(object sender, MouseEventArgs e) { .... IDataObject dataObject = new DataObject(DataFormats.FileDrop, new string[] { Task.Info.FilePath }); if (dataObject != null) { Program.MainForm.AllowDrop = false; dragBoxFromMouseDown = Rectangle.Empty; pbThumbnail.DoDragDrop(dataObject, DragDropEffects.Copy | DragDropEffects.Move); Program.MainForm.AllowDrop = true; } .... } 
PVS-Studio- Warnung : 
V3022 [CWE-571] Der Ausdruck 'dataObject! = Null' ist immer wahr. TaskThumbnailPanel.cs 415
Achten Sie darauf, die Variable 
dataObject auf 
null zu überprüfen. Wofür ist sie hier? 
dataObject kann in diesem Fall einfach nicht 
null sein , da es mit einem Verweis auf das erstellte Objekt initialisiert wird. Infolgedessen haben wir eine redundante Überprüfung. Ist es kritisch? Nein. Sieht prägnant aus? Nein. Diese Prüfung wird deutlich besser entfernt, um den Code nicht zu überladen.
Werfen wir einen Blick auf einen anderen Code, zu dem Sie ähnliche Kommentare abgeben können:
 private static Image GetDIBImage(MemoryStream ms) { .... try { .... return new Bitmap(bmp); .... } finally { if (gcHandle != IntPtr.Zero) { GCHandle.FromIntPtr(gcHandle).Free(); } } .... } private static Image GetImageAlternative() { .... using (MemoryStream ms = dataObject.GetData(format) as MemoryStream) { if (ms != null) { try { Image img = GetDIBImage(ms); if (img != null) { return img; } } catch (Exception e) { DebugHelper.WriteException(e); } } } .... } 
PVS-Studio Warnung : 
V3022 [CWE-571] Der Ausdruck 'img! = Null' ist immer wahr. ClipboardHelpers.cs 289
Die 
GetImageAlternative- Methode überprüft erneut, ob die Variable 
img unmittelbar nach dem Erstellen einer neuen Instanz der 
Bitmap- Klasse nicht 
null ist . Der Unterschied zum vorherigen Beispiel besteht darin, dass zum Initialisieren der Variablen 
img nicht der Konstruktor, sondern die 
GetDIBImage- Methode verwendet wird. Der Autor des Codes geht davon aus, dass bei dieser Methode eine Ausnahme auftreten kann, deklariert jedoch nur 
try und blockiert 
schließlich , wobei 
catch weggelassen wird. Wenn eine Ausnahme auftritt, erhält die aufrufende Methode 
- GetImageAlternative - daher keinen Verweis auf ein Objekt vom Typ 
Bitmap, sondern muss die Ausnahme in einem eigenen 
catch-Block behandeln . In diesem Fall wird die Variable 
img nicht initialisiert und der Ausführungsthread erreicht nicht einmal die Prüfung 
img! = Null , sondern fällt sofort in den 
catch-Block . Daher zeigte der Analysator eine redundante Validierung an.
Betrachten Sie das folgende 
Warnbeispiel mit Code 
V3022 :
 private void btnCopyLink_Click(object sender, EventArgs e) { .... if (lvClipboardFormats.SelectedItems.Count == 0) { url = lvClipboardFormats.Items[0].SubItems[1].Text; } else if (lvClipboardFormats.SelectedItems.Count > 0) { url = lvClipboardFormats.SelectedItems[0].SubItems[1].Text; } .... } 
PVS-Studio Warnung : 
V3022 [CWE-571] Der Ausdruck 'lvClipboardFormats.SelectedItems.Count> 0' ist immer wahr. AfterUploadForm.cs 155
Schauen wir uns den zweiten bedingten Ausdruck an. Dort überprüfen wir den Wert der schreibgeschützten 
Count- Eigenschaft. Diese Eigenschaft zeigt die Anzahl der Elemente in einer Instanz der 
SelectedItems- Auflistung an. Die Bedingung ist nur erfüllt, wenn die 
Count- Eigenschaft größer als Null ist. Alles wäre in Ordnung, aber nur in der externen 
if-Anweisung ist der 
Count bereits überprüft. Eine Instanz der 
SelectedItems- Auflistung kann nicht die Anzahl der Elemente kleiner als Null haben. Daher nimmt 
Count einen Wert an, der entweder gleich Null oder größer als Null ist. Da wir bereits in der ersten 
if-Anweisung überprüft haben, dass 
Count Null ist und sich als falsch herausgestellt hat, ist es nicht sinnvoll, eine weitere Prüfung für den else-Zweig zu schreiben, bei dem 
Count größer als Null ist.
Das letzte Beispiel für die Fehlernummer 
V3022 ist das folgende Codefragment:
 private void DrawCursorGraphics(Graphics g) { .... int cursorOffsetX = 10, cursorOffsetY = 10, itemGap = 10, itemCount = 0; Size totalSize = Size.Empty; int magnifierPosition = 0; Bitmap magnifier = null; if (Options.ShowMagnifier) { if (itemCount > 0) totalSize.Height += itemGap; .... } .... } 
PVS-Studio Warnung : 
V3022 Der Ausdruck 'itemCount> 0' ist immer falsch. RegionCaptureForm.cs 1100.
Der Analysator hat festgestellt, dass die Bedingung 
itemCount> 0 immer falsch ist, da eine etwas höhere Deklaration durchgeführt wird und gleichzeitig die Variable 
itemCount auf Null gesetzt wird. Bis zur Bedingung wird diese Variable nirgendwo verwendet und ändert sich nicht. Daher hat der Analysator die richtige Schlussfolgerung über den bedingten Ausdruck gezogen, dessen Wert immer falsch ist.
Schauen wir uns jetzt etwas wirklich Interessantes an.
Der beste Weg, um den Fehler zu verstehen, besteht darin, den Fehler zu visualisieren.Es scheint uns, dass an dieser Stelle ein ziemlich interessanter Fehler gefunden wurde:
 public static void Pixelate(Bitmap bmp, int pixelSize) { .... float r = 0, g = 0, b = 0, a = 0; float weightedCount = 0; for (int y2 = y; y2 < yLimit; y2++) { for (int x2 = x; x2 < xLimit; x2++) { ColorBgra color = unsafeBitmap.GetPixel(x2, y2); float pixelWeight = color.Alpha / 255; r += color.Red * pixelWeight; g += color.Green * pixelWeight; b += color.Blue * pixelWeight; a += color.Alpha * pixelWeight; weightedCount += pixelWeight; } } .... ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount), (byte)(g / weightedCount), (byte)(r / weightedCount), (byte)(a / pixelCount)); .... } 
Ich möchte nicht sofort alle Karten aufdecken und zeigen, was unser Analysator hier gefunden hat. Verschieben wir diesen Moment also für eine kurze Zeit.
Anhand des Namens der Methode lässt sich leicht erraten, was sie bewirkt: Sie senden ihr ein Bild oder ein Fragment des Bildes als Eingabe und es führt seine Pixelierung durch. Der Methodencode ist ziemlich lang, daher geben wir ihn hier nicht vollständig an, sondern versuchen einfach, seinen Algorithmus zu erklären und zu erklären, welche Art von Fehler PVS-Studio hier gefunden hat.
Diese Methode akzeptiert zwei Parameter als Eingabe: ein Objekt vom Typ 
Bitmap und einen Wert vom Typ 
int , der die Größe der Pixel angibt. Der Operationsalgorithmus ist recht einfach:
1) Wir zerlegen das Fragment des am Eingang empfangenen Bildes in Quadrate mit einer Seite, die der Größe der Pixelierung entspricht. Wenn wir beispielsweise eine Pixelgröße von 15 haben, erhalten wir ein Quadrat mit 15 x 15 = 225 Pixel.
2) Als nächstes gehen wir um jedes Pixel in diesem Quadrat herum und akkumulieren die Werte der Felder 
Rot , 
Grün , 
Blau und 
Alpha in Zwischenvariablen und multiplizieren zuvor den Wert der entsprechenden Farbe und den Alphakanalwert mit der Variablen 
pixelWeight , die durch Teilen des 
Alpha- Werts durch 255 erhalten wird (die 
Alpha- Variable hat Typ 
Byte ). Außerdem durchlaufen wir beim Durchlaufen von Pixeln die in 
pixelWeight aufgezeichneten 
Werte zu einer Variablen namens 
weightedCount .
Das Code-Snippet, das die obigen Schritte ausführt, lautet wie folgt:
 ColorBgra color = unsafeBitmap.GetPixel(x2, y2); float pixelWeight = color.Alpha / 255; r += color.Red * pixelWeight; g += color.Green * pixelWeight; b += color.Blue * pixelWeight; a += color.Alpha * pixelWeight; weightedCount += pixelWeight; 
Beachten Sie übrigens, dass 
pixelWeight der Variablen 
weightedCount für dieses Pixel keinen Wert hinzufügt, wenn der Wert der 
Alpha- Variablen Null ist. Wir werden das in Zukunft brauchen.
3) Nachdem wir alle Pixel im aktuellen Quadrat umgangen haben, können wir die allgemeine „durchschnittliche“ Farbe für dieses Quadrat ermitteln. Der Code, der diese Aktionen ausführt, lautet wie folgt:
 ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount), (byte)(g / weightedCount), (byte)(r / weightedCount), (byte)(a / pixelCount)); 
4) 
Nachdem wir die endgültige Farbe haben und sie in die Variable 
durchschnittlicher Farbe 
schreiben , können wir jedes Pixel im Quadrat erneut 
umgehen und ihm einen Wert aus der 
durchschnittlichen Farbe zuweisen .
5) Wir kehren zu Schritt 2 zurück, solange noch rohe Quadrate vorhanden sind.
Wir stellen erneut fest, dass die Variable 
weightedCount nicht der Anzahl aller quadratischen Pixel entspricht. Wenn beispielsweise ein absolut transparentes Pixel im Bild auftritt (der Wert im Alphakanal ist Null), ist die Variable 
pixelWeight für dieses Pixel Null ( 
0/255 = 0). Daher trägt dieses Pixel nicht zur Bildung des Werts der Variablen 
weightedCount bei . Dies ist logisch - es macht keinen Sinn, die Farben eines absolut transparenten Pixels zu berücksichtigen.
Alles scheint ziemlich vernünftig - Pixelierung sollte korrekt funktionieren. Und es funktioniert wirklich richtig. Dies gilt nur nicht für PNG-Bilder mit Pixeln mit Werten im Alphakanal von weniger als 255 und ungleich Null. Achten Sie auf das pixelige Bild unten:
Hast du Pixelierung gesehen? Und wir sind nicht. Nun wollen wir diese kleine Intrige enthüllen und erklären, wo genau der Fehler in dieser Methode versteckt ist. Der Fehler hat sich in die Zeile zur Berechnung des Werts der 
pixelWeight- Variablen eingeschlichen:
 float pixelWeight = color.Alpha / 255; 
Tatsache ist, dass der Autor des Codes, der die Variable 
pixelWeight als float deklarierte, implizierte, dass beim Teilen des 
Alpha- Feldes durch 255 zusätzlich zu Null und Eins Bruchzahlen erhalten werden sollten. Hier liegt das Problem, da die 
Alpha- Variable vom Typ 
Byte ist und wenn wir sie durch 255 teilen, erhalten wir einen ganzzahligen Wert, und nur dann wird er implizit in 
float umgewandelt , sodass der Bruchteil verloren geht.
Die Unfähigkeit, PNG-Bilder mit einem gewissen Grad an Transparenz zu pixelisieren, ist leicht zu erklären. Da die Alpha-Kanalwerte für diese Pixel im Bereich 0 <Alpha <255 liegen, erhalten wir beim Teilen der 
Alpha- Variablen durch 255 immer 0. Daher sind auch die Werte der 
Variablen pixelWeight , 
r , 
g , 
b , 
a und 
weightedCount immer immer wird Null sein. Infolgedessen hat unsere durchschnittliche 
Farbdurchschnittsfarbe über alle Kanäle hinweg Nullwerte: Rot - 0, Blau - 0, Grün - 0, Alpha - 0. Wenn Sie das Quadrat mit dieser Farbe 
füllen, ändern wir die ursprüngliche Farbe der Pixel nicht, da die 
Durchschnittsfarbe absolut transparent ist . Um diesen Fehler zu beheben, müssen Sie nur das 
Alpha- Feld explizit in den 
Float- Typ 
umwandeln . Die korrigierte Codezeile kann folgendermaßen aussehen:
 float pixelWeight = (float)color.Alpha / 255; 
Und es ist Zeit, die Nachricht zu zitieren, die PVS-Studio an den falschen Code ausgegeben hat:
PVS-Studio Warnung : 
V3041 [CWE-682] Der Ausdruck wurde implizit vom Typ 'int' in den Typ 'float' umgewandelt. Erwägen Sie die Verwendung eines expliziten Typgusses, um den Verlust eines Bruchteils zu vermeiden. Ein Beispiel: double A = (double) (X) / Y; ImageHelpers.cs 1119.
Zum Vergleich geben wir einen Screenshot eines wirklich pixeligen Bildes, das mit einer festen Version der Anwendung erhalten wurde:
Mögliche NullReferenceException public static bool AddMetadata(Image img, int id, string text) { .... pi.Value = bytesText; if (pi != null) { img.SetPropertyItem(pi); return true; } .... } 
PVS-Studio Warnung: V3095 [CWE-476] Das ' 
pi' -Objekt wurde verwendet, bevor es gegen null verifiziert wurde. Überprüfen Sie die Zeilen: 801, 803. ImageHelpers.cs 801
Dieses Codefragment zeigt, dass der Autor erwartet hat, dass die Variable 
pi null ist, weshalb die Prüfung 
pi! = Null durchgeführt wird, bevor die 
SetPropertyItem- Methode 
aufgerufen wird. Es ist seltsam, dass vor dieser Überprüfung der Eigenschaft 
pi.Value ein Byte-Array zugewiesen wird, da bei 
pi null eine Ausnahme vom Typ 
NullReferenceException ausgelöst wird.
Eine ähnliche Situation wurde anderswo gesehen:
 private static void Task_TaskCompleted(WorkerTask task) { .... task.KeepImage = false; if (task != null) { if (task.RequestSettingUpdate) { Program.MainForm.UpdateCheckStates(); } .... } .... } 
PVS-Studio Warnung: V3095 [CWE-476] Das Objekt 'task' wurde verwendet, bevor es gegen null verifiziert wurde. Überprüfen Sie die Zeilen: 268, 270. TaskManager.cs 268
PVS-Studio hat einen weiteren ähnlichen Fehler gefunden. Die Bedeutung ist immer noch dieselbe, daher besteht keine große Notwendigkeit, ein Codefragment anzugeben. Wir beschränken uns auf die Analysator-Nachricht.
PVS-Studio Warnung: V3095 [CWE-476] Das Objekt 'Config.PhotobucketAccountInfo' wurde verwendet, bevor es gegen Null verifiziert wurde. Überprüfen Sie die Zeilen: 216, 219. UploadersConfigForm.cs 216
Der gleiche RückgabewertIn der 
EvalWindows- Methode der 
WindowsList- Klasse wurde ein verdächtiger Code entdeckt, der unter keinen Umständen 
true zurückgibt:
 public class WindowsList { public List<IntPtr> IgnoreWindows { get; set; } .... public WindowsList() { IgnoreWindows = new List<IntPtr>(); } public WindowsList(IntPtr ignoreWindow) : this() { IgnoreWindows.Add(ignoreWindow); } .... private bool EvalWindows(IntPtr hWnd, IntPtr lParam) { if (IgnoreWindows.Any(window => hWnd == window)) { return true;  
PVS-Studio Warnung: V3009 Es ist seltsam, dass diese Methode immer ein und denselben Wert von 'true' zurückgibt. WindowsList.cs 82
Es erscheint logisch, dass die Methode 
false zurückgeben sollte, wenn in der Liste ein Zeiger mit demselben Wert wie 
hWnd mit dem Namen 
IgnoreWindows gefunden wurde.
Die 
IgnoreWindows- Liste kann entweder durch Aufrufen des 
WindowsList- Konstruktors 
(IntPtr ignoreWindow) oder direkt durch Zugriff auf die Eigenschaft 
ausgefüllt werden , da sie öffentlich ist. Auf die eine oder andere Weise wird diese Liste laut Visual Studio derzeit im Code in keiner Weise ausgefüllt. Dies ist ein weiterer seltsamer Ort dieser Methode.
Unsicherer Aufruf von Ereignishandlern protected void OnNewsLoaded() { if (NewsLoaded != null) { NewsLoaded(this, EventArgs.Empty); } } 
PVS-Studio Warnung: V3083 [CWE-367] Unsicherer Aufruf des Ereignisses 'NewsLoaded', NullReferenceException ist möglich. Überlegen Sie, ob Sie einer lokalen Variablen ein Ereignis zuweisen möchten, bevor Sie sie aufrufen. NewsListControl.cs 111
In diesem Fall kann die folgende unangenehme Situation auftreten: Nach dem Überprüfen der 
NewsLoaded- Variablen auf 
Null- Ungleichung kann die Methode, die das Ereignis verarbeitet, beispielsweise in einem anderen Thread abgemeldet werden, und wenn wir in den Hauptteil der bedingten 
if-Anweisung gelangen , wird die 
NewsLoaded- Variable gleich 
null . 
Wenn Sie versuchen, Abonnenten für ein 
NewsLoaded- Ereignis 
anzurufen , das 
null ist, wird eine 
NullReferenceException ausgelöst . Es ist viel sicherer, den bedingten Nulloperator zu verwenden und den obigen Code wie folgt neu zu schreiben:
 protected void OnNewsLoaded() { NewsLoaded?.Invoke(this, EventArgs.Empty); } 
Der Analysator zeigte 
68 weitere ähnliche Stellen an. Wir werden sie hier nicht beschreiben - das Muster des Ereignisaufrufs in ihnen ist ähnlich.
Geben Sie null von ToString zurückVor nicht allzu langer Zeit habe ich aus dem 
interessanten Artikel eines Kollegen herausgefunden, dass Microsoft nicht empfiehlt, 
null von einer überschriebenen 
ToString- Methode zurückzugeben. PVS-Studio ist sich dessen bewusst:
 public override string ToString() { lock (loggerLock) { if (sbMessages != null && sbMessages.Length > 0) { return sbMessages.ToString(); } return null; } } 
PVS-Studio Warnung: V3108 Es wird nicht empfohlen, 'null' von der 'ToSting ()' - Methode zurückzugeben. Logger.cs 167
Warum angemessen, wenn nicht verwendet? public SeafileCheckAccInfoResponse GetAccountInfo() { string url = URLHelpers.FixPrefix(APIURL); url = URLHelpers.CombineURL(APIURL, "account/info/?format=json"); .... } 
PVS-Studio Warnung: V3008 Der Variablen 'url' werden zweimal hintereinander Werte zugewiesen. Vielleicht ist das ein Fehler. Kontrollzeilen: 197, 196. Seafile.cs 197
Wie Sie dem Beispiel 
entnehmen können, wird der Deklaration der 
URL- Variablen ein Wert zugewiesen, der von der 
FixPrefix- Methode zurückgegeben wird. In der nächsten Zeile „schleifen“ wir den resultierenden Wert, auch ohne ihn irgendwo zu verwenden. Wir bekommen etwas Ähnliches wie "toter Code" - es macht die Arbeit, es hat keinen Einfluss auf das Endergebnis. Dieser Fehler ist höchstwahrscheinlich das Ergebnis des Kopierens und Einfügens, da solche Codefragmente in 9 weiteren Methoden gefunden werden.
Als Beispiel geben wir zwei Methoden mit einer ähnlichen ersten Zeile an:
 public bool CheckAuthToken() { string url = URLHelpers.FixPrefix(APIURL); url = URLHelpers.CombineURL(APIURL, "auth/ping/?format=json"); .... } .... public bool CheckAPIURL() { string url = URLHelpers.FixPrefix(APIURL); url = URLHelpers.CombineURL(APIURL, "ping/?format=json"); .... } 
Insgesamt
Wie wir sehen können, hängt die Komplexität der Einrichtung der automatischen Überprüfung durch den Analysator nicht vom ausgewählten CI-System ab. In nur 15 Minuten und wenigen Mausklicks richten wir die Überprüfung unseres Projektcodes mit einem statischen Analysator ein.
Abschließend empfehlen wir Ihnen 
, den Analysator für Ihre Projekte 
herunterzuladen und zu testen .

Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Oleg Andreev, Ilya Gainulin. 
PVS-Studio in den Clouds: Azure DevOps .