
Heute müssen wir ein weiteres hochwertiges Microsoft-Projekt prüfen, das wir heldenhaft untersuchen, um Fehler mit PVS-Studio zu finden. SARIF, eine Abkürzung für Static Analysis Interchange Format (Dateiformat), ist ein Standard, mit dem die Ergebnisse von statischen Analysegeräten mit anderen Tools interagiert und ausgetauscht werden können: IDEs, komplexe Tools zur Codeüberprüfung und -analyse (z. B. SonarQube), kontinuierliche Integrationssysteme, etc. Das SARIF-SDK enthält .NET-Entwicklertools zur Unterstützung von SARIF sowie zusätzliche Dateien.
SARIF hat seinen Ursprung bei Microsoft und ist heute ein von OASIS (einem gemeinnützigen Konsortium, das sich mit offenen Standards befasst) entwickelter Standard. SARIF soll nicht nur die Ergebnisse des Analysators weitergeben, sondern auch Metadaten über das Tool sowie Daten zu dessen Start, Zeitmarken usw. Weitere Informationen finden Sie auf der
OASIS- Website. Der Quellcode des SARIF SDK kann aus dem Repository von
GiHub heruntergeladen werden . Die Homepage des Projekts ist per
Link erreichbar .
Über das Projekt
Das SARIF SDK-Projekt erwies sich als klein: 799 .cs-Dateien (ca. 98.000 nicht leere Codezeilen). Das Projekt enthält Tests, die ich immer von der Prüfung ausschließe. Der Teil des Codes, an dem wir interessiert waren, waren also 642 .cs-Dateien (ungefähr 79.000 nicht leere Codezeilen). Es ist sicherlich nicht genug. Positiv ist zu vermerken, dass die Überprüfung und Analyse zwischen diesen beiden Punkten einfach und schnell war, was ich am Anfang versucht habe, auf das Bild zu reflektieren. Trotzdem gelang es mir, einige unheimliche Fälle aufzuspüren. 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 { ....
Das Feld wird mit dem Wert eines anderen Feldes initialisiert, das noch keinen Wert erhalten hat. Infolgedessen erhält
Default standardmäßig den Nullwert für den
Zeichenfolgentyp . Höchstwahrscheinlich blieb der Fehler unbemerkt, da das Feld
Standard nirgendwo verwendet wird. Aber die Dinge können sich ändern, und dann wird der Entwickler mit einem übermäßigen Ergebnis oder dem Absturz des Programms konfrontiert.
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 Autor des Codes verwendet den Parameter
logicalLocationToIndexMap in keiner Weise, sondern schreibt einen anderen Wert hinein. Seltsamerweise ist der vorherige Wert genau dasselbe leere Wörterbuch, das im Aufrufercode 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. Sowohl beim Erstellen des
Tool- Objekts als auch beim Schreiben eines Werts in die
Tool- Eigenschaft sind keine zusätzlichen Arbeiten erforderlich. Daher riecht die Neuzuweisung nach Fisch.
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 der Wert der
loc- Variablen
null ist , wird versucht, den Wert vom rechten Teil des ?? Operator, der den Zugriff durch Nullreferenz zur Folge hat.
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; .... }
Entwickler verwenden
unsichere und sichere Zugriffsoptionen durch eine möglicherweise null
formatString- Referenz in zwei parallelen Zweigen des bedingten Operators ?:.
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 (....) {
Hier gab der Analysator bereits zwei Warnungen über einen möglichen Zugriff durch die
Nullnachrichtentextreferenz aus . Es sieht nicht bedrohlich aus, ist aber immer noch ein Fehler.
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 vermutet, dass beim Arbeiten mit der
fileDataVersionOne.Uri- Referenz eine
NullReferenceException möglich ist. Lassen Sie uns sehen, woher diese Variable kommt und herausfinden, ob der Analysator richtig ist. Sehen wir uns dazu den Hauptteil der
CreateFileDataVersionOne- Methode
genauer an :
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; } .... }
Beim Erstellen des Objekts der
FileDataVersionOne- Klasse erhält die
Uri- Eigenschaft möglicherweise den
Nullwert . Dies ist ein hervorragendes Beispiel für die Zusammenarbeit von Datenflussanalyse und interproceduraler Analyse.
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(); }
In diesem Fragment ist ein Tippfehler. Es ist klar, dass
_jsonTextWriter! = Null im Zustand des zweiten Blocks sein muss. Dieser Code ist gefährlich, da er höchstwahrscheinlich nicht abstürzt, da
_jsonTextWriter nicht
gültig ist . Außerdem bleibt der Strom 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(....); } .... }
Ereignisse werden unsicher behandelt. Es ist ein unkritischer Fehler, der leicht behoben werden kann, indem Sie beispielsweise dem Visual Studio-Tipp folgen. Hier ist der von der IDE vorgeschlagene Ersatz:
private void ReadRule(....) { .... RuleRead?.Invoke(....); .... }
Es dauert nur ein paar Sekunden, um das Problem zu beheben, aber der Analysator beschwert sich nicht mehr darüber 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 war der Ansicht, dass die
v1Location- Referenz möglicherweise null ist, und fügte eine entsprechende Überprüfung hinzu. Während oben sehen wir, dass diese Referenz ohne Prüfung behandelt wird. Unaufmerksames Refactoring? Nun, du weißt es nie.
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; }
Wie immer kommt hier ein umgekehrter Fall. Zuerst wird die
v1StackFrame- Referenz auf
null überprüft, und dann wird die Überprüfung fehlgeschlagen. Dieser Fall hat 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, während
stackFrame null bleibt
. Gefolgt von dem Programmabsturz aufgrund eines Aufrufs von
stackFrame.Location , da hier keine Überprüfungen stattfinden. Es kommt also nicht einmal zu der gefährlichen
v1StackFrame- Nutzung, die der Analysator angibt. Dieser Code funktioniert nur, wenn Sie keine
v1StackFrame- Werte an die
CreateStackFrame- Methode übergeben. Ich vermutete, dass der Anrufercode ihn irgendwie kontrolliert.
CreateStackFrame- Aufrufe sehen folgendermaßen aus:
Frames = v1Stack.Frames?.Select(CreateStackFrame).ToList()
CreateStackFrame wird als Selektor verwendet. Übergebene Referenzen werden hier nicht auf
null überprüft. Vielleicht wird beim Füllen der
Frames- Sammlung das Schreiben von Nullreferenzen kontrolliert, aber ich habe nicht zu tief gegraben. Die Schlussfolgerung ist bereits offensichtlich - der Code erfordert die Aufmerksamkeit der Autoren.
Fazit
Wie Sie sehen, ist der Artikel nicht lang, aber ich hoffe, Sie haben diese leichte Lektüre genossen :) Nur für den Fall, dass Sie unseren Analyzer jederzeit
herunterladen können, um selbst nach Fehlern in Ihren oder anderen Projekten zu suchen.
Und zum Schluss noch eine kleine Ankündigung: In meinem nächsten Artikel geht es um die interessantesten Fehler, die meine Kollegen und ich 2019 in Projekten gefunden haben. Folgen Sie unserem
Blog . Wir sehen uns!
Um mehr über neue Blog-Posts zu erfahren, können Sie die folgenden Kanäle abonnieren: