SARIF SDK e seus erros

Quadro 2


Hoje, estamos testando outro projeto de alta qualidade da Microsoft, no qual ainda tentamos heroicamente procurar erros usando o PVS-Studio. SARIF, sigla para Static Analysis Results Interchange Format (formato de arquivo), é um padrão (formato de arquivo) projetado para interação e troca dos resultados de analisadores estáticos com outras ferramentas: IDEs, ferramentas abrangentes de verificação e análise de código (por exemplo, SonarQube), sistemas de integração contínua etc. O SARIF SDK, respectivamente, contém as ferramentas de desenvolvedor .NET para oferecer suporte a SARIF, bem como arquivos de suporte.
O SARIF se originou na Microsoft e agora é um padrão desenvolvido pelo OASIS (um consórcio sem fins lucrativos envolvido em padrões abertos). O SARIF foi projetado para transmitir não apenas os resultados do analisador, mas também metadados sobre a ferramenta, além de dados sobre como foi lançado, registros de data e hora e assim por diante. Mais detalhes sobre o padrão podem ser encontrados no site da OASIS . O código-fonte SARIF SDK pode ser baixado do repositório no GiHub . A home page do projeto está disponível aqui .

Sobre o projeto


O projeto SARIF SDK era pequeno: 799 arquivos .cs (aproximadamente 98.000 linhas de código não vazias). O projeto contém testes, os quais sempre excluo da verificação. Portanto, a parte do código que nos interessa era 642 arquivos .cs (aproximadamente 79.000 linhas de código não vazias). Isso, é claro, não é suficiente. Mas a verificação e a análise foram rápidas e fáceis, entre as coisas, que tentei refletir na figura no início do artigo. No entanto, alguns erros interessantes foram encontrados. Vamos olhar para eles.

Erros


V3070 [CWE-457] A variável não inicializada 'Binário' é usada ao inicializar a variável 'Padrão'. 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"; .... } 

O campo Padrão é inicializado com o valor de outro campo que ainda não recebeu um valor. Como resultado, o padrão receberá o valor padrão para o tipo string - null. Provavelmente, o erro não foi percebido, porque o campo Padrão não é usado em nenhum lugar. Mas tudo pode mudar e, em seguida, o desenvolvedor encontrará um resultado inesperado ou uma falha no programa.

V3061 O parâmetro 'logicLocationToIndexMap' sempre é reescrito no corpo do método antes de ser usado. PrereleaseCompatibilityTransformer.cs 1963

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

O parâmetro logicLocationToIndexMap não é usado de forma alguma, gravando outro valor nele. Curiosamente, o valor antigo é exatamente o mesmo dicionário vazio criado no método de chamada:

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

Código estranho e suspeito.

V3008 [CWE-563] A variável 'run.Tool' recebe valores duas vezes sucessivamente. Talvez isso seja um erro. Verifique as linhas: 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(....); // <= .... } 

A propriedade run.Tool recebe um valor duas vezes. Como ao criar o objeto Tool e ao gravar na propriedade Tool , nenhum trabalho adicional é realizado. Portanto, a reatribuição parece suspeita.

V3042 [CWE-476] Possível NullReferenceException. O '?' e '.' operadores são usados ​​para acessar membros do objeto 'loc' WhereComparer.cs 152

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

Se a variável loc for nula , será feita uma tentativa de retornar o valor do lado direito do operador ??, o que resultará no acesso por uma referência nula.

V3042 [CWE-476] Possível NullReferenceException. O '?' e '.' operadores são usados ​​para acessar membros do objeto 'formatString' InsertOptionalDataVisitor.cs 194

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

Em duas ramificações paralelas da instrução condicional ?: Eles usam opções de acesso inseguras e seguras usando uma referência formatString potencialmente nula.

V3042 [CWE-476] Possível NullReferenceException. O '?' e '.' operadores são usados ​​para acessar membros do objeto 'messageText' FortifyFprConverter.cs 1210

V3042 [CWE-476] Possível NullReferenceException. O '?' e '.' operadores são usados ​​para acessar membros do 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(....); } .... } 

Aqui, o analisador emitiu imediatamente dois avisos sobre possível acesso via referência nula messageText . Parece trivial, mas o erro não deixa de ser um erro.

V3080 [CWE-476] Possível desreferência nula. Considere inspecionar 'fileDataVersionOne.Uri'. SarifCurrentToVersionOneVisitor.cs 1030

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

O analisador suspeitou que uma NullReferenceException fosse possível ao trabalhar com o link fileDataVersionOne.Uri . Vamos ver de onde vem essa variável e se o analisador está certo. Para fazer isso, examine o corpo do 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 fato, ao criar um objeto da classe FileDataVersionOne, a propriedade Uri pode ser definida como nula . Um bom exemplo da colaboração de análise de fluxo de dados e mecanismos de análise interprocedural.

V3080 [CWE-476] Possível desreferência nula. Considere inspecionar '_jsonTextWriter'. SarifLogger.cs 242

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

Este é um erro de digitação. Obviamente, a condição do segundo bloco if deve ser _jsonTextWriter! = Null . O perigo desse código é que ele provavelmente não falha, porque _jsonTextWriter não é exatamente nulo . Mas, ao mesmo tempo, o fluxo permanece aberto.

V3083 [CWE-367] Chamada não segura do evento 'RuleRead', NullReferenceException é possível. Considere atribuir um evento a uma variável local antes de invocá-lo. FxCopConverter.cs 897

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

Eles não estão trabalhando com o evento com segurança. Um erro não crítico que pode ser facilmente corrigido, por exemplo, seguindo o prompt do Visual Studio. Aqui está uma oferta de substituição do IDE:

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

Ele funciona por alguns segundos, mas o analisador não jura mais e o IDE não destaca o código. Outro erro semelhante:

  • V3083 [CWE-367] Chamada não segura do evento 'ResultRead', NullReferenceException é possível. Considere atribuir um evento a uma variável local antes de invocá-lo. FxCopConverter.cs 813

V3095 [CWE-476] O objeto 'v1Location' foi usado antes de ser verificado com relação a nulo. Verifique as linhas: 333, 335. SarifVersionOneToCurrentVisitor.cs 333

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

O autor do código sugeriu que o link v1Location poderia ser nulo; portanto, ele adicionou a verificação apropriada. Ao mesmo tempo, no código um pouco mais alto, por algum motivo, ele funciona com esse link sem nenhuma verificação. Desatenção durante a refatoração? Difícil dizer.

V3125 [CWE-476] O objeto 'v1StackFrame' foi usado após a verificação contra nulo. Verifique as linhas: 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, o oposto é verdadeiro. Primeiro, o link v1StackFrame é verificado como nulo e eles se esqueceram de verificar. Mas há uma ressalva importante: as variáveis ​​v1StackFrame e stackFrame estão relacionadas logicamente. Veja se v1StackFrame for = null , o objeto StackFrame não será criado e o stackFrame permanecerá = null . Nesse caso, o programa cairá em uma chamada para stackFrame.Location , pois não há verificações aqui. Ou seja, o uso perigoso do v1StackFrame , apontado pelo analisador, nem chegará ao ponto. Esse código funciona apenas se valores diferentes de zero v1StackFrame forem passados ​​para o método CreateStackFrame . Eu suspeitava que isso fosse de alguma forma controlado no código de chamada. As chamadas CreateStackFrame são assim:

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

CreateStackFrame é usado como um seletor. Não há verificação das referências de igualdade nula passadas. Talvez em algum lugar ao preencher a coleção Frames, isso (gravando zero referências) seja controlado, mas eu não fui tão longe. A conclusão já é óbvia - o código requer a atenção dos autores.

Conclusão


O artigo acabou sendo pequeno, mas ninguém teve tempo de se entediar :) Lembro que você sempre pode fazer o download do nosso analisador para procurar independentemente erros em seus próprios projetos ou nos de outras pessoas.

E, finalmente, um pequeno anúncio: meu próximo artigo será sobre os erros mais interessantes que eu e meus colegas encontramos em projetos de C # em 2019. Siga o nosso blog . Até breve!

Para aprender sobre as novas postagens do blog, você pode se inscrever nos seguintes canais:




Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Sergey Khrenov. SARIF SDK e seus erros .

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


All Articles