
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 { ....
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 (....) {
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: