Auf den Spuren von Taschenrechnern: SpeedCrunch

Bild 4

Hier sind wir und erforschen weiterhin den Code von Taschenrechnern! Heute werfen wir einen Blick auf das Projekt SpeedCrunch, den zweitbeliebtesten kostenlosen Taschenrechner.

Einführung


SpeedCrunch ist ein hochpräziser wissenschaftlicher Taschenrechner mit einer schnellen, tastaturgesteuerten Benutzeroberfläche. Es handelt sich um kostenlose Open-Source-Software, die unter der GPL lizenziert ist und unter Windows, Linux und MacOS ausgeführt wird.

Der Quellcode ist auf BitBucket verfügbar. Ich war etwas enttäuscht von der Build-Dokumentation, die detaillierter sein könnte. Es heißt, dass Sie "Qt 5.2 oder höher" benötigen, um das Projekt zu erstellen, aber es waren tatsächlich einige spezifische Pakete erforderlich, die aus dem CMake-Protokoll nicht einfach herauszufinden waren. Übrigens wird es heutzutage als bewährte Methode angesehen, eine Docker-Datei in das Projekt aufzunehmen, um dem Benutzer das Einrichten der Entwicklungsumgebung zu erleichtern.

Die folgende Ausgabe des Dienstprogramms Cloc zeigt, wie SpeedCrunch mit anderen Taschenrechnern verglichen wird:

Bild 2

Bug Reviews für die anderen Projekte:


Die Analyse wurde mit dem statischen Analysegerät PVS-Studio durchgeführt . Dies ist ein Lösungspaket zur Softwarequalitätskontrolle und Erkennung von Fehlern und potenziellen Schwachstellen. PVS-Studio unterstützt C, C ++, C # und Java und läuft unter Windows, Linux und MacOS.

Seltsame Logik in einer Schleife


V560 Ein Teil des bedingten Ausdrucks ist immer wahr:! 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(); } } .... } .... } 

Beachten Sie die Variable ruleFound : Sie wird bei jeder Iteration auf false gesetzt. Im Hauptteil der Schleife wird diese Variable unter bestimmten Bedingungen auf true gesetzt, bei der nächsten Iteration jedoch wieder auf false. Die Variable ruleFound sollte wahrscheinlich vor der Schleife deklariert worden sein.

Verdächtige Vergleiche


V560 Ein Teil des bedingten Ausdrucks ist immer wahr: 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); } 

Wenn der Wert der Variable shouldStop wahr ist , nimmt die Variable m_scrollDirection einen der beiden Werte an: -1 oder 1. Daher unterscheidet sich ihr Wert in der nächsten bedingten Anweisung definitiv von Null, worüber der Analysator warnt.

V668 Es macht keinen Sinn, den Zeiger 'item' gegen null zu testen, da der Speicher mit dem Operator 'new' zugewiesen wurde. Die Ausnahme wird bei einem Speicherzuordnungsfehler generiert. 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); .... } .... } 

Der Speicher für ein Objekt vom Typ QTreeWidgetItem wird mit dem neuen Operator zugewiesen. Dies bedeutet, dass ein Speicherzuordnungsfehler dazu führt, dass eine std :: bad_alloc () - Ausnahme ausgelöst wird . Das Überprüfen des Elementzeigers ist daher redundant und kann entfernt werden.

Mögliche NULL-Dereferenzierung


V595 Der Zeiger 'ioparams' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 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; } .... } 

Der ioparams- Zeiger wird vor der Prüfung dereferenziert. Es sieht so aus, als ob hier ein Fehler vorliegt. Da der Dereferenzierung eine Reihe von Bedingungen vorausgehen, tritt der Fehler nicht häufig auf, wirkt sich jedoch drastisch aus.

Division durch Null


V609 Durch Null teilen. Nennerbereich [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; } 

Die lgbase- Funktion kann Null zurückgeben, die dann als Divisor verwendet werden kann. Die Funktion kann möglicherweise mit einem beliebigen Wert aufgerufen werden, nicht nur mit 2, 8 oder 16.

Undefiniertes Verhalten


V610 Undefiniertes Verhalten. Überprüfen Sie den Schichtführer '<<'. Der linke Operand '(~ 0)' ist negativ. 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; } 

Da das Ergebnis der Invertierung von Null in einem vorzeichenbehafteten int gespeichert wird, ist der resultierende Wert eine negative Zahl, die dann verschoben wird. Das Verschieben eines negativen Werts nach links ist ein undefiniertes Verhalten.

Hier ist eine vollständige Liste all dieser Fälle:

  • V610 Undefiniertes Verhalten. Überprüfen Sie den Schichtführer '<<'. Der linke Operand '(- 1)' ist negativ. floatnum.c 289
  • V610 Undefiniertes Verhalten. Überprüfen Sie den Schichtführer '<<'. Der linke Operand '(- 1)' ist negativ. floatnum.c 325
  • V610 Undefiniertes Verhalten. Überprüfen Sie den Schichtführer '<<'. Der linke Operand '(- 1)' ist negativ. floatnum.c 344
  • V610 Undefiniertes Verhalten. Überprüfen Sie den Schichtführer '<<'. Der linke Operand '(- 1)' ist negativ. floatnum.c 351

Nicht geschlossene HTML-Tags


V735 Möglicherweise ein falsches HTML. Das schließende Tag "</ body>" wurde gefunden, während das Tag "</ div>" erwartet wurde. 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; } 

Wie so oft bei C / C ++ - Code hilft das Studium der Quelle nicht viel, um die Dinge herauszufinden. Wir werden uns stattdessen den vorverarbeiteten Code ansehen:

Bild 3



Der Analysator hat ein nicht geschlossenes div- Tag erkannt. Diese Datei enthält viele HTML-Schnipsel, und die Entwickler müssen auch diesen Code überprüfen.

Hier sind einige andere verdächtige Fälle, die von PVS-Studio gefunden wurden:

  • V735 Möglicherweise ein falsches HTML. Das schließende Tag "</ td>" wurde gefunden, während das Tag "</ sub>" erwartet wurde. book.cpp 344
  • V735 Möglicherweise ein falsches HTML. Das schließende Tag "</ td>" wurde gefunden, während das Tag "</ sub>" erwartet wurde. book.cpp 347

Zuweisungsoperator


V794 Der Zuweisungsoperator sollte vor dem Fall 'this == & other' geschützt werden. 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; } 

Es wird empfohlen, Situationen zu überprüfen, in denen ein Objekt sich selbst zugewiesen ist, indem Sie die Zeiger vergleichen. Mit anderen Worten, fügen Sie am Anfang des Funktionskörpers die folgenden zwei Zeilen hinzu:

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

Zur Erinnerung


V601 Der Wert 'false' wird implizit in den Integer-Typ umgewandelt. 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. } 

Manchmal sagen Sie in Kommentaren, dass möglicherweise einige der Warnungen durch unvollständigen Code ausgelöst werden. Ja, das passiert ab und zu, aber wir weisen ausdrücklich auf solche Fälle hin.

Fazit


Wir haben bereits den Code von drei Taschenrechnerprogrammen überprüft - Windows Calculator, Qalculate! Und SpeedCrunch - und werden nicht aufhören. Sie können gerne Projekte vorschlagen, die wir überprüfen sollen, da Software-Rankings nicht immer den tatsächlichen Stand der Dinge widerspiegeln.

Willkommen zum Herunterladen von PVS-Studio und zum Ausprobieren auf Ihrem eigenen „Rechner“. :-)

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


All Articles