Soluções para desafios de detecção de bugs oferecidos pela equipe PVS-Studio nas conferências em 2018-2019

Quadro 2


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:

Quadro 4

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 cromo

static 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ção
Esse 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; // <= day } else { return time.month <= kDaysInMonth[time.month]; // <= day } 

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ção
A 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); // Expand shorthands if (ssize_t idx = tagNames.find("3a") != -1) { ssize_t end = tagNames.find(",", idx); char* start = tagNames.lockBuffer(tagNames.size()); start[idx] = '\0'; .... } .... } 

Solução
O 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ção
O 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ção
A 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; } 

Solução
O ponteiro mobj é tratado de maneira insegura: primeiro desreferenciada e depois verificada. Um clássico.

O bug foi mencionado no artigo " Uma terceira verificação do Qt 5 com PVS-Studio ".

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ção
desreferenciava 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ção
O 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ção
Dereferê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ção
Um 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; // allow reasonable amount of // capitalized words } 

Por que o programa calcula incorretamente o número de palavras em maiúsculas?

Solução
A 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ção
A 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!

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


All Articles