Suivre la trace des calculatrices: SpeedCrunch

Image 4

L'étude du code de la calculatrice continue! Dans cette revue, le projet SpeedCrunch sera examiné - le deuxiÚme plus populaire parmi les calculatrices gratuites.

Présentation


SpeedCrunch est une calculatrice scientifique de précision pilotée par clavier avec une interface utilisateur rapide. Il s'agit d'un logiciel gratuit et open source disponible sur Windows, Linux et macOS.

Le code source est hĂ©bergĂ© sur BitBucket . Je n'aimais pas vraiment la documentation d'assemblage, qui, Ă  mon avis, devrait ĂȘtre Ă©crite plus en dĂ©tail. Les exigences spĂ©cifient «Qt 5.2 ou version ultĂ©rieure», bien que plusieurs packages spĂ©cifiques aient Ă©tĂ© nĂ©cessaires, ce qui n'Ă©tait pas facile Ă  apprendre du journal CMake. Soit dit en passant, il est dĂ©sormais recommandĂ© d'appliquer un Dockerfile Ă  un projet pour configurer rapidement l'environnement de dĂ©veloppement souhaitĂ©.

Pour comparaison avec d'autres calculatrices, j'apporte la sortie de l'utilitaire Cloc:

Image 2


Avis de bugs dans d'autres projets:


PVS-Studio a Ă©tĂ© utilisĂ© comme outil d'analyse statique. Il s'agit d'un ensemble de solutions pour le contrĂŽle de la qualitĂ© du code, la recherche d'erreurs et les vulnĂ©rabilitĂ©s potentielles. Les langages pris en charge incluent: C, C ++, C # et Java. L'analyseur peut ĂȘtre lancĂ© sur Windows, Linux et macOS.

Logique étrange dans une boucle


V560 Une partie de l'expression conditionnelle est toujours vraie:! 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(); } } .... } .... } 

Notez la variable ruleFound . À chaque itĂ©ration, elle est dĂ©finie sur false. Mais si vous regardez le corps de l'ensemble du cycle, dans certaines conditions, cette variable est dĂ©finie sur true, mais elle ne sera pas prise en compte lors de la nouvelle itĂ©ration du cycle. TrĂšs probablement, la variable ruleFound devait ĂȘtre dĂ©clarĂ©e avant la boucle.

Comparaisons suspectes


V560 Une partie de l'expression conditionnelle est toujours vraie: 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 la variable shouldStop est vraie , alors la variable m_scrollDirection aura l'une des deux valeurs: -1 ou 1. Par conséquent, dans l'instruction conditionnelle suivante, la valeur de la variable m_scrollDirection ne sera certainement pas nulle, ce que l'analyseur avertit.

V668 Il est inutile de tester le pointeur 'item' sur null, car la mémoire a été allouée à l'aide de l'opérateur 'new'. L'exception sera générée en cas d'erreur d'allocation de mémoire. 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 mĂ©moire d'un objet de type QTreeWidgetItem est allouĂ©e Ă  l'aide du nouvel opĂ©rateur. Cela signifie que si l'allocation dynamique de mĂ©moire n'est pas possible, une exception std :: bad_alloc () sera levĂ©e. Par consĂ©quent, la vĂ©rification du pointeur de l' Ă©lĂ©ment est superflue et peut ĂȘtre supprimĂ©e.

Déréférence NULL potentielle


V595 Le pointeur «ioparams» a Ă©tĂ© utilisĂ© avant d'ĂȘtre vĂ©rifiĂ© par rapport Ă  nullptr. VĂ©rifiez les lignes: 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; } .... } 

Le pointeur ioparams est dĂ©rĂ©fĂ©rencĂ© avant d'ĂȘtre vĂ©rifiĂ© pour sa validitĂ©. TrĂšs probablement, une erreur potentielle s'est glissĂ©e dans le code. Le dĂ©rĂ©fĂ©rencement Ă©tant soumis Ă  plusieurs conditions, le problĂšme peut se manifester rarement, mais avec prĂ©cision.

Division par zéro


V609 Divisez par zéro. Plage du dénominateur [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 fonction lgbase permet de renvoyer une valeur nulle, par laquelle la division est ensuite effectuĂ©e. Potentiellement, tout autre Ă©lĂ©ment que les valeurs 2, 8 et 16 peut ĂȘtre transmis Ă  la fonction.

Comportement indéfini


V610 Comportement indéfini . Vérifiez l'opérateur de décalage «<<». L'opérande gauche '(~ 0)' est négatif. 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; } 

Le résultat de l'inversion de zéro est placé dans le type de signe int , donc le résultat sera un nombre négatif, pour lequel un décalage est ensuite effectué. Décaler un nombre négatif vers la gauche est un comportement indéfini.

La liste complĂšte des endroits dangereux:

  • V610 Comportement indĂ©fini. VĂ©rifiez l'opĂ©rateur de dĂ©calage «<<». L'opĂ©rande gauche '(- 1)' est nĂ©gatif. floatnum.c 289
  • V610 Comportement indĂ©fini. VĂ©rifiez l'opĂ©rateur de dĂ©calage «<<». L'opĂ©rande gauche '(- 1)' est nĂ©gatif. floatnum.c 325
  • V610 Comportement indĂ©fini. VĂ©rifiez l'opĂ©rateur de dĂ©calage «<<». L'opĂ©rande gauche '(- 1)' est nĂ©gatif. floatnum.c 344
  • V610 Comportement indĂ©fini. VĂ©rifiez l'opĂ©rateur de dĂ©calage «<<». L'opĂ©rande gauche '(- 1)' est nĂ©gatif. floatnum.c 351

Balises HTML non fermées


V735 Peut-ĂȘtre un HTML incorrect. La balise de fermeture "</body>" a Ă©tĂ© rencontrĂ©e, tandis que la balise "</div>" Ă©tait attendue. 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; } 

Comme cela arrive souvent avec le code C / C ++, rien n'est clair à partir de la source, alors passons au code prétraité pour ce fragment:

Image 3



L'analyseur a dĂ©tectĂ© une Ă©tiquette div non fermĂ©e. Il y a beaucoup de fragments de code html dans ce fichier et maintenant il devrait ĂȘtre vĂ©rifiĂ© en plus par les dĂ©veloppeurs.

Voici quelques endroits plus suspects qui ont été trouvés en utilisant PVS-Studio:

  • V735 Peut-ĂȘtre un HTML incorrect. La balise de fermeture "</td>" a Ă©tĂ© rencontrĂ©e, tandis que la balise "</sub>" Ă©tait attendue. book.cpp 344
  • V735 Peut-ĂȘtre un HTML incorrect. La balise de fermeture "</td>" a Ă©tĂ© rencontrĂ©e, tandis que la balise "</sub>" Ă©tait attendue. book.cpp 347

Opérateur d'affectation


V794 L'opĂ©rateur d'affectation doit ĂȘtre protĂ©gĂ© contre le cas de 'this == & other'. quantitĂ©.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; } 

Il est recommandĂ© de considĂ©rer la situation lorsque l'objet est assignĂ© Ă  lui-mĂȘme en comparant les pointeurs.

En d'autres termes, les deux lignes de code suivantes doivent ĂȘtre ajoutĂ©es au dĂ©but du corps de la fonction:

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

Rappel


V601 La valeur 'false' est implicitement convertie en type entier. 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. } 

Parfois, dans les commentaires de nos articles, ils suggÚrent que certains avertissements sont émis sur un code incomplet. Oui, cela arrive, mais quand c'est vraiment le cas, c'est écrit directement à ce sujet.

Conclusion


Examens dĂ©jĂ  disponibles de trois calculatrices: Windows Calculator, Qalculate! et SpeedCrunch. Nous sommes prĂȘts Ă  continuer de rechercher le code des calculatrices populaires. Vous pouvez proposer des projets pour vĂ©rification, car les Ă©valuations du logiciel ne reflĂštent pas toujours l'image rĂ©elle.

Vérifiez votre «calculatrice» en téléchargeant PVS-Studio et en essayant votre projet :-)



Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Svyatoslav Razmyslov. Suivre les traces des calculatrices: SpeedCrunch

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


All Articles