
Oi Embora a temporada de conferência de 2019 ainda não tenha terminado, gostaríamos de falar sobre os desafios de detecção de bugs que oferecemos aos visitantes em nosso estande durante as conferências anteriores. A partir do outono de 2019, apresentamos um novo conjunto de desafios, para que agora possamos revelar as soluções para as tarefas anteriores de 2018 e a primeira metade de 2019 - afinal, muitas delas vieram de artigos publicados anteriormente, e tivemos um link ou código QR com informações sobre os respectivos artigos impressos em nossos folhetos desafio.
Se você participou de eventos em que participamos de um estande, provavelmente já viu ou tentou resolver alguns de nossos desafios. Esses são trechos de código de projetos reais de código aberto escritos em C, C ++, C # ou Java. Cada trecho contém um erro, e os convidados são desafiados a tentar encontrá-lo. Uma solução bem-sucedida (ou simplesmente a participação na discussão do bug) é recompensada com um prêmio: um status de área de trabalho em espiral, um chaveiro e similares:
Quer um pouco também? Bem-vindo ao nosso estande nos próximos eventos.
A propósito, nos artigos "
Horário da conferência! Resumo de 2018 " e "
Conferências. Subtotais para o primeiro semestre de 2019 ", compartilhamos nossa experiência de participação nos eventos realizados no início deste ano e em 2018.
Ok, vamos jogar o nosso jogo "Encontre o bug". Primeiro, vamos dar uma olhada nos desafios anteriores de 2018, agrupados por idioma.
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]; } }
SoluçãoEsse bug encontrado no Chromium foi provavelmente o desafio mais "de longa duração"; estávamos oferecendo durante todo o ano de 2018 e também o incluímos em várias apresentações.
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 nas instruções de retorno:
time.month foi gravado acidentalmente pela segunda vez, em vez de
time.day . Esse erro faz com que a função retorne
verdadeira o tempo todo. O bug é discutido em detalhes no artigo "
31 de fevereiro " e é um exemplo legal de um bug que não é facilmente detectado pela revisão de código. Este caso também é uma boa demonstração de como usamos a 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) { .... }
SoluçãoA primeira coisa a observar aqui é que o último argumento da função
VertInfluencedByActiveBone () tem um valor padrão e não precisa ser especificado. Agora observe o bloco
if de uma forma simplificada:
if (!foo(....) && !foo(....) && !foo(....) & arg)
O bug é agora claramente visível. Devido ao erro de digitação, a terceira chamada da função
VertInfluencedByActiveBone () é executada com três argumentos em vez de quatro, com o valor de retorno participando de uma operação
& (bit a bit AND: o operando esquerdo é o valor do tipo
bool retornado por
VertInfluencedByActiveBone ( ), e operando à direita é a variável BoneIndex3 número inteiro). O código ainda é compilável. Esta é a versão fixa (uma vírgula adicionada, o parêntese de fechamento foi movido para o final da expressão):
if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2], &BoneIndex3))
Este erro foi mencionado originalmente no artigo "
Uma verificação aguardada do Unreal Engine 4 ", onde foi intitulada "o erro mais agradável", com a qual concordo totalmente.
Erros do Android void TagMonitor::parseTagsToMonitor(String8 tagNames) { std::lock_guard<std::mutex> lock(mMonitorMutex);
SoluçãoO programador teve suposições erradas sobre a precedência das operações na condição do bloco
if . Este código não funciona como esperado:
if (ssize_t idx = (tagNames.find("3a") != -1))
A variável
idx receberá o valor 0 ou 1 e se a condição for verdadeira ou falsa dependerá desse valor, o que é um erro. Esta é a versão fixa:
ssize_t idx = tagNames.find("3a"); if (idx != -1)
Esse bug foi mencionado no artigo "
Verificamos o código-fonte do Android pelo PVS-Studio, ou Nothing is Perfect ".
Aqui está outro desafio não trivial com um bug do 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)); }
SoluçãoO problema está na expressão
(d >> 24) + 1 .
O programador queria verificar se os 8 bits mais significativos da variável
d estão definidos como 1, mas não todos por vez. Em outras palavras, eles queriam verificar se o byte mais significativo armazena qualquer valor, exceto 0x00 e 0xFF. Primeiro, o programador verifica se há nulos nos bits mais significativos usando a expressão (d >> 24). Em seguida, eles mudam os oito bits mais significativos para o byte menos significativo, esperando que o bit de sinal mais significativo seja duplicado em todos os outros bits. Ou seja, se a variável d tem o valor 0b11111111'00000000'00000000'00000000, ele vai se transformar em 0b11111111'11111111'11111111'11111111 após a mudança. Adicionando 1 ao valor
int 0xFFFFFFFF, o programador espera obter 0 (-1 + 1 = 0). Assim, a expressão
((d >> 24) +1) é usada para verificar se nem todos os oito bits mais significativos estão definidos como 1.
No entanto, o bit de sinal mais significativo não é necessariamente "espalhado" quando deslocado. É o que diz o padrão: “O valor de E1 >> E2 é E1 posicionado em E2 com deslocamento à 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 . "
Então, este é um exemplo de comportamento definido pela implementação. O funcionamento exato desse código depende da arquitetura da CPU e da implementação do compilador. Os bits mais significativos podem acabar como zeros após o turno e a expressão
((d >> 24) +1) sempre retornaria um valor diferente de 0, ou seja, um valor sempre verdadeiro.
Esse é realmente um desafio não trivial. Como o bug anterior, este foi discutido originalmente no artigo "
Verificamos o código-fonte do Android pelo PVS-Studio, ou Nothing is Perfect ".
2019
C ++
"É tudo culpa do GCC" 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 culpa o compilador GCC 8 para o bug. É realmente culpa do GCC?
SoluçãoA função retorna valores negativos, pois o compilador não gera código para o AND (&) bit a bit. O bug está relacionado ao comportamento indefinido. O compilador percebe que a variável
r é usada para calcular e armazenar uma soma, com apenas valores positivos envolvidos. A variável
r não deve exceder o limite, porque isso seria um comportamento indefinido, com o qual o compilador não deve contar. Portanto, conclui que, como
r não pode ter um valor negativo no final do loop, a operação
r & 0x7fffffff , que apaga o bit de sinal, é desnecessária; portanto, simplesmente instrui a função a retornar o valor de
r .
Este erro foi descrito no artigo "
Lançamento do PVS-Studio 6.26 ".
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}\" "); }
Soluçãodesreferenciava nula do valor variável pode ocorrer quando se avaliam os value.Equals (defaultValue) expressão. Isso acontecerá quando os valores das variáveis forem tais que
defaultValue! = Null e
value == null .
Este bug é do artigo "
Quais erros se escondem 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);
Qual será a saída do programa no console? O que há de errado com a classe
FastString ?
SoluçãoO programa emitirá o valor 32. O motivo é o nome incorreto da variável passada para o método
Init no construtor:
public FastString(int iniCapacity){ Init(initCapacity); }
O iniCapacity parâmetro de construtor não será usado ;
o que é passado em vez disso é o initCapacity constante .
O bug foi discutido no artigo "
Os relatórios mais rápidos do oeste selvagem - e um punhado de 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; }
SoluçãoDereferência nula potencial de
corrente na expressão
current.FullSpan.Contains (....) . A variável
atual pode ser atribuída a um valor nulo como resultado da chamada do método
nodeOrToken.AsNode () .
Esse bug é 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() ....
SoluçãoUm erro de digitação: o operador
& é usado em vez de
&& . Isso resulta na execução da verificação
t.staticFieldBytes.Length> 0 o tempo todo, mesmo que a variável
t.staticFieldBytes seja
nula , o que, por sua vez, leva a uma desreferência nula.
Esse bug foi originalmente mostrado no artigo "
Discutindo erros nos componentes de código aberto 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;
Por que o programa calcula incorretamente o número de palavras em maiúsculas?
SoluçãoA função deve retornar verdadeiro se o número de palavras em maiúsculas é inferior a 20%. Mas a verificação não funciona por causa da divisão inteira, que é avaliada apenas como 0 ou 1. A função retornará
false somente se todas as palavras estiverem em maiúsculas. Caso contrário, a divisão resultará em 0 e a função retornará
true .
Este bug é 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"); .... }
O que há de errado com a pesquisa da tag xml?
SoluçãoA condição de
contagem <4 será sempre verdadeira, pois a
contagem de variáveis não é incrementada dentro do loop. A tag xml estava destinado a ser procurado nas primeiras quatro linhas do arquivo, mas por causa do incremento de falta, o programa estará lendo o arquivo inteiro.
Como o bug anterior, este foi descrito no artigo "
PVS-Studio for Java ".
Isso é tudo por hoje. Venha nos ver nos próximos eventos - procure o unicórnio. Ofereceremos novos desafios interessantes e, é claro, daremos prêmios. Até mais!