Anteriormente, fizemos análises de código de grandes pacotes matemáticos, por exemplo, Scilab e Octave, e as calculadoras permaneceram de lado como pequenas utilidades nas quais é difícil cometer erros devido ao seu pequeno tamanho de código. Estávamos enganados em não prestar atenção neles. O caso com a publicação do código-fonte da calculadora do Windows mostrou que todos estão interessados em discutir quais erros estão ocultos ali, e há erros mais do que suficientes para escrever um artigo sobre o assunto. Meus colegas e eu decidimos examinar o código de várias calculadoras populares e verificamos que o código da calculadora do Windows não era tão ruim (spoiler).
1. Introdução
Qalculate! - calculadora universal multiplataforma. É fácil de usar, mas fornece o poder e a versatilidade normalmente encontrados em pacotes matemáticos complexos, além de ferramentas úteis para as necessidades diárias (como conversão de moeda e cálculo de juros). O projeto consiste em dois componentes:
libqalculate (biblioteca e CLI) e
qalculate-gtk (GTK + UI). Somente o código libqalculate está envolvido no estudo.
Para comparar mais convenientemente o projeto com a mesma calculadora do Windows que examinamos recentemente, forneço a saída do utilitário Cloc para libqalculate:
Subjetivamente, há mais erros e eles são mais críticos do que no código da calculadora do Windows. Mas eu recomendo que você tire conclusões lendo esta revisão de código.
A propósito, aqui está um link para um artigo sobre como verificar uma calculadora da Microsoft: "
Contando bugs em uma calculadora do Windows "
.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.
Copie e cole e erros de digitação novamente!
V523 A
instrução 'then' é equivalente à instrução 'else'. Number.cc 4018
bool Number::square() { .... if(mpfr_cmpabs(i_value->internalLowerFloat(), i_value->internalUpperFloat()) > 0) { mpfr_sqr(f_tmp, i_value->internalLowerFloat(), MPFR_RNDU); mpfr_sub(f_rl, f_rl, f_tmp, MPFR_RNDD); } else { mpfr_sqr(f_tmp, i_value->internalLowerFloat(), MPFR_RNDU); mpfr_sub(f_rl, f_rl, f_tmp, MPFR_RNDD); } .... }
O código na
instrução if e
else é exatamente o mesmo. Os fragmentos de código vizinhos são muito semelhantes a isso, mas usam funções diferentes:
internalLowerFloat () e
internalUpperFloat () . É seguro assumir que aqui o programador copiou o código e esqueceu de corrigir o nome da função.
V501 Existem sub-expressões idênticas '! Mtr2.number (). IsReal ()' à esquerda e à direita do '||' operador. BuiltinFunctions.cc 6274
int IntegrateFunction::calculate(....) { .... if(!mtr2.isNumber() || !mtr2.number().isReal() || !mtr.isNumber() || !mtr2.number().isReal()) b_unknown_precision = true; .... }
Aqui surgiram expressões duplicadas porque, em um lugar, em vez do nome
mtr, eles escreveram
mtr2 . Portanto, na condição, não há chamada para a função
mtr.number (). IsReal () .
V501 Existem
subexpressões idênticas 'vargs [1] .represententsNonPositive ()' à esquerda e à direita da '||' operador. BuiltinFunctions.cc 5785

Encontrar anomalias nesse código manualmente é irreal! Mas eles são. Além disso, no arquivo original, esses fragmentos são escritos em uma linha. O analisador detectou uma expressão duplicada
vargs [1] .represententsNonPositive () , que pode indicar um erro de digitação e, portanto, um erro em potencial.
Aqui está toda a lista de lugares suspeitos que você dificilmente pode descobrir:
- V501 Existem subexpressões idênticas 'vargs [1] .represententsNonPositive ()' à esquerda e à direita da '||' operador. BuiltinFunctions.cc 5788
- V501 Existem subexpressões idênticas 'anexadas' à esquerda e à direita do operador '&&'. MathStructure.cc 1780
- V501 Existem subexpressões idênticas 'anexadas' à esquerda e à direita do operador '&&'. MathStructure.cc 2043
- V501 Existem subexpressões idênticas '(* v_subs [v_order [1]]). RepresentsNegative (true)' à esquerda e à direita do operador '&&'. MathStructure.cc 5569
Loop inválido
V534 É provável que uma variável incorreta esteja sendo comparada dentro do operador 'for'. Considere revisar 'i'. MathStructure.cc 28741
bool MathStructure::isolate_x_sub(....) { .... for(size_t i = 0; i < mvar->size(); i++) { if((*mvar)[i].contains(x_var)) { mvar2 = &(*mvar)[i]; if(mvar->isMultiplication()) { for(size_t i2 = 0; i < mvar2->size(); i2++) { if((*mvar2)[i2].contains(x_var)) {mvar2 = &(*mvar2)[i2]; break;} } } break; } } .... }
No loop interno, o contador é a variável
i2 , mas devido a um erro de digitação, ocorreu um erro - na condição de o loop parar, a variável
i do loop externo é usada.
Redundância ou erro?
V590 Considere inspecionar esta expressão. A expressão é excessiva ou contém uma impressão incorreta. Number.cc 6564
bool Number::add(const Number &o, MathOperation op) { .... if(i1 >= COMPARISON_RESULT_UNKNOWN && (i2 == COMPARISON_RESULT_UNKNOWN || i2 != COMPARISON_RESULT_LESS)) return false; .... }
Tendo analisado esse código, há três anos, escrevi uma nota para ajudar a mim e a outros programadores: "
Expressões lógicas em C / C ++. Como os profissionais estão errados ". Ao encontrar esse código, estou convencido de que a nota não se tornou menos relevante. Você pode procurar no artigo, encontrar o padrão de erro correspondente ao código e descobrir todas as nuances.
No caso deste exemplo, vá para a seção "Expressão == || ! = ”E aprendemos que a expressão
i2 == COMPARISON_RESULT_UNKNOWN não afeta nada.
Desreferenciando ponteiros não verificados
V595 O ponteiro 'o_data' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 1108, 1112. DataSet.cc 1108
string DataObjectArgument::subprintlong() const { string str = _("an object from"); str += " \""; str += o_data->title();
O ponteiro
o_data em uma função é desreferenciado sem verificação e com verificação. Pode ser um código redundante ou um erro em potencial. Estou inclinado para a última opção.
Existem mais dois lugares semelhantes:
- V595 O ponteiro 'o_assumption' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 229, 230. Variable.cc 229
- V595 O ponteiro 'i_value' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 3412, 3427. Number.cc 3412
grátis () ou excluir []?
V611 A memória foi alocada usando o operador 'novo', mas foi liberada usando a função 'livre'. Considere inspecionar as lógicas de operação por trás da variável 'remcopy'. Number.cc 8123
string Number::print(....) const { .... while(!exact && precision2 > 0) { if(try_infinite_series) { remcopy = new mpz_t[1];
A memória para a matriz
remcopy é alocada e liberada de várias maneiras, o que é um erro grave.
Mudanças perdidas
bool expand_partial_fractions(MathStructure &m, ....) { .... if(b_poly && !mquo.isZero()) { MathStructure m = mquo; if(!mrem.isZero()) { m += mrem; m.last() *= mtest[i]; m.childrenUpdated(); } expand_partial_fractions(m, eo, false); return true; } .... }
A variável
m é aceita na função por referência, o que implica sua modificação. Mas o analisador descobriu que o código contém a variável local com o mesmo nome, que se sobrepõe ao escopo do parâmetro de função, permitindo a perda de alterações.
Ponteiros estranhos
V774 O ponteiro 'cu' foi usado após o lançamento da memória. Calculator.cc 3595
MathStructure Calculator::convertToBestUnit(....) { .... CompositeUnit *cu = new CompositeUnit("", "...."); cu->add(....); Unit *u = getBestUnit(cu, false, eo.local_currency_conversion); if(u == cu) { delete cu;
O analisador avisa que o código contém uma chamada para o método do objeto
cu após liberar memória. Mas se você tentar entender o código, será ainda mais estranho. Em primeiro lugar, a chamada para
excluir cu sempre ocorre - na condição e depois. Em segundo lugar, o código após a condição pressupõe que os ponteiros
u e
cu não
sejam iguais; portanto, após limpar o objeto
cu , é lógico usar o objeto
u . Provavelmente, um erro de digitação foi feito no código e foi planejado usar apenas a variável
u .
Usando a função Find
V797 A função 'find' é usada como se retornasse um tipo de bool. O valor de retorno da função provavelmente deve ser comparado com std :: string :: npos. Unit.cc 404
MathStructure &AliasUnit::convertFromFirstBaseUnit(....) const { if(i_exp != 1) mexp /= i_exp; ParseOptions po; if(isApproximate() && suncertainty.empty() && precision() == -1) { if(sinverse.find(DOT) || svalue.find(DOT)) po.read_precision = READ_PRECISION_WHEN_DECIMALS; else po.read_precision = ALWAYS_READ_PRECISION; } .... }
Embora o código seja compilado com êxito, ele parece suspeito, pois a função
find retorna um número do tipo
std :: string :: size_type . A condição será verdadeira se o ponto for encontrado em qualquer lugar da linha, exceto se o ponto estiver no início. Este é um teste estranho. Não tenho certeza, mas talvez o código deva ser reescrito da seguinte maneira:
if( sinverse.find(DOT) != std::string::npos || svalue.find(DOT) != std::string::npos) { po.read_precision = READ_PRECISION_WHEN_DECIMALS; }
Potencial vazamento de memória
V701 realloc () possível vazamento: quando realloc () falha na alocação de memória, o ponteiro original 'buffer' é perdido. Considere atribuir realloc () a um ponteiro temporário. util.cc 703
char *utf8_strdown(const char *str, int l) { #ifdef HAVE_ICU .... outlength = length + 4; buffer = (char*) realloc(buffer, outlength * sizeof(char));
Ao trabalhar com a função
realloc () , é recomendável usar um buffer intermediário, pois se for impossível alocar memória, o ponteiro para a memória antiga será irremediavelmente perdido.
Conclusão
Projeto Qalculate! encabeça a lista das melhores calculadoras gratuitas, enquanto contém muitos erros sérios. Mas não vimos seus concorrentes. Vamos tentar passar por todas as calculadoras populares.
Quanto à comparação com a qualidade da calculadora do mundo Windows, enquanto o utilitário da Microsoft parece mais confiável e de alta qualidade.
Verifique sua “Calculadora” baixando o
PVS-Studio e testando-a em 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: Qalculate!