SARIF SDK und seine Fehler

Bild 2


Heute testen wir ein weiteres qualitativ hochwertiges Microsoft-Projekt, in dem wir immer noch versuchen, mithilfe von PVS-Studio heldenhaft nach Fehlern zu suchen. SARIF steht für Static Analysis Results Interchange Format (Austauschformat für statische Analyseergebnisse). Dies ist ein Standard (Dateiformat), das für die Interaktion und den Austausch der Ergebnisse von statischen Analysegeräten mit anderen Tools entwickelt wurde: IDEs, umfassende Tools zur Codeüberprüfung und -analyse (z. B. SonarQube) und Systeme zur kontinuierlichen Integration usw. Das SARIF-SDK enthält die .NET-Entwicklertools zur Unterstützung von SARIF sowie unterstützende Dateien.
SARIF hat seinen Ursprung bei Microsoft und ist heute ein Standard, der von OASIS (einem gemeinnützigen Konsortium, das sich mit offenen Standards befasst) entwickelt wurde. SARIF wurde entwickelt, um nicht nur die Ergebnisse des Analysators zu übertragen, sondern auch Metadaten über das Tool sowie Daten zu dessen Start, Zeitstempeln usw. Weitere Details zum Standard finden Sie auf der OASIS- Website. Der SARIF SDK-Quellcode kann aus dem Repository von GiHub heruntergeladen werden . Die Projekthomepage finden Sie hier .

Über das Projekt


Das SARIF SDK-Projekt war klein: 799 .cs-Dateien (ca. 98.000 nicht leere Codezeilen). Das Projekt enthält Tests, die ich immer von der Überprüfung ausschließen. Der Teil des Codes, der uns interessiert, waren also 642 .cs-Dateien (ungefähr 79.000 nicht leere Codezeilen). Das reicht natürlich nicht aus. Die Überprüfung und Analyse verlief jedoch schnell und einfach zwischen den Dingen, die ich zu Beginn des Artikels im Bild zu reflektieren versuchte. Trotzdem wurden einige interessante Fehler gefunden. Schauen wir sie uns an.

Fehler


V3070 [CWE-457] Nicht initialisierte Variable 'Binär' wird beim Initialisieren der 'Standard'-Variablen verwendet. MimeType.cs 90

public static class MimeType { .... /// <summary>The MIME type to use when no better MIME type is known.</summary> public static readonly string Default = Binary; .... /// <summary>The MIME type for binaries.</summary> public static readonly string Binary = "application/octet-stream"; .... } 

Das Standardfeld wird mit dem Wert eines anderen Feldes initialisiert, das noch keinen Wert erhalten hat. Als Ergebnis erhält Default den Standardwert für den Typ string - null. Wahrscheinlich wurde der Fehler nicht bemerkt, da das Feld Standard nirgendwo verwendet wird. Aber alles kann sich ändern, und dann wird der Entwickler auf ein unerwartetes Ergebnis oder einen Programmabsturz stoßen.

V3061 Der Parameter 'logicalLocationToIndexMap' wird vor der Verwendung immer im Methodentext neu geschrieben. PrereleaseCompatibilityTransformer.cs 1963

 private static JArray ConvertLogicalLocationsDictionaryToArray( .... Dictionary<LogicalLocation, int> logicalLocationToIndexMap, ....) { .... logicalLocationToIndexMap = new Dictionary<LogicalLocation, int>(LogicalLocation.ValueComparer); .... } 

Der Parameter logicalLocationToIndexMap wird in keiner Weise verwendet und schreibt einen anderen Wert in ihn. Seltsamerweise ist der alte Wert genau dasselbe leere Wörterbuch, das in der aufrufenden Methode erstellt wurde:

 private static bool ApplyChangesFromTC25ThroughTC30(....) { .... Dictionary<LogicalLocation, int> logicalLocationToIndexMap = null; .... logicalLocationToIndexMap = new Dictionary<LogicalLocation, int>(LogicalLocation.ValueComparer); run["logicalLocations"] = ConvertLogicalLocationsDictionaryToArray( ...., logicalLocationToIndexMap, ....); } 

Seltsamer und verdächtiger Code.

V3008 [CWE-563] Der Variablen 'run.Tool' werden nacheinander zweimal Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 116, 114. ExportRulesMetadataCommandBase.cs 116

 public partial class Run { .... public Tool Tool { get; set; } .... } public partial class Tool : .... { .... public Tool() { } .... } private void OutputSarifRulesMetada(....) { .... var run = new Run(); run.Tool = new Tool(); run.Tool = Tool.CreateFromAssemblyData(....); // <= .... } 

Der run.Tool- Eigenschaft wird zweimal ein Wert zugewiesen. Wie beim Erstellen des Tool- Objekts und beim Schreiben in die Tool- Eigenschaft werden keine zusätzlichen Arbeiten ausgeführt. Neuzuweisung sieht daher verdächtig aus.

V3042 [CWE-476] Mögliche NullReferenceException. Das '?.' und '.' Operatoren werden verwendet, um auf Mitglieder des 'loc'-Objekts WhereComparer.cs 152 zuzugreifen

 private static Uri ArtifactUri(ArtifactLocation loc, Run run) { return loc?.Uri ?? loc.Resolve(run)?.Uri; } 

Wenn sich herausstellt, dass die Variable loc den Wert null hat , wird versucht, den Wert von der rechten Seite des Operators ?? zurückzugeben, was zum Zugriff durch eine Nullreferenz führt.

V3042 [CWE-476] Mögliche NullReferenceException. Das '?.' und '.' Operatoren werden für den Zugriff auf Mitglieder des 'formatString'-Objekts InsertOptionalDataVisitor.cs 194 verwendet

 public override Message VisitMessage(Message node) { .... node.Text = node.Arguments?.Count > 0 ? string.Format(...., formatString.Text, ....) : formatString?.Text; .... } 

In zwei parallelen Zweigen der bedingten Anweisung ?: Sie verwenden unsichere und sichere Zugriffsoptionen unter Verwendung einer möglicherweise leeren formatString- Referenz.

V3042 [CWE-476] Mögliche NullReferenceException. Das '?.' und '.' Operatoren werden für den Zugriff auf Mitglieder des 'messageText'-Objekts FortifyFprConverter.cs 1210 verwendet

V3042 [CWE-476] Mögliche NullReferenceException. Das '?.' und '.' Operatoren werden für den Zugriff auf Mitglieder des 'messageText'-Objekts FortifyFprConverter.cs 1216 verwendet

 private void AddMessagesToResult(Result result) { .... string messageText = (rule.ShortDescription ?? rule.FullDescription)?.Text; .... if (....) { // Replace the token with an embedded hyperlink. messageText = messageText.Replace(....); } else { // Replace the token with plain text. messageText = messageText.Replace(....); } .... } 

Hier gab der Analysator sofort zwei Warnungen über einen möglichen Zugriff per messageText null reference aus. Es sieht trivial aus, aber der Fehler daraus hört nicht auf, ein Fehler zu sein.

V3080 [CWE-476] Mögliche Null-Dereferenzierung. Sehen Sie sich 'fileDataVersionOne.Uri' an. SarifCurrentToVersionOneVisitor.cs 1030

 private IDictionary<string, FileDataVersionOne> CreateFileDataVersionOneDictionary() { .... FileDataVersionOne fileDataVersionOne = CreateFileDataVersionOne(v2File); if (fileDataVersionOne.Uri.OriginalString.Equals(key)) { .... } .... } 

Der Analysator vermutete, dass beim Arbeiten mit dem Link fileDataVersionOne.Uri eine NullReferenceException möglich war. Mal sehen, woher diese Variable kommt und ob der Analysator richtig ist. Untersuchen Sie dazu den Body der CreateFileDataVersionOne- Methode:

 private FileDataVersionOne CreateFileDataVersionOne(Artifact v2FileData) { FileDataVersionOne fileData = null; if (v2FileData != null) { .... fileData = new FileDataVersionOne { .... Uri = v2FileData.Location?.Uri, .... }; .... } return fileData; } public partial class FileDataVersionOne { .... public Uri Uri { get; set; } .... } 

In der Tat kann beim Erstellen eines Objekts der Klasse FileDataVersionOne die Uri- Eigenschaft auf null gesetzt werden . Ein gutes Beispiel für die Zusammenarbeit von Datenflussanalyse und interproceduralen Analysemechanismen.

V3080 [CWE-476] Mögliche Null-Dereferenzierung. Betrachten Sie '_jsonTextWriter'. SarifLogger.cs 242

 public virtual void Dispose() { .... if (_closeWriterOnDispose) { if (_textWriter != null) { _textWriter.Dispose(); } if (_jsonTextWriter == null) { _jsonTextWriter.Close(); } // <= } .... } 

Dies ist ein Tippfehler. Offensichtlich muss die Bedingung des zweiten if- Blocks _jsonTextWriter! = Null sein . Die Gefahr dieses Codes besteht darin, dass er höchstwahrscheinlich nicht abstürzt, da _jsonTextWriter nicht genau null ist . Gleichzeitig bleibt der Durchfluss aber offen.

V3083 [CWE-367] Unsicherer Aufruf des Ereignisses 'RuleRead', NullReferenceException ist möglich. Überlegen Sie, ob Sie einer lokalen Variablen ein Ereignis zuweisen möchten, bevor Sie sie aufrufen. FxCopConverter.cs 897

 private void ReadRule(....) { .... if (RuleRead != null) { RuleRead(....); } .... } 

Sie arbeiten nicht sicher mit der Veranstaltung. Ein nicht kritischer Fehler, der leicht behoben werden kann, z. B. nach der Aufforderung von Visual Studio. Hier bietet eine Ersatz-IDE:

 private void ReadRule(....) { .... RuleRead?.Invoke(....); .... } 

Es funktioniert einige Sekunden lang, aber der Analysator schwört nicht mehr und die IDE hebt den Code nicht hervor. Ein weiterer ähnlicher Fehler:

  • V3083 [CWE-367] Unsicherer Aufruf des Ereignisses 'ResultRead', NullReferenceException ist möglich. Überlegen Sie, ob Sie einer lokalen Variablen ein Ereignis zuweisen möchten, bevor Sie sie aufrufen. FxCopConverter.cs 813

V3095 [CWE-476] Das Objekt 'v1Location' wurde verwendet, bevor es gegen null verifiziert wurde. Überprüfen Sie die Zeilen: 333, 335. SarifVersionOneToCurrentVisitor.cs 333

 internal Location CreateLocation(LocationVersionOne v1Location) { .... string key = v1Location.LogicalLocationKey ?? v1Location.FullyQualifiedLogicalName; if (v1Location != null) { .... } .... } 

Der Autor des Codes schlug vor, dass der v1Location- Link möglicherweise null ist, und fügte daher die entsprechende Prüfung hinzu. Zur gleichen Zeit, im Code etwas höher, aus irgendeinem Grund funktioniert es mit diesem Link ohne Prüfung. Unaufmerksamkeit beim Refactoring? Schwer zu sagen.

V3125 [CWE-476] Das Objekt 'v1StackFrame' wurde verwendet, nachdem es gegen null verifiziert wurde. Zeilen prüfen: 1182, 1171. SarifVersionOneToCurrentVisitor.cs 1182

 internal StackFrame CreateStackFrame(StackFrameVersionOne v1StackFrame) { StackFrame stackFrame = null; if (v1StackFrame != null) { stackFrame = new StackFrame { .... }; } stackFrame.Location = CreateLocation(v1StackFrame.FullyQualifiedLogicalName, v1StackFrame.LogicalLocationKey, ....); return stackFrame; } 

Traditionell ist das Gegenteil der Fall. Zuerst wird der v1StackFrame- Link auf null überprüft und dann vergessen, dies zu überprüfen. Es gibt jedoch eine wichtige Einschränkung: Die Variablen v1StackFrame und stackFrame sind logisch miteinander verknüpft. Wenn v1StackFrame = null ist , wird das StackFrame- Objekt nicht erstellt und stackFrame bleibt = null . In diesem Fall erhält das Programm einen Aufruf von stackFrame.Location , da hier keine Prüfungen durchgeführt werden. Das heißt, die gefährliche Verwendung von v1StackFrame , auf die der Analysator hingewiesen hat, erreicht nicht einmal den Punkt. Dieser Code funktioniert nur, wenn v1StackFrame- Werte ungleich Null an die CreateStackFrame-Methode übergeben werden . Ich vermutete, dass dies irgendwie im aufrufenden Code kontrolliert wurde. CreateStackFrame- Aufrufe sehen folgendermaßen aus:

 Frames = v1Stack.Frames?.Select(CreateStackFrame).ToList() 

CreateStackFrame wird als Selektor verwendet. Es findet keine Überprüfung der übergebenen Null- Gleichheitsreferenzen statt. Vielleicht wird dies irgendwo beim Ausfüllen der Frames- Sammlung (Aufnahme von Nullreferenzen) kontrolliert, aber ich habe nicht so weit gegraben. Die Schlussfolgerung ist bereits offensichtlich - der Code erfordert die Aufmerksamkeit der Autoren.

Fazit


Der Artikel erwies sich als klein, aber niemand hatte Zeit, sich zu langweilen :) Ich erinnere Sie daran, dass Sie unseren Analyzer jederzeit herunterladen können, um unabhängig nach Fehlern in Ihren eigenen oder fremden Projekten zu suchen.

Zum Schluss noch eine kleine Ankündigung: In meinem nächsten Artikel geht es um die interessantesten Fehler, die ich und meine Kollegen 2019 in C # -Projekten gefunden haben. Folgen Sie unserem Blog . Bis dann!

Um mehr über neue Blog-Posts zu erfahren, können Sie die folgenden Kanäle abonnieren:




Wenn Sie diesen Artikel mit einem englischsprachigen Publikum teilen möchten, verwenden Sie bitte den Link zur Übersetzung: Sergey Khrenov. SARIF SDK und seine Fehler .

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


All Articles