SDK SARIF et ses erreurs

Image 2

Aujourd'hui, nous avons un autre projet Microsoft de haute qualité à vérifier, que nous allons approfondir en essayant de trouver des erreurs avec PVS-Studio. SARIF, acronyme de Static Analysis Interchange Format, qui est un standard (format de fichier), conçu pour interagir et partager les résultats des analyseurs statiques avec d'autres outils: IDE, outils complexes de vérification et d'analyse de code (par exemple SonarQube), systèmes d'intégration continue, etc. Le SDK SARIF, respectivement, contient des outils de développement .NET pour prendre en charge SARIF ainsi que des fichiers supplémentaires.

SARIF est originaire de Microsoft et est maintenant une norme développée par OASIS (un consortium à but non lucratif qui traite des normes ouvertes). SARIF est censé transmettre non seulement les résultats de l'analyseur, mais également les métadonnées sur l'outil, ainsi que les données sur la façon dont il a été lancé, les balises temporelles, etc. Pour plus d'informations, visitez 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 par lien .

À propos du projet


Le projet SARIF SDK s'est avéré être 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éressait était 642 fichiers .cs (environ 79 000 lignes de code non vides). Ce n'est certainement pas suffisant. Sur le plan positif, la vérification et l'analyse ont été faciles et rapides, entre ce moment et ensuite, que j'ai essayé de refléter sur l'image au début. Néanmoins, j'ai réussi à retrouver certains cas étranges. Jetons-y un œil.

Des 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 est initialisé par la valeur d'un autre champ, qui n'a pas encore reçu de valeur. Par conséquent, Default recevra la valeur nulle par défaut pour le type de chaîne . Très probablement, l'erreur est restée inaperçue, car le champ Par défaut n'est utilisé nulle part. Mais les choses peuvent changer, puis le développeur sera confronté à un résultat indu ou au 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); .... } 

L'auteur du code n'utilise en aucune façon le paramètre logicLocationToIndexMap , mais y écrit une valeur différente. Curieusement, la valeur précédente est exactement le même dictionnaire vide, créé dans le code de l'appelant:

 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. Tant lors de la création de l'objet Tool que lors de l'écriture d'une valeur dans la propriété Tool , aucun travail supplémentaire n'est requis. Par conséquent, la réaffectation sent le poisson.

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 valeur de la variable loc est nulle , une tentative sera faite pour renvoyer la valeur de la partie droite du ?? , entraînant l'accès par 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; .... } 

Les développeurs utilisent des options d'accès non sécurisées et sécurisées par une référence formatString potentiellement nulle dans deux branches parallèles de l'opérateur conditionnel ?:.

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 déjà émis deux avertissements concernant un accès possible par la référence null messageText . Cela semble plutôt non menaçant, mais c'est toujours 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 soupçonne que NullReferenceException est possible lors de l' utilisation de la référence fileDataVersionOne.Uri . Voyons d'où vient cette variable et découvrons si l'analyseur a raison. Pour ce faire, examinons de près 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 de l'objet de la classe FileDataVersionOne , la propriété Uri peut recevoir la valeur nulle . Il s'agit d'un excellent exemple d'analyse de flux de données et de mécanismes d'analyse interprocédurale fonctionnant ensemble.

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

Il y a une faute de frappe dans ce fragment. Il est clair que _jsonTextWriter! = Null doit être dans l'état du deuxième bloc. Ce morceau de code est en danger car, très probablement, il ne plante pas, car _jsonTextWriter n'est pas nul . En outre, 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(....); } .... } 

Les événements sont traités de manière non sécurisée. Il s'agit d'un bogue non critique qui peut être facilement corrigé, par exemple, en suivant l'astuce de Visual Studio. Voici le remplacement suggéré par l'IDE:

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

Cela ne prend que quelques secondes pour le réparer, mais l'analyseur ne s'en plaindra plus et l'IDE ne mettra pas en évidence 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 pensait que la référence v1Location pouvait être nulle et a ajouté une vérification appropriée. Alors que ci-dessus, nous pouvons voir que cette référence est gérée sans aucun contrôle. Refactorisation inattentive? Eh bien, on ne sait jamais.

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

Comme toujours, voici un cas inverse. D'abord, la référence v1StackFrame est vérifiée pour null , puis la vérification est égarée. Mais ce cas a une mise en garde importante: les variables v1StackFrame et stackFrame sont logiquement liées. Voir, si v1StackFrame est null , l'objet StackFrame ne sera pas créé, tandis que stackFrame restera nul. Suivi du plantage du programme en raison d'un appel de stackFrame.Location , car il n'y a aucune vérification ici. Il ne sera donc même pas question de l'utilisation dangereuse de v1StackFrame , indiquée par l'analyseur. Ce code ne fonctionne que si vous transmettez des valeurs v1StackFrame non nulles à la méthode CreateStackFrame . Je soupçonnais que le code de l'appelant le contrôlait d'une manière ou d'une autre. Les appels CreateStackFrame ressemblent à ceci:

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

CreateStackFrame est utilisé comme sélecteur. Les références passées ne sont pas vérifiées pour null ici. Peut-être que lors du remplissage de la collection Frames , elle (l'écriture de références nulles) est contrôlée, mais je ne suis pas allé trop loin. La conclusion est déjà évidente - le code requiert l'attention des auteurs.

Conclusion


Comme vous le voyez, l'article n'est pas long mais j'espère que vous avez apprécié cette lecture légère :) Au cas où, vous pouvez toujours télécharger notre analyseur pour rechercher vous-même les erreurs dans vos projets ou ceux de quelqu'un.

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 en 2019. Suivez notre blog . A bientôt!

Pour en savoir plus sur les nouveaux articles de blog, vous pouvez vous abonner aux chaînes suivantes:

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


All Articles