Seguindo a trilha das calculadoras: SpeedCrunch

Quadro 4

O estudo do código da calculadora continua! Nesta revisão, o projeto SpeedCrunch será revisado - o segundo mais popular entre as calculadoras gratuitas.

1. Introdução


SpeedCrunch é uma calculadora científica de precisão com teclado, com uma interface de usuário rápida. Este é um software de código aberto gratuito disponível no Windows, Linux e macOS.

O código fonte está hospedado no BitBucket . Não gostei muito da documentação da montagem, que, na minha opinião, deveria ser escrita com mais detalhes. Os requisitos especificam “Qt 5.2 ou posterior”, embora vários pacotes específicos fossem necessários, o que não foi fácil de aprender com o log do CMake. A propósito, agora é uma boa prática aplicar um Dockerfile a um projeto para configurar rapidamente o ambiente de desenvolvedor desejado.

Para comparação com outras calculadoras, trago a saída do utilitário Cloc:

Quadro 2


Revisões de bugs em outros projetos:


O PVS-Studio foi utilizado como uma ferramenta de análise estática. Este é um conjunto de soluções para controle de qualidade de código, pesquisa de erros e possíveis vulnerabilidades. Os idiomas suportados incluem: C, C ++, C # e Java. O analisador pode ser iniciado 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 . A cada iteração, é definido como falso. Porém, se você observar o corpo de todo o ciclo, então, sob certas condições, essa variável será configurada como verdadeira, mas não será levada em consideração na nova iteração do ciclo. Provavelmente, a variável ruleFound precisava ser 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 a variável shouldStop for verdadeira , a variável m_scrollDirection terá um dos dois valores: -1 ou 1. Portanto, na instrução condicional a seguir, o valor da variável m_scrollDirection definitivamente não será zero, o que o analisador avisa.

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, se a alocação dinâmica de memória não for possível, uma exceção std :: bad_alloc () será lançada. Portanto, verificar o ponteiro do item é supérfluo e pode ser excluído.

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 de ser verificado quanto à validade. Provavelmente, um erro em potencial entrou no código. Como a desreferenciação está sob várias condições, o problema pode se manifestar raramente, mas com precisão.

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 permite que um valor nulo seja retornado, pelo qual a divisão é executada. Potencialmente, qualquer coisa que não seja os valores 2, 8 e 16 pode ser passado para a função.

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; } 

O resultado da inversão de zero é colocado no tipo de sinal int , de modo que o resultado será um número negativo, para o qual uma mudança é realizada. Mudar um número negativo para a esquerda é um comportamento indefinido.

Toda a lista de lugares perigosos:

  • 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 acontecer com o código C / C ++, nada fica claro na fonte, então vamos ao código pré-processado para este fragmento:

Quadro 3



O analisador detectou uma etiqueta div não fechada. Existem muitos fragmentos de código html neste arquivo e agora deve ser verificado adicionalmente pelos desenvolvedores.

Aqui estão alguns lugares mais suspeitos que foram encontrados usando o 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; } 

Recomenda-se considerar a situação em que o objeto é atribuído a si mesmo, comparando os ponteiros.

Em outras palavras, as duas linhas de código a seguir devem ser adicionadas ao início do corpo da função:

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

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, nos comentários de nossos artigos, eles sugerem que alguns avisos sejam emitidos em um código incompleto. Sim, acontece, mas quando realmente é, está escrito diretamente sobre isso.

Conclusão


Revisões já disponíveis de três calculadoras: Windows Calculator, Qalculate! e SpeedCrunch. Estamos prontos para continuar pesquisando o código das calculadoras populares. Você pode oferecer projetos para verificação, uma vez que as classificações do software nem sempre refletem a imagem real.

Verifique sua “Calculadora” baixando o PVS-Studio e testando seu projeto :-)



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Svyatoslav Razmyslov. Seguindo os passos das calculadoras: SpeedCrunch

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


All Articles