SDK SARIF et ses erreurs

Image 2


Aujourd'hui, nous testons un autre projet Microsoft de haute qualité, dans lequel nous essayons toujours de rechercher héroïquement les erreurs à l'aide de PVS-Studio. SARIF signifie Static Analysis Results Interchange Format, un format standard (format de fichier) destiné à l'interaction et à l'échange des résultats des analyseurs statiques avec d'autres outils: IDE, outils complets de vérification et d'analyse de code (par exemple, SonarQube), systèmes d'intégration continue etc. Le SDK SARIF, respectivement, contient les outils de développement .NET pour prendre en charge SARIF, ainsi que les fichiers de prise en charge.
SARIF est originaire de Microsoft et est maintenant une norme développée par OASIS (un consortium à but non lucratif engagé dans des normes ouvertes). SARIF est conçu pour transmettre non seulement les résultats de l'analyseur, mais également des métadonnées sur l'outil, ainsi que des données sur la façon dont il a été lancé, les horodatages, etc. Vous trouverez plus de détails sur la norme sur le site Web d' OASIS . Le code source du SDK SARIF peut être téléchargé à partir du référentiel sur GiHub . La page d'accueil du projet est disponible ici .

À propos du projet


Le projet SDK SARIF était petit: 799 fichiers .cs (environ 98 000 lignes de code non vides). Le projet contient des tests, que j'exclus toujours de la vérification. Ainsi, la partie du code qui nous intéresse était de 642 fichiers .cs (environ 79 000 lignes de code non vides). Bien sûr, cela ne suffit pas. Mais la vérification et l'analyse ont été rapides et faciles, entre les choses, ce que j'ai essayé de refléter dans l'image au début de l'article. Néanmoins, quelques erreurs intéressantes ont été trouvées. Regardons-les.

Erreurs


V3070 [CWE-457] La ​​variable 'Binary' non initialisée est utilisée lors de l'initialisation de la variable 'Default'. 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"; .... } 

Le champ par défaut est initialisé avec la valeur d'un autre champ qui n'a pas encore reçu de valeur. Par conséquent, Default recevra la valeur par défaut pour la chaîne de type - null. L'erreur n'a probablement pas été remarquée, car le champ Par défaut n'est utilisé nulle part. Mais tout peut changer, puis le développeur rencontrera un résultat inattendu ou un plantage du programme.

V3061 Le paramètre 'logicLocationToIndexMap' est toujours réécrit dans le corps de la méthode avant d'être utilisé. PrereleaseCompatibilityTransformer.cs 1963

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

Le paramètre logicLocationToIndexMap n'est utilisé en aucune façon, y écrivant une autre valeur. Curieusement, l'ancienne valeur est exactement le même dictionnaire vide créé dans la méthode d'appel:

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

Code étrange et suspect.

V3008 [CWE-563] La variable 'run.Tool' reçoit des valeurs successives deux fois. C'est peut-être une erreur. Vérifiez les lignes: 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(....); // <= .... } 

La propriété run.Tool se voit attribuer une valeur deux fois. Comme lors de la création de l'objet Tool et lors de l'écriture dans la propriété Tool , aucun travail supplémentaire n'est effectué. Par conséquent, la réaffectation semble suspecte.

V3042 [CWE-476] Possible NullReferenceException. Le '?.' et '.' les opérateurs sont utilisés pour accéder aux membres de l'objet «loc» WhereComparer.cs 152

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

Si la variable loc s'avère nulle , une tentative sera faite pour renvoyer la valeur du côté droit de l'opérateur ??, ce qui entraînera l'accès par une référence nulle.

V3042 [CWE-476] Possible NullReferenceException. Le '?.' et '.' les opérateurs sont utilisés pour accéder aux membres de l'objet 'formatString' InsertOptionalDataVisitor.cs 194

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

Dans deux branches parallèles de l'instruction conditionnelle?: Ils utilisent des options d'accès non sécurisées et sécurisées à l'aide d'une référence formatString potentiellement nulle.

V3042 [CWE-476] Possible NullReferenceException. Le '?.' et '.' les opérateurs sont utilisés pour accéder aux membres de l'objet 'messageText' FortifyFprConverter.cs 1210

V3042 [CWE-476] Possible NullReferenceException. Le '?.' et '.' les opérateurs sont utilisés pour accéder aux membres de l'objet 'messageText' FortifyFprConverter.cs 1216

 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(....); } .... } 

Ici, l'analyseur a immédiatement émis deux avertissements concernant un accès possible via la référence null messageText . Cela semble trivial, mais l'erreur qui en résulte ne cesse pas d'être une erreur.

V3080 [CWE-476] Déréférence nulle possible. Pensez à inspecter «fileDataVersionOne.Uri». SarifCurrentToVersionOneVisitor.cs 1030

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

L'analyseur suspectait qu'une exception NullReferenceException était possible lors de l' utilisation du lien fileDataVersionOne.Uri . Voyons d'où vient cette variable et si l'analyseur a raison. Pour ce faire, examinez le corps de la méthode CreateFileDataVersionOne :

 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; } .... } 

En effet, lors de la création d'un objet de classe FileDataVersionOne, la propriété Uri peut être définie sur null . Un bon exemple de collaboration entre les mécanismes d'analyse de flux de données et d'analyse interprocédurale.

V3080 [CWE-476] Déréférence nulle possible. Pensez à inspecter '_jsonTextWriter'. SarifLogger.cs 242

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

Ceci est une faute de frappe. Évidemment, la condition du second bloc if doit être _jsonTextWriter! = Null . Le danger de ce code est qu'il ne se bloque probablement pas, car _jsonTextWriter n'est pas exactement nul . Mais en même temps, le flux reste ouvert.

V3083 [CWE-367] L'appel non sécurisé de l'événement 'RuleRead', NullReferenceException est possible. Pensez à affecter un événement à une variable locale avant de l'invoquer. FxCopConverter.cs 897

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

Ils ne travaillent pas en toute sécurité avec l'événement. Une erreur non critique qui peut être facilement corrigée, par exemple, en suivant l'invite de Visual Studio. Voici une offre de remplacement IDE:

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

Cela fonctionne pendant quelques secondes, mais l'analyseur ne jure plus et l'IDE ne met pas en surbrillance le code. Une autre erreur similaire:

  • V3083 [CWE-367] L'appel non sécurisé de l'événement 'ResultRead', NullReferenceException est possible. Pensez à affecter un événement à une variable locale avant de l'invoquer. FxCopConverter.cs 813

V3095 [CWE-476] L'objet 'v1Location' a été utilisé avant d'être vérifié par rapport à null. Vérifiez les lignes: 333, 335. SarifVersionOneToCurrentVisitor.cs 333

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

L'auteur du code a suggéré que le lien v1Location pourrait être nul, il a donc ajouté la vérification appropriée. Dans le même temps, dans le code un peu plus élevé, pour une raison quelconque, il fonctionne avec ce lien sans aucun contrôle. Inattention lors de la refactorisation? C'est difficile à dire.

V3125 [CWE-476] L'objet 'v1StackFrame' a été utilisé après avoir été vérifié par rapport à null. Vérifiez les lignes: 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; } 

Traditionnellement, le contraire est vrai. Tout d'abord, le lien v1StackFrame est vérifié pour null , puis ils ont oublié de vérifier. Mais il y a une mise en garde importante: les variables v1StackFrame et stackFrame sont logiquement liées. Voir, si v1StackFrame est = null , alors l'objet StackFrame ne sera pas créé et stackFrame restera = null . Dans ce cas, le programme tombera sur un appel à stackFrame.Location , car il n'y a aucune vérification ici. Autrement dit, l'utilisation dangereuse de v1StackFrame , que l'analyseur a souligné, n'atteindra même pas le point. Ce code ne fonctionne que si des valeurs v1StackFrame non nulles sont transmises à la méthode CreateStackFrame . Je soupçonnais que cela était en quelque sorte contrôlé dans le code d'appel. Les appels CreateStackFrame ressemblent à ceci:

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

CreateStackFrame est utilisé comme sélecteur. Il n'y a aucune vérification des références égales nulles passées. Peut-être quelque part lors du remplissage de la collection Frames , cela (l'enregistrement de zéro référence) est contrôlé, mais je n'ai pas creusé aussi loin. La conclusion est déjà évidente - le code requiert l'attention des auteurs.

Conclusion


L'article s'est avéré être petit, mais personne n'a eu le temps de s'ennuyer :) Je vous rappelle que vous pouvez toujours télécharger notre analyseur pour rechercher des erreurs dans votre propre projet ou celui d'autres personnes.

Et enfin, une petite annonce: mon prochain article portera sur les erreurs les plus intéressantes que mes collègues et moi avons trouvées dans les projets C # en 2019. Suivez notre blog . A très bientôt!

Pour en savoir plus sur les nouveaux articles de blog, vous pouvez vous abonner aux canaux suivants:




Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Sergey Khrenov. SDK SARIF et ses erreurs .

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


All Articles