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 .