Siguiendo los pasos de las calculadoras: calcular


Anteriormente realizamos revisiones de c贸digo de grandes paquetes matem谩ticos, por ejemplo, Scilab y Octave, por lo que las calculadoras se mantuvieron distantes como peque帽as utilidades, en las que es dif铆cil cometer errores debido a su peque帽a base de c贸digo. Nos equivocamos al no haberles prestado atenci贸n. El caso de publicar el c贸digo fuente de la calculadora de Windows mostr贸 que en realidad todos estaban interesados 鈥嬧媏n discutir los tipos de errores que se ocultan en 茅l. Adem谩s, la cantidad de errores all铆 fue m谩s que suficiente para escribir un art铆culo sobre eso. Mis colegas y yo decidimos explorar el c贸digo de varias calculadoras populares, y result贸 que el c贸digo de la calculadora de Windows no era tan malo (spoiler).

Introduccion


Qalculate! es una calculadora de escritorio multiplataforma multiprop贸sito. Es f谩cil de usar, pero proporciona potencia y versatilidad normalmente reservadas para paquetes matem谩ticos complicados, as铆 como herramientas 煤tiles para las necesidades diarias (como conversi贸n de moneda y c谩lculo de porcentajes). El proyecto consta de dos componentes: libqalculate (biblioteca y CLI) y qalculate-gtk (GTK + UI). El estudio incluy贸 solo el c贸digo libqalculate.

Para comparar f谩cilmente el proyecto con la Calculadora de Windows, que hemos verificado recientemente, estoy citando el resultado de la utilidad Cloc para libqalculate:


Considerando esto subjetivamente, hay m谩s errores y son m谩s cr铆ticos que en el c贸digo de la calculadora de Windows. Sin embargo, recomendar铆a sacar conclusiones por su cuenta, despu茅s de leer esta descripci贸n general del c贸digo.

Por cierto, aqu铆 hay un enlace a un art铆culo sobre la verificaci贸n de la calculadora de Microsoft: " Contando errores en la calculadora de Windows ".

La herramienta de an谩lisis es el analizador de c贸digo est谩tico PVS-Studio . Es un conjunto de soluciones para el control de calidad del c贸digo, b煤squeda de errores y vulnerabilidades potenciales. Los lenguajes compatibles incluyen: C, C ++, C # y Java. Puede ejecutar el analizador en Windows, Linux y macOS.

Copiar y pegar errores tipogr谩ficos de nuevo!


V523 La declaraci贸n 'then' es equivalente a la declaraci贸n '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); } .... } 

El c贸digo es absolutamente el mismo en los bloques if y else . Los fragmentos de c贸digo adyacentes son muy similares a este, pero se utilizan diferentes funciones en ellos: internalLowerFloat () y internalUpperFloat () . Es seguro asumir que un desarrollador copi贸 el c贸digo y se olvid贸 de corregir el nombre de la funci贸n aqu铆.

V501 Hay subexpresiones id茅nticas '! Mtr2.number (). IsReal ()' a la izquierda y a la derecha de '||' operador BuiltinFunctions.cc 6274

 int IntegrateFunction::calculate(....) { .... if(!mtr2.isNumber() || !mtr2.number().isReal() || !mtr.isNumber() || !mtr2.number().isReal()) b_unknown_precision = true; .... } 

En este caso, aparecieron expresiones duplicadas debido al hecho de que en un lugar se escribi贸 mtr2 en lugar de mtr. Por lo tanto, una llamada de la funci贸n mtr.number (). IsReal () est谩 ausente en la condici贸n.

V501 Hay subexpresiones id茅nticas 'vargs [1] .representsNonPositive ()' a la izquierda y a la derecha de '||' operador BuiltinFunctions.cc 5785



隆Nunca habr铆amos encontrado defectos en este c贸digo manualmente! Pero aqu铆 los hay. Adem谩s, en el archivo original, estos fragmentos est谩n escritos en una sola l铆nea. El analizador ha detectado una expresi贸n duplicada vargs [1] .representsNonPositive () , que puede indicar un error tipogr谩fico o, en consecuencia, un posible error.

Aqu铆 est谩 la lista completa de lugares sospechosos, que apenas se pueden descifrar.

  • V501 Hay subexpresiones id茅nticas 'vargs [1] .representsNonPositive ()' a la izquierda y a la derecha de '||' operador BuiltinFunctions.cc 5788
  • V501 Hay subexpresiones id茅nticas 'anexar' a la izquierda y a la derecha del operador '&&'. MathStructure.cc 1780
  • V501 Hay subexpresiones id茅nticas 'anexar' a la izquierda y a la derecha del operador '&&'. MathStructure.cc 2043
  • V501 Hay subexpresiones id茅nticas '(* v_subs [v_order [1]]). Representa Negative (true)' a la izquierda y a la derecha del operador '&&'. MathStructure.cc 5569

Bucle con condici贸n incorrecta


V534 Es probable que se compare una variable incorrecta dentro del 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; } } .... } 

En el bucle interno, la variable i2 representa un contador, pero debido a un error tipogr谩fico se cometi贸 un error: la variable i del bucle externo se usa en la condici贸n de salida del bucle.

驴Redundancia o un error?


V590 Considere inspeccionar esta expresi贸n. La expresi贸n es excesiva o contiene un error de imprenta. 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; .... } 

Hace 3 a帽os, despu茅s de haber visto ese c贸digo, escrib铆 una hoja de trucos para m铆 y otros desarrolladores: " Expresiones l贸gicas en C / C ++. Errores cometidos por profesionales ". Cuando me encuentro con ese c贸digo, me aseguro de que la nota no se ha vuelto menos relevante. Puede consultar el art铆culo, encontrar un patr贸n del error correspondiente al c贸digo y descubrir todos los matices.

En el caso de este ejemplo, iremos a la secci贸n "Expresi贸n == || ! = "Y descubra que la expresi贸n i2 == COMPARISON_RESULT_UNKNOWN no afecta a nada.

Desreferencia de punteros no controlados


V595 El puntero 'o_data' se utiliz贸 antes de que se verificara contra nullptr. L铆neas de verificaci贸n: 1108, 1112. DataSet.cc 1108

 string DataObjectArgument::subprintlong() const { string str = _("an object from"); str += " \""; str += o_data->title(); // <= str += "\""; DataPropertyIter it; DataProperty *o = NULL; if(o_data) { // <= o = o_data->getFirstProperty(&it); } .... } 

En una funci贸n, el puntero o_data se desreferencia tanto con un check como sin 茅l. Esto puede ser c贸digo redundante o un posible error. Me estoy inclinando hacia lo 煤ltimo.

Hay dos lugares similares:

  • V595 El puntero 'o_assumption' se utiliz贸 antes de que se verificara contra nullptr. L铆neas de verificaci贸n: 229, 230. Variable.cc 229
  • V595 El puntero 'i_value' se utiliz贸 antes de verificarlo con nullptr. L铆neas de verificaci贸n: 3412, 3427. Number.cc 3412

free () o eliminar []?


V611 La memoria fue asignada usando el operador 'nuevo' pero fue liberada usando la funci贸n 'libre'. Considere inspeccionar las l贸gicas de operaci贸n detr谩s de la variable 'remcopy'. Number.cc 8123

 string Number::print(....) const { .... while(!exact && precision2 > 0) { if(try_infinite_series) { remcopy = new mpz_t[1]; // <= mpz_init_set(*remcopy, remainder); } mpz_mul_si(remainder, remainder, base); mpz_tdiv_qr(remainder, remainder2, remainder, d); exact = (mpz_sgn(remainder2) == 0); if(!started) { started = (mpz_sgn(remainder) != 0); } if(started) { mpz_mul_si(num, num, base); mpz_add(num, num, remainder); } if(try_infinite_series) { if(started && first_rem_check == 0) { remainders.push_back(remcopy); } else { if(started) first_rem_check--; mpz_clear(*remcopy); free(remcopy); // <= } } .... } .... } 

La memoria para la matriz de remcopy se asigna y libera de diferentes maneras, lo cual es un error grave.

Cambios perdidos


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

La variable m en la funci贸n se pasa por referencia, lo que significa su modificaci贸n. Sin embargo, el analizador ha detectado que el c贸digo contiene la variable con el mismo nombre, que se superpone al alcance del par谩metro de la funci贸n, lo que permite la p茅rdida de cambios.

Punteros extra帽os


V774 El puntero 'cu' se us贸 despu茅s de liberar la memoria. 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; // <= return mstruct_new; } delete cu; // <= if(eo.approximation == APPROXIMATION_EXACT && cu->hasApproximateRelationTo(u, true)) { // <= if(!u->isRegistered()) delete u; return mstruct_new; } .... } 

El analizador advierte que el c贸digo llama a un m茅todo del objeto cu justo despu茅s de desasignar la memoria. Pero cuando se trata de lidiar con 茅l, el c贸digo resulta a煤n m谩s extra帽o. En primer lugar, llamar a delete cu siempre ocurre, tanto en la condici贸n como despu茅s de eso. En segundo lugar, el c贸digo despu茅s de la condici贸n implica que los punteros u y cu no son iguales, lo que significa que despu茅s de eliminar el objeto cu es bastante l贸gico usar el objeto u . Lo m谩s probable es que se haya cometido un error tipogr谩fico en el c贸digo y el autor del c贸digo quer铆a usar solo la variable u .

Uso de la funci贸n de b煤squeda


V797 La funci贸n 'buscar' se usa como si devolviera un tipo bool. El valor de retorno de la funci贸n probablemente deber铆a compararse con 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; } .... } 

Aunque el c贸digo se puede compilar con 茅xito, parece sospechoso, ya que la funci贸n de b煤squeda devuelve el n煤mero del tipo std :: string :: size_type . La condici贸n ser谩 verdadera si el punto se encuentra en cualquier parte de la cadena, excepto si el punto est谩 al principio. Es un cheque extra帽o. No estoy seguro, pero, tal vez, este c贸digo deber铆a reescribirse de la siguiente manera:

 if( sinverse.find(DOT) != std::string::npos || svalue.find(DOT) != std::string::npos) { po.read_precision = READ_PRECISION_WHEN_DECIMALS; } 

Posible fuga de memoria


V701 realloc () posible fuga: cuando realloc () falla en la asignaci贸n de memoria, el puntero original 'buffer' se pierde. Considere asignar realloc () a un puntero temporal. util.cc 703

 char *utf8_strdown(const char *str, int l) { #ifdef HAVE_ICU .... outlength = length + 4; buffer = (char*) realloc(buffer, outlength * sizeof(char)); // <= .... #else return NULL; #endif } 

Cuando se trabaja con la funci贸n realloc () , se recomienda utilizar un b煤fer intermedio, ya que en caso de que sea imposible asignar memoria, el puntero al 谩rea de memoria anterior se perder谩 irremediablemente.

Conclusi贸n


El Qalculate! El proyecto encabeza la lista de las mejores calculadoras gratuitas, mientras que contiene muchos errores graves. Por otro lado, a煤n no hemos revisado sus competidores. Intentaremos repasar todas las calculadoras populares.

En cuanto a la comparaci贸n con la calidad de la calculadora del mundo de Windows, la utilidad de Microsoft parece m谩s confiable y bien trabajada hasta ahora.

Compruebe su propia "Calculadora": descargue PVS-Studio y pru茅belo para su proyecto. :-)

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


All Articles