Während Stockholm die 118. Nobelwoche abhielt, saß ich in unserem Büro, wo wir den statischen Analysator PVS-Studio entwickelten und an einer Analyseüberprüfung des ROOT-Projekts arbeiteten, einem Rahmen für die Verarbeitung großer Datenmengen, der in der wissenschaftlichen Forschung verwendet wird. Dieser Code würde natürlich keinen Preis gewinnen, aber die Autoren können sich definitiv auf eine detaillierte Überprüfung der interessantesten Mängel sowie eine kostenlose Lizenz verlassen, um das Projekt selbst gründlich zu überprüfen.
Einführung
ROOT ist ein modulares wissenschaftliches Software-Toolkit. Es bietet alle Funktionen, die für die Verarbeitung, statistische Analyse, Visualisierung und Speicherung von Big Data erforderlich sind. Es ist hauptsächlich in C ++ geschrieben. ROOT wurde am 
CERN geboren , im Zentrum der Forschung zur Hochenergiephysik. Täglich verwenden Tausende von Physikern ROOT-Anwendungen, um ihre Daten zu analysieren oder Simulationen durchzuführen.
PVS-Studio ist ein Tool zum Erkennen von Softwarefehlern und potenziellen Schwachstellen im Quellcode von Programmen, die in C, C ++, C # und Java geschrieben wurden. Es läuft unter 64-Bit-Windows, Linux und MacOS und kann Quellcode analysieren, der für 32-Bit-, 64-Bit- und eingebettete ARM-Plattformen geschrieben wurde.
Das Debüt einer neuen Diagnose
V1046 Unsichere Verwendung der Typen bool 'und' int 'zusammen in der Operation' & = '. GSLMultiRootFinder.h 175
int AddFunction(const ROOT::Math::IMultiGenFunction & func) { ROOT::Math::IMultiGenFunction * f = func.Clone(); if (!f) return 0; fFunctions.push_back(f); return fFunctions.size(); } template<class FuncIterator> bool SetFunctionList( FuncIterator begin, FuncIterator end) { bool ret = true; for (FuncIterator itr = begin; itr != end; ++itr) { const ROOT::Math::IMultiGenFunction * f = *itr; ret &= AddFunction(*f); } return ret; } 
Zunächst einmal ist hier ein wunderbarer Fehler, der von der Beta-Version von PVS-Studio gefunden wurde, die ich für diesen Test verwendet habe.
Erwartungen Die 
SetFunctionList- Funktion durchläuft eine Iteratorliste. Wenn mindestens ein Iterator ungültig ist, gibt die Funktion 
false oder andernfalls 
true zurück .
Realität Die 
SetFunctionList- Funktion kann auch für gültige Iteratoren 
false zurückgeben . Lassen Sie uns herausfinden, warum. Die 
AddFunction- Funktion gibt die Anzahl der gültigen Iteratoren in der 
fFunctions- Liste zurück. Das heißt, das Hinzufügen von Nicht-Null-Iteratoren führt dazu, dass die Liste schrittweise größer wird: 1, 2, 3, 4 usw. Hier kommt der Fehler ins Spiel:
 ret &= AddFunction(*f); 
Da die Funktion einen Wert vom Typ 
int anstelle von 
bool zurückgibt, gibt die Operation '& =' für gerade Werte 
false zurück , da das niedrigstwertige Bit einer geraden Zahl immer auf Null gesetzt wird. Auf diese Weise kann ein subtiler Fehler den Rückgabewert von 
SetFunctionsList auch dann 
aufheben , wenn seine Argumente gültig sind.
Fehler in bedingten Ausdrücken
V501 Links und rechts vom Operator '&&' befinden sich identische Unterausdrücke: Modul && Modul rootcling_impl.cxx 3650
 virtual void HandleDiagnostic(....) override { .... bool isROOTSystemModuleDiag = module && ....; bool isSystemModuleDiag = module && module && module->IsSystem; if (!isROOTSystemModuleDiag && !isSystemModuleDiag) fChild->HandleDiagnostic(DiagLevel, Info); .... } 
Beginnen wir mit dem am wenigsten schädlichen Fehler. Der Modulzeiger wird zweimal überprüft. Eine der Überprüfungen ist wahrscheinlich überflüssig, es wäre jedoch ratsam, sie zu beheben, um künftige Verwirrung zu vermeiden.
V501 Links und rechts vom '||' befinden sich identische Unterausdrücke 'strchr (fHostAuth-> GetHost (),' * ')'. Betreiber. TAuthenticate.cxx 300
 TAuthenticate::TAuthenticate(TSocket *sock, const char *remote, const char *proto, const char *user) { ....  
Die 
Zeichenfolge fHostAuth-> GetHost () wird zweimal nach dem Zeichen '*' durchsucht. Eine dieser Überprüfungen sollte wahrscheinlich nach dem '?' Zeichen, da diese beiden Zeichen normalerweise zum Angeben verschiedener Platzhaltermasken verwendet werden.
V517 Die Verwendung des 
Musters 'if (A) {...} else if (A) {...}' wurde erkannt. Es besteht die Wahrscheinlichkeit eines logischen Fehlers. Überprüfen Sie die Zeilen: 163, 165. TProofMonSenderML.cxx 163
 Int_t TProofMonSenderML::SendSummary(TList *recs, const char *id) { .... if (fSummaryVrs == 0) { if ((dsn = recs->FindObject("dataset"))) recs->Remove(dsn); } else if (fSummaryVrs == 0) {  
Die Variable 
fSummaryVrs wird zweimal mit Null verglichen, sodass die Ausführung niemals den Code im Zweig 
else-if erreicht . Und da ist ziemlich viel Code ...
V523 Die Anweisung 'then' entspricht der Anweisung 'else'. TKDTree.cxx 805
 template <typename Index, typename Value> void TKDTree<Index, Value>::UpdateRange(....) { .... if (point[fAxis[inode]]<=fValue[inode]){  
Der gleiche Codeblock, bei dem es sich um einen Copy-Paste-Klon handelt, wird unabhängig von der Bedingung ausgeführt. Ich denke, es gibt eine Verwechslung zwischen den Wörtern 
links und 
rechts .
Das Projekt ist voll von solchen verdächtigen Stellen:
- V523 Die Anweisung 'then' entspricht der Anweisung 'else'. TContainerConverters.cxx 51
- V523 Die Anweisung 'then' entspricht der Anweisung 'else'. TWebFile.cxx 1310
- V523 Die Anweisung 'then' entspricht der Anweisung 'else'. MethodMLP.cxx 423
- V523 Die Anweisung 'then' entspricht der Anweisung 'else'. RooAbsCategory.cxx 394
V547 Ausdruck '! File_name_value.empty ()' ist immer falsch. SelectionRules.cxx 1423
 bool SelectionRules::AreAllSelectionRulesUsed() const { for(auto&& rule : fClassSelectionRules){ .... std::string file_name_value; if (!rule.GetAttributeValue("file_name", file_name_value)) file_name_value.clear(); if (!file_name_value.empty()) {  
Dies ist wahrscheinlich kein Fehler; Der Analysator hat gerade einen Code gefunden, der vereinfacht werden kann. Da der Rückgabewert von 
file_name_value.empty () bereits zu Beginn der Schleife überprüft wird, kann die zweite doppelte Prüfung entfernt werden, wodurch eine große Menge unnötigen Codes 
weggeworfen wird.
V590 Überprüfen Sie die '! File1 || c <= 0 || c == '*' || c! = '(' 'Ausdruck. Der Ausdruck ist übermäßig oder enthält einen Druckfehler. TTabCom.cxx 840
 TString TTabCom::DetermineClass(const char varName[]) { .... c = file1.get(); if (!file1 || c <= 0 || c == '*' || c != '(') { Error("TTabCom::DetermineClass", "variable \"%s\" not defined?", varName); goto cleanup; } .... } 
Hier ist der Problemteil des vom Analysator gemeldeten bedingten Ausdrucks:
 if (.... || c == '*' || c != '(') { .... } 
Die Prüfung auf das Sternchen wirkt sich nicht auf das Ergebnis der Bedingung aus. Dieser Teil gilt immer für andere Zeichen als '('. Sie können ihn leicht selbst überprüfen, indem Sie eine Wahrheitstabelle zeichnen.
Zwei weitere Warnungen vor Zuständen mit seltsamer Logik:
- V590 Überprüfen Sie diesen Ausdruck. Der Ausdruck ist übertrieben oder enthält einen Druckfehler. TFile.cxx 3963
- V590 Überprüfen Sie diesen Ausdruck. Der Ausdruck ist übertrieben oder enthält einen Druckfehler. TStreamerInfoActions.cxx 3084
V593 Überprüfen Sie den Ausdruck der Art 'A = B <C'. Der Ausdruck wird wie folgt berechnet: 'A = (B <C)'. TProofServ.cxx 1903
 Int_t TProofServ::HandleSocketInput(TMessage *mess, Bool_t all) { .... if (Int_t ret = fProof->AddWorkers(workerList) < 0) { Error("HandleSocketInput:kPROOF_GETSLAVEINFO", "adding a list of worker nodes returned: %d", ret); } .... } 
Dieser Fehler zeigt sich nur bei fehlerhaftem Verhalten des Programms. Die Variable 
ret soll den Rückkehrcode der 
AddWorkers- Funktion 
speichern und diesen Wert im Fehlerfall in das Protokoll schreiben. Aber es funktioniert nicht wie beabsichtigt. Der Bedingung fehlen zusätzliche Klammern, die die gewünschte Reihenfolge der Bewertung erzwingen. Was die Variable 
ret tatsächlich speichert, ist nicht der Rückkehrcode, sondern das Ergebnis des logischen Vergleichs, dh entweder 0 oder 1.
Ein weiteres ähnliches Problem:
- V593 Überprüfen Sie den Ausdruck der Art 'A = B <C'. Der Ausdruck wird wie folgt berechnet: 'A = (B <C)'. TProofServ.cxx 3897
V768 Die Aufzählungskonstante 'kCostComplexityPruning' wird als Variable eines Booleschen Typs verwendet. MethodDT.cxx 283
 enum EPruneMethod {kExpectedErrorPruning=0, kCostComplexityPruning, kNoPruning}; void TMVA::MethodDT::ProcessOptions() { .... if (fPruneStrength < 0) fAutomatic = kTRUE; else fAutomatic = kFALSE; if (fAutomatic && fPruneMethod==!DecisionTree::kCostComplexityPruning){ Log() << kFATAL << "Sorry automatic pruning strength determination is ...." << Endl; } .... } 
Hm ... Warum den konstanten Wert 
kCostComplexityPruning negieren? Ich vermute, dass das Negationszeichen ein Tippfehler ist, der jetzt die Ausführungslogik verzerrt.
Zeigerhandhabungsfehler
V522 Eine Dereferenzierung des Nullzeigers 'pre' kann stattfinden. TSynapse.cxx 61
 void TSynapse::SetPre(TNeuron * pre) { if (pre) { Error("SetPre","this synapse is already assigned to a pre-neuron."); return; } fpre = pre; pre->AddPost(this); } 
Ich habe mein Bestes getan, um diesen seltsamen Code zu verstehen, und es scheint, dass die Idee darin bestand, dem Feld 
fpre keinen neuen Wert 
zuzuweisen . In diesem Fall überprüft der Programmierer versehentlich den falschen Zeiger. Die aktuelle Implementierung führt zur Dereferenzierung eines Nullzeigers, wenn Sie den 
Nullptr- Wert an die 
SetPre- Funktion übergeben.
Ich denke, dieses Snippet sollte wie folgt repariert werden:
 void TSynapse::SetPre(TNeuron * pre) { if (fpre) { Error("SetPre","this synapse is already assigned to a pre-neuron."); return; } fpre = pre; pre->AddPost(this); } 
Dies würde jedoch die Übergabe eines Nullzeigers an die Funktion nicht verhindern, aber zumindest ist diese Version logisch konsistenter als die ursprüngliche.
Ein leicht modifizierter Klon dieses Codes befindet sich an einer anderen Stelle:
- V522 Es kann zu einer Dereferenzierung des Nullzeigers 'post' kommen. TSynapse.cxx 74
V595 Der ' 
N' -Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 484, 488. Scanner.cxx 484
 bool RScanner::shouldVisitDecl(clang::NamedDecl *D) { if (auto M = D->getOwningModule()) {  
Dies ist ein äußerst gefährlicher Code! Der 
N- Zeiger wird nicht auf Null geprüft, bevor er beim ersten Mal dereferenziert wird. 
Darüber hinaus können Sie dies hier nicht sehen, da die Dereferenzierung innerhalb der Funktion 
shouldVisitDecl stattfindet .
Diese Diagnose generiert traditionell eine Reihe relevanter Warnungen. Hier nur einige Beispiele:
- V595 Der 'Datei'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 141, 153. TFileCacheRead.cxx 141
- V595 Der Zeiger 'fFree' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 2029, 2038. TFile.cxx 2029
- V595 Der Zeiger 'tbuf' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 586, 591. TGText.cxx 586
- V595 Der Zeiger 'fPlayer' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 3425, 3430. TProof.cxx 3425
- V595 Der Zeiger 'gProofServ' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 1192, 1194. TProofPlayer.cxx 1192
- V595 Der Zeiger 'projDataTmp' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 791, 804. RooSimultaneous.cxx 791
Der nächste ist kein Fehler, aber ein weiteres Beispiel dafür, wie Makros 
das Schreiben von fehlerhaftem oder redundantem Code 
fördern .
V571 Wiederkehrende Prüfung. Die Bedingung 'if (fCanvasImp)' wurde bereits in Zeile 799 überprüft. TCanvas.cxx 800
 #define SafeDelete(p) { if (p) { delete p; p = 0; } } void TCanvas::Close(Option_t *option) { .... if (fCanvasImp) SafeDelete(fCanvasImp); .... } 
Der 
fCanvasImp- Zeiger wird zweimal überprüft, wobei eine der Überprüfungen bereits im 
SafeDelete- Makro implementiert ist. Eines der Probleme mit Makros besteht darin, dass es schwierig ist, innerhalb des Codes zu navigieren. Dies ist der Grund, warum viele Programmierer ihren Inhalt vor der Verwendung nicht untersuchen.
Fehler bei der Array-Behandlung
V519 Der Variablen 'Line [Cursor]' werden zweimal hintereinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 352, 353. Editor.cpp 353
 size_t find_last_non_alnum(const std::string &str, std::string::size_type index = std::string::npos) { .... char tmp = Line.GetText()[Cursor]; Line[Cursor] = Line[Cursor - 1]; Line[Cursor] = tmp; .... } 
Dem Element 
Zeile [Cursor] wird ein neuer Wert zugewiesen, der dann sofort überschrieben wird. Das sieht nicht richtig aus ...
V557 Array-Überlauf ist möglich. Der 'ivar'-Index zeigt über die Array-Grenze hinaus. BasicMinimizer.cxx 130
 bool BasicMinimizer::SetVariableValue(unsigned int ivar, double val) { if (ivar > fValues.size() ) return false; fValues[ivar] = val; return true; } 
Dieser Fehler beim Überprüfen von Array-Indizes ist ein aktueller Trend. Wir sehen es in fast jedem dritten Projekt. Während die Indizierung in ein Array innerhalb einer Schleife einfach ist - normalerweise verwenden Sie den Operator '<', um den Index mit der Größe des Arrays zu vergleichen - erfordern Überprüfungen wie die oben gezeigte den Operator '> =', nicht '>'. Andernfalls besteht die Gefahr, dass Sie ein Element über die Grenzen des Arrays hinaus indizieren.
Dieser Fehler wurde einige Male im gesamten Code geklont:
- V557 Array-Überlauf ist möglich. Der 'ivar'-Index zeigt über die Array-Grenze hinaus. BasicMinimizer.cxx 186
- V557 Array-Überlauf ist möglich. Der 'ivar'-Index zeigt über die Array-Grenze hinaus. BasicMinimizer.cxx 194
- V557 Array-Überlauf ist möglich. Der 'ivar'-Index zeigt über die Array-Grenze hinaus. BasicMinimizer.cxx 209
- V557 Array-Überlauf ist möglich. Der 'ivar'-Index zeigt über die Array-Grenze hinaus. BasicMinimizer.cxx 215
- V557 Array-Überlauf ist möglich. Der 'ivar'-Index zeigt über die Array-Grenze hinaus. BasicMinimizer.cxx 230
V621 Überprüfen Sie den ' 
for' -Operator. Es ist möglich, dass die Schleife falsch oder gar nicht ausgeführt wird. TDataMember.cxx 554
 Int_t TDataMember::GetArrayDim() const { if (fArrayDim<0 && fInfo) { R__LOCKGUARD(gInterpreterMutex); TDataMember *dm = const_cast<TDataMember*>(this); dm->fArrayDim = gCling->DataMemberInfo_ArrayDim(fInfo);  
In der 
for- Schleife 
wollten die Entwickler anscheinend die 
dim- Variable mit 
dm-> fArrayDim und nicht mit 
fArrayDim vergleichen . Der Wert von 
fArrayDim ist negativ, was durch die Bedingung am Anfang der Funktion garantiert wird. Folglich wird diese Schleife niemals ausgeführt.
V767 Verdächtiger Zugriff auf das Element des 'aktuellen' Arrays durch einen konstanten Index innerhalb einer Schleife. TClingUtils.cxx 3082
 llvm::StringRef ROOT::TMetaUtils::DataMemberInfo__ValidArrayIndex(....) { .... while (current!=0) {  
Dieser Code analysiert und überprüft eine Zeichenfolge. Wenn das erste Zeichen der 
aktuellen Zeichenfolge (dh bei Index 0) als Zahl erkannt wurde, durchläuft die Schleife alle übrigen Zeichen, um sicherzustellen, dass es sich bei allen um Zahlen handelt. Zumindest ist das die Idee. Das Problem ist, dass der 
i- Zähler in der Schleife nicht verwendet wird. Die Bedingung sollte so umgeschrieben werden, dass sie 
aktuell [i] und nicht 
aktuell [0] prüft.
Speicherverlust
V773 Die Funktion wurde beendet, ohne den Zeiger 'Optionsliste' loszulassen. Ein Speicherverlust ist möglich. TDataMember.cxx 355
 void TDataMember::Init(bool afterReading) { .... TList *optionlist = new TList();  
Der 
optionList- Zeiger wird vor der Rückkehr von der Funktion nicht freigegeben. Ich weiß nicht, ob eine solche Freigabe in diesem speziellen Fall erforderlich ist, aber wenn wir solche Fehler melden, beheben Entwickler sie normalerweise. Es hängt alles davon ab, ob Ihr Programm im Fehlerfall weiter ausgeführt werden soll oder nicht. ROOT weist eine Reihe solcher Mängel auf, daher würde ich den Autoren raten, das Projekt selbst zu überprüfen.
wieder memset
V597 Der Compiler könnte den Funktionsaufruf 'memset' löschen, mit dem der 'x'-Puffer geleert wird. Die Funktion memset_s () sollte verwendet werden, um die privaten Daten zu löschen. TMD5.cxx 366
 void TMD5::Transform(UInt_t buf[4], const UChar_t in[64]) { UInt_t a, b, c, d, x[16]; ....  
Viele denken, dass der Kommentar nach der Kompilierung nicht in die Binärdatei gelangt, und sie sind absolut korrekt: D. Einige wissen möglicherweise nicht, dass der Compiler auch die 
Memset- Funktion entfernt. Und das wird sicher passieren. Wenn der betreffende Puffer im Code nicht mehr weiter verwendet wird, optimiert der Compiler den Funktionsaufruf. Technisch gesehen ist es eine vernünftige Entscheidung, aber wenn der Puffer private Daten gespeichert hat, bleiben diese Daten dort. Dies ist eine klassische Sicherheitsschwäche 
CWE-14 .
Verschiedenes
V591 Non-void-Funktion sollte einen Wert zurückgeben. LogLikelihoodFCN.h 108
 LogLikelihoodFCN & operator = (const LogLikelihoodFCN & rhs) { SetData(rhs.DataPtr() ); SetModelFunction(rhs.ModelFunctionPtr() ); fNEffPoints = rhs.fNEffPoints; fGrad = rhs.fGrad; fIsExtended = rhs.fIsExtended; fWeight = rhs.fWeight; fExecutionPolicy = rhs.fExecutionPolicy; } 
Der überladene Operator hat keinen Rückgabewert. Dies ist ein weiterer aktueller Trend.
V596 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Das Schlüsselwort 'throw' könnte fehlen: throw runtime_error (FOO); RTensor.hxx 363
 template <typename Value_t, typename Container_t> inline RTensor<Value_t, Container_t> RTensor<Value_t, Container_t>::Transpose() { if (fLayout == MemoryLayout::RowMajor) { fLayout = MemoryLayout::ColumnMajor; } else if (fLayout == MemoryLayout::ColumnMajor) { fLayout = MemoryLayout::RowMajor; } else { std::runtime_error("Memory layout is not known."); } .... } 
Das Problem ist, dass der Programmierer das Schlüsselwort 
throw versehentlich weggelassen hat, wodurch das Auslösen einer Ausnahme im Fehlerfall verhindert wird.
Es gab nur zwei Warnungen dieses Typs. Hier ist der zweite:
- V596 Das Objekt wurde erstellt, wird jedoch nicht verwendet. Das Schlüsselwort 'throw' könnte fehlen: throw runtime_error (FOO); Forest.hxx 137
V609 Durch Null teilen. Nennerbereich [0..100]. TGHtmlImage.cxx 340
 const char *TGHtml::GetPctWidth(TGHtmlElement *p, char *opt, char *ret) { int n, m, val; .... if (n < 0 || n > 100) return z; if (opt[0] == 'h') { val = fCanvas->GetHeight() * 100; } else { val = fCanvas->GetWidth() * 100; } if (!fInTd) { snprintf(ret, 15, "%d", val / n);  
Dieser ähnelt den zuvor diskutierten Beispielen für die Array-Behandlung. Die Variable 
n ist auf den Bereich von 0 bis 100 begrenzt. Aber dann gibt es einen Zweig, der eine Division durch die Variable 
n durchführt , die den Wert 0 haben kann. Ich denke, die Bereichsgrenzen von 
n sollten wie folgt festgelegt werden:
 if (n <= 0 || n > 100) return z; 
V646 Überprüfen Sie die Logik der Anwendung. Möglicherweise fehlt das Schlüsselwort "else". TProofServ.cxx 729
 TProofServ::TProofServ(Int_t *argc, char **argv, FILE *flog) : TApplication("proofserv", argc, argv, 0, -1) { .... if (!logmx.IsDigit()) { if (logmx.EndsWith("K")) { xf = 1024; logmx.Remove(TString::kTrailing, 'K'); } else if (logmx.EndsWith("M")) { xf = 1024*1024; logmx.Remove(TString::kTrailing, 'M'); } if (logmx.EndsWith("G")) { xf = 1024*1024*1024; logmx.Remove(TString::kTrailing, 'G'); } } .... } 
Der Analysator meldet eine seltsam formatierte 
if- Anweisung mit dem fehlenden 
else- Schlüsselwort. Die Art und Weise, wie dieser Code aussieht, deutet darauf hin, dass er behoben werden muss.
Noch ein paar Warnungen dieses Typs:
- V646 Überprüfen Sie die Logik der Anwendung. Möglicherweise fehlt das Schlüsselwort "else". TFormula_v5.cxx 3702
- V646 Überprüfen Sie die Logik der Anwendung. Möglicherweise fehlt das Schlüsselwort "else". RooAbsCategory.cxx 604
V663 Endlosschleife ist möglich. Die Bedingung 'cin.eof ()' reicht nicht aus, um die Schleife zu verlassen. Fügen Sie dem bedingten Ausdruck möglicherweise den Funktionsaufruf 'cin.fail ()' hinzu. MethodKNN.cxx 602
 void TMVA::MethodKNN::ReadWeightsFromStream(std::istream& is) { .... while (!is.eof()) { std::string line; std::getline(is, line); if (line.empty() || line.find("#") != std::string::npos) { continue; } .... } .... } 
Wenn Sie mit der Klasse 
std :: istream arbeiten , 
reicht es nicht aus, die Funktion 
eof () aufzurufen , um die Schleife zu beenden. Die Funktion 
eof () gibt immer 
false zurück, wenn die Daten nicht gelesen werden können und dieser Code keine anderen Endpunkte enthält. Um die Beendigung der Schleife zu gewährleisten, ist eine zusätzliche Überprüfung des von der Funktion 
fail () zurückgegebenen Werts erforderlich:
 while (!is.eof() && !is.fail()) { .... } 
Alternativ kann es wie folgt umgeschrieben werden:
 while (is) { .... } 
V678 Ein Objekt wird als Argument für seine eigene Methode verwendet. Überprüfen Sie das erste tatsächliche Argument der Funktion 'Kopieren'. TFormLeafInfo.cxx 2414
 TFormLeafInfoMultiVarDim::TFormLeafInfoMultiVarDim( const TFormLeafInfoMultiVarDim& orig) : TFormLeafInfo(orig) { fNsize = orig.fNsize; fSizes.Copy(fSizes);  
Beenden wir den Artikel mit diesem netten kleinen Tippfehler. Die 
Kopierfunktion sollte mit 
orig.fSizes aufgerufen werden , nicht mit 
fSizes .
Fazit
Vor ungefähr einem Jahr haben wir das 
NCBI Genome Workbench- Projekt überprüft, ein weiteres Programm, das in der wissenschaftlichen Forschung zur Genomanalyse eingesetzt wird. Ich erwähne dies, weil die Qualität wissenschaftlicher Software äußerst wichtig ist, Entwickler sie jedoch tendenziell unterschätzen.
MacOS 10.15 Catalina wurde übrigens neulich veröffentlicht, wo die Unterstützung von 32-Bit-Anwendungen eingestellt wurde. Glücklicherweise bietet PVS-Studio eine Vielzahl von Diagnosen, die speziell zur Erkennung von Fehlern entwickelt wurden, die mit der Portierung von Programmen auf 64-Bit-Systeme einhergehen. Weitere Informationen finden Sie in diesem 
Beitrag des PVS-Studio-Teams.