Siguiendo los pasos de las calculadoras: SpeedCrunch

Cuadro 4

¡Aquí estamos, continuando explorando el código de las calculadoras! Hoy vamos a echar un vistazo al proyecto llamado SpeedCrunch, la segunda calculadora gratuita más popular.

Introduccion


SpeedCrunch es una calculadora científica de alta precisión que presenta una interfaz de usuario rápida y controlada por teclado. Es un software gratuito y de código abierto, con licencia bajo la GPL y que se ejecuta en Windows, Linux y macOS.

El código fuente está disponible en BitBucket . Me decepcionó un poco la documentación de compilación, que podría ser más detallada. Dice que necesita "Qt 5.2 o posterior" para construir el proyecto, pero en realidad requiere algunos paquetes específicos, lo que no fue fácil de deducir del registro de CMake. Por cierto, hoy en día se considera una buena práctica incluir un Dockerfile en el proyecto para que sea más fácil para el usuario configurar el entorno de desarrollo.

Aquí está la salida de la utilidad Cloc que muestra cómo SpeedCrunch se compara con otras calculadoras:

Imagen 2

Revisiones de errores para los otros proyectos:


El análisis se realizó con el analizador estático PVS-Studio . Este es un paquete de soluciones para el control de calidad del software y la detección de errores y vulnerabilidades potenciales. PVS-Studio es compatible con C, C ++, C # y Java, y se ejecuta en Windows, Linux y macOS.

Extraña lógica en un bucle


V560 Una parte de la expresión condicional siempre es cierta :! RuleFound. evaluator.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(); } } .... } .... } 

Tenga en cuenta la variable ruleFound : se establece en falso en cada iteración. Sin embargo, dentro del cuerpo del bucle, esa variable se establece en verdadero en ciertas condiciones, pero se volverá a establecer en falso en la próxima iteración. La variable ruleFound probablemente debería haberse declarado antes del bucle.

Comparaciones sospechosas


V560 Una parte de la expresión condicional siempre es verdadera: 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); } 

Si el valor de la variable shouldStop es verdadero , entonces la variable m_scrollDirection tomará uno de los dos valores: -1 o 1. Por lo tanto, su valor definitivamente será diferente de cero en la siguiente declaración condicional, que es sobre lo que el analizador está advirtiendo.

V668 No tiene sentido probar el puntero 'item' contra nulo, ya que la memoria fue asignada usando el operador 'nuevo'. La excepción se generará en caso de error de asignación de memoria. 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); .... } .... } 

La memoria para un objeto de tipo QTreeWidgetItem se asigna utilizando el nuevo operador. Significa que una falla de asignación de memoria conducirá a lanzar una excepción std :: bad_alloc () . Comprobar el puntero del elemento es, por lo tanto, redundante y se puede eliminar.

Desreferencia NULL potencial


V595 El puntero 'ioparams' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 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; } .... } 

El puntero ioparams se desreferencia antes de la verificación. Parece que hay algún error aquí. Dado que la desreferencia está precedida por una serie de condiciones, el error no aparecerá con frecuencia, pero tendrá un efecto drástico cuando lo haga.

División por cero


V609 Divide por cero. Rango del 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; } 

La función lgbase puede devolver cero, que luego podría usarse como divisor. La función se puede llamar potencialmente con cualquier valor, no solo 2, 8 o 16.

Comportamiento indefinido


V610 Comportamiento indefinido. Verifique el operador de turno '<<'. El operando izquierdo '(~ 0)' es 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; } 

Debido a que el resultado de invertir cero se almacena en un int con signo, el valor resultante será un número negativo, que luego se desplaza. Desplazar a la izquierda un valor negativo es un comportamiento indefinido.

Aquí hay una lista completa de todos estos casos:

  • V610 Comportamiento indefinido. Verifique el operador de turno '<<'. El operando izquierdo '(- 1)' es negativo. floatnum.c 289
  • V610 Comportamiento indefinido. Verifique el operador de turno '<<'. El operando izquierdo '(- 1)' es negativo. floatnum.c 325
  • V610 Comportamiento indefinido. Verifique el operador de turno '<<'. El operando izquierdo '(- 1)' es negativo. floatnum.c 344
  • V610 Comportamiento indefinido. Verifique el operador de turno '<<'. El operando izquierdo '(- 1)' es negativo. floatnum.c 351

Etiquetas HTML no cerradas


V735 Posiblemente un HTML incorrecto. Se encontró la etiqueta de cierre "</body>", mientras que se esperaba la etiqueta "</div>". 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 suele ser el caso con el código C / C ++, el estudio de la fuente no ayuda mucho a descifrar las cosas, por lo que echaremos un vistazo al código preprocesado:

Cuadro 3



El analizador ha detectado una etiqueta div no cerrada . Este archivo contiene muchos fragmentos en HTML, y los desarrolladores también deberán verificar ese código.

Aquí hay un par de otros casos sospechosos encontrados por PVS-Studio:

  • V735 Posiblemente un HTML incorrecto. Se encontró la etiqueta de cierre "</td>", mientras que se esperaba la etiqueta "</sub>". book.cpp 344
  • V735 Posiblemente un HTML incorrecto. Se encontró la etiqueta de cierre "</td>", mientras que se esperaba la etiqueta "</sub>". book.cpp 347

Operador de asignación


V794 El operador de asignación debe estar protegido del caso de 'this == & other'. cantidad.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; } 

Se recomienda que compruebe situaciones en las que un objeto se asigna a sí mismo comparando los punteros. En otras palabras, agregue las siguientes dos líneas al comienzo del cuerpo de la función:

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

Como recordatorio


V601 El valor 'falso' se convierte implícitamente en el tipo entero. 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. } 

Algunas veces usted dice en los comentarios que quizás algunas de las advertencias son activadas por un código incompleto. Sí, eso sucede de vez en cuando, pero señalamos específicamente tales casos.

Conclusión


Ya hemos verificado el código de tres programas de calculadora: Windows Calculator, Qalculate! Y SpeedCrunch, y no vamos a parar. No dude en sugerir proyectos que desea que verifiquemos porque las clasificaciones de software no siempre reflejan el estado real de las cosas.

Bienvenido a descargar PVS-Studio y probarlo en su propia "Calculadora". :-)

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


All Articles