
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 { ....
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 (....) {
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 .