PVS-Studio para Java

PVS-Studio para Java

Na sétima versão do analisador estático PVS-Studio, adicionamos suporte para a linguagem Java. É hora de falar um pouco sobre como começamos a dar suporte à linguagem Java, o que fizemos e quais planos futuros. E, é claro, o artigo mostrará os primeiros testes do analisador em projetos abertos.

PVS-Studio


Para desenvolvedores de Java que nunca ouviram falar da ferramenta PVS-Studio, darei uma breve descrição dela.

O PVS-Studio é uma ferramenta para detectar erros e possíveis vulnerabilidades no código fonte de programas escritos em C, C ++, C # e Java. É executado no Windows, Linux e macOS.

O PVS-Studio realiza análise estática de código e gera um relatório que ajuda o programador a encontrar e corrigir defeitos. Para aqueles que estão interessados ​​exatamente em como o PVS-Studio está procurando erros, sugiro que você leia o artigo " Tecnologias usadas no analisador de código do PVS-Studio para encontrar erros e possíveis vulnerabilidades ".

Iniciar


Eu poderia criar uma história inteligente, já que há dois anos estamos pensando em qual idioma usar no PVS-Studio. O fato de o Java ser uma escolha razoável com base na alta popularidade dessa linguagem e assim por diante.

No entanto, como acontece na vida, tudo foi decidido não por análises aprofundadas, mas por experimentos :). Sim, estávamos pensando em qual direção o analisador PVS-Studio deveria ser desenvolvido. Foram consideradas linguagens de programação como: Java, PHP, Python, JavaScript, IBM RPG. E nós estávamos inclinados à linguagem Java, mas a escolha final ainda não foi feita. Aqueles cujos olhos estão presos em um IBM RPG desconhecido, refiro-me a esta nota aqui , da qual tudo ficará claro.

No final de 2017, o colega Egor Bredikhin analisou quais bibliotecas prontas para análise de código (em outras palavras, analisadores) estão disponíveis para novas direções que nos interessam. E me deparei com vários projetos para analisar o código Java. Com base em Spoon , ele rapidamente conseguiu criar um analisador de protótipo com alguns diagnósticos. Além disso, ficou claro que podemos usar alguns mecanismos do analisador C ++ com a ajuda do SWIG no analisador Java. Examinamos o que aconteceu e percebemos que nosso próximo analisador seria para Java.

Agradecemos a Egor por sua empresa e pelo trabalho ativo realizado por ele no analisador Java. Como o desenvolvimento prosseguiu, ele descreveu no artigo " Desenvolvimento de um novo analisador estático: PVS-Studio Java ".

Concorrentes?


Existem muitos analisadores de código estático gratuitos e comerciais para Java no mundo. Não faz sentido listá-las todas no artigo, e apenas deixo um link para " Lista de ferramentas para análise de código estático " (consulte a seção Java e multilíngue).

No entanto, eu sei que antes de tudo, seremos questionados sobre o IntelliJ IDEA, FindBugs e SonarQube (SonarJava).

IntelliJ IDEA

O IntelliJ IDEA possui um analisador de código estático muito poderoso incorporado. Além disso, o analisador está em desenvolvimento e seus autores estão monitorando de perto nossas atividades. Com o IntelliJ IDEA, seremos os mais difíceis. Não conseguiremos superar o IntelliJ IDEA em recursos de diagnóstico, pelo menos por enquanto. Portanto, tentaremos nos concentrar em nossas outras vantagens.

A análise estática no IntelliJ IDEA é, antes de tudo, um dos chips do ambiente de desenvolvimento, que impõe certas restrições a ele. Somos livres no que podemos fazer com nosso analisador. Por exemplo, podemos adaptar rapidamente o analisador às necessidades específicas do cliente. Suporte rápido e profundo é a nossa vantagem competitiva. Nossos clientes se comunicam diretamente com os programadores que desenvolvem uma parte específica do PVS-Studio.

O PVS-Studio tem muitas possibilidades para integrá-lo ao ciclo de desenvolvimento de grandes projetos antigos. Isso é integração com o SonarQube . Essa é uma supressão maciça das mensagens do analisador, o que permite que você comece a usar imediatamente o analisador em um projeto grande para rastrear erros apenas no código novo ou alterado. O PVS-Studio é integrado ao processo de integração contínua. Acho que esses e outros recursos ajudarão nosso analisador a encontrar um lugar sob o sol no mundo Java.

Findbugs

O projeto FindBugs é abandonado . Mas deve ser lembrado pelo motivo de que este talvez seja o analisador estático livre mais conhecido do código Java.

O sucessor do FindBugs é o projeto SpotBugs . No entanto, ele é menos popular, e o que acontecerá com ele também ainda não está totalmente claro.

Em geral, acreditamos que, embora o FindBugs tenha sido e permaneça extremamente popular, e também um analisador gratuito, não devemos pensar nisso. Este projeto é apenas calma e calmamente uma coisa do passado.

PS A propósito, agora o PVS-Studio também pode ser usado gratuitamente quando se trabalha com projetos abertos.

SonarQube (SonarJava)

Acreditamos que não competimos com o SonarQube, mas o complementamos. O PVS-Studio se integra ao SonarQube, que permite que os desenvolvedores encontrem mais erros e possíveis vulnerabilidades em seus projetos. Como integrar a ferramenta PVS-Studio e outros analisadores ao SonarQube, conversamos regularmente em master classes realizadas em várias conferências ( exemplo ).

Como iniciar o PVS-Studio for Java


Disponibilizamos aos usuários as formas mais populares de integrar o analisador ao sistema de montagem:

  • Plugin para Maven;
  • Plugin para Gradle;
  • Plugin para IntelliJ IDEA

No estágio de teste, encontramos muitos usuários que possuem sistemas de montagem auto-escritos, especialmente no desenvolvimento móvel. Eles gostaram da capacidade de executar o analisador diretamente, listando as fontes e o caminho da classe.

Você pode encontrar informações detalhadas sobre todos os métodos para iniciar o analisador na página de documentação " Como iniciar o PVS-Studio Java ".

Como não podíamos ignorar a plataforma de controle de qualidade de código do SonarQube , tão popular entre os desenvolvedores de Java, adicionamos suporte à linguagem Java ao nosso plug-in do SonarQube .

Planos adicionais


Temos muitas idéias que precisam de mais estudos, mas alguns planos específicos para qualquer um de nossos analisadores são assim:

  • Criação de novos diagnósticos e aprimoramento dos existentes;
  • Desenvolvimento de análise de fluxo de dados;
  • Melhorando a confiabilidade e a usabilidade.

Podemos encontrar tempo para adaptar o plugin IntelliJ IDEA para o CLion. Oi C ++ para desenvolvedores lendo sobre o analisador Java :-)

Exemplos de erros encontrados em projetos de código aberto


Eu não serei eu se não mostrar nenhum erro encontrado usando o novo analisador no artigo. Poderíamos pegar um grande projeto Java de código aberto e escrever um artigo clássico com análise de erro, como costumamos fazer .

No entanto, prevejo imediatamente a questão de saber se podemos encontrar algo em projetos como IntelliJ IDEA, FindBugs e assim por diante. Portanto, simplesmente não tenho uma saída e começarei precisamente com esses projetos. Então, decidi verificar e escrever rapidamente alguns exemplos interessantes de erros dos seguintes projetos:

  • IntelliJ IDEA Community Edition . Eu acho que não há necessidade de explicar por que esse projeto foi escolhido :).
  • SpotBugs Como escrevi anteriormente, o projeto FindBugs não está sendo desenvolvido. Então, dê uma olhada no projeto SpotBugs, que é o sucessor do FindBugs. SpotBugs é um analisador de código Java estático clássico.
  • Alguns dos projetos do SonarSource, que desenvolve software para controle contínuo da qualidade do código. Dê uma olhada nos projetos SonarQube e SonarJava .

Escrever sobre bugs nesses projetos é uma tarefa difícil. O fato é que esses projetos são de alta qualidade. Na verdade, isso não é surpreendente. Como mostram nossas observações, o código dos analisadores estáticos é sempre bem testado e verificado usando outras ferramentas.

Apesar de tudo isso, tenho que começar com esses mesmos projetos. Não terei uma segunda chance de escrever algo sobre eles. Estou certo de que, após o lançamento do PVS-Studio para Java, os desenvolvedores desses projetos levarão o PVS-Studio para o serviço e começarão a usá-lo para verificações regulares ou pelo menos periódicas de seu código. Por exemplo, eu sei que Tagir Valeyev (lany), um dos desenvolvedores do JetBrains que está envolvido no analisador de código estático IntelliJ IDEA, já está executando a versão beta inteira do PVS-Studio enquanto estou escrevendo o artigo. Ele já nos escreveu cerca de 15 cartas com recomendações e relatórios de erros. Obrigado Tagir!

Felizmente, não preciso encontrar o maior número possível de erros em um projeto específico. Agora, minha tarefa é mostrar que o analisador PVS-Studio para Java não apareceu em vão e será capaz de reabastecer a linha de outras ferramentas projetadas para melhorar a qualidade do código. Apenas olhei nos relatórios do analisador e escrevi alguns erros que me pareciam interessantes. Sempre que possível, tentei escrever erros de vários tipos. Vamos ver o que aconteceu.

Divisão Inteira IntelliJ IDEA


private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2; // allow reasonable amount of // capitalized words } 

Aviso do PVS-Studio: V6011 [CWE-682] O literal '0.2' do tipo 'double' é comparado a um valor do tipo 'int'. TitleCapitalizationInspection.java 169

Como pretendido, uma função deve retornar true se menos de 20% das palavras começarem com uma letra maiúscula. De fato, a verificação não funciona, pois ocorre uma divisão inteira. Como resultado da divisão, apenas dois valores podem ser obtidos: 0 ou 1.

A função retornará um valor falso apenas se todas as palavras começarem com uma letra maiúscula. Em todos os outros casos, a divisão produzirá 0 e a função retornará o valor verdadeiro.

Ciclo Suspeito de IntelliJ IDEA


 public int findPreviousIndex(int current) { int count = myPainter.getErrorStripeCount(); int foundIndex = -1; int foundLayer = 0; if (0 <= current && current < count) { current--; for (int index = count - 1; index >= 0; index++) { // <= int layer = getLayer(index); if (layer > foundLayer) { foundIndex = index; foundLayer = layer; } } .... } 

Aviso do PVS-Studio: V6007 [CWE-571] A expressão 'index> = 0' é sempre verdadeira. Updater.java 184

Primeiro, observe a condição (0 <= atual && current <count) . Só é executado se o valor da variável count for maior que 0.

Agora veja o loop:

 for (int index = count - 1; index >= 0; index++) 

O índice da variável é inicializado pela expressão count - 1 . Como a variável count é maior que 0, o valor inicial da variável index é sempre maior ou igual a 0. Acontece que o loop será executado até que a variável index exceda.

Provavelmente, este é apenas um erro de digitação e o incremento não deve ser executado, mas o decremento da variável:

 for (int index = count - 1; index >= 0; index--) 

IDEA IntelliJ, copiar e colar


 @NonNls public static final String BEFORE_STR_OLD = "before:"; @NonNls public static final String AFTER_STR_OLD = "after:"; private static boolean isBeforeOrAfterKeyword(String str, boolean trimKeyword) { return (trimKeyword ? LoadingOrder.BEFORE_STR.trim() : LoadingOrder.BEFORE_STR).equalsIgnoreCase(str) || (trimKeyword ? LoadingOrder.AFTER_STR.trim() : LoadingOrder.AFTER_STR).equalsIgnoreCase(str) || LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str) || // <= LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str); // <= } 

PVS-Studio Warning: V6001 [CWE-570] Existem sub-expressões idênticas 'LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase (str)' à esquerda e à direita da '||' operador. Verifique as linhas: 127, 128. ExtensionOrderConverter.java 127

Bom efeito antigo da última linha . O programador se apressou e, multiplicando uma linha de código, esqueceu de corrigi-la. Como resultado, o dobro da string str é comparado a BEFORE_STR_OLD . Provavelmente, uma das comparações deve ser com AFTER_STR_OLD .

Erro de IntelliJ IDEA


 public synchronized boolean isIdentifier(@NotNull String name, final Project project) { if (!StringUtil.startsWithChar(name,'\'') && !StringUtil.startsWithChar(name,'\"')) { name = "\"" + name; } if (!StringUtil.endsWithChar(name,'"') && !StringUtil.endsWithChar(name,'\"')) { name += "\""; } .... } 

PVS-Studio Warning: V6001 [CWE-571] Existem sub-expressões idênticas '! StringUtil.endsWithChar (name,' "')' à esquerda e à direita do operador '&&'. JsonNamesValidator.java 27

Este trecho de código verifica se o nome está entre aspas simples ou duplas. Se não for esse o caso, as aspas duplas serão adicionadas automaticamente.

Devido a um erro de digitação, o final do nome é verificado apenas para aspas duplas. Como resultado, o nome entre aspas simples não será processado corretamente.

Primeiro nome

 'Abcd' 

devido à adição de aspas duplas extras, ele se transformará em:

 'Abcd'" 

IntelliJ IDEA, proteção de estouro de matriz incorreta


 static Context parse(....) { .... for (int i = offset; i < endOffset; i++) { char c = text.charAt(i); if (c == '<' && i < endOffset && text.charAt(i + 1) == '/' && startTag != null && CharArrayUtil.regionMatches(text, i + 2, endOffset, startTag)) { endTagStartOffset = i; break; } } .... } 

Aviso do PVS-Studio: V6007 [CWE-571] A expressão 'i <endOffset' sempre é verdadeira. EnterAfterJavadocTagHandler.java 183

A subexpressão i <endOffset na condição da instrução if não faz sentido. A variável i é sempre menor que endOffset , a seguir da condição para executar o loop.

Provavelmente, o programador queria se proteger de sair da linha ao chamar funções:

  • text.charAt (i + 1)
  • CharArrayUtil.regionMatches (texto, i + 2, endOffset, startTag)

Nesse caso, a subexpressão para verificar o índice deve ser assim: i <endOffset - 2 .

Verificação de repetição do IntelliJ IDEA


 public static String generateWarningMessage(....) { .... if (buffer.length() > 0) { if (buffer.length() > 0) { buffer.append(" ").append( IdeBundle.message("prompt.delete.and")).append(" "); } } .... } 

Aviso do PVS-Studio: V6007 [CWE-571] A expressão 'buffer.length ()> 0' é sempre verdadeira. DeleteUtil.java 62

Pode ser um código redundante inofensivo ou um erro grave.

Se uma verificação duplicada aparecer por acaso, por exemplo, durante a refatoração, não há nada de errado nisso. A segunda verificação pode ser simplesmente excluída.

Mas outro cenário é possível. A segunda verificação deve ser completamente diferente e o código não se comporta conforme o planejado. Então este é um erro real.

Nota A propósito, existem muitas verificações redundantes diferentes. Além disso, é comum ver que isso não é um erro. No entanto, as mensagens do analisador também não podem ser chamadas de falsos positivos. Para esclarecer, aqui está um exemplo, também retirado do IntelliJ IDEA:

 private static boolean isMultiline(PsiElement element) { String text = element.getText(); return text.contains("\n") || text.contains("\r") || text.contains("\r\n"); } 

O analisador diz que a função text.contains ("\ r \ n") sempre retorna false. De fato, se os símbolos "\ n" e "\ r" não forem encontrados, não faz sentido procurar "\ r \ n". Isso não é um erro, e o código é ruim apenas porque funciona um pouco mais devagar, realizando uma pesquisa sem sentido por uma substring.

Como lidar com esse código, em cada caso, cabe aos programadores decidir. Ao escrever artigos, como regra, simplesmente não presto atenção a esse código.

IntelliJ IDEA, algo está errado


 public boolean satisfiedBy(@NotNull PsiElement element) { .... @NonNls final String text = expression.getText().replaceAll("_", ""); if (text == null || text.length() < 2) { return false; } if ("0".equals(text) || "0L".equals(text) || "0l".equals(text)) { return false; } return text.charAt(0) == '0'; } 

Aviso do PVS-Studio: V6007 [CWE-570] A expressão '"0" .equals (text)' é sempre falsa. ConvertIntegerToDecimalPredicate.java 46

Esse código definitivamente contém um erro lógico. Mas acho difícil dizer o que o programador queria verificar e como corrigir o defeito. Portanto, aqui vou apontar apenas uma verificação sem sentido.

No início, é verificado se a sequência deve conter pelo menos dois caracteres. Caso contrário, a função retornará false .

A seguir, é apresentada uma verificação de "0" .equals (text) . Não faz sentido, pois uma sequência não pode conter apenas um caractere.

Em geral, algo está errado aqui e o código deve ser corrigido.

SpotBugs (sucessor de FindBugs), erro de limite de iteração


 public static String getXMLType(@WillNotClose InputStream in) throws IOException { .... String s; int count = 0; while (count < 4) { s = r.readLine(); if (s == null) { break; } Matcher m = tag.matcher(s); if (m.find()) { return m.group(1); } } throw new IOException("Didn't find xml tag"); .... } 

Aviso do PVS-Studio: V6007 [CWE-571] A expressão 'contagem <4' é sempre verdadeira. Util.java 394

Conforme planejado, a pesquisa pela tag xml deve ser realizada apenas nas quatro primeiras linhas do arquivo. Mas, devido ao fato de que eles esqueceram de incrementar a contagem de variáveis, o arquivo inteiro será lido.

Primeiramente, isso pode se tornar uma operação muito lenta e, em segundo lugar, em algum lugar no meio do arquivo, pode ser encontrado algo que será interpretado como uma tag xml, mas não será.

SpotBugs (sucessor de FindBugs), substituindo valores


 private void reportBug() { int priority = LOW_PRIORITY; String pattern = "NS_NON_SHORT_CIRCUIT"; if (sawDangerOld) { if (sawNullTestVeryOld) { priority = HIGH_PRIORITY; // <= } if (sawMethodCallOld || sawNumericTestVeryOld && sawArrayDangerOld) { priority = HIGH_PRIORITY; // <= pattern = "NS_DANGEROUS_NON_SHORT_CIRCUIT"; } else { priority = NORMAL_PRIORITY; // <= } } bugAccumulator.accumulateBug( new BugInstance(this, pattern, priority).addClassAndMethod(this), this); } 

Aviso do PVS-Studio: V6021 [CWE-563] O valor é atribuído à variável 'priority' mas não é usado. FindNonShortCircuit.java 197

O valor da variável de prioridade é definido dependendo do valor da variável sawNullTestVeryOld . No entanto, isso não desempenha nenhum papel. Além disso, a variável de prioridade receberá um valor diferente em qualquer caso. Um erro óbvio na lógica da função.

SonarQube, copiar e colar


 public class RuleDto { .... private final RuleDefinitionDto definition; private final RuleMetadataDto metadata; .... private void setUpdatedAtFromDefinition(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } private void setUpdatedAtFromMetadata(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } .... } 

PVS-Studio: V6032 É estranho que o corpo do método 'setUpdatedAtFromDefinition' seja totalmente equivalente ao corpo de outro método 'setUpdatedAtFromMetadata'. Verifique as linhas: 396, 405. RuleDto.java 396

O método setUpdatedAtFromMetadata usa o campo de definição . Provavelmente, o campo de metadados deve ser usado. Isso é muito semelhante às conseqüências da falha de copiar e colar.

SonarJava, duplicatas na inicialização do mapa


 private final Map<JavaPunctuator, Tree.Kind> assignmentOperators = Maps.newEnumMap(JavaPunctuator.class); public KindMaps() { .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... } 

Aviso do PVS-Studio: V6033 [CWE-462] Um item com a mesma chave 'JavaPunctuator.PLUSEQU' já foi adicionado. Verifique as linhas: 104, 100. KindMaps.java 104

O mesmo par de valores-chave é colocado duas vezes no cartão. Muito provavelmente, isso acabou por ser desatento e, de fato, não há nenhum erro real. No entanto, em qualquer caso, esse código precisa ser verificado, porque você pode ter esquecido de adicionar outro par.

Conclusão


Mas que conclusão pode haver ?! Convido todos, sem demora, a baixar o PVS-Studio e tentar testar seus projetos de trabalho em Java! Faça o download do PVS-Studio .

Obrigado a todos pela atenção. Espero que em breve encantemos os leitores com uma série de artigos dedicados à verificação de vários projetos Java abertos.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Andrey Karpov. PVS-Studio para Java .

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


All Articles