
Hoy, estamos probando otro proyecto de alta calidad de Microsoft, en el que todavía tratamos de buscar heroicamente errores con PVS-Studio. SARIF significa formato de intercambio de resultados de análisis estático. Es un estándar (formato de archivo) diseñado para la interacción e intercambio de los resultados de analizadores estáticos con otras herramientas: IDE, herramientas integrales de verificación y análisis de código (por ejemplo, SonarQube), sistemas de integración continua. etc. El SDK de SARIF, respectivamente, contiene las herramientas de desarrollo de .NET para admitir SARIF, así como archivos de soporte.
SARIF se originó en Microsoft y ahora es un estándar desarrollado por OASIS (un consorcio sin fines de lucro dedicado a estándares abiertos). SARIF está diseñado para transmitir no solo los resultados del analizador, sino también metadatos sobre la herramienta, así como datos sobre cómo se lanzó, marcas de tiempo, etc. Se pueden encontrar más detalles sobre el estándar en el sitio web de
OASIS . El código fuente del SDK de SARIF se puede descargar del repositorio en
GiHub . La página de inicio del proyecto está disponible
aquí .
Sobre el proyecto
El proyecto SARIF SDK era 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 interesa eran 642 archivos .cs (aproximadamente 79,000 líneas de código no vacías). Esto, por supuesto, no es suficiente. Pero la verificación y el análisis fueron rápidos y fáciles, entre cosas, que intenté reflejar en la imagen al comienzo del artículo. Sin embargo, se encontraron algunos errores interesantes. Miremos 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 { ....
El campo
Predeterminado se inicializa con el valor de otro campo que aún no ha recibido un valor. Como resultado,
Default recibirá el valor predeterminado para el tipo
string - null. Probablemente, el error no se notó, porque el campo
Predeterminado no se usa en ninguna parte. Pero todo puede cambiar, y luego el desarrollador encontrará un resultado inesperado o un 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 parámetro
logicalLocationToIndexMap no se utiliza de ninguna manera, ya que le escribe otro valor. Curiosamente, el valor anterior es exactamente el mismo diccionario vacío que se crea en el método de llamada:
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. Al igual que al crear el objeto
Herramienta , y al escribir en la propiedad
Herramienta , no se realiza ningún trabajo adicional. Por lo tanto, la reasignación parece sospechosa.
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 la variable
loc resulta ser
nula , se intentará devolver el valor desde el lado derecho del operador ??, lo que dará como resultado el acceso por una 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; .... }
¿En dos ramas paralelas de la declaración condicional ?: Usan opciones de acceso inseguras y seguras utilizando una referencia de
cadena de formato potencialmente nula.
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 (....) {
Aquí, el analizador emitió inmediatamente dos advertencias sobre el posible acceso a través de la referencia nula
messageText . Parece trivial, pero el error de esto no deja de ser 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 sospechaba que
era posible una
NullReferenceException al trabajar con el enlace
fileDataVersionOne.Uri . Veamos de dónde viene esta variable y si el analizador es correcto. Para hacer esto, examine el 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 un objeto de clase
FileDataVersionOne, la propiedad
Uri se puede establecer en
nulo . Un buen ejemplo de la colaboración de análisis de flujo de datos y mecanismos de análisis interprocedimiento.
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(); }
Este es un error tipográfico. Obviamente, la condición del segundo bloque
if debe ser
_jsonTextWriter! = Null . El peligro de este código es que lo más probable es que no se bloquee, porque
_jsonTextWriter no es exactamente
nulo . Pero al mismo tiempo, el flujo permanece abierto.
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(....); } .... }
No están trabajando con el evento de manera segura. Un error no crítico que se puede solucionar fácilmente, por ejemplo, siguiendo el aviso de Visual Studio. Aquí hay un reemplazo que ofrece IDE:
private void ReadRule(....) { .... RuleRead?.Invoke(....); .... }
Funciona durante unos segundos, pero el analizador ya no jura y el IDE no resalta 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 del código sugirió que el enlace
v1Location podría ser nulo, por lo que agregó la verificación adecuada. Al mismo tiempo, en el código un poco más alto, por alguna razón funciona con este enlace sin ninguna comprobación. ¿Falta de atención al refactorizar? Es dificil de decir.
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; }
Tradicionalmente, lo contrario es cierto. Primero,
se verifica que el enlace
v1StackFrame sea nulo y luego se olvidaron de verificarlo. Pero hay una advertencia importante: las
variables v1StackFrame y
stackFrame están lógicamente relacionadas. Vea, si
v1StackFrame es =
nulo , entonces el objeto
StackFrame no se creará y
stackFrame permanecerá =
nulo . En este caso, el programa caerá en una llamada a
stackFrame.Location , ya que no hay verificaciones aquí. Es decir, el uso peligroso de
v1StackFrame , que señaló el analizador, ni siquiera llegará al punto. Este código solo
funciona si se pasan valores
v1StackFrame distintos de cero
al método CreateStackFrame . Sospeché que esto estaba controlado de alguna manera en el código de llamada.
Las llamadas de
CreateStackFrame se ven así:
Frames = v1Stack.Frames?.Select(CreateStackFrame).ToList()
CreateStackFrame se usa como un selector. No hay verificación de las referencias de igualdad
nula pasadas. Quizás en algún lugar al completar la colección
Frames esto (registrar cero referencias) está controlado, pero no caminé tan lejos. La conclusión ya es obvia: el código requiere la atención de los autores.
Conclusión
El artículo resultó ser pequeño, pero nadie tuvo tiempo de aburrirse :) Les recuerdo que siempre pueden
descargar nuestro analizador para buscar errores en sus propios proyectos o en los de otras personas.
Y finalmente, un pequeño anuncio: mi próximo artículo será sobre los errores más interesantes que yo y mis colegas encontramos en los proyectos de C # en 2019. Sigue nuestro
blog Hasta pronto!
Para conocer las nuevas publicaciones de blog, puede suscribirse a los siguientes canales:

Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Sergey Khrenov.
SDK de SARIF y sus errores .