
Oi Apesar de a temporada de conferências de 2019 ainda estar em pleno andamento, gostaríamos de discutir as tarefas que foram oferecidas aos visitantes de nosso estande anteriormente. Iniciamos o outono de 2019 com um novo conjunto de tarefas, para que já seja possível publicar a solução de problemas antigos para 2018 e para o primeiro semestre de 2019. Além disso, muitos deles foram retirados de artigos publicados anteriormente, e os folhetos de tarefas continham um link ou um código QR com informações sobre o artigo.
Se você participou de conferências em que ficamos de pé, provavelmente viu ou até resolveu alguns dos nossos problemas. Esses sempre são trechos de código de projetos reais de código aberto nas linguagens de programação C, C ++, C # ou Java. O código contém erros que sugerimos que os visitantes procurem. Para a solução (ou apenas uma discussão sobre o erro), distribuímos prêmios - status na área de trabalho, bugigangas etc.:
Você quer o mesmo? Venha ao nosso estande nas próximas conferências.
A propósito, os artigos "
Horário da conferência! Resumindo os resultados de 2018 " e "
Conferências. Resultados intermediários do primeiro semestre de 2019 " contêm uma descrição de nossa atividade nas conferências deste e do ano passado.
Então, vamos começar o jogo "Encontre um erro no código". Primeiro, considere as tarefas mais antigas para 2018, usaremos o agrupamento por linguagens de programação.
2018
C ++
Bug de cromostatic const int kDaysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; bool ValidateDateTime(const DateTime& time) { if (time.year < 1 || time.year > 9999 || time.month < 1 || time.month > 12 || time.day < 1 || time.day > 31 || time.hour < 0 || time.hour > 23 || time.minute < 0 || time.minute > 59 || time.second < 0 || time.second > 59) { return false; } if (time.month == 2 && IsLeapYear(time.year)) { return time.month <= kDaysInMonth[time.month] + 1; } else { return time.month <= kDaysInMonth[time.month]; } }
A respostaTalvez a tarefa mais "de longa duração" do nosso set. Sugerimos que esse erro no projeto Chromium seja encontrado pelos visitantes de nosso estande ao longo de 2018. Também foi apresentado em vários relatórios.
if (time.month == 2 && IsLeapYear(time.year)) { return time.month <= kDaysInMonth[time.month] + 1;
O corpo do último bloco
If-else contém erros de digitação no valor de retorno. Em vez de
time.day, o programador especificou duas vezes, por engano,
time.month . Isso levou ao fato de que
true sempre será retornado. O erro é descrito em detalhes no artigo "
31 de fevereiro ". Um ótimo exemplo de um bug que não é fácil de detectar na revisão de código. Também é uma boa ilustração do uso da tecnologia de análise de fluxo de dados.
Bug irreal do motor bool VertInfluencedByActiveBone( FParticleEmitterInstance* Owner, USkeletalMeshComponent* InSkelMeshComponent, int32 InVertexIndex, int32* OutBoneIndex = NULL); void UParticleModuleLocationSkelVertSurface::Spawn(....) { .... int32 BoneIndex1, BoneIndex2, BoneIndex3; BoneIndex1 = BoneIndex2 = BoneIndex3 = INDEX_NONE; if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2]) &BoneIndex3) { .... }
A respostaA primeira coisa a observar é que o último argumento da função
VertInfluencedByActiveBone () tem um valor padrão e pode não ser especificado. Agora, dê uma olhada no bloco
if . Simplificado, pode ser reescrito da seguinte maneira:
if (!foo(....) && !foo(....) && !foo(....) & arg)
Agora está claro que um erro foi cometido. Devido a um erro de digitação, a terceira chamada à função
VertInfluencedByActiveBone () é feita com três argumentos em vez de quatro, e o operador
& é aplicado ao resultado dessa chamada (AND bit a bit, à esquerda é o resultado da função
VertInfluencedByActiveBone () do tipo
bool , à direita é a variável inteira
BoneIndex3 ). O código é compilado. Versão corrigida do código (vírgula adicionada, o colchete de fechamento foi movido para outro local):
if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2], &BoneIndex3))
O erro descrito originalmente no artigo "A
tão esperada verificação do Unreal Engine 4 ". O artigo é intitulado "O mais bonito dos erros encontrados" no artigo. Eu concordo com esta afirmação.
Erros do Android void TagMonitor::parseTagsToMonitor(String8 tagNames) { std::lock_guard<std::mutex> lock(mMonitorMutex);
A respostaA condição if block confunde a prioridade das operações. O código não funciona como o programador pretendia:
if (ssize_t idx = (tagNames.find("3a") != -1))
A variável
idx receberá os valores 0 ou 1, e o cumprimento da condição dependerá desse valor, que é um erro. Versão corrigida do código:
ssize_t idx = tagNames.find("3a"); if (idx != -1)
Erro do artigo "
Verificamos os códigos-fonte do Android usando o PVS-Studio, ou ninguém é perfeito ."
E mais uma tarefa para encontrar um erro não trivial no Android:
typedef int32_t GGLfixed; GGLfixed gglFastDivx(GGLfixed n, GGLfixed d) { if ((d>>24) && ((d>>24)+1)) { n >>= 8; d >>= 8; } return gglMulx(n, gglRecip(d)); }
A respostaO problema está na expressão
(d >> 24) + 1 .
O programador queria verificar se os 8 bits de ordem superior da variável
d contêm unidades, mas não todos os bits de uma vez. Em outras palavras, o programador queria verificar se qualquer valor diferente de 0x00 e 0xFF está no byte alto. Primeiro, ele verificou se os bits mais significativos são diferentes de zero escrevendo uma expressão (d >> 24). Em seguida, ele muda os oito bits altos para o byte baixo. Ao mesmo tempo, calcula que o bit de sinal mais significativo é duplicado em todos os outros bits. Ou seja, se a variável d é igual a 0b11111111'00000000'00000000'00000000, depois do turno, obtemos o valor 0b11111111'11111111'111111111111111111. Adicionando 1 ao valor 0xFFFFFFFF do tipo
int , o programador planeja obter 0 (-1 + 1 = 0). Assim, com a expressão
((d >> 24) +1), ele verifica se nem todos os oito bits altos são iguais a 1.
No entanto, ao mudar, o bit mais significativo não é necessariamente "manchado". O padrão diz: “O valor de E1 >> E2 é E1 com posições de bits E2 deslocadas à direita. Se E1 tiver um tipo não assinado ou E1 tiver um tipo assinado e um valor não negativo, o valor do resultado será parte integrante do quociente E1 / 2 ^ E2.
Se E1 tiver um tipo assinado e um valor negativo, o valor resultante será definido pela implementação . "
Portanto, este é um exemplo de comportamento definido pela implementação. O funcionamento desse código depende da arquitetura do microprocessador e da implementação do compilador. Após a mudança, os zeros podem aparecer nos bits mais significativos e, em seguida, o resultado da expressão
((d >> 24) +1) sempre será diferente de 0, ou seja, sempre será um valor verdadeiro.
De fato, uma tarefa difícil. Este erro, como o anterior, foi descrito no artigo "
Verificamos os códigos-fonte do Android usando o PVS-Studio, ou ninguém é perfeito ".
2019
C ++
"O GCC é o culpado" int foo(const unsigned char *s) { int r = 0; while(*s) { r += ((r * 20891 + *s *200) | *s ^ 4 | *s ^ 3) ^ (r >> 1); s++; } return r & 0x7fffffff; }
O programador afirma que esse código funciona com um erro devido à falha do compilador GCC 8. É isso?
A respostaA função retorna valores negativos. O motivo é que o compilador não gera código para o operador AND (&) bit a bit. O erro ocorre devido a um comportamento indefinido. O compilador vê que uma certa quantidade é considerada na variável
r . Nesse caso, apenas números positivos são adicionados. Estouros excessivos da variável
r não devem ocorrer, caso contrário, esse é um comportamento indefinido que o compilador não deve considerar e levar em consideração de nenhuma maneira. Portanto, o compilador acredita que, como o valor na variável
r após o final do ciclo não pode ser negativo, a operação
r & 0x7fffffff para redefinir o bit de sinal é supérflua e o compilador simplesmente retorna o valor da variável
r a partir da função.
Erro do artigo "
PVS-Studio 6.26 Release ".
Bug QT static inline const QMetaObjectPrivate *priv(const uint* data) { return reinterpret_cast<const QMetaObjectPrivate*>(data); } bool QMetaEnum::isFlag() const { const int offset = priv(mobj->d.data)->revision >= 8 ? 2 : 1; return mobj && mobj->d.data[handle + offset] & EnumIsFlag; }
C #
Bug do Infer.NET public static void WriteAttribute(TextWriter writer, string name, object defaultValue, object value, Func<object, string> converter = null) { if ( defaultValue == null && value == null || value.Equals(defaultValue)) { return; } string stringValue = converter == null ? value.ToString() : converter(value); writer.Write($"{name}=\"{stringValue}\" "); }
A respostaNa expressão
value.Equals (defaultValue) , o acesso é possível por meio da referência nula do
valor . Isso acontecerá com esses valores de variável, quando
defaultValue! = Null e
value == null .
O erro do artigo "
Quais erros estão ocultos no código Infer.NET? "
FastReport bug public class FastString { private const int initCapacity = 32; private void Init(int iniCapacity) { sb = new StringBuilder(iniCapacity); .... } public FastString() { Init(initCapacity); } public FastString(int iniCapacity) { Init(initCapacity); } public StringBuilder StringBuilder => sb; } .... Console.WriteLine(new FastString(256).StringBuilder.Capacity);
O que será exibido no console? O que há de errado com a classe
FastString ?
A respostaSerá exibido no console 32. O motivo é um erro de digitação no nome da variável passado para o método
Init no construtor:
public FastString(int iniCapacity){ Init(initCapacity); }
O
parâmetro do construtor
iniCapacity não será usado. Em vez disso, a constante
initCapacity é passada para o método
Init .
O erro foi descrito no artigo "
Os relatórios mais rápidos no oeste selvagem. Além disso, há alguns bugs ... "
Roslyn bug private SyntaxNode GetNode(SyntaxNode root) { var current = root; .... while (current.FullSpan.Contains(....)) { .... var nodeOrToken = current.ChildThatContainsPosition(....); .... current = nodeOrToken.AsNode(); } .... } public SyntaxNode AsNode() { if (_token != null) { return null; } return _nodeOrParent; }
A respostaO acesso é possível através da referência nula
atual na expressão
current.FullSpan.Contains (....) . A variável
atual pode obter um valor nulo como resultado do método
nodeOrToken.AsNode () .
Erro do artigo "
Verificando o código fonte do Roslyn ".
Unity bug .... staticFields = packedSnapshot.typeDescriptions .Where(t => t.staticFieldBytes != null & t.staticFieldBytes.Length > 0) .Select(t => UnpackStaticFields(t)) .ToArray() ....
A respostaErro de digitação permitido: em vez do operador
&& , o operador
& foi usado. Isso leva ao fato de que a verificação
t.staticFieldBytes.Length> 0 é sempre executada, mesmo que a variável
nula seja
t.staticFieldBytes , que, por sua vez, resultará em acesso por uma referência nula.
Este erro foi mostrado pela primeira vez no artigo "
Analisando erros nos componentes abertos do Unity3D ".
Java
Bug do IntelliJ IDEA private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2;
Propõe-se determinar por que o número de palavras com letras maiúsculas será calculado incorretamente.
A respostaA função deve retornar true se menos de 20% das palavras começarem com uma letra maiúscula. Mas a verificação não funciona, porque ocorre a divisão inteira, cujo resultado será apenas os valores 0 ou 1. A função retornará um valor falso apenas se todas as palavras começarem com uma letra maiúscula. Em outros casos, a divisão produzirá 0 e a função retornará true.
Erro do artigo "
PVS-Studio for Java ".
Bug de Spotbugs 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"); .... }
Propõe-se determinar qual é o erro de pesquisa da tag xml.
A respostaA condição de
contagem <4 sempre será atendida, pois a
contagem de variáveis não
é incrementada dentro do loop. Supunha-se que a busca pela tag xml deveria ser realizada apenas nas quatro primeiras linhas do arquivo, mas, devido a um erro, o arquivo inteiro será lido.
Este erro, como o anterior, foi descrito no artigo "
PVS-Studio for Java ".
Isso é tudo. Estamos esperando por você nas próximas conferências. Procure um carrinho de unicórnio. Daremos novos quebra-cabeças interessantes e, é claro, prêmios. Até breve!

Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Sergey Khrenov.
Soluções para desafios de detecção de bugs oferecidos pela equipe PVS-Studio nas conferências em 2018-2019 .