Outro ano está prestes a terminar, então é hora de fazer café e reler os relatórios de erros do ano passado. Obviamente, isso levará muito tempo, portanto este artigo foi escrito. Sugiro dar uma olhada nos lugares sombrios mais interessantes dos projetos que conhecemos em 2019 em projetos escritos em C e C ++.
Décimo lugar: "Qual é o nosso sistema operacional?"
V1040 Possível erro de digitação na ortografia de um nome de macro predefinido. A macro '__MINGW32_' é semelhante a '__MINGW32__'. winapi.h 4112
#if !defined(__UNICODE_STRING_DEFINED) && defined(__MINGW32_) #define __UNICODE_STRING_DEFINED #endif
Aqui, um erro de digitação foi feito no nome da macro
__MINGW32 _ (MINGW32 declara __MINGW32__). Em outros locais do projeto, a verificação é gravada corretamente:
Este, aliás, não foi apenas o primeiro erro no artigo “
CMake: o caso em que o projeto é imperdoável pela qualidade de seu código ”, mas, em geral, o primeiro erro real encontrado pelo diagnóstico da V1040 em um projeto aberto real (19 de agosto de 2019).
Nono lugar: "Quem é o primeiro?"
V502 Talvez o operador '?:'
Funcione de maneira diferente do esperado. O operador '?:' Tem uma prioridade mais baixa que o operador '=='. mir_parser.cpp 884
enum Opcode : uint8 { kOpUndef, .... OP_intrinsiccall, OP_intrinsiccallassigned, .... kOpLast, }; bool MIRParser::ParseStmtIntrinsiccall(StmtNodePtr &stmt, bool isAssigned) { Opcode o = !isAssigned ? (....) : (....); auto *intrnCallNode = mod.CurFuncCodeMemPool()->New<IntrinsiccallNode>(....); lexer.NextToken(); if (o == !isAssigned ? OP_intrinsiccall : OP_intrinsiccallassigned) { intrnCallNode->SetIntrinsic(GetIntrinsicID(lexer.GetTokenKind())); } else { intrnCallNode->SetIntrinsic(static_cast<MIRIntrinsicID>(....)); } .... }
Estamos interessados na seguinte parte deste código:
if (o == !isAssigned ? OP_intrinsiccall : OP_intrinsiccallassigned) { .... }
O operador '==' tem uma prioridade mais alta que o operador ternário (? :). Por esse motivo, a expressão condicional não é avaliada corretamente. O código escrito é equivalente ao seguinte:
if ((o == !isAssigned) ? OP_intrinsiccall : OP_intrinsiccallassigned) { .... }
E levando em consideração o fato de que as constantes
OP_intrinsiccall e
OP_intrinsiccall atribuídas têm valores diferentes de zero, essa condição sempre retorna o valor verdadeiro. O corpo do ramo
else é um código inacessível.
Este erro chegou ao topo do artigo "
Verificando o código do Ark Compiler aberto recentemente pela Huawei ".
Oitavo lugar: "O perigo de operações bit a bit"
V1046 Uso inseguro dos tipos bool 'e' int 'juntos na operação' & = '. GSLMultiRootFinder.h 175
int AddFunction(const ROOT::Math::IMultiGenFunction & func) { ROOT::Math::IMultiGenFunction * f = func.Clone(); if (!f) return 0; fFunctions.push_back(f); return fFunctions.size(); } template<class FuncIterator> bool SetFunctionList( FuncIterator begin, FuncIterator end) { bool ret = true; for (FuncIterator itr = begin; itr != end; ++itr) { const ROOT::Math::IMultiGenFunction * f = *itr; ret &= AddFunction(*f); } return ret; }
Com base no código,
esperava- se
que a função
SetFunctionList ignorasse a lista de iteradores. E se pelo menos um deles for inválido, o valor de retorno será
falso , caso contrário,
verdadeiro .
No entanto, na realidade, a função
SetFunctionList pode retornar
false, mesmo para iteradores válidos. Vamos dar uma
olhada na situação. A
função AddFunction retorna o número de iteradores válidos na lista
fFunctions . I.e. ao adicionar iteradores diferentes de zero, o tamanho desta lista aumentará sequencialmente: 1, 2, 3, 4 etc. É aqui que o erro no código começa a se manifestar:
ret &= AddFunction(*f);
Porque Como a função retorna um resultado do tipo
int , não
bool , a operação '& =' com números pares fornecerá o valor
false . Afinal, o bit menos significativo de números pares sempre será zero. Portanto, esse erro não óbvio estragará o resultado da função
SetFunctionsList , mesmo para argumentos válidos.
Se você ler atentamente o código do exemplo (e ler atentamente, certo?), Poderá perceber que esse é o código do projeto ROOT. Obviamente, nós o testamos: "
Análise de código ROOT - uma estrutura para a análise de dados de pesquisas científicas ".
Sétimo lugar: “Confusão nas variáveis”
V1001 [CWE-563] A variável 'Mode' está atribuída, mas não é usada no final da função. SIModeRegister.cpp 48
struct Status { unsigned Mask; unsigned Mode; Status() : Mask(0), Mode(0){}; Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { Mode &= Mask; }; .... };
É muito perigoso atribuir aos argumentos das funções os mesmos nomes dos membros da classe. Muito fácil ficar confuso. Diante de nós é exatamente esse caso. Esta expressão não faz sentido:
Mode &= Mask;
O argumento da função muda. E é isso. Este argumento não é mais usado. Provavelmente, foi necessário escrever assim:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { this->Mode &= Mask; };
E este é um erro do
LLVM . De tempos em tempos, temos uma tradição em analisar esse projeto. Este ano também temos um
artigo sobre verificação.
Sexto lugar: "C ++ tem suas próprias leis"
O erro a seguir aparece no código devido ao fato de que as regras C ++ nem sempre coincidem com as regras matemáticas ou com o "senso comum". Observe por si mesmo onde está o erro em um pequeno trecho de código?
V709 Comparação suspeita encontrada: 'f0 == f1 == m_fractureBodies.size ()'. Lembre-se de que 'a == b == c' não é igual a 'a == b && b == c'. btFractureDynamicsWorld.cpp 483
btAlignedObjectArray<btFractureBody*> m_fractureBodies; void btFractureDynamicsWorld::fractureCallback() { for (int i = 0; i < numManifolds; i++) { .... int f0 = m_fractureBodies.findLinearSearch(....); int f1 = m_fractureBodies.findLinearSearch(....); if (f0 == f1 == m_fractureBodies.size()) continue; .... } .... }
Parece que a condição verifica se
f0 é igual a
f1 e igual ao número de elementos em
m_fractureBodies . Parece que essa comparação deveria ter verificado se
f0 e
f1 estão no final da matriz
m_fractureBodies , pois contêm a posição do objeto encontrado pelo método
findLinearSearch () . No entanto, de fato, essa expressão se transforma em um teste para verificar se
f0 e
f1 são iguais e verificar se
m_fractureBodies.size () é igual ao resultado de
f0 == f1 . Como resultado, o terceiro operando aqui é comparado com 0 ou 1.
Belo erro! E, felizmente, bastante raro. Até agora, nós a
encontramos apenas em três projetos abertos e, curiosamente, todos eles eram apenas mecanismos de jogo. A propósito, este não é o único erro que encontramos em Bullet. Os mais interessantes entraram no nosso artigo “O
PVS-Studio analisou o mecanismo Red Dead Redemption - Bullet ”.
Quinto lugar: "Qual é o fim da linha?"
O seguinte erro é facilmente detectado se você souber sobre uma sutileza.
O V739 EOF não deve ser comparado com um valor do tipo 'char'. O 'ch' deve ser do tipo 'int'. json.cpp 762
void JsonIn::skip_separator() { signed char ch; .... if (ch == ',') { if( ate_separator ) { .... } .... } else if (ch == EOF) { .... }
Este é um daqueles erros que podem ser difíceis de perceber se você não souber que o
EOF está definido como -1. Portanto, se você tentar compará-lo com uma variável do tipo
char assinado , a condição será quase sempre
falsa . A única exceção é se o código de caractere for 0xFF (255). Ao comparar, esse símbolo se tornará -1 e a condição será verdadeira.
Existem muitos erros relacionados a jogos neste top: de mecanismos a jogos abertos. Como você deve ter adivinhado, este lugar também veio a nós desta área. Você pode ver mais erros no artigo "
Dias sombrios do cataclismo adiante, análise estática e bagels ".
Quarto lugar: “A Magia do Número Pi”
V624 Provavelmente, há uma impressão
incorreta na constante '3.141592538'. Considere usar a constante M_PI de <math.h>. PhysicsClientC_API.cpp 4109
B3_SHARED_API void b3ComputeProjectionMatrixFOV(float fov, ....) { float yScale = 1.0 / tan((3.141592538 / 180.0) * fov / 2); .... }
Um pequeno erro de digitação no número Pi (3,141592653 ...), faltando o número "6" na 7ª posição na parte fracionária.
Talvez o erro na décima-milionésima casa decimal não leve a consequências tangíveis, mas você ainda deve usar as constantes da biblioteca existentes sem erros de digitação. Para Pi, por exemplo, há uma constante M_PI no cabeçalho math.h.
Este erro é do artigo “O
PVS-Studio analisou o mecanismo Red Dead Redemption - Bullet ”, já familiar para nós em sexto lugar. Se você não adiar para mais tarde, essa é a última chance.
Uma ligeira digressão
Portanto, estamos perto dos três principais erros mais interessantes. Como você deve ter notado, eles não são classificados pelas consequências catastróficas de sua presença, mas pela complexidade da detecção. Afinal, a vantagem mais importante da análise estática sobre a revisão de código é que a máquina não se cansa e não esquece nada. :)
E agora trago à sua atenção os três primeiros.
Terceiro lugar: "A exceção ilusória"
As classes
V702 sempre devem ser derivadas de std :: exception (e similares) como 'public' (nenhuma palavra-chave foi especificada, então o compilador o padroniza como 'private'). CalcManager CalcException.h 4
class CalcException : std::exception { public: CalcException(HRESULT hr) { m_hr = hr; } HRESULT GetException() { return m_hr; } private: HRESULT m_hr; };
O analisador detectou uma classe herdada da classe
std :: exception por meio do modificador
privado (o modificador padrão, se nada for especificado). O problema com esse código é que, quando você tenta capturar a exceção geral
std :: exception, uma exceção do tipo
CalcException será ignorada. Esse comportamento ocorre porque a herança privada impede a conversão implícita de tipo.
Sim, eu não gostaria de ver o programa travar devido ao modificador
público ausente
. A propósito, tenho certeza de que você definitivamente usou o aplicativo em sua vida pelo menos uma vez, cujo código fonte acabamos de analisar. Esta é a boa e antiga
calculadora padrão do
Windows que também
testamos .
Segundo lugar: tags HTML não fechadas
V735 Possivelmente um HTML incorreto. A tag de fechamento "</body>" foi encontrada, enquanto a tag "</div>" era esperada. book.cpp 127
static QString makeAlgebraLogBaseConversionPage() { return BEGIN INDEX_LINK TITLE(Book::tr("Logarithmic Base Conversion")) FORMULA(y = log(x) / log(a), log<sub>a</sub>x = log(x) / log(a)) END; }
Como geralmente acontece com o código C / C ++, nada fica claro na fonte, então vamos ao código pré-processado para este fragmento:
O analisador detectou uma
tag <div> não fechada . Existem muitos fragmentos de código html neste arquivo e agora deve ser verificado adicionalmente pelos desenvolvedores.
Surpreso que possamos verificar e tal? Quando vi isso pela primeira vez, fiquei impressionado. Então, analisamos um pouco do código html. Verdadeiro, apenas no código C ++. :)
Este não é apenas o segundo lugar, mas também a segunda calculadora em nossa parte superior. Você pode encontrar uma lista de todos os erros no artigo "
Seguindo a trilha das calculadoras: SpeedCrunch ".
Primeiro Lugar: “Características Padrão Indescritíveis”
Então chegamos ao primeiro lugar. Um problema impressionantemente estranho que passou por uma revisão de código.
Tente descobrir você mesmo:
static int EatWhitespace (FILE * InFile) { int c; for (c = getc (InFile); isspace (c) && ('\n' != c); c = getc (InFile)) ; return (c); }
Agora vamos ver o que o analisador jura:
V560 Uma parte da expressão condicional é sempre verdadeira: ('\ n'! = C). params.c 136.
Estranho, não é? Vejamos algo interessante no mesmo projeto, mas em um arquivo diferente (charset.h):
#ifdef isspace #undef isspace #endif .... #define isspace(c) ((c)==' ' || (c) == '\t')
Então, e isso já é realmente estranho ... Acontece que, se a variável
c for igual a
'\ n', a função absolutamente inofensiva à primeira vista
isspace (c) retornará falsa e a segunda parte deste teste não será executada devido à avaliação de curto-circuito. Se
isspace (c) for executado, a variável
c é
'' ou
'\ t', e isso claramente não é igual a
'\ n' .
Obviamente, você pode dizer que essa macro é como
#define true false e esse código nunca passará na revisão de código. No entanto, esse código passou na revisão e estava esperando por nós com segurança no repositório do projeto.
Se você precisar de uma análise mais detalhada do erro, está no artigo "
Para quem quer ser o detetive: encontre o erro na função do Midnight Commander ".
Conclusão
No ano passado, encontramos muitos erros. Esses eram os erros comuns de copiar e colar, constantes em constantes, tags não fechadas e muitos outros problemas. Mas nosso analisador está aprimorando e
aprendendo a procurar cada vez mais bugs, portanto isso está longe do fim, e novos artigos sobre a verificação de projetos serão publicados sempre que antes.
Se alguém ler nossos artigos pela primeira vez, esclarecemos que todos esses erros foram encontrados usando o analisador de código estático PVS-Studio, que sugerimos
fazer o download e tentar. O analisador pode detectar erros no código dos programas escritos nas linguagens: C, C ++, C # e Java.
Então chegamos ao fim! Se você perdeu os dois primeiros níveis, sugiro não perder a oportunidade e passar por eles conosco:
C # e
Java .

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