Auf den Spuren der Taschenrechner: SpeedCrunch

Bild 4

Das Studium des Taschenrechnercodes geht weiter! In dieser Überprüfung wird das SpeedCrunch-Projekt überprüft - das zweitbeliebteste unter kostenlosen Taschenrechnern.

Einführung


SpeedCrunch ist ein hochpräziser, wissenschaftlicher Tastaturrechner mit schneller Benutzeroberfläche. Dies ist kostenlose Open Source-Software, die unter Windows, Linux und macOS verfügbar ist.

Der Quellcode wird auf BitBucket gehostet. Die Montagedokumentation, die meiner Meinung nach ausführlicher geschrieben werden sollte, hat mir nicht wirklich gefallen. Die Anforderungen spezifizieren "Qt 5.2 oder höher", obwohl mehrere spezifische Pakete benötigt wurden, die nicht einfach aus dem CMake-Protokoll zu lernen waren. Übrigens empfiehlt es sich jetzt, eine Docker-Datei auf ein Projekt anzuwenden, um die gewünschte Entwicklerumgebung schnell zu konfigurieren.

Zum Vergleich mit anderen Taschenrechnern bringe ich die Ausgabe des Cloc-Dienstprogramms:

Bild 2


Bewertungen von Fehlern in anderen Projekten:


PVS-Studio wurde als statisches Analysewerkzeug verwendet. Dies ist eine Reihe von Lösungen für die Codequalitätskontrolle, die Suche nach Fehlern und potenziellen Schwachstellen. Unterstützte Sprachen sind: C, C ++, C # und Java. Der Analysator kann unter Windows, Linux und MacOS gestartet werden.

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 . Bei jeder Iteration wird der Wert auf false gesetzt. Wenn Sie sich jedoch den Hauptteil des gesamten Zyklus ansehen, wird diese Variable unter bestimmten Bedingungen auf true gesetzt, sie wird jedoch bei der neuen Iteration des Zyklus nicht berücksichtigt. Höchstwahrscheinlich musste die Variable ruleFound vor der Schleife deklariert werden.

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 die Variable shouldStop true ist , hat die Variable m_scrollDirection einen von zwei Werten: -1 oder 1. Daher ist in der folgenden bedingten Anweisung der Wert der Variablen m_scrollDirection definitiv nicht 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 eine Ausnahme std :: bad_alloc () ausgelöst wird, wenn keine dynamische Speicherzuordnung möglich ist. Daher ist das Überprüfen des Elementzeigers überflüssig und kann gelöscht 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 dereferenziert, bevor er auf Gültigkeit überprüft wird. Höchstwahrscheinlich hat sich ein potenzieller Fehler in den Code eingeschlichen. Da die Dereferenzierung unter verschiedenen Bedingungen erfolgt, kann sich das Problem selten, aber genau manifestieren.

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

Mit der Funktion lgbase kann ein Nullwert zurückgegeben werden, durch den dann die Division durchgeführt wird. Möglicherweise kann alles andere als die Werte 2, 8 und 16 an die Funktion übergeben werden.

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

Das Ergebnis der Umkehrung von Null wird in den vorzeichenbehafteten Typ int gesetzt , sodass das Ergebnis eine negative Zahl ist, für die dann eine Verschiebung durchgeführt wird. Das Verschieben einer negativen Zahl nach links ist ein undefiniertes Verhalten.

Die ganze Liste gefährlicher Orte:

  • 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 ist aus der Quelle nichts ersichtlich. Wenden wir uns daher dem vorverarbeiteten Code für dieses Fragment zu:

Bild 3



Der Analysator hat ein nicht geschlossenes Div-Tag erkannt. Diese Datei enthält viele Fragmente von HTML-Code, die jetzt zusätzlich von den Entwicklern überprüft werden sollten.

Hier sind einige weitere verdächtige Orte, die mit 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, die Situation zu berücksichtigen, in der das Objekt sich selbst zugewiesen wird, indem die Zeiger verglichen werden.

Mit anderen Worten, die folgenden zwei Codezeilen sollten am Anfang des Funktionskörpers hinzugefügt werden:

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

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

In den Kommentaren zu unseren Artikeln wird manchmal darauf hingewiesen, dass einige Warnungen auf einen unvollständigen Code ausgegeben werden. Ja, es passiert, aber wenn es wirklich so ist, wird es direkt darüber geschrieben.

Fazit


Bereits verfügbare Testberichte zu drei Taschenrechnern: Windows Calculator, Qalculate! und SpeedCrunch. Wir sind bereit, den Code gängiger Taschenrechner weiter zu erforschen. Sie können Projekte zur Überprüfung anbieten, da die Bewertungen der Software nicht immer das tatsächliche Bild widerspiegeln.

Überprüfen Sie Ihren "Rechner", indem Sie PVS-Studio herunterladen und Ihr Projekt anprobieren :-)



Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Svyatoslav Razmyslov. Auf den Spuren von Taschenrechnern: SpeedCrunch

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


All Articles