As bibliotecas do .NET Core são um dos projetos C # mais populares no GitHub. Não é de surpreender, dada a sua grande popularidade e usabilidade. É ainda mais interessante tentar descobrir quais cantos escuros podem ser encontrados no código-fonte dessas bibliotecas, o que tentaremos fazer usando o analisador estático PVS-Studio. Você acha que conseguiu descobrir alguma coisa interessante no final?
Fui a este artigo por mais de um ano e meio. Em algum momento, pensei em que as bibliotecas do .NET Core são um petisco, e dar uma olhada nelas será interessante. Várias vezes que verifiquei o projeto, o analisador encontrou lugares cada vez mais interessantes, mas não foi além de uma rolagem rápida pela lista de avisos. E aqui está - pronto! O projeto está marcado, o artigo está na sua frente.
Mais sobre o projeto e a análise
Se você está ansioso para mergulhar na análise do código - você pode pular esta seção, mas eu realmente gostaria que você o lesse - aqui eu falo um pouco mais sobre o projeto e o analisador, bem como sobre como eu analisei e reproduzi os erros.
Projeto Auditado
Provavelmente, seria possível não dizer o que são CoreFX (bibliotecas do .NET Core), mas, se você não ouviu, a descrição está abaixo. Não o reformulei e o tirei
da página do projeto no GitHub , onde você também pode baixar os fontes.
Descrição:
este repositório contém a implementação da biblioteca (chamada "CoreFX") para o .NET Core. Inclui System.Collections, System.IO, System.Xml e muitos outros componentes. O repositório correspondente do .NET Core Runtime (chamado "CoreCLR") contém a implementação de tempo de execução do .NET Core. Inclui RyuJIT, o .NET GC e muitos outros componentes. O código da biblioteca específica do tempo de execução (System.Private.CoreLib) fica no repositório CoreCLR. Ele precisa ser construído e versionado em conjunto com o tempo de execução. O restante do CoreFX é independente da implementação do tempo de execução e pode ser executado em qualquer tempo de execução .NET compatível (por exemplo, CoreRT) .
Analisador usado e método de análise
Eu verifiquei o código fonte usando o
analisador estático PVS-Studio . Em geral, o PVS-Studio pode analisar não apenas o código C #, mas também C, C ++, Java. Até agora, a análise do código C # funciona apenas no Windows, enquanto o código em C, C ++, Java você pode analisar no Windows, Linux e macOS.
Normalmente, uso o plug-in PVS-Studio para o Visual Studio para testar projetos C # (versões 2010-2019 são suportadas), pois essa é provavelmente a maneira mais fácil e conveniente de analisar: abra uma solução, inicie a análise, trabalhe com uma lista de avisos. Com o CoreFX, no entanto, as coisas ficaram um pouco mais complicadas.
O fato é que o projeto não possui um único arquivo .sln, portanto, a abertura no Visual Studio e a realização de uma análise completa usando o plug-in PVS-Studio falharão. Provavelmente, é bom - eu realmente não sei como o Visual Studio lidaria com uma solução desse tamanho.
No entanto, não houve problemas com a análise, pois o kit de distribuição PVS-Studio inclui uma versão em linha de comando do analisador para projetos MSBuild (e, de fato, .sln). Tudo o que era necessário para mim era escrever um pequeno script que executasse “PVS-Studio_Cmd.exe” para cada .sln no diretório CoreFX e coloque os resultados da análise em um diretório separado (indicado pelo sinalizador de inicialização do analisador).
Voila! - na saída eu tenho um conjunto de logs, que tem muitas coisas interessantes. Se desejado, os logs podem ser combinados usando o utilitário PlogConverter, que acompanha o kit de distribuição. Mas era mais conveniente trabalhar com logs individuais, por isso não comecei a combiná-los.
Ao descrever alguns erros, refiro-me à documentação de docs.microsoft.com e aos pacotes NuGet disponíveis para download em nuget.org. Admito que o código descrito na documentação / encontrado nos pacotes possa diferir ligeiramente do analisado. No entanto, será muito estranho se, por exemplo, a documentação não contiver uma descrição das exceções geradas para vários dados de entrada e elas aparecerem na nova versão do pacote - concordar que isso será uma surpresa duvidosa. A reprodução de erros nos pacotes do NuGet nos mesmos dados de entrada usados para as bibliotecas de depuração mostra que o problema não é novo e, mais importante, que pode ser "tocado" sem criar o projeto a partir da origem.
Assim, assumindo a possibilidade de alguma sincronia teórica do código, acho permitido fazer referência à descrição dos métodos correspondentes no docs.microsoft.com e à reprodução de problemas nos pacotes do nuget.org.
Também observo que a descrição dos links fornecidos, bem como as informações (comentários) nos pacotes (em outras versões) podem mudar durante a redação do artigo.
Outros projetos comprovados
A propósito, este não é um artigo único, escrevemos outros artigos sobre a verificação de projetos, cuja lista
pode ser encontrada aqui . Além disso, no site coletamos não apenas artigos sobre a análise de projetos, mas também vários artigos técnicos sobre C, C ++, C #, Java, além de apenas notas interessantes. Você pode encontrar tudo isso no
blog .
Meu colega testou anteriormente as bibliotecas do .NET Core em 2015. Os resultados da análise anterior podem ser encontrados no artigo correspondente: "
Verificação de ano novo das bibliotecas do .NET Core (CoreFX) ".
Erros descobertos, lugares suspeitos e interessantes
Como sempre, para maior interesse, sugiro que você primeiro procure por erros nos fragmentos e só então leia o aviso do analisador e a descrição do problema.
Por conveniência, separei explicitamente os fragmentos em questão usando rótulos do formulário
Problema N - é mais fácil entender onde termina a descrição de um erro e começa a análise do outro. Sim, e se referir a fragmentos específicos também é mais fácil.
Edição 1abstract public class Principal : IDisposable { .... public void Save(PrincipalContext context) { .... if ( context.ContextType == ContextType.Machine || _ctx.ContextType == ContextType.Machine) { throw new InvalidOperationException( SR.SaveToNotSupportedAgainstMachineStore); } if (context == null) { Debug.Assert(this.unpersisted == true); throw new InvalidOperationException(SR.NullArguments); } .... } .... }
PVS-Studio Warning :
V3095 O objeto 'context' foi usado antes de ser verificado com relação a nulo. Verifique as linhas: 340, 346. Principal.cs 340
Os desenvolvedores indicam explicitamente que o valor
nulo do parâmetro
context não é válido e desejam enfatizar isso com uma exceção do tipo
InvalidOperationException . No entanto, um pouco mais alto, na condição anterior, há uma desreferenciação incondicional do link de
contexto -
context.ContextType . Como resultado, se o valor do
contexto for
nulo , uma exceção do tipo
NullReferenceException será
lançada em vez da
InvalidOperationExcetion esperada.
Vamos tentar reproduzir o problema. Conecte a biblioteca apropriada (
System.DirectoryServices.AccountManagement ) ao projeto e execute o seguinte código:
GroupPrincipal groupPrincipal = new GroupPrincipal(new PrincipalContext(ContextType.Machine)); groupPrincipal.Save(null);
GroupPrincipal é o sucessor da classe abstrata
Principal , que contém a implementação do método
Save de que precisamos. Executamos o código para execução e vemos o que era necessário para provar.
Por diversão, você pode tentar baixar o pacote apropriado do NuGet e tentar repetir o problema da mesma maneira. Instalei a versão do pacote 4.5.0 e obtive o resultado esperado.
Edição 2 private SearchResultCollection FindAll(bool findMoreThanOne) { searchResult = null; DirectoryEntry clonedRoot = null; if (_assertDefaultNamingContext == null) { clonedRoot = SearchRoot.CloneBrowsable(); } else { clonedRoot = SearchRoot.CloneBrowsable(); } .... }
PVS-Studio Warning :
V3004 A
instrução 'then' é equivalente à instrução 'else'. DirectorySearcher.cs 629
Independentemente da veracidade da condição
_assertDefaultNamingContext == null , as mesmas ações serão executadas, desde
então e
as ramificações da
instrução if terão os mesmos corpos. Ou deve haver outra ação em algum ramo, ou você pode omitir a
instrução if para não confundir os programadores e o analisador.
Edição 3 public class DirectoryEntry : Component { .... public void RefreshCache(string[] propertyNames) { .... object[] names = new object[propertyNames.Length]; for (int i = 0; i < propertyNames.Length; i++) names[i] = propertyNames[i]; .... if (_propertyCollection != null && propertyNames != null) .... .... } .... }
PVS-Studio Warning :
V3095 O objeto 'propertyNames' foi usado antes de ser verificado com valor nulo. Verifique as linhas: 990, 1004. DirectoryEntry.cs 990
Mais uma vez, vemos um procedimento estranho. O método possui uma verificação
propertyNames! = Null , ou seja, os desenvolvedores garantem que o método retorne
nulo . Aqui estão logo acima, você pode observar vários acessos a essa referência potencialmente nula -
propertyNames.Length e
propertyNames [i] . O resultado é bastante previsível - uma exceção do tipo
NullReferenceExcepption ocorrerá se uma referência nula for passada para o método.
Que coincidência o
RefreshCache é um método público em uma classe pública. Tente repetir o problema? Para fazer isso, conecte ao projeto a biblioteca desejada -
System.DirectoryServices - e escreva o código assim:
DirectoryEntry de = new DirectoryEntry(); de.RefreshCache(null);
Execute o código para execução e veja a imagem esperada.
Por diversão, você pode tentar reproduzir o problema na versão do pacote NuGet. Conectamos o pacote
System.DirectoryServices ao projeto NuGet (usei a versão 4.5.0) e executamos o código já familiar para execução. O resultado é menor.
Edição 4Agora iremos do oposto - primeiro tente escrever o código que usa uma instância da classe e depois olhe dentro. Vejamos a estrutura de
System.Drawing.CharacterRange da biblioteca
System.Drawing.Common e o pacote NuGet com o mesmo nome.
O código usado será o seguinte:
CharacterRange range = new CharacterRange(); bool eq = range.Equals(null); Console.WriteLine(eq);
Apenas para
garantir a atualização da memória, voltaremos para
docs.microsoft.com para lembrar qual valor de retorno é esperado da expressão
obj.Equals (null) :
As instruções a seguir devem ser verdadeiras para todas as implementações do método Equals (Object) . Na lista, x, ye z representam referências a objetos que não são nulos.....x.Equals (null) retorna false.Você acha que o texto "False" será exibido no console? Claro que não, isso seria fácil demais. :) Executamos o código e olhamos para o resultado.
Esta foi a conclusão ao executar o código acima usando o pacote NuGet
System.Drawing.Common versão 4.5.1. Executamos o mesmo código com a versão de depuração da biblioteca e vemos o seguinte:
Agora, vejamos o código-fonte - a implementação do método
Equals na estrutura
CharacterRange e o aviso do analisador:
public override bool Equals(object obj) { if (obj.GetType() != typeof(CharacterRange)) return false; CharacterRange cr = (CharacterRange)obj; return ((_first == cr.First) && (_length == cr.Length)); }
Aviso do PVS-Studio :
V3115 A passagem de 'null' para o método 'Equals' não deve resultar em 'NullReferenceException'. CharacterRange.cs 56
Vemos o que precisamos provar - o parâmetro
obj é processado incorretamente, por causa do qual uma exceção do tipo
NullReferenceException ocorre ao chamar o método de instância
GetType na expressão condicional.
Edição 5Ao explorar esta biblioteca, considere outro lugar interessante - o método
Icon.Save . Antes do estudo, consulte a descrição do método.
Não há descrição do método:
Passamos para docs.microsoft.com - "
Método Icon.Save (Stream) ". No entanto, também não há restrições nos valores de entrada e não há informações sobre as exceções geradas.
Agora vamos para a pesquisa de código.
public sealed partial class Icon : MarshalByRefObject, ICloneable, IDisposable, ISerializable { .... public void Save(Stream outputStream) { if (_iconData != null) { outputStream.Write(_iconData, 0, _iconData.Length); } else { .... if (outputStream == null) throw new ArgumentNullException("dataStream"); .... } } .... }
PVS-Studio Warning :
V3095 O objeto '
outputStream ' foi usado antes de ser verificado com valor nulo. Verifique as linhas: 654, 672. Icon.Windows.cs 654
Novamente, a história que já conhecemos é a possível desreferenciação de uma referência nula, uma vez que o parâmetro do método é desreferenciado sem verificar se é
nulo . Mais uma vez, uma boa combinação de circunstâncias - tanto a classe quanto o método - é pública, o que significa que você pode tentar reproduzir o problema.
A tarefa é simples - trazer a execução do código para a expressão
outputStream.Write (_iconData, 0, _iconData.Length); enquanto mantém o valor da variável
outputStream -
null . Para fazer isso, basta que a condição
_iconData! = Null seja atendida.
Vejamos o construtor público mais simples:
public Icon(string fileName) : this(fileName, 0, 0) { }
Ele simplesmente delega o trabalho para outro construtor. Bem, olhe mais longe - o construtor usado aqui.
public Icon(string fileName, int width, int height) : this() { using (FileStream f = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { Debug.Assert(f != null, "File.OpenRead returned null instead of throwing an exception"); _iconData = new byte[(int)f.Length]; f.Read(_iconData, 0, _iconData.Length); } Initialize(width, height); }
Aqui está o que você precisa. Após chamar este construtor, se lermos com êxito os dados do arquivo e se não ocorrerem falhas no método
Initialize , o campo
_iconData conterá um link para algum objeto, e é disso que precisamos.
Acontece que, para reproduzir o problema, você precisa criar uma instância da classe
Icon com o ícone real e, em seguida, chamar o método
Save , passando
nulo como argumento, o que faremos. O código pode parecer, por exemplo, o seguinte:
Icon icon = new Icon(@"D:\document.ico"); icon.Save(null);
O resultado da execução é esperado.
Edição 6Continuamos a revisão e vamos para a biblioteca
System.Management . Tente encontrar três diferenças entre as ações executadas no
caso CimType.UInt32 e o restante do
caso .
private static string ConvertToNumericValueAndAddToArray(....) { string retFunctionName = string.Empty; enumType = string.Empty; switch(cimType) { case CimType.UInt8: case CimType.SInt8: case CimType.SInt16: case CimType.UInt16: case CimType.SInt32: arrayToAdd.Add(System.Convert.ToInt32( numericValue, (IFormatProvider)CultureInfo.InvariantCulture .GetFormat(typeof(int)))); retFunctionName = "ToInt32"; enumType = "System.Int32"; break; case CimType.UInt32: arrayToAdd.Add(System.Convert.ToInt32( numericValue, (IFormatProvider)CultureInfo.InvariantCulture .GetFormat(typeof(int)))); retFunctionName = "ToInt32"; enumType = "System.Int32"; break; } return retFunctionName; }
Obviamente, não há diferenças sobre as quais o analisador alerta.
Aviso do PVS-Studio :
V3139 Dois ou mais ramificações de casos executam as mesmas ações. WMIGenerator.cs 5220
Esse estilo de código não é muito claro para mim pessoalmente. Se não há erro aqui, acho que não valia a pena espalhar a mesma lógica em casos diferentes.
Edição 7Biblioteca
Microsoft.CSharp .
private static IList<KeyValuePair<string, object>> QueryDynamicObject(object obj) { .... List<string> names = new List<string>(mo.GetDynamicMemberNames()); names.Sort(); if (names != null) { .... } .... }
Aviso do PVS-Studio :
V3022 A expressão 'names! = Null' sempre é verdadeira. DynamicDebuggerProxy.cs 426
Provavelmente, eu poderia ignorar esse aviso junto com muitos semelhantes que foram emitidos pelos
diagnósticos V3022 e
V3063 . Havia muitos (muito) cheques estranhos, mas isso de alguma forma afundou em minha alma. É possível que, antes de comparar os
nomes de variáveis locais com
nulos a essa variável, além de ser uma referência ao objeto recém-criado, ele também chame o método de instância
Sort . Isso não é um erro, é claro, mas o lugar é interessante, quanto a mim.
Edição 8Aqui está outro pedaço interessante de código.
private static void InsertChildNoGrow(Symbol child) { .... while (sym?.nextSameName != null) { sym = sym.nextSameName; } Debug.Assert(sym != null && sym.nextSameName == null); sym.nextSameName = child; .... }
Aviso do PVS-Studio :
V3042 NullReferenceException possível. O '?' e '.' operadores são usados para acessar membros do objeto 'sym' SymbolStore.cs 56
Veja o que está acontecendo aqui. O ciclo termina quando uma das duas condições é atendida:
- sym == nulo ;
- sym.nextSameName == null .
Não há problema com a segunda condição, o que não pode ser dito sobre a primeira, pois abaixo é uma chamada incondicional ao campo da instância
nextSameName e, se
sym for
nulo , uma exceção do tipo
NullReferenceException será lançada
durante a chamada .
Você é cego? Há também uma chamada para
Debug.Assert , onde é verificado que
sym! = Null "- alguém pode se opor. Mas isso é tudo sal! Ao trabalhar na versão do
Debug.Assert, nada ajudará e, com o estado descrito acima, tudo o que obtemos é uma
NullReferenceException . Além disso, eu já vi um erro semelhante em outro projeto da Microsoft -
Roslyn , onde havia uma situação muito semelhante com o
Debug.Assert . Um pouco distraído por Roslyn com sua permissão.
O problema pode ser reproduzido usando
as bibliotecas
Microsoft.CodeAnalysis ou diretamente no Visual Studio usando o Syntax Visualizer. No Visual Studio versão 16.1.6 + Syntax Visualizer 1.0, esse problema ainda está sendo reproduzido.
Para reproduzir, o seguinte código é suficiente:
class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } }
Em seguida, no Syntax Visualizer, você precisa encontrar o nó da árvore de sintaxe do tipo
ConstantPatternSyntax , correspondente a
null no código, e solicitar
TypeSymbol para ele.
Depois disso, o Visual Studio será reiniciado. Se formos ao Visualizador de Eventos, encontraremos informações sobre problemas nas bibliotecas:
Application: devenv.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.Resources.MissingManifestResourceException at System.Resources.ManifestBasedResourceGroveler .HandleResourceStreamMissing(System.String) at System.Resources.ManifestBasedResourceGroveler.GrovelForResourceSet( System.Globalization.CultureInfo, System.Collections.Generic.Dictionary'2 <System.String,System.Resources.ResourceSet>, Boolean, Boolean, System.Threading.StackCrawlMark ByRef) at System.Resources.ResourceManager.InternalGetResourceSet( System.Globalization.CultureInfo, Boolean, Boolean, System.Threading.StackCrawlMark ByRef) at System.Resources.ResourceManager.InternalGetResourceSet( System.Globalization.CultureInfo, Boolean, Boolean) at System.Resources.ResourceManager.GetString(System.String, System.Globalization.CultureInfo) at Roslyn.SyntaxVisualizer.DgmlHelper.My. Resources.Resources.get_SyntaxNodeLabel() ....
E sobre o problema com devenv.exe:
Faulting application name: devenv.exe, version: 16.1.29102.190, time stamp: 0x5d1c133b Faulting module name: KERNELBASE.dll, version: 10.0.18362.145, time stamp: 0xf5733ace Exception code: 0xe0434352 Fault offset: 0x001133d2 ....
Tendo versões de depuração das bibliotecas Roslyn, você pode encontrar o local onde ocorreu a exceção:
private Conversion ClassifyImplicitBuiltInConversionSlow( TypeSymbol source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); if ( source.SpecialType == SpecialType.System_Void || destination.SpecialType == SpecialType.System_Void) { return Conversion.NoConversion; } .... }
Aqui, como no código acima das bibliotecas do .NET Core, também há uma verificação no
Debug.Assert , que, no entanto, não ajudou em nada ao usar as versões de lançamento das bibliotecas.
Edição 9Distraído um pouco - e basta, de volta às bibliotecas do .NET Core. O pacote
System.IO.IsolatedStorage contém o seguinte código interessante.
private bool ContainsUnknownFiles(string directory) { .... return (files.Length > 2 || ( (!IsIdFile(files[0]) && !IsInfoFile(files[0]))) || (files.Length == 2 && !IsIdFile(files[1]) && !IsInfoFile(files[1])) ); }
Aviso PVS-Studio :
V3088 A expressão foi colocada entre parênteses duas vezes: ((expressão)). Um par de parênteses é desnecessário ou a impressão incorreta está presente. IsolatedStorageFile.cs 839
Dizer que a formatação do código é confusa é não dizer nada. Olhando brevemente para este código, eu diria que o operando esquerdo do primeiro operador ||
> files.Length> 2 , o
caminho certo é entre parênteses. Pelo menos o código está formatado assim. Olhando um pouco mais de perto, você pode entender que isso não é verdade. De fato, o operando certo é
((! IsIdFile (arquivos [0]) &&! IsInfoFile (arquivos [0]))) . Na minha opinião, esse código é bastante confuso.
Edição 10No release do PVS-Studio 7.03, foi adicionada a regra de diagnóstico V3138, que procura erros nas linhas interpoladas. Mais precisamente, nas linhas com maior probabilidade de serem interpoladas, mas por causa do caractere
$ ausente, elas não são. As bibliotecas
System.Net encontraram várias respostas interessantes a essa regra de diagnóstico.
internal static void CacheCredential(SafeFreeCredentials newHandle) { try { .... } catch (Exception e) { if (!ExceptionCheck.IsFatal(e)) { NetEventSource.Fail(null, "Attempted to throw: {e}"); } } }
PVS-Studio Warning :
V3138 O literal de
sequência contém uma potencial expressão interpolada. Considere inspecionar: e. SSPIHandleCache.cs 42
É muito provável que o segundo argumento para o método
Fail seja uma string interpolada, na qual a representação de string da exceção
e seja substituída. No entanto, devido ao caractere
$ ausente, nenhuma representação de seqüência da exceção é lançada.
Edição 11Eu conheci outro caso semelhante.
public static async Task<string> GetDigestTokenForCredential(....) { .... if (NetEventSource.IsEnabled) NetEventSource.Error(digestResponse, "Algorithm not supported: {algorithm}"); .... }
PVS-Studio Warning : O literal da string
V3138 contém uma expressão interpolada em potencial. Considere inspecionar: algoritmo. AuthenticationHelper.Digest.cs 58
A situação é semelhante à descrita acima, o símbolo
$ é pulado novamente - a linha errada vai para o método
Error .
Edição 12Pacote
System.Net.Mail . O método é pequeno, vou trazê-lo na íntegra para que o erro seja um pouco mais interessante.
internal void SetContent(Stream stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (_streamSet) { _stream.Close(); _stream = null; _streamSet = false; } _stream = stream; _streamSet = true; _streamUsedOnce = false; TransferEncoding = TransferEncoding.Base64; }
PVS-Studio Warning :
V3008 A variável '_streamSet' recebe valores duas vezes sucessivamente. Talvez isso seja um erro. Verifique as linhas: 123, 119. MimePart.cs 123
A atribuição dupla do valor da variável
_streamSet parece estranha (a princípio - sob a condição; depois - fora). A mesma história com zerar a variável
_stream . Como resultado,
_stream ainda será definido como
fluxo e
_streamSet como
true .
Edição 13Um local interessante da biblioteca
System.Linq.Expressions , para a qual o analisador emitiu imediatamente 2 avisos. Nesse caso, é mais um recurso do que um bug, mas, no entanto, o método é muito interessante ...
Avisos do PVS-Studio :
- V3010 O valor de retorno da função 'GetType' deve ser utilizado. Instruction.cs 36
- V3080 Possível desreferência nula. Considere inspecionar 'o'. Instruction.cs 36
Provavelmente não há nada para comentar.
Edição 14Vejamos outro caso com o qual trabalharemos "de fora". Primeiro, escreveremos o código, identificaremos os problemas e depois examinaremos o interior. Para estudar, pegue a biblioteca
System.Configuration.ConfigurationManager e o pacote NuGet com o mesmo nome. Eu usei a versão do pacote 4.5.0. Iremos trabalhar com a classe
System.Configuration.CommaDelimitedStringCollection .
Vamos fazer algo não muito complicado. Por exemplo, crie um objeto, extraia sua representação de string, obtenha o comprimento dessa string e imprima-a. Código relevante:
CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); Console.WriteLine(collection.ToString().Length);
Apenas no caso, veja a descrição do método
ToString :
Nada fora do comum - a representação em cadeia do objeto é simplesmente retornada. Apenas por precaução, também analiso docs.microsoft.com - "
Método CommaDelimitedStringCollection.ToString ". Parece não ser nada de especial.
Ok, execute o código para execução, ii ...
Hmm, inesperadamente. Bem, vamos tentar adicionar um elemento à coleção e obter sua representação de string. "Completamente por acidente", adicionaremos uma string vazia :). O código mudará e ficará assim:
CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); collection.Add(String.Empty); Console.WriteLine(collection.ToString().Length);
Nós lançamos e vemos ...
O que, de novo ?! Bem, vamos finalmente dar uma olhada na implementação do método
ToString da classe
CommaDelimitedStringCollection . O código é apresentado abaixo:
public override string ToString() { if (Count <= 0) return null; StringBuilder sb = new StringBuilder(); foreach (string str in this) { ThrowIfContainsDelimiter(str);
Avisos do PVS-Studio :
- V3108 Não é recomendável retornar 'null' do método 'ToSting ()'. StringAttributeCollection.cs 57
- V3108 Não é recomendável retornar 'null' do método 'ToSting ()'. StringAttributeCollection.cs 71
Aqui vemos dois lugares onde a implementação atual do
ToString pode retornar
nula . Lembre-se do que a Microsoft recomenda ao implementar o método
ToString , para o qual voltamos novamente a docs.microsoft.com - "
Método Object.ToString ":
Notas para os herdeiros .... As substituições do método ToString () devem seguir estas diretrizes:- ....
- Sua substituição ToString () não deve retornar vazia ou uma seqüência nula .
- ....
Na verdade, é sobre isso que o PVS-Studio alerta. Os dois trechos de código acima que escrevemos para reproduzir o problema atingem diferentes pontos de saída - o primeiro e o segundo lugares onde retornos
nulos , respectivamente. Cavando um pouco mais fundo.
O primeiro caso.
Count - uma propriedade da classe base
StringCollection . Como nenhum elemento foi adicionado,
Contagem == 0 , a condição
Contagem <= 0 é atendida,
nulo é retornado.
No segundo caso, adicionamos um elemento usando o método de instância
CommaDelimitedStringCollection.Add para isso.
public new void Add(string value) { ThrowIfReadOnly(); ThrowIfContainsDelimiter(value); _modified = true; base.Add(value.Trim()); }
As verificações no método
ThrowIf ... passam com êxito e o item é adicionado à coleção base. Assim, o valor de
Count se torna igual a 1. Agora, retornamos ao método
ToString . O valor da expressão
Count <= 0 é
false , portanto, não há como sair do método e o código continua sendo executado. A travessia da coleção interna começa e 2 elementos são adicionados ao
StringBuilder - uma sequência vazia e uma vírgula. Como resultado, verifica-se que
sb contém apenas uma vírgula, o valor da propriedade
Length , respectivamente, é um. O valor da expressão
sb.Length> 0 é
verdadeiro , subtração e gravação para
sb.Length é executado , agora o valor de
sb.Length é 0. Isso leva ao fato de que o método retorna
nulo novamente.
Edição 15Inesperadamente, eu queria usar a classe
System.Configuration.ConfigurationProperty . Tome o construtor com mais parâmetros:
public ConfigurationProperty( string name, Type type, object defaultValue, TypeConverter typeConverter, ConfigurationValidatorBase validator, ConfigurationPropertyOptions options, string description);
Vamos ver a descrição do último parâmetro:
A descrição do construtor em docs.microsoft.com diz a mesma coisa. Bem, vamos dar uma olhada em como esse parâmetro é usado no corpo do construtor:
public ConfigurationProperty(...., string description) { ConstructorInit(name, type, options, validator, typeConverter); SetDefaultValue(defaultValue); }
E o parâmetro não é usado.
PVS-Studio Warning : O parâmetro
V3117 'description' do construtor não é usado. ConfigurationProperty.cs 62
Eles provavelmente não o usam de propósito, mas a descrição do parâmetro correspondente é confusa.
Edição 16Eu conheci outro lugar semelhante. Tente encontrar o erro você mesmo, o código do construtor está abaixo.
internal SectionXmlInfo( string configKey, string definitionConfigPath, string targetConfigPath, string subPath, string filename, int lineNumber, object streamVersion, string rawXml, string configSource, string configSourceStreamName, object configSourceStreamVersion, string protectionProviderName, OverrideModeSetting overrideMode, bool skipInChildApps) { ConfigKey = configKey; DefinitionConfigPath = definitionConfigPath; TargetConfigPath = targetConfigPath; SubPath = subPath; Filename = filename; LineNumber = lineNumber; StreamVersion = streamVersion; RawXml = rawXml; ConfigSource = configSource; ConfigSourceStreamName = configSourceStreamName; ProtectionProviderName = protectionProviderName; OverrideModeSetting = overrideMode; SkipInChildApps = skipInChildApps; }
PVS-Studio :
V3117 Constructor parameter 'configSourceStreamVersion' is not used. SectionXmlInfo.cs 16
, :
internal object ConfigSourceStreamVersion { set { } }
, . , / , .
Issue 17,
System.Runtime.WindowsRuntime.UI.Xaml NuGet .
public struct RepeatBehavior : IFormattable { .... public override string ToString() { return InternalToString(null, null); } .... }
PVS-Studio :
V3108 It is not recommended to return 'null' from 'ToSting()' method. RepeatBehavior.cs 113
, —
ToString null . - , ,
RepeatBehavior.ToString , - . , Microsoft.
, ,
ToString null —
InternalToString .
internal string InternalToString(string format, IFormatProvider formatProvider) { switch (_Type) { case RepeatBehaviorType.Forever: return "Forever"; case RepeatBehaviorType.Count: StringBuilder sb = new StringBuilder(); sb.AppendFormat( formatProvider, "{0:" + format + "}x", _Count); return sb.ToString(); case RepeatBehaviorType.Duration: return _Duration.ToString(); default: return null; } }
, ,
switch default ,
InternalToString null , ,
null ToString .
RepeatBehavior — ,
ToString — , .
RepeatBehavior ,
ToString , ,
_Type RepeatBehaviorType.Forever ,
RepeatBehaviorType.Count RepeatBehaviorType.Duration .
_Type — , :
public struct RepeatBehavior : IFormattable { .... private RepeatBehaviorType _Type; .... public RepeatBehaviorType Type { get { return _Type; } set { _Type = value; } } .... }
. , ,
RepeatBehaviorType .
public enum RepeatBehaviorType { Count, Duration, Forever }
,
RepeatBehaviorType — , .
switch . , , ,
default .
System.Runtime.WindowsRuntime.UI.Xaml ( 4.3.0) .
RepeatBehavior behavior = new RepeatBehavior() { Type = (RepeatBehaviorType)666 }; Console.WriteLine(behavior.ToString() is null);
True ,
ToString null , ..
_Type case default . , , .
, ,
docs.microsoft.com , ,
null .
Issue 18System.Private.DataContractSerialization .
private static class CharType { public const byte None = 0x00; public const byte FirstName = 0x01; public const byte Name = 0x02; public const byte Whitespace = 0x04; public const byte Text = 0x08; public const byte AttributeText = 0x10; public const byte SpecialWhitespace = 0x20; public const byte Comment = 0x40; } private static byte[] s_charType = new byte[256] { .... CharType.None, CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, CharType.None, CharType.None, CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace, CharType.None, .... };
PVS-Studio :
- V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' operador. XmlUTF8TextReader.cs 56
- V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' operador. XmlUTF8TextReader.cs 58
- V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' operador. XmlUTF8TextReader.cs 64
CharType.Comment| CharType.Comment . ,
(CharType.Comment | CharType.Comment) == CharType.Comment . ,
CharType.Comment , .
Issue 19.
XmlBinaryWriterSession.TryAdd docs.microsoft.com — "
XmlBinaryWriterSession.TryAdd(XmlDictionaryString, Int32) Method ":
Returns: true if the string could be added; otherwise, false.:
public virtual bool TryAdd(XmlDictionaryString value, out int key) { IntArray keys; if (value == null) throw System.Runtime .Serialization .DiagnosticUtility .ExceptionUtility .ThrowHelperArgumentNull(nameof(value)); if (_maps.TryGetValue(value.Dictionary, out keys)) { key = (keys[value.Key] - 1); if (key != -1) {
PVS-Studio :
V3009 It's odd that this method always returns one and the same value of 'true'. XmlBinaryWriterSession.cs 29
,
true , ,
false .
Issue 20, —
false :
internal virtual bool OnHandleReference(....) { if (xmlWriter.depth < depthToCheckCyclicReference) return false; if (canContainCyclicReference) { if (_byValObjectsInScope.Contains(obj)) throw ....; _byValObjectsInScope.Push(obj); } return false; }
PVS-Studio :
V3009 It's odd that this method always returns one and the same value of 'false'. XmlObjectSerializerWriteContext.cs 415
, ! , , — , , , …
, , . :)
Issue 21System.Security.Cryptography.Algorithms .
public override byte[] GenerateMask(byte[] rgbSeed, int cbReturn) { using (HashAlgorithm hasher = (HashAlgorithm)CryptoConfig.CreateFromName(_hashNameValue)) { byte[] rgbCounter = new byte[4]; byte[] rgbT = new byte[cbReturn]; uint counter = 0; for (int ib = 0; ib < rgbT.Length;) {
PVS-Studio :
V3080 Possible null dereference. Consider inspecting 'hasher'. PKCS1MaskGenerationMethod.cs 37
,
hasher.TransformBlock hasher null ,
NullReferenceException . .
, ,
hasher null ,
CreateFromName .
public static object CreateFromName(string name) { return CreateFromName(name, null); }
— .
CreateFromName , .
public static object CreateFromName(string name, params object[] args) { .... if (retvalType == null) { return null; } .... if (cons == null) { return null; } .... if (candidates.Count == 0) { return null; } .... if (rci == null || typeof(Delegate).IsAssignableFrom(rci.DeclaringType)) { return null; } .... return retval; }
, ,
null . , , , ,
NullReferenceException .
— , . . .
public class PKCS1MaskGenerationMethod : ....
:
1, 3 .
public . , — .
2 . — , — , . , .
4 .
CreateFromName null — , .
5, 6 .
cbReturn > 0 (, , ).
cbReturn > 0 ib < rgbT.Length .
7 .
Helpres.ConvertIntToByteArray .
, , , :
- rgbCeed — new byte[] { 0, 1, 2, 3 };
- cbReturn — 42.
, «»
CryptoConfig.CreateFromName ,
_hashNameValue . , , - :
public string HashName { get { return _hashNameValue; } set { _hashNameValue = value ?? DefaultHash; } }
''
HashName ( —
_hashNameValue ),
null CreateFromName . (, ), .
,
NullReferenceException , :
PKCS1MaskGenerationMethod tempObj = new PKCS1MaskGenerationMethod(); tempObj.HashName = "Dummy"; tempObj.GenerateMask(new byte[] { 1, 2, 3 }, 42);
, :
NuGet 4.3.1.
, , docs.microsoft.com — "
PKCS1MaskGenerationMethod.GenerateMask(Byte[], Int32) Method ".
, 2 «» :
OutOfMemoryException .
NullReferenceException rgbSeed.Length . ,
hasher ,
rgbSeed.Length .
Issue 22.
public class SignatureDescription { .... public string FormatterAlgorithm { get; set; } public string DeformatterAlgorithm { get; set; } public SignatureDescription() { } .... public virtual AsymmetricSignatureDeformatter CreateDeformatter( AsymmetricAlgorithm key) { AsymmetricSignatureDeformatter item = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName(DeformatterAlgorithm); item.SetKey(key);
PVS-Studio :
- V3080 Possible null dereference. Consider inspecting 'item'. SignatureDescription.cs 31
- V3080 Possible null dereference. Consider inspecting 'item'. SignatureDescription.cs 38
,
FormatterAlgorithm DeformatterAlgorithm ,
CryptoConfig.CreateFromName null CreateDeformatter CreateFormatter . ,
SetKey NullReferenceException . , , :
SignatureDescription signature = new SignatureDescription() { DeformatterAlgorithm = "Dummy", FormatterAlgorithm = "Dummy" }; signature.CreateDeformatter(null);
CreateDeformatter ,
CreateFormatter NullReferenceException .
Issue 23System.Private.Xml .
public override void WriteBase64(byte[] buffer, int index, int count) { if (!_inAttr && (_inCDataSection || StartCDataSection())) _wrapped.WriteBase64(buffer, index, count); else _wrapped.WriteBase64(buffer, index, count); }
PVS-Studio :
V3004 The 'then' statement is equivalent to the 'else' statement. QueryOutputWriterV1.cs 242
,
then else if . , ,
if .
Issue 24 internal void Depends(XmlSchemaObject item, ArrayList refs) { .... if (content is XmlSchemaSimpleTypeRestriction) { baseType = ((XmlSchemaSimpleTypeRestriction)content).BaseType; baseName = ((XmlSchemaSimpleTypeRestriction)content).BaseTypeName; } else if (content is XmlSchemaSimpleTypeList) { .... } else if (content is XmlSchemaSimpleTypeRestriction) { baseName = ((XmlSchemaSimpleTypeRestriction)content).BaseTypeName; } else if (t == typeof(XmlSchemaSimpleTypeUnion)) { .... } .... }
PVS-Studio :
V3003 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 381, 396. ImportContext.cs 381
if-else-if —
content is XmlSchemaSimpleTypeRestriction . —
then - . ,
then - ( ), , , .
Issue 25, .
public bool MatchesXmlType(IList<XPathItem> seq, int indexType) { XmlQueryType typBase = GetXmlType(indexType); XmlQueryCardinality card; switch (seq.Count) { case 0: card = XmlQueryCardinality.Zero; break; case 1: card = XmlQueryCardinality.One; break; default: card = XmlQueryCardinality.More; break; } if (!(card <= typBase.Cardinality)) return false; typBase = typBase.Prime; for (int i = 0; i < seq.Count; i++) { if (!CreateXmlType(seq[0]).IsSubtypeOf(typBase)) return false; } return true; }
— !
— PVS-Studio :
V3102 Suspicious access to element of 'seq' object by a constant index inside a loop. XmlQueryRuntime.cs 738
for ,
i < seq.Count . ,
seq . (
seq[i] ), — (
seq[0] ).
Issue 26, .
public override void WriteValue(string value) { WriteValue(value); }
PVS-Studio :
V3110 Possible infinite recursion inside 'WriteValue' method. XmlAttributeCache.cs 166
, .
Issue 27 public IList<XPathNavigator> DocOrderDistinct(IList<XPathNavigator> seq) { if (seq.Count <= 1) return seq; XmlQueryNodeSequence nodeSeq = (XmlQueryNodeSequence)seq; if (nodeSeq == null) nodeSeq = new XmlQueryNodeSequence(seq); return nodeSeq.DocOrderDistinct(_docOrderCmp); }
PVS-Studio :
V3095 The 'seq' object was used before it was verified against null. Check lines: 880, 884. XmlQueryRuntime.cs 880
null , -
Count NullReferenceException .
nodeSeq ,
seq , — .
seq —
null , - .
seq —
null , :
- InvalidCastException , ;
- , nodeSeq , null .
Issue 284 , . , , .
PVS-Studio :
- V3117 Constructor parameter 'securityUrl' is not used. XmlSecureResolver.cs 15
- V3117 Constructor parameter 'strdata' is not used. XmlEntity.cs 18
- V3117 Constructor parameter 'location' is not used. Compilation.cs 58
- V3117 Constructor parameter 'access' is not used. XmlSerializationILGen.cs 38
( , ). O que? . , .
public XmlSecureResolver(XmlResolver resolver, string securityUrl) { _resolver = resolver; }
, docs.microsoft.com — "
XmlSecureResolver Constructors "
securityUrl :
The URL used to create the PermissionSet that will be applied to the underlying XmlResolver. The XmlSecureResolver calls PermitOnly() on the created PermissionSet before calling GetEntity(Uri, String, Type) on the underlying XmlResolver.Issue 29System.Private.Uri , Microsoft
ToString . "
Object.ToString Method ":
Your ToString() override should not throw an exception .:
public override string ToString() { if (_username.Length == 0 && _password.Length > 0) { throw new UriFormatException(SR.net_uri_BadUserPassword); } .... }
PVS-Studio :
V3108 It is not recommended to throw exceptions from 'ToSting()' method. UriBuilder.cs 406
,
UserName Password _username _password ,
ToString , . :
UriBuilder uriBuilder = new UriBuilder() { UserName = String.Empty, Password = "Dummy" }; String stringRepresentation = uriBuilder.ToString(); Console.WriteLine(stringRepresentation);
, — , docs.microsoft.com — "
UriBuilder.ToString Method ".
Issue 30,
System.Data.Common .
private ArrayList _tables; private DataTable GetTable(string tableName, string ns) { .... if (_tables.Count == 0) return (DataTable)_tables[0]; .... }
PVS-Studio :
V3106 Possibly index is out of bound. The '0' index is pointing beyond '_tables' bound. XMLDiffLoader.cs 277
? , ?
ArgumentOutOfRangeException ? , , . , .
Issue 31 internal XmlNodeOrder ComparePosition(XPathNodePointer other) { RealFoliate(); other.RealFoliate(); Debug.Assert(other != null); .... }
PVS-Studio :
V3095 The 'other' object was used before it was verified against null. Check lines: 1095, 1096. XPathNodePointer.cs 1095
other != null Debug.Assert ,
ComparePosition null . , .
other RealFoliate . ,
other null ,
NullReferenceException Assert .
Issue 32 private PropertyDescriptorCollection GetProperties(Attribute[] attributes) { .... foreach (Attribute attribute in attributes) { Attribute attr = property.Attributes[attribute.GetType()]; if ( (attr == null && !attribute.IsDefaultAttribute()) || !attr.Match(attribute)) { match = false; break; } } .... }
PVS-Studio :
V3080 Possible null dereference. Consider inspecting 'attr'. DbConnectionStringBuilder.cs 534
if .
Match — .
attr == null ,
null — () . , || ,
attr —
null ,
NullReferenceException .
, :
- attr — null . &&.
- !attribute.IsDefaultAttribute() — false . && — false .
- || false , .
- attr — null , Match .
Issue 33 private int ReadOldRowData( DataSet ds, ref DataTable table, ref int pos, XmlReader row) { .... if (table == null) { row.Skip();
PVS-Studio :
V3021 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains method return. This means that the second 'if' statement is senseless XMLDiffLoader.cs 301
if , —
table == null . then- — -1, — .
table . , .
Issue 34System.ComponentModel.TypeConverter . , :
Removes the last character from the formatted string. (Remove last character in virtual string). On exit the out param contains the position where the operation was actually performed. This position is relative to the test string. The MaskedTextResultHint out param gives more information about the operation result. Returns true on success, false otherwise.: ,
true , —
false . , .
public bool Remove(out int testPosition, out MaskedTextResultHint resultHint) { .... if (lastAssignedPos == INVALID_INDEX) { .... return true;
PVS-Studio :
V3009 It's odd that this method always returns one and the same value of 'true'. MaskedTextProvider.cs 1529
, —
true .
Issue 35 public void Clear() { if (_table != null) { .... } if (_table.fInitInProgress && _delayLoadingConstraints != null) { .... } .... }
PVS-Studio :
V3125 The '_table' object was used after it was verified against null. Check lines: 437, 423. ConstraintCollection.cs 437
_table != null —
_table null . , .
_table null —
_table .fInitInProgress .
Issue 36,
System.Runtime.Serialization.Formatters .
private void Write(....) { .... if (memberNameInfo != null) { .... _serWriter.WriteObjectEnd(memberNameInfo, typeNameInfo); } else if ((objectInfo._objectId == _topId) && (_topName != null)) { _serWriter.WriteObjectEnd(topNameInfo, typeNameInfo); .... } else if (!ReferenceEquals(objectInfo._objectType, Converter.s_typeofString)) { _serWriter.WriteObjectEnd(typeNameInfo, typeNameInfo); } }
PVS-Studio :
V3038 The argument was passed to method several times. It is possible that other argument should be passed instead. BinaryObjectWriter.cs 262
_serWriter.WriteObjectEnd —
typeNameInfo . , . ,
WriteObjectEnd .
internal void WriteObjectEnd(NameInfo memberNameInfo, NameInfo typeNameInfo) { }
… . :)
Issue 37 internal void WriteSerializationHeader( int topId, int headerId, int minorVersion, int majorVersion) { var record = new SerializationHeaderRecord( BinaryHeaderEnum.SerializedStreamHeader, topId, headerId, minorVersion, majorVersion); record.Write(this); }
, , . , .
PVS-Studio :
V3066 Possible incorrect order of arguments passed to 'SerializationHeaderRecord' constructor: 'minorVersion' and 'majorVersion'. BinaryFormatterWriter.cs 111
SerializationHeaderRecord .
internal SerializationHeaderRecord( BinaryHeaderEnum binaryHeaderEnum, int topId, int headerId, int majorVersion, int minorVersion) { _binaryHeaderEnum = binaryHeaderEnum; _topId = topId; _headerId = headerId; _majorVersion = majorVersion; _minorVersion = minorVersion; }
,
majorVersion ,
minorVersion ;
minorVersion ,
majorVersion . . ( ?) — , .
Issue 38 internal ObjectManager( ISurrogateSelector selector, StreamingContext context, bool checkSecurity, bool isCrossAppDomain) { _objects = new ObjectHolder[DefaultInitialSize]; _selector = selector; _context = context; _isCrossAppDomain = isCrossAppDomain; }
PVS-Studio :
V3117 Constructor parameter 'checkSecurity' is not used. ObjectManager.cs 33
checkSecurity . . , , , .
Issue 39, . 1 1 . :
:
private void EnlargeArray() { int newLength = _values.Length * 2; if (newLength < 0) { if (newLength == int.MaxValue) { throw new SerializationException(SR.Serialization_TooManyElements); } newLength = int.MaxValue; } FixupHolder[] temp = new FixupHolder[newLength]; Array.Copy(_values, 0, temp, 0, _count); _values = temp; }
PVS-Studio :
- V3022 Expression 'newLength == int.MaxValue' is always false. ObjectManager.cs 1423
- V3022 Expression 'newLength == int.MaxValue' is always false. ObjectManager.cs 1511
- V3022 Expression 'newLength == int.MaxValue' is always false. ObjectManager.cs 1558
, —
temp (
FixupHolder ,
long object ). - copy-paste…
Issue 40System.Data.Odbc .
public string UnquoteIdentifier(....) { .... if (!string.IsNullOrEmpty(quotePrefix) || quotePrefix != " ") { .... } .... }
PVS-Studio :
V3022 Expression '!string.IsNullOrEmpty(quotePrefix) || quotePrefix != " "' is always true. OdbcCommandBuilder.cs 338
,
true . . ,
quotePrefix — . .
||, ,
true , ( )
true . . ,
false . , ,
true , —
false ,
true .
, ,
string.IsNullOrEmpty(quotePrefix) —
true , , :
- quotePrefix == null ;
- quotePrefix.Length == 0 .
quotePrefix != " " , . , —
true ,
quotePrefix .
Issue 41:
private sealed class PendingGetConnection { public PendingGetConnection( long dueTime, DbConnection owner, TaskCompletionSource<DbConnectionInternal> completion, DbConnectionOptions userOptions) { DueTime = dueTime; Owner = owner; Completion = completion; } public long DueTime { get; private set; } public DbConnection Owner { get; private set; } public TaskCompletionSource<DbConnectionInternal> Completion { get; private set; } public DbConnectionOptions UserOptions { get; private set; } }
PVS-Studio :
V3117 Constructor parameter 'userOptions' is not used. DbConnectionPool.cs 26
, —
userOptions , , . , .
Issue 42, 2 . .
private DataTable ExecuteCommand(....) { .... foreach (DataRow row in schemaTable.Rows) { resultTable.Columns .Add(row["ColumnName"] as string, (Type)row["DataType"] as Type); } .... }
PVS-Studio :
- V3051 An excessive type cast. The object is already of the 'Type' type. DbMetaDataFactory.cs 176
- V3051 An excessive type cast. The object is already of the 'Type' type. OdbcMetaDataFactory.cs 1109
(Type)row[«DataType»] as Type . , —
as .
row[«DataType»] —
null , ''
Add .
row[«DataType»] ,
Type ,
InvalidCastException . , ? A questão está aberta.
Issue 43System.Runtime.InteropServices.RuntimeInformation .
public static string FrameworkDescription { get { if (s_frameworkDescription == null) { string versionString = (string)AppContext.GetData("FX_PRODUCT_VERSION"); if (versionString == null) { .... versionString = typeof(object).Assembly .GetCustomAttribute< AssemblyInformationalVersionAttribute>() ?.InformationalVersion; .... int plusIndex = versionString.IndexOf('+'); .... } .... } .... } }
PVS-Studio :
V3105 The 'versionString' variable was used after it was assigned through null-conditional operator. NullReferenceException is possible. RuntimeInformation.cs 29
NullReferenceException IndexOf versionString . '?.',
NullReferenceException InfromationalVersion . ,
GetCustomAttribute<...> null , , —
IndexOf ,
versionString null .
Issue 44System.ComponentModel.Composition . 2 :
public static bool CanSpecialize(....) { .... object[] genericParameterConstraints = ....; GenericParameterAttributes[] genericParameterAttributes = ....;
PVS-Studio :
- V3125 The 'genericParameterConstraints' object was used after it was verified against null. Check lines: 603, 589. GenericSpecializationPartCreationInfo.cs 603
- V3125 The 'genericParameterAttributes' object was used after it was verified against null. Check lines: 604, 594. GenericSpecializationPartCreationInfo.cs 604
genericParameterAttributes != null genericParameterConstraints != null . ,
null — , .
null , — . , -
null , ? , ,
NullReferenceException .
Issue 45. , — , . NuGet prerelease ( 4.6.0-preview6.19303.8). , , :
LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(null); Console.WriteLine(eq);
Equals , docs.microsoft.com .NET Core, .NET Framework. ("
LazyMemberInfo.Equals(Object) Method ") — —
true false , .
:
, :
LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(typeof(String)); Console.WriteLine(eq);
.
, .
Equals .
public override bool Equals(object obj) { LazyMemberInfo that = (LazyMemberInfo)obj;
PVS-Studio :
V3115 Passing 'null' to 'Equals' method should not result in 'NullReferenceException'. LazyMemberInfo.cs 116
, ,
that._memberType . , , —
(LazyMemberInfo)obj . .
InvalidCastException , , .
NullReferenceException ? ,
LazyMemberInfo — , , .
null NullReferenceException . — . .
Issue 46- , ,
System.Drawing.Common ,
TriState .
public override bool Equals(object o) { TriState state = (TriState)o; return _value == state._value; }
PVS-Studio :
V3115 Passing 'null' to 'Equals' method should not result in 'NullReferenceException'. TriState.cs 53
, .
Issue 47System.Text.Json .
, ,
ToString null ? .
public override string ToString() { switch (TokenType) { case JsonTokenType.None: case JsonTokenType.Null: return string.Empty; case JsonTokenType.True: return bool.TrueString; case JsonTokenType.False: return bool.FalseString; case JsonTokenType.Number: case JsonTokenType.StartArray: case JsonTokenType.StartObject: {
null , .
PVS-Studio :
V3108 It is not recommended to return 'null' from 'ToSting()' method. JsonElement.cs 1460
GetString() . :
public string GetString() { CheckValidInstance(); return _parent.GetString(_idx, JsonTokenType.String); }
—
GetString :
internal string GetString(int index, JsonTokenType expectedType) { .... if (tokenType == JsonTokenType.Null) { return null; } .... }
,
null — ,
ToString .
Issue 48:
internal JsonPropertyInfo CreatePolymorphicProperty(....) { JsonPropertyInfo runtimeProperty = CreateProperty(property.DeclaredPropertyType, runtimePropertyType, property.ImplementedPropertyType, property?.PropertyInfo, Type, options); property.CopyRuntimeSettingsTo(runtimeProperty); return runtimeProperty; }
PVS-Studio :
V3042 Possible NullReferenceException. The '?.' and '.' operators are used for accessing members of the 'property' object JsonClassInfo.AddProperty.cs 179
CreateProperty property :
property.DeclaredPropertyType ,
property.ImplementedPropertyType ,
property?.PropertyInfo . , '?.'.
property null , ,
NullReferenceException .
Issue 49System.Security.Cryptography.Xml , . copy-paste, .
:
public void Write(StringBuilder strBuilder, DocPosition docPos, AncestralNamespaceContextManager anc) { docPos = DocPosition.BeforeRootElement; foreach (XmlNode childNode in ChildNodes) { if (childNode.NodeType == XmlNodeType.Element) { CanonicalizationDispatcher.Write( childNode, strBuilder, DocPosition.InRootElement, anc); docPos = DocPosition.AfterRootElement; } else { CanonicalizationDispatcher.Write(childNode, strBuilder, docPos, anc); } } }
:
public void WriteHash(HashAlgorithm hash, DocPosition docPos, AncestralNamespaceContextManager anc) { docPos = DocPosition.BeforeRootElement; foreach (XmlNode childNode in ChildNodes) { if (childNode.NodeType == XmlNodeType.Element) { CanonicalizationDispatcher.WriteHash( childNode, hash, DocPosition.InRootElement, anc); docPos = DocPosition.AfterRootElement; } else { CanonicalizationDispatcher.WriteHash(childNode, hash, docPos, anc); } } }
PVS-Studio :
- V3061 Parameter 'docPos' is always rewritten in method body before being used. CanonicalXmlDocument.cs 37
- V3061 Parameter 'docPos' is always rewritten in method body before being used. CanonicalXmlDocument.cs 54
docPos , , , , , .
Issue 50System.Data.SqlClient .
private bool IsBOMNeeded(MetaType type, object value) { if (type.NullableType == TdsEnums.SQLXMLTYPE) { Type currentType = value.GetType(); if (currentType == typeof(SqlString)) { if (!((SqlString)value).IsNull && ((((SqlString)value).Value).Length > 0)) { if ((((SqlString)value).Value[0] & 0xff) != 0xff) return true; } } else if ((currentType == typeof(string)) && (((String)value).Length > 0)) { if ((value != null) && (((string)value)[0] & 0xff) != 0xff) return true; } else if (currentType == typeof(SqlXml)) { if (!((SqlXml)value).IsNull) return true; } else if (currentType == typeof(XmlDataFeed)) { return true;
PVS-Studio :
V3095 The 'value' object was used before it was verified against null. Check lines: 8696, 8708. TdsParser.cs 8696
value != null . , ,
value .
value null — .
Issue 51, , .
protected virtual TDSMessageCollection CreateQueryResponse(....) { .... if (....) { .... } else if ( lowerBatchText.Contains("name") && lowerBatchText.Contains("state") && lowerBatchText.Contains("databases") && lowerBatchText.Contains("db_name"))
PVS-Studio :
V3053 An excessive expression. Examine the substrings 'name' and 'db_name'. QueryEngine.cs 151
,
lowerBatchText.Contains(«name») lowerBatchText.Contains(«db_name») . ,
«db_name» ,
«name» .
«name» ,
«db_name» . ,
lowerBatchText.Contains(«name») . ,
«name» .
Issue 52System.Net.Requests .
protected override PipelineInstruction PipelineCallback( PipelineEntry entry, ResponseDescription response, ....) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Command:{entry?.Command} Description:{response?.StatusDescription}");
PVS-Studio :
V3125 The 'entry' object was used after it was verified against null. Check lines: 270, 227. FtpControlStream.cs 270
entry?.Command response?.Description . '.' '?.',
NullReferenceException , -
null . . , ,
null response ( ,
response == null ),
entry . ,
entry —
null , ,
entry.Command ( '.', '?.') .
, — , , .
? . :)
Issue 53-
System.Collections.Immutable .
System.Collections.Immutable.ImmutableArray<T> .
IStructuralEquatable.Equals IStructuralComparable.CompareTo .
IStructuralEquatable.Equals . , , :
bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { var self = this; Array otherArray = other as Array; if (otherArray == null) { var theirs = other as IImmutableArray; if (theirs != null) { otherArray = theirs.Array; if (self.array == null && otherArray == null) { return true; } else if (self.array == null) { return false; } } } IStructuralEquatable ours = self.array; return ours.Equals(otherArray, comparer); }
? — , . :)
PVS-Studio :
V3125 The 'ours' object was used after it was verified against null. Check lines: 1212, 1204. ImmutableArray_1.cs 1212
Equals ours ,
return , ,
NullReferenceException . ? , .
bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { .... if (....) { .... if (....) { .... if (self.array == null && otherArray == null) { .... } else if (self.array == null) { .... } } } IStructuralEquatable ours = self.array; return ours.Equals(otherArray, comparer); }
,
ours self.array .
self.array == null . ,
self.array ,
ours ,
null . , . ? . .
bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { var self = this;
1 .
self.array == this.array (-
self = this ). ,
this.array == null .
2 .
if , , .
if , ,
other Array other null .
as otherArray ,
if .
3 . .
if (,
theirs != null ).
then -, 5
self.array == null 4. ,
if 3, :
- other null ;
- other IImmutableArray .
5 .
self.array == null , , ,
NullReferenceException .
, .
:
this.array — null .
— :
- other — null ;
- other Array ;
- other Array , IImmutableArray .
array — , :
internal T[] array;
ImmutableArray<T> — , ( ),
array , —
null . , .
, , , .
, . , , , .
1 .
var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(null, comparer);
2 .
var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(new string[] { }, comparer);
3 .
var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(typeof(Object), comparer);
, .
Issue 54, , . :) . , .
int IStructuralComparable.CompareTo(object other, IComparer comparer) { var self = this; Array otherArray = other as Array; if (otherArray == null) { var theirs = other as IImmutableArray; if (theirs != null) { otherArray = theirs.Array; if (self.array == null && otherArray == null) { return 0; } else if (self.array == null ^ otherArray == null) { throw new ArgumentException( SR.ArrayInitializedStateNotEqual, nameof(other)); } } } if (otherArray != null) { IStructuralComparable ours = self.array; return ours.CompareTo(otherArray, comparer);
PVS-Studio :
V3125 The 'ours' object was used after it was verified against null. Check lines: 1265, 1251. ImmutableArray_1.cs 1265
, .
:
Object other = ....; var comparer = Comparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralComparable)immutableArray).CompareTo(other, comparer);
, ,
NullReferenceException .
:
other —
new String[]{ } ;
:
, , .
Issue 55System.Net.HttpListener , , , . copy-paste. , , :
public override IAsyncResult BeginRead(byte[] buffer, ....) { if (NetEventSource.IsEnabled) { NetEventSource.Enter(this); NetEventSource.Info(this, "buffer.Length:" + buffer.Length + " size:" + size + " offset:" + offset); } if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } .... }
PVS-Studio :
V3095 The 'buffer' object was used before it was verified against null. Check lines: 51, 53. HttpRequestStream.cs 51
ArgumentNullException buffer == null ,
null — . ,
NetEventSource.IsEnabled —
true ,
buffer —
null ,
NullReferenceException buffer.Length . ,
buffer == null .
PVS-Studio, :
- V3095 The 'buffer' object was used before it was verified against null. Check lines: 49, 51. HttpResponseStream.cs 49
- V3095 The 'buffer' object was used before it was verified against null. Check lines: 74, 75. HttpResponseStream.cs 74
Issue 56System.Transactions.Local .
internal override void EnterState(InternalTransaction tx) { if (tx._outcomeSource._isoLevel == IsolationLevel.Snapshot) { throw TransactionException.CreateInvalidOperationException( TraceSourceType.TraceSourceLtm, SR.CannotPromoteSnapshot, null, tx == null ? Guid.Empty : tx.DistributedTxId); } .... }
PVS-Studio :
V3095 The 'tx' object was used before it was verified against null. Check lines: 3282, 3285. TransactionState.cs 3282
InvalidOperationException .
tx ,
null ,
NullReferenceException tx.DistributedTxId . , ,
tx —
null ,
if tx —
tx._outcomeSource._isoLevel .
Issue 57System.Runtime.Caching .
internal void SetLimit(int cacheMemoryLimitMegabytes) { long cacheMemoryLimit = cacheMemoryLimitMegabytes; cacheMemoryLimit = cacheMemoryLimit << MEGABYTE_SHIFT; _memoryLimit = 0;
PVS-Studio :
V3022 Expression 'cacheMemoryLimit != 0 && _memoryLimit != 0' is always false. CacheMemoryMonitor.cs 250
, , —
cacheMemoryLimit != 0 && _memoryLimit != 0 —
false .
_memoryLimit 0 (
if ), &&
false , ,
false .
Issue 58System.Diagnostics.TraceSource .
public override object Pop() { StackNode n = _stack.Value; if (n == null) { base.Pop(); } _stack.Value = n.Prev; return n.Value; }
PVS-Studio :
V3125 The 'n' object was used after it was verified against null. Check lines: 115, 111. CorrelationManager.cs 115
. -
n == null ,
null — . —
NullReferenceException —
n.Prev .
n null ,
base.Pop() .
Issue 59System.Drawing.Primitives . . :
public static string ToHtml(Color c) { string colorString = string.Empty; if (c.IsEmpty) return colorString; if (ColorUtil.IsSystemColor(c)) { switch (c.ToKnownColor()) { case KnownColor.ActiveBorder: colorString = "activeborder"; break; case KnownColor.GradientActiveCaption: case KnownColor.ActiveCaption: colorString = "activecaption"; break; case KnownColor.AppWorkspace: colorString = "appworkspace"; break; case KnownColor.Desktop: colorString = "background"; break; case KnownColor.Control: colorString = "buttonface"; break; case KnownColor.ControlLight: colorString = "buttonface"; break; case KnownColor.ControlDark: colorString = "buttonshadow"; break; case KnownColor.ControlText: colorString = "buttontext"; break; case KnownColor.ActiveCaptionText: colorString = "captiontext"; break; case KnownColor.GrayText: colorString = "graytext"; break; case KnownColor.HotTrack: case KnownColor.Highlight: colorString = "highlight"; break; case KnownColor.MenuHighlight: case KnownColor.HighlightText: colorString = "highlighttext"; break; case KnownColor.InactiveBorder: colorString = "inactiveborder"; break; case KnownColor.GradientInactiveCaption: case KnownColor.InactiveCaption: colorString = "inactivecaption"; break; case KnownColor.InactiveCaptionText: colorString = "inactivecaptiontext"; break; case KnownColor.Info: colorString = "infobackground"; break; case KnownColor.InfoText: colorString = "infotext"; break; case KnownColor.MenuBar: case KnownColor.Menu: colorString = "menu"; break; case KnownColor.MenuText: colorString = "menutext"; break; case KnownColor.ScrollBar: colorString = "scrollbar"; break; case KnownColor.ControlDarkDark: colorString = "threeddarkshadow"; break; case KnownColor.ControlLightLight: colorString = "buttonhighlight"; break; case KnownColor.Window: colorString = "window"; break; case KnownColor.WindowFrame: colorString = "windowframe"; break; case KnownColor.WindowText: colorString = "windowtext"; break; } } else if (c.IsNamedColor) { if (c == Color.LightGray) {
-, … ? , , .
:
switch (c.ToKnownColor()) { .... case KnownColor.Control: colorString = "buttonface"; break; case KnownColor.ControlLight: colorString = "buttonface"; break; .... }
PVS-Studio :
V3139 Two or more case-branches perform the same actions. ColorTranslator.cs 302
, , - . , ,
case , . copy-paste , .
.
ToHtml «buttonface» , ():
- SystemColors.Control ;
- SystemColors.ControlLight .
ARGB, :
- SystemColors.Control — (255, 240, 240, 240) ;
- SystemColors.ControlLight — (255, 227, 227, 227) .
(
«buttonface» ) —
FromHtml ,
Control (255, 240, 240, 240) .
FromHtml ControlLight ? Sim , ( ) . :
s_htmlSysColorTable["threedhighlight"] = ColorUtil.FromKnownColor(KnownColor.ControlLight);
,
FromHtml ControlLight (255, 227, 227, 227) «threedhighlight» . ,
case KnownColor.ControlLight .
Issue 60System.Text.RegularExpressions .
internal virtual string TextposDescription() { var sb = new StringBuilder(); int remaining; sb.Append(runtextpos); if (sb.Length < 8) sb.Append(' ', 8 - sb.Length); if (runtextpos > runtextbeg) sb.Append(RegexCharClass.CharDescription(runtext[runtextpos - 1])); else sb.Append('^'); sb.Append('>'); remaining = runtextend - runtextpos; for (int i = runtextpos; i < runtextend; i++) { sb.Append(RegexCharClass.CharDescription(runtext[i])); } if (sb.Length >= 64) { sb.Length = 61; sb.Append("..."); } else { sb.Append('$'); } return sb.ToString(); }
PVS-Studio :
V3137 The 'remaining' variable is assigned but is not used by the end of the function. RegexRunner.cs 612
remaining - , . , - , , , . - .
Issue 61 public void AddRange(char first, char last) { _rangelist.Add(new SingleRange(first, last)); if (_canonical && _rangelist.Count > 0 && first <= _rangelist[_rangelist.Count - 1].Last) { _canonical = false; } }
PVS-Studio :
V3063 A part of conditional expression is always true if it is evaluated: _rangelist.Count > 0. RegexCharClass.cs 523
, —
_rangelist.Count > 0 —
true , . ,
_rangelist , , —
_rangelist.Add(....) — .
Issue 62V3128 System.Drawing.Common System.Transactions.Local .
private class ArrayEnumerator : IEnumerator { private object[] _array; private object _item; private int _index; private int _startIndex; private int _endIndex; public ArrayEnumerator(object[] array, int startIndex, int count) { _array = array; _startIndex = startIndex; _endIndex = _index + count; _index = _startIndex; } .... }
PVS-Studio :
V3128 The '_index' field is used before it is initialized in constructor. PrinterSettings.Windows.cs 1679
_endIndex —
_index , —
default(int) ,
0 .
_index . —
_index , .
Issue 63 internal class TransactionTable { .... private int _timerInterval; .... internal TransactionTable() {
PVS-Studio :
V3128 The '_timerInterval' field is used before it is initialized in constructor. TransactionTable.cs 151
.
_timerInterval (
default(int) )
_timer ,
_timerInterval .
Issue 64, . , .
copy-paste , .
private bool ProcessNotifyConnection(....) { .... WeakReference reference = (WeakReference)( LdapConnection.s_handleTable[referralFromConnection]); if ( reference != null && reference.IsAlive && null != ((LdapConnection)reference.Target)._ldapHandle) { .... } .... }
PVS-Studio () : VXXXX TODO_MESSAGE. LdapSessionOptions.cs 974
,
reference.IsAlive ,
WeakReference ,
reference.Target null . —
_ldapHandle NullReferenceException . , IsAlive Microsoft. docs.microsoft.com — "
WeakReference.IsAlive Property ":
Because an object could potentially be reclaimed for garbage collection immediately after the IsAlive property returns true, using this property is not recommended unless you are testing only for a false return value., ? , ! , . , , , , , . ( ), , , - . , , , .
,
V3022 V3063 . , :
String str = null; if (str == null) ....
, , .
lock statement this .. —
V3090 ; —
V3083 ; ,
IDisposable ,
Dispose /
Close —
V3072 ; .
( — , - ) , . , , . , - .
, , —
.
. — , . , , .
— , , , . , , .
, — . / , . , PVS-Studio.
Conclusão
, — ! , . - , — .
, ,
PVS-Studio , . - —
support@viva64.com . :)
!
PS .NET Core
, ! — . , . , , — , , / (
).

, : Sergey Vasiliev.
Checking the .NET Core Libraries Source Code by the PVS-Studio Static Analyzer