Seguindo os passos das calculadoras: SpeedCrunch

Quadro 4

Aqui estamos, continuando a explorar o código das calculadoras! Hoje vamos dar uma olhada no projeto chamado SpeedCrunch, a segunda calculadora gratuita mais popular.

1. Introdução


SpeedCrunch é uma calculadora científica de alta precisão com uma interface de usuário rápida, acionada por teclado. É um software gratuito e de código aberto, licenciado sob a GPL e executando no Windows, Linux e macOS.

O código fonte está disponível no BitBucket . Fiquei um pouco decepcionado com a documentação da compilação, que poderia ser mais detalhada. Ele diz que você precisa do “Qt 5.2 ou posterior” para criar o projeto, mas na verdade foram necessários alguns pacotes específicos, o que não foi fácil de descobrir no log do CMake. A propósito, atualmente é considerado uma boa prática incluir um Dockerfile no projeto para facilitar ao usuário a configuração do ambiente de desenvolvimento.

Aqui está o resultado do utilitário Cloc, mostrando como o SpeedCrunch se compara a outras calculadoras:

Quadro 2

Revisões de erros para os outros projetos:


A análise foi realizada com o analisador estático PVS-Studio . Este é um pacote de soluções para controle de qualidade de software e detecção de bugs e possíveis vulnerabilidades. O PVS-Studio suporta C, C ++, C # e Java e é executado no Windows, Linux e macOS.

Lógica estranha em um loop


V560 Uma parte da expressão condicional é sempre verdadeira :! RuleFound. assessator.cpp 1410

void Evaluator::compile(const Tokens& tokens) { .... while (!syntaxStack.hasError()) { bool ruleFound = false; // <= // Rule for function last argument: id (arg) -> arg. if (!ruleFound && syntaxStack.itemCount() >= 4) { // <= Token par2 = syntaxStack.top(); Token arg = syntaxStack.top(1); Token par1 = syntaxStack.top(2); Token id = syntaxStack.top(3); if (par2.asOperator() == Token::AssociationEnd && arg.isOperand() && par1.asOperator() == Token::AssociationStart && id.isIdentifier()) { ruleFound = true; // <= syntaxStack.reduce(4, MAX_PRECEDENCE); m_codes.append(Opcode(Opcode::Function, argCount)); #ifdef EVALUATOR_DEBUG dbg << "\tRule for function last argument " << argCount << " \n"; #endif argCount = argStack.empty() ? 0 : argStack.pop(); } } .... } .... } 

Observe a variável ruleFound : ela é definida como false a cada iteração. No interior do corpo do loop, porém, essa variável é configurada como true em determinadas condições, mas será configurada novamente como false na próxima iteração. A variável ruleFound provavelmente deveria ter sido declarada antes do loop.

Comparações suspeitas


V560 Uma parte da expressão condicional é sempre verdadeira: m_scrollDirection! = 0. resultdisplay.cpp 242

 void ResultDisplay::fullContentScrollEvent() { QScrollBar* bar = verticalScrollBar(); int value = bar->value(); bool shouldStop = (m_scrollDirection == -1 && value <= 0) || (m_scrollDirection == 1 && value >= bar->maximum()); if (shouldStop && m_scrollDirection != 0) { // <= stopActiveScrollingAnimation(); return; } scrollLines(m_scrollDirection * 10); } 

Se o valor da variável shouldStop for verdadeiro , a variável m_scrollDirection assumirá um dos dois valores: -1 ou 1. Portanto, seu valor será definitivamente diferente de zero na próxima instrução condicional, sobre a qual o analisador está alertando.

V668 Não há sentido em testar o ponteiro 'item' contra nulo, pois a memória foi alocada usando o operador 'novo'. A exceção será gerada no caso de erro de alocação de memória. editor.cpp 998

 void EditorCompletion::showCompletion(const QStringList& choices) { .... for (int i = 0; i < choices.count(); ++i) { QStringList pair = choices.at(i).split(':'); QTreeWidgetItem* item = new QTreeWidgetItem(m_popup, pair); if (item && m_editor->layoutDirection() == Qt::RightToLeft) item->setTextAlignment(0, Qt::AlignRight); .... } .... } 

A memória para um objeto do tipo QTreeWidgetItem é alocada usando o novo operador. Isso significa que uma falha na alocação de memória levará ao lançamento de uma exceção std :: bad_alloc () . A verificação do ponteiro do item é, portanto, redundante e pode ser removida.

Desreferência potencial NULL


V595 O ponteiro 'ioparams' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 969, 983. floatio.c 969

 int cattokens(....) { .... if (printexp) { if (expbase < 2) expbase = ioparams->expbase; // <= .... } dot = '.'; expbegin = "("; expend = ")"; if (ioparams != NULL) // <= { dot = ioparams->dot; expbegin = ioparams->expbegin; expend = ioparams->expend; } .... } 

O ponteiro ioparams é desreferenciado antes da verificação. Parece que há algum erro aqui. Como a desreferência é precedida por várias condições, o bug não será exibido com frequência, mas terá um efeito drástico quando ocorrer.

Divisão por zero


V609 Divida por zero. Intervalo do denominador [0..4]. floatconvert.c 266

 static int lgbase( signed char base) { switch(base) { case 2: return 1; case 8: return 3; case 16: return 4; } return 0; // <= } static void _setlongintdesc( p_ext_seq_desc n, t_longint* l, signed char base) { int lg; n->seq.base = base; lg = lgbase(base); // <= n->seq.digits = (_bitlength(l) + lg - 1) / lg; // <= n->seq.leadingSignDigits = 0; n->seq.trailing0 = _lastnonzerobit(l) / lg; // <= n->seq.param = l; n->getdigit = _getlongintdigit; } 

A função lgbase pode retornar zero, que pode ser usada como um divisor. A função pode ser chamada potencialmente com qualquer valor, não apenas 2, 8 ou 16.

Comportamento indefinido


V610 Comportamento indefinido. Verifique o operador de turno '<<'. O operando esquerdo '(~ 0)' é negativo. floatlogic.c 64

 static char _signextend( t_longint* longint) { unsigned mask; signed char sign; sign = _signof(longint); mask = (~0) << SIGNBIT; // <= if (sign < 0) longint->value[MAXIDX] |= mask; else longint->value[MAXIDX] &= ~mask; return sign; } 

Como o resultado da inversão de zero é armazenado em um int assinado, o valor resultante será um número negativo, que é então deslocado. Mudar para a esquerda um valor negativo é um comportamento indefinido.

Aqui está uma lista completa de todos esses casos:

  • V610 Comportamento indefinido. Verifique o operador de turno '<<'. O operando esquerdo '(- 1)' é negativo. floatnum.c 289
  • V610 Comportamento indefinido. Verifique o operador de turno '<<'. O operando esquerdo '(- 1)' é negativo. floatnum.c 325
  • V610 Comportamento indefinido. Verifique o operador de turno '<<'. O operando esquerdo '(- 1)' é negativo. floatnum.c 344
  • V610 Comportamento indefinido. Verifique o operador de turno '<<'. O operando esquerdo '(- 1)' é negativo. floatnum.c 351

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 costuma ser o caso do código C / C ++, estudar a fonte não ajuda muito a entender as coisas; portanto, veremos o código pré-processado:

Quadro 3



O analisador detectou uma etiqueta div não fechada. Este arquivo contém muitos trechos em HTML, e os desenvolvedores também terão que verificar esse código.

Aqui estão alguns outros casos suspeitos encontrados pelo PVS-Studio:

  • V735 Possivelmente um HTML incorreto. A tag de fechamento "</td>" foi encontrada, enquanto a tag "</sub>" era esperada. book.cpp 344
  • V735 Possivelmente um HTML incorreto. A tag de fechamento "</td>" foi encontrada, enquanto a tag "</sub>" era esperada. book.cpp 347

Operador de atribuição


V794 O operador de atribuição deve ser protegido do caso 'this == & other'. Quantity.cpp 373

 Quantity& Quantity::operator=(const Quantity& other) { m_numericValue = other.m_numericValue; m_dimension = other.m_dimension; m_format = other.m_format; stripUnits(); if(other.hasUnit()) { m_unit = new CNumber(*other.m_unit); m_unitName = other.m_unitName; } cleanDimension(); return *this; } 

É recomendável que você verifique situações em que um objeto é atribuído a si mesmo, comparando os ponteiros. Em outras palavras, adicione as duas linhas a seguir no início do corpo da função:

 if (this == &other) return *this; 

Como lembrete


V601 O valor 'false' é convertido implicitamente no tipo inteiro. cmath.cpp 318

 /** * Returns -1, 0, 1 if n1 is less than, equal to, or more than n2. * Only valid for real numbers, since complex ones are not an ordered field. */ int CNumber::compare(const CNumber& other) const { if (isReal() && other.isReal()) return real.compare(other.real); else return false; // FIXME: Return something better. } 

Às vezes, você diz nos comentários que talvez alguns dos avisos sejam acionados por código incompleto. Sim, isso acontece de vez em quando, mas apontamos especificamente esses casos.

Conclusão


Já verificamos o código de três programas da calculadora - Windows Calculator, Qalculate! E SpeedCrunch - e não vamos parar. Sinta-se à vontade para sugerir projetos que você deseja que analisemos, porque as classificações de software nem sempre refletem o estado real das coisas.

Bem-vindo ao baixar o PVS-Studio e tente por conta própria “Calculadora”. :-)

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


All Articles