2019 está chegando ao fim, e a equipe PVS-Studio está resumindo os resultados do ano que se inicia. No início de 2019, expandimos os recursos do analisador, suportando a linguagem Java. Portanto, a lista de nossas publicações sobre verificação de projetos abertos foi reabastecida com revisões de projetos Java. Muitos erros foram encontrados durante o ano, e decidimos preparar o Top 10 mais interessante deles.
Décimo lugar: byte icônico
Fonte:
Análise do código-fonte da estrutura RPC Apache Dubbo pelo analisador estático PVS-StudioA expressão
V6007 'endKey [i] <0xff' sempre é verdadeira. OptionUtil.java (32)
public static final ByteSequence prefixEndOf(ByteSequence prefix) { byte[] endKey = prefix.getBytes().clone(); for (int i = endKey.length - 1; i >= 0; i--) { if (endKey[i] < 0xff) {
Muitos programadores acreditam que um tipo chamado
byte não terá sinal. E, de fato, frequentemente em diferentes idiomas, esse é exatamente o caso. Por exemplo, em C #, o tipo de
byte não está assinado. Em Java, esse não é o caso.
Na condição
endKey [i] <0xff, o autor do método compara uma variável do tipo
byte com o número 255 (0xff) representado na representação hexadecimal. Aparentemente, ao escrever o método, o desenvolvedor esqueceu que o intervalo de valores do tipo
byte em Java é [-128, 127]. Essa condição é sempre verdadeira, portanto, o loop for sempre processará apenas o último elemento da matriz
endKey .
Nono lugar: dois em um
Fonte:
PVS-Studio for Java é enviado para o caminho. A próxima parada é a ElasticsearchA expressão
V6007 '(int) x <0' é sempre falsa. BCrypt.java (429)
V6025 Possivelmente o índice '(int) x' está fora dos limites. BCrypt.java (431)
private static byte char64(char x) { if ((int)x < 0 || (int)x > index_64.length) return -1; return index_64[(int)x]; }
Hoje temos uma oferta especial! Dois erros em um método ao mesmo tempo. A causa do primeiro erro é o tipo
char , que não está assinado em Java, e é por isso que a condição
(int) x <0 é sempre falsa. O segundo erro é o banal saindo dos
limites da matriz
index_64 quando
(int) x == index_64.length . Essa situação é possível devido à condição
(int) x> index_64.length . Para se livrar dos limites da matriz, é necessário substituir a condição '>' por '> ='. A condição correta seria:
(int) x> = index_64.length .
Oitavo lugar: decisão e suas consequências
Fonte:
Análise de código da plataforma CUBA com PVS-StudioA expressão
V6007 'previousMenuItemFlatIndex> = 0' sempre é verdadeira. CubaSideMenuWidget.java (328)
protected MenuItemWidget findNextMenuItem(MenuItemWidget currentItem) { List<MenuTreeNode> menuTree = buildVisibleTree(this); List<MenuItemWidget> menuItemWidgets = menuTreeToList(menuTree); int menuItemFlatIndex = menuItemWidgets.indexOf(currentItem); int previousMenuItemFlatIndex = menuItemFlatIndex + 1; if (previousMenuItemFlatIndex >= 0) {
O autor do método
findNextMenuItem deseja se livrar do -1 retornado pelo método
indexOf se a lista
menuItemWidgets não contiver
currentItem . Para fazer isso, ele adiciona um ao resultado
indexOf (variável
menuItemFlatIndex ) e armazena o valor resultante na variável
previousMenuItemFlatIndex , que é mais usada no método. Esta solução para o problema -1 não tem êxito porque leva a vários erros ao mesmo tempo:
- código nulo return nunca será executado, porque a expressão previousMenuItemFlatIndex> = 0 sempre é verdadeira, o que significa que o retorno do método findNextMenuItem sempre ocorrerá dentro do if ;
- uma IndexOutOfBoundsException será lançada quando a lista menuItemWidgets estiver vazia, porque o primeiro elemento da lista vazia será acessado;
- uma exceção IndexOutOfBoundsException ocorrerá quando o argumento currentItem for o último na lista menuItemWidget .
Sétimo lugar: criando um arquivo do nada
Fonte:
Huawei Cloud: hoje está nublado no PVS-StudioV6008 Dereferência nula potencial de 'dataTmpFile'. CacheManager.java (91)
@Override public void putToCache(PutRecordsRequest putRecordsRequest) { .... if (dataTmpFile == null || !dataTmpFile.exists()) { try { dataTmpFile.createNewFile();
Ao escrever o método
putToCache, o programador fez um erro de digitação na condição
dataTmpFile == null || ! dataTmpFile.exists () antes de criar um novo arquivo
dataTmpFile.createNewFile (). Um erro de digitação é o uso do operador '==' em vez de '! ='. Este erro irá
gerar um
NullPointerException ao chamar o método
createNewFile . A condição após a correção de um erro de digitação é assim:
if (dataTmpFile != null || !dataTmpFile.exists())
“O erro foi encontrado, corrigido. Você pode relaxar ”, você pensará. Mas não importa como!
Depois de corrigir um erro, encontramos outro. Agora, uma
NullPointerException pode ocorrer ao chamar
dataTmpFile.exists () . Agora, para se livrar da exceção, é necessário substituir o operador '||' na condição em '&&'. A condição sob a qual todos os erros desaparecem será a seguinte:
if (dataTmpFile != null && !dataTmpFile.exists())
Sexto lugar: um erro lógico muito estranho
Fonte:
PVS-Studio for JavaV6007 [CWE-570] A expressão '"0" .equals (text)' é sempre falsa. ConvertIntegerToDecimalPredicate.java 46
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)) {
Este método é interessante, pois contém um erro lógico óbvio. Se o método
satisfeitoBy não retornar um valor após o primeiro
se , torna-se conhecido que a sequência de
texto consiste em pelo menos dois caracteres. Por isso, a primeira verificação
"0". Igual (texto) na próxima
se não tiver sentido. O que o desenvolvedor realmente quis dizer permanece um mistério.
Quinto lugar: é a vez!
Fonte:
PVS-Studio visitando o Apache HiveV6034 A mudança no valor de 'bitShiftsInWord - 1' pode ser inconsistente com o tamanho do tipo: 'bitShiftsInWord - 1' = [-1 ... 30]. UnsignedInt128.java (1791)
private void shiftRightDestructive(int wordShifts, int bitShiftsInWord, boolean roundUp) { if (wordShifts == 0 && bitShiftsInWord == 0) { return; } assert (wordShifts >= 0); assert (bitShiftsInWord >= 0); assert (bitShiftsInWord < 32); if (wordShifts >= 4) { zeroClear(); return; } final int shiftRestore = 32 - bitShiftsInWord;
Com os argumentos de entrada
wordShifts = 3 e
bitShiftsInWord = 0 , a variável
roundCarryMask , que armazena o resultado da troca de bits
(1 << (bitShiftsInWord - 1)) , será um número negativo. Talvez o desenvolvedor não esperasse esse comportamento.
Quarto lugar: as exceções sairão para uma caminhada?
Fonte:
PVS-Studio visitando o Apache HiveV6051 O uso da instrução 'return' no bloco 'final' pode levar à perda de exceções não tratadas. ObjectStore.java (9080)
private List<MPartitionColumnStatistics> getMPartitionColumnStatistics(....) throws NoSuchObjectException, MetaException { boolean committed = false; try { .... committed = commitTransaction(); return result; } catch (Exception ex) { LOG.error("Error retrieving statistics via jdo", ex); if (ex instanceof MetaException) { throw (MetaException) ex; } throw new MetaException(ex.getMessage()); } finally { if (!committed) { rollbackTransaction(); return Lists.newArrayList(); } } }
A declaração do método
getMPartitionColumnStatistics está mentindo para nós, dizendo que pode
gerar uma exceção. Quando qualquer exceção ocorre na
tentativa , a variável
confirmada permanece
falsa ; portanto, no bloco
final , a
instrução return retorna o valor do método, e todas as exceções lançadas são perdidas e não podem ser processadas fora do método. Portanto, qualquer exceção criada nesse método nunca poderá sair dela.
Terceiro lugar: torço, giro, quero uma nova máscara
Fonte:
PVS-Studio visitando o Apache HiveV6034 A mudança no valor de 'j' pode ser inconsistente com o tamanho do tipo: 'j' = [0 ... 63]. IoTrace.java (272)
public void logSargResult(int stripeIx, boolean[] rgsToRead) { .... for (int i = 0, valOffset = 0; i < elements; ++i, valOffset += 64) { long val = 0; for (int j = 0; j < 64; ++j) { int ix = valOffset + j; if (rgsToRead.length == ix) break; if (!rgsToRead[ix]) continue; val = val | (1 << j);
Outro erro relacionado à mudança bit a bit, mas desta vez não apenas ele estava envolvido no caso. No loop
for interno, a variável
j [0 ... 63] é usada como contador de loop. Este contador está envolvido em um deslocamento de
1 << j . Nada indica problemas, no entanto, o literal inteiro '1' do tipo
int (valor de 32 bits) entra em jogo aqui. Segue-se que os resultados da troca de bits começarão a se repetir depois que
j for maior que 31. Se o comportamento descrito for indesejável, a unidade deverá ser representada por
muito tempo , por exemplo,
1L << j ou
(long) 1 << j .
Segundo lugar: ordem de inicialização
Fonte:
Huawei Cloud: hoje está nublado no PVS-StudioO ciclo de inicialização da classe
V6050 está presente. A inicialização de 'INSTANCE' aparece antes da inicialização de 'LOG'. UntrustedSSL.java (32), UntrustedSSL.java (59), UntrustedSSL.java (33)
public class UntrustedSSL { private static final UntrustedSSL INSTANCE = new UntrustedSSL(); private static final Logger LOG = LoggerFactory.getLogger(UntrustedSSL.class); .... private UntrustedSSL() { try { .... } catch (Throwable t) { LOG.error(t.getMessage(), t);
A ordem na qual os campos são declarados em uma classe é importante porque os campos são inicializados na ordem em que são declarados. No entanto, quando se esquecem, ocorrem erros sutis, como este.
O analisador indicou que o campo
LOG estático é desreferenciado no construtor quando é inicializado como
nulo , o que leva à cadeia de exceção
NullPointerException ->
ExceptionInInitializerError .
“Por que, no momento da chamada do construtor, o campo estático do
LOG é nulo ?”, Você pergunta.
A
exceção ExceptionInInitializerError é uma dica. O fato é que esse construtor é usado para inicializar o campo estático
INSTANCE declarado na classe anterior ao campo
LOG . Portanto, no momento da chamada do construtor, o campo
LOG ainda não foi inicializado. Para que o código funcione corretamente, é necessário inicializar o campo
LOG antes de chamar o construtor.
Primeiro lugar: programação orientada para copiar e colar
Fonte:
Código Apache Hadoop Qualidade: teste de produção VSV6072 Dois fragmentos de código semelhantes foram encontrados. Talvez seja um erro de digitação e a variável 'localFiles' deva ser usada em vez de 'localArchives'. LocalDistributedCacheManager.java (183), LocalDistributedCacheManager.java (178), LocalDistributedCacheManager.java (176), LocalDistributedCacheManager.java (181)
public synchronized void setup(JobConf conf, JobID jobId) throws IOException { ....
E o primeiro lugar é tomado por copiar e colar, ou melhor, um erro que surgiu devido ao descuido de quem cometeu essa coisa pecaminosa. É altamente provável que o segundo
se tenha sido criado por copiar e colar o primeiro com a substituição de variáveis:
- localArchives em localFiles ;
- MRJobConfig.CACHE_LOCALARCHIVES em MRJobConfig.CACHE_LOCALFILES .
No entanto, mesmo com uma operação tão simples, foi cometido um erro, pois a variável
localArchives ainda era usada na segunda linha
se no segundo
analisador , embora o uso de
localFiles provavelmente
estivesse implícito.
Conclusão
A correção de erros encontrados nas fases posteriores do desenvolvimento ou após o lançamento de um projeto requer recursos significativos. O analisador estático PVS-Studio simplifica a detecção de erros ao escrever código, o que reduz significativamente a quantidade de recursos gastos na sua correção. O uso constante do analisador já simplificou a vida dos desenvolvedores de muitas
empresas . Se você deseja programar com grande prazer, tente nosso
analisador .
Nossa equipe não para por aí e continuará a melhorar e aprimorar o analisador. Espere novos diagnósticos e artigos com bugs ainda mais interessantes no próximo ano.
Eu assisto você adora aventura! Primeiro, os
10 principais erros nos projetos de C # para 2019 venceram e agora o Java conseguiu superar! Bem-vindo ao próximo nível no artigo sobre os
melhores erros de 2019 em projetos C ++ .

Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Valery Komarov.
Os 10 principais erros encontrados em projetos Java em 2019 .