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