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.