SARIF SDK e seus erros

Quadro 2

Hoje, temos outro projeto da Microsoft de alta qualidade a ser verificado, que heroicamente nos aprofundaremos na tentativa de encontrar erros com o PVS-Studio. SARIF, um acrônimo para Static Analysis Interchange Format, que é um padrão (formato de arquivo), projetado para interagir e compartilhar os resultados de analisadores estáticos com outras ferramentas: IDEs, ferramentas complexas 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 ferramentas de desenvolvedor .NET para oferecer suporte a SARIF, além de arquivos adicionais.

O SARIF teve origem na Microsoft e agora é um padrão desenvolvido pelo OASIS (um consórcio sem fins lucrativos que lida com padrões abertos). O SARIF destina-se a transmitir não apenas os resultados do analisador, mas também metadados sobre a ferramenta, além de dados sobre como ela foi lançada, etiquetas de tempo e assim por diante. Para mais informações, visite o site da OASIS . O código fonte do SARIF SDK pode ser baixado do repositório no GiHub . A página inicial do projeto está disponível por link .

Sobre o projeto


O projeto SARIF SDK acabou sendo pequeno: 799 arquivos .cs (aproximadamente 98.000 linhas de código não vazias). O projeto contém testes que eu sempre excluo da verificação. Portanto, a parte do código em que estávamos interessados ​​era 642 arquivos .cs (aproximadamente 79.000 linhas de código não vazias). Certamente não é suficiente. No lado positivo, a verificação e a análise foram fáceis e rápidas, entre este e o momento, que tentei refletir na imagem no início. No entanto, eu consegui rastrear alguns casos estranhos. Vamos dar uma olhada neles.

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 é inicializado pelo valor de outro campo, que ainda não recebeu um valor. Como resultado, o padrão receberá o valor nulo por padrão para o tipo de sequência . Provavelmente, o erro permaneceu despercebido, pois o campo Padrão não é usado em nenhum lugar. Mas as coisas podem mudar e, em seguida, o desenvolvedor enfrentará um resultado indevido ou a falha do 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 autor do código não usa o parâmetro logicLocationToIndexMap de forma alguma, mas grava nele um valor diferente. Curiosamente, o valor anterior é exatamente o mesmo dicionário vazio, criado no código do chamador:

 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. Tanto na criação do objeto Tool quanto na gravação de um valor na propriedade Tool , nenhum trabalho adicional é necessário. Portanto, reatribuir cheiros suspeitos.

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 o valor da variável loc for nulo , será feita uma tentativa de retornar o valor da parte direita da ?? operador, resultando no acesso por 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; .... } 

Os desenvolvedores usam opções de acesso não seguro e seguro por uma referência formatString potencialmente nula em duas ramificações paralelas do operador condicional ?:.

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 já emitiu dois avisos sobre o possível acesso pela referência nula messageText . Parece um pouco ameaçador, mas ainda é 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 NullReferenceException fosse possível ao trabalhar com a referência fileDataVersionOne.Uri . Vamos ver de onde vem essa variável e descobrir se o analisador está correto. Para fazer isso, vamos dar uma olhada no 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 o objeto da classe FileDataVersionOne , a propriedade Uri pode receber o valor nulo . Este é um ótimo exemplo de análise de fluxo de dados e mecanismos de análise interprocedural trabalhando juntos.

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

Há um erro de digitação neste fragmento. É claro que _jsonTextWriter! = Null deve estar na condição do segundo bloco. Esse trecho de código está comprometendo, pois provavelmente não trava, devido ao fato de _jsonTextWriter não ser nulo . Além disso, 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(....); } .... } 

Os eventos são tratados de maneira insegura. É um bug não crítico que pode ser facilmente corrigido, por exemplo, seguindo a dica do Visual Studio. Aqui está a substituição sugerida pelo IDE:

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

Demora apenas alguns segundos para corrigi-lo, mas o analisador não se queixa 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 pensou que a referência v1Location pode ser nula e adicionou uma verificação apropriada. Considerando que, acima, podemos ver que essa referência é tratada sem nenhuma verificação. Refatoração desatenta? Bem, você nunca sabe.

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

Como sempre, aqui vem um caso inverso. Primeiro, a referência do v1StackFrame é verificada como nula e, em seguida, a verificação se perde. Mas este caso tem uma ressalva importante: as variáveis v1StackFrame e stackFrame estão logicamente relacionadas. Veja, se v1StackFrame for nulo , o objeto StackFrame não será criado, enquanto o stackFrame permanecerá nulo. Seguido pela falha do programa devido a uma chamada de stackFrame.Location , pois não há verificações aqui. Portanto, nem chegará ao uso perigoso do v1StackFrame , indicado pelo analisador. Esse código funciona apenas se você passar valores v1StackFrame não nulos para o método CreateStackFrame . Suspeitei que o código do chamador de alguma forma o controla. As chamadas do CreateStackFrame têm a seguinte aparência:

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

CreateStackFrame é usado como um seletor. As referências passadas não são verificadas como nulas aqui. Talvez, ao preencher a coleção Frames, ele (gravação de referências nulas) seja controlado, mas eu não procurei muito fundo. A conclusão já é óbvia - o código requer a atenção dos autores.

Conclusão


Como você vê, o artigo não é longo, mas espero que você tenha gostado dessa leitura leve :) Para garantir que você sempre possa fazer o download do nosso analisador, procure erros nos seus projetos ou de alguém.

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

Para saber mais sobre as novas postagens do blog, assine os seguintes canais:

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


All Articles