SDK de SARIF y sus errores

Imagen 2

Hoy tenemos otro proyecto de Microsoft de alta calidad para verificar, que heroicamente profundizaremos en tratar de encontrar errores con PVS-Studio. SARIF, un acrónimo de Static Analysis Interchange Format, que es un estándar (formato de archivo), diseñado para interactuar y compartir los resultados de los analizadores estáticos con otras herramientas: IDE, herramientas complejas de verificación y análisis de código (por ejemplo, SonarQube), sistemas de integración continua, etc. El SDK de SARIF, respectivamente, contiene herramientas de desarrollo .NET para admitir SARIF, así como archivos adicionales.

SARIF se originó en Microsoft y ahora es un estándar desarrollado por OASIS (un consorcio sin fines de lucro que se ocupa de los estándares abiertos). SARIF está destinado a transmitir no solo los resultados del analizador, sino también los metadatos sobre la herramienta, así como los datos sobre cómo se lanzó, las etiquetas de tiempo, etc. Para obtener más información, visite el sitio web de OASIS . El código fuente de SARIF SDK se puede descargar del repositorio en GiHub . La página de inicio del proyecto está disponible por enlace .

Sobre el proyecto


El proyecto del SDK de SARIF resultó ser pequeño: 799 archivos .cs (aproximadamente 98,000 líneas de código no vacías). El proyecto contiene pruebas que siempre excluyo de la verificación. Por lo tanto, la parte del código que nos interesaba era 642 archivos .cs (aproximadamente 79,000 líneas de código no vacías). Ciertamente no es suficiente. En el lado positivo, la verificación y el análisis fueron fáciles y rápidos, entre esto y entonces, que traté de reflejar en la imagen al principio. No obstante, logré localizar algunos casos extraños. Echemos un vistazo a ellos.

Errores


V3070 [CWE-457] La ​​variable no inicializada 'Binary' se usa al inicializar 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"; .... } 

El campo se inicializa por el valor de otro campo, que aún no ha recibido un valor. Como resultado, Default recibirá el valor nulo por defecto para el tipo de cadena . Lo más probable es que el error haya pasado desapercibido, ya que el campo Predeterminado no se usa en ningún lado. Pero las cosas pueden cambiar, y luego el desarrollador enfrentará un resultado indebido o el bloqueo del programa.

V3061 El parámetro 'logicalLocationToIndexMap' siempre se reescribe en el cuerpo del método antes de usarse. PrereleaseCompatibilityTransformer.cs 1963

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

El autor del código no utiliza el parámetro logicalLocationToIndexMap de ninguna manera, pero escribe un valor diferente en él. Curiosamente, el valor anterior es exactamente el mismo diccionario vacío, creado en el código de la persona que llama:

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

Código extraño y sospechoso.

V3008 [CWE-563] La variable 'run.Tool' recibe valores asignados dos veces seguidas. Quizás esto sea un error. Líneas de verificación: 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 propiedad run.Tool tiene asignado un valor dos veces. Tanto al crear el objeto Herramienta como al escribir un valor en la propiedad Herramienta , no se requiere trabajo adicional. Por lo tanto, la reasignación huele a pescado.

V3042 [CWE-476] Posible NullReferenceException. El '?.' y '.' Los operadores se utilizan para acceder a los miembros del objeto 'loc' WhereComparer.cs 152

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

Si el valor de la variable loc es nulo , se intentará devolver el valor de la parte derecha de ?? operador, lo que resulta en el acceso por referencia nula.

V3042 [CWE-476] Posible NullReferenceException. El '?.' y '.' Los operadores se utilizan para acceder a los miembros del objeto 'formatString' InsertOptionalDataVisitor.cs 194

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

Los desarrolladores utilizan opciones de acceso seguro y no seguro mediante un formato potencialmente nulo ¿ Referencia de cadena en dos ramas paralelas del condicional ?: Operador.

V3042 [CWE-476] Posible NullReferenceException. El '?.' y '.' Los operadores se utilizan para acceder a los miembros del objeto 'messageText' FortifyFprConverter.cs 1210

V3042 [CWE-476] Posible NullReferenceException. El '?.' y '.' Los operadores se utilizan para acceder a los miembros del objeto '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(....); } .... } 

Aquí el analizador ya emitió dos advertencias sobre el posible acceso por parte de la referencia nula de mensaje de texto . Parece bastante amenazante, pero sigue siendo un error.

V3080 [CWE-476] Posible desreferencia nula. Considere inspeccionar 'fileDataVersionOne.Uri'. SarifCurrentToVersionOneVisitor.cs 1030

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

El analizador sospecha que NullReferenceException es posible cuando se trabaja con la referencia fileDataVersionOne.Uri . Veamos de dónde proviene esta variable y descubramos si el analizador es correcto. Para hacer esto, echemos un vistazo al cuerpo del método 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; } .... } 

De hecho, al crear el objeto de la clase FileDataVersionOne , la propiedad Uri podría recibir el valor nulo . Este es un gran ejemplo de análisis de flujo de datos y mecanismos de análisis interprocedimiento trabajando juntos.

V3080 [CWE-476] Posible desreferencia nula. Considere la posibilidad de inspeccionar '_jsonTextWriter'. SarifLogger.cs 242

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

Hay un error tipográfico en este fragmento. Está claro que _jsonTextWriter! = Null tiene que estar en la condición del segundo bloque. Este código pone en peligro ya que, muy probablemente, no se bloquea , debido a que _jsonTextWriter no es nulo . Además, la corriente permanece abierta.

V3083 [CWE-367] La ​​invocación insegura del evento 'RuleRead', NullReferenceException es posible. Considere asignar un evento a una variable local antes de invocarlo. FxCopConverter.cs 897

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

Los eventos se manejan de manera insegura. Es un error no crítico que se puede solucionar fácilmente, por ejemplo, siguiendo el consejo de Visual Studio. Aquí está el reemplazo sugerido por el IDE:

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

Solo toma unos segundos arreglarlo, pero el analizador ya no se quejará y el IDE no resaltará el código. Otro error similar.

  • V3083 [CWE-367] La ​​invocación insegura del evento 'ResultRead', NullReferenceException es posible. Considere asignar un evento a una variable local antes de invocarlo. FxCopConverter.cs 813

V3095 [CWE-476] El objeto 'v1Location' se usó antes de que se verificara como nulo. Líneas de verificación: 333, 335. SarifVersionOneToCurrentVisitor.cs 333

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

El autor pensó que la referencia de v1Location puede ser nula y agregó una verificación adecuada. Mientras que arriba podemos ver que esta referencia se maneja sin ninguna verificación. ¿Refactorización desatendida? Bueno, nunca se sabe.

V3125 [CWE-476] El objeto 'v1StackFrame' se usó después de que se verificó contra nulo. Líneas de verificación: 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; } 

Como siempre, aquí viene un caso inverso. Primero se verifica que la referencia v1StackFrame sea nula , y luego la verificación se ha extraviado. Pero este caso tiene una advertencia importante: las variables v1StackFrame y stackFrame están relacionadas lógicamente. Vea, si v1StackFrame es nulo , el objeto StackFrame no se creará, mientras que stackFrame seguirá siendo nulo. Seguido por el bloqueo del programa debido a una llamada de stackFrame.Location , ya que no hay verificaciones aquí. Por lo tanto, ni siquiera llegará al uso peligroso v1StackFrame , indicado por el analizador. Este código solo funciona si pasa valores no nulos de v1StackFrame al método CreateStackFrame . Sospeché que el código de llamada de alguna manera lo controla. Las llamadas CreateStackFrame se ven así:

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

CreateStackFrame se usa como un selector. Las referencias aprobadas no se verifican aquí como nulas . Quizás, al llenar la colección Frames, se controla (escribir referencias nulas), pero no fui a cavar demasiado profundo. La conclusión ya es obvia: el código requiere la atención de los autores.

Conclusión


Como puede ver, el artículo no es largo, pero espero que haya disfrutado esta lectura ligera :) Por si acaso, siempre puede descargar nuestro analizador para buscar errores en sus proyectos o los de usted mismo.

Y finalmente, un pequeño anuncio: mi próximo artículo será sobre los errores más interesantes que mis colegas y yo encontramos en los proyectos en 2019. Siga nuestro blog . Nos vemos

Para obtener más información sobre nuevas publicaciones de blog, puede suscribirse a los siguientes canales:

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


All Articles