Analizando el Código de ROOT, Marco de Análisis de Datos Científicos

Cuadro 3
Mientras Estocolmo celebraba la 118a Semana Nobel, estaba sentado en nuestra oficina, donde desarrollamos el analizador estático PVS-Studio, trabajando en una revisión de análisis del proyecto ROOT, un marco de procesamiento de grandes datos utilizado en la investigación científica. Este código no ganaría un premio, por supuesto, pero los autores definitivamente pueden contar con una revisión detallada de los defectos más interesantes más una licencia gratuita para verificar a fondo el proyecto por su cuenta.

Introduccion


Imagen 1

ROOT es un kit de herramientas de software científico modular. Proporciona todas las funcionalidades necesarias para lidiar con el procesamiento de big data, análisis estadístico, visualización y almacenamiento. Está escrito principalmente en C ++. ROOT nació en el CERN , en el corazón de la investigación sobre física de alta energía. Todos los días, miles de físicos utilizan aplicaciones ROOT para analizar sus datos o realizar simulaciones.

PVS-Studio es una herramienta para detectar errores de software y vulnerabilidades potenciales en el código fuente de programas escritos en C, C ++, C # y Java. Se ejecuta en Windows, Linux y macOS de 64 bits y puede analizar el código fuente escrito para plataformas ARM integradas y de 32 bits.

El debut de un nuevo diagnóstico.


V1046 Uso inseguro de los tipos bool 'e' int 'juntos en la operación' & = '. 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; } 

En primer lugar, aquí hay un error maravilloso encontrado por la versión beta de PVS-Studio, que estaba usando para esta revisión.

Las expectativas La función SetFunctionList atraviesa una lista de iteradores. Si al menos un iterador no es válido, la función devuelve falso o verdadero en caso contrario.

Realidad La función SetFunctionList puede devolver false incluso para iteradores válidos. Averigüemos por qué. La función AddFunction devuelve el número de iteradores válidos en la lista de fFunctions . Es decir, agregar iteradores no nulos hará que la lista crezca gradualmente en tamaño: 1, 2, 3, 4, etc. Aquí es donde entra en juego el error:

 ret &= AddFunction(*f); 

Dado que la función devuelve un valor de tipo int en lugar de bool , la operación '& =' devolverá falso para valores pares porque el bit menos significativo de un número par siempre se establece en cero. Así es como un error sutil puede romper el valor de retorno de SetFunctionsList incluso cuando sus argumentos son válidos.

Imagen 2

Errores en expresiones condicionales


V501 Hay subexpresiones idénticas a la izquierda y a la derecha del operador '&&': módulo && module rootcling_impl.cxx 3650

 virtual void HandleDiagnostic(....) override { .... bool isROOTSystemModuleDiag = module && ....; bool isSystemModuleDiag = module && module && module->IsSystem; if (!isROOTSystemModuleDiag && !isSystemModuleDiag) fChild->HandleDiagnostic(DiagLevel, Info); .... } 

Comencemos con el error menos dañino. El puntero del módulo se verifica dos veces. Probablemente una de las verificaciones sea redundante, sin embargo, sería prudente solucionarlo para evitar confusiones en el futuro.

V501 Hay subexpresiones idénticas 'strchr (fHostAuth-> GetHost (),' * ')' a la izquierda y a la derecha de '||' operador TAuthenticate.cxx 300

 TAuthenticate::TAuthenticate(TSocket *sock, const char *remote, const char *proto, const char *user) { .... // If generic THostAuth (ie with wild card or user == any) // make a personalized memory copy of this THostAuth if (strchr(fHostAuth->GetHost(),'*') || strchr(fHostAuth->GetHost(),'*') || fHostAuth->GetServer() == -1 ) { fHostAuth = new THostAuth(*fHostAuth); fHostAuth->SetHost(fqdn); fHostAuth->SetUser(checkUser); fHostAuth->SetServer(servtype); } .... } 

La cadena fHostAuth-> GetHost () se explora dos veces para el carácter '*'. Probablemente, uno de estos controles estaba destinado a buscar el '?' carácter, ya que estos dos caracteres suelen ser los que se utilizan para especificar varias máscaras comodín.

V517 El uso del patrón 'if (A) {...} else if (A) {...}' fue detectado. Hay una probabilidad de presencia de error lógico. Líneas de verificación: 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) { // Only the first records xrecs = new TList; xrecs->SetOwner(kFALSE); TIter nxr(recs); TObject *o = 0; while ((o = nxr())) { if (!strcmp(o->GetName(), "vmemmxw")) break; xrecs->Add(o); } } .... } 

La variable fSummaryVrs se compara con cero dos veces, por lo que la ejecución nunca alcanza el código en la rama else-if . Y hay bastante código allí ...

V523 La declaración 'then' es equivalente a la declaración 'else'. TKDTree.cxx 805

 template <typename Index, typename Value> void TKDTree<Index, Value>::UpdateRange(....) { .... if (point[fAxis[inode]]<=fValue[inode]){ //first examine the node that contains the point UpdateRange(GetLeft(inode),point, range, res); UpdateRange(GetRight(inode),point, range, res); } else { UpdateRange(GetLeft(inode),point, range, res); UpdateRange(GetRight(inode),point, range, res); } .... } 

El mismo bloque de código, que es un clon de copiar y pegar, se ejecuta sin importar la condición. Supongo que hay una confusión entre las palabras izquierda y derecha .

El proyecto está lleno de puntos sospechosos como ese:

  • V523 La declaración 'then' es equivalente a la declaración 'else'. TContainerConverters.cxx 51
  • V523 La declaración 'then' es equivalente a la declaración 'else'. TWebFile.cxx 1310
  • V523 La declaración 'then' es equivalente a la declaración 'else'. Método MLP.cxx 423
  • V523 La declaración 'then' es equivalente a la declaración 'else'. RooAbsCategory.cxx 394

La expresión V547 '! File_name_value.empty ()' siempre es falsa. 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()) { // <= // don't complain about defined_in rules continue; } const char* attrName = nullptr; const char* attrVal = nullptr; if (!file_name_value.empty()) { // <= attrName = "file name"; attrVal = file_name_value.c_str(); } else { attrName = "class"; if (!name.empty()) attrVal = name.c_str(); } ROOT::TMetaUtils::Warning(0,"Unused %s rule: %s\n", attrName, attrVal); } .... } 

Esto probablemente no sea un error; El analizador acaba de encontrar un código que se puede simplificar. Dado que el valor de retorno de file_name_value.empty () ya está marcado al comienzo del ciclo, la segunda verificación duplicada se puede eliminar, lo que arroja una buena cantidad de código innecesario.

V590 Considere inspeccionar el '! File1 || c <= 0 || c == '*' || c! = '(' 'expresión. La expresión es excesiva o contiene un error de imprenta. 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; } .... } 

Aquí está la parte problemática de la expresión condicional informada por el analizador:

 if (.... || c == '*' || c != '(') { .... } 

La verificación del carácter de asterisco no afectará el resultado de la condición. Esta parte siempre será cierta para cualquier carácter que no sea '('. Puede verificarlo usted mismo fácilmente dibujando una tabla de verdad.

Dos advertencias más sobre condiciones con lógica extraña:

  • V590 Considere inspeccionar esta expresión. La expresión es excesiva o contiene un error de imprenta. TFile.cxx 3963
  • V590 Considere inspeccionar esta expresión. La expresión es excesiva o contiene un error de imprenta. TStreamerInfoActions.cxx 3084

V593 Considere revisar la expresión del tipo 'A = B <C'. La expresión se calcula de la siguiente manera: '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); } .... } 

Este error se revela solo en el caso del comportamiento defectuoso del programa. Se supone que la variable ret almacena el código de retorno de la función AddWorkers y escribe ese valor en el registro en caso de error. Pero no funciona según lo previsto. La condición carece de paréntesis adicionales que fuercen el orden de evaluación deseado. Lo que la variable ret realmente almacena no es el código de retorno sino el resultado de la comparación lógica, es decir, 0 o 1.

Otro problema similar:

  • V593 Considere revisar la expresión del tipo 'A = B <C'. La expresión se calcula de la siguiente manera: 'A = (B <C)'. TProofServ.cxx 3897

V768 La constante de enumeración 'kCostComplexityPruning' se usa como una variable de tipo booleano. 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 ... ¿Por qué negar el valor constante kCostComplexityPruning ? Sospecho que el personaje de negación es un error tipográfico, que ahora distorsiona la lógica de ejecución.

Errores de manejo del puntero


V522 Puede tener lugar la desreferenciación del puntero nulo 'pre'. 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); } 

Hice mi mejor esfuerzo tratando de entender este extraño código, y parece que la idea era evitar asignar un nuevo valor al campo fpre . Si es así, el programador está comprobando accidentalmente el puntero incorrecto. La implementación actual lleva a desreferenciar un puntero nulo si pasa el valor nullptr a la función SetPre .

Creo que este fragmento debe repararse de la siguiente manera:

 void TSynapse::SetPre(TNeuron * pre) { if (fpre) { Error("SetPre","this synapse is already assigned to a pre-neuron."); return; } fpre = pre; pre->AddPost(this); } 

Sin embargo, esto no evitaría el paso de un puntero nulo a la función, pero al menos esta versión es lógicamente más consistente que la original.

Un clon ligeramente modificado de este código se puede encontrar en otro lugar:

  • V522 Puede tener lugar la desreferenciación del puntero nulo 'post'. TSynapse.cxx 74

V595 El puntero 'N' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 484, 488. Scanner.cxx 484

 bool RScanner::shouldVisitDecl(clang::NamedDecl *D) { if (auto M = D->getOwningModule()) { // <= 2 return fInterpreter.getSema().isModuleVisible(M); } return true; } bool RScanner::VisitNamespaceDecl(clang::NamespaceDecl* N) { if (fScanType == EScanType::kOnePCM) return true; if (!shouldVisitDecl(N)) // <= 1 return true; if((N && N->isImplicit()) || !N){ // <= 3 return true; } .... } 

¡Este es un código extremadamente peligroso! El puntero N no se verifica como nulo antes de que se desreferencia la primera vez. Además, no puede ver que suceda aquí porque la desreferencia se lleva a cabo dentro de la función shouldVisitDecl .

Este diagnóstico tradicionalmente genera un montón de advertencias relevantes. Estos son solo algunos ejemplos:

  • V595 El puntero 'archivo' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 141, 153. TFileCacheRead.cxx 141
  • V595 El puntero 'fFree' se utilizó antes de verificarlo con nullptr. Líneas de verificación: 2029, 2038. TFile.cxx 2029
  • V595 El puntero 'tbuf' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 586, 591. TGText.cxx 586
  • V595 El puntero 'fPlayer' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 3425, 3430. TProof.cxx 3425
  • V595 El puntero 'gProofServ' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 1192, 1194. TProofPlayer.cxx 1192
  • V595 El puntero 'projDataTmp' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 791, 804. RooSim simultánea.cxx 791

El siguiente no es un error, pero es otro ejemplo de cómo las macros fomentan la escritura de código defectuoso o redundante.

V571 Verificación recurrente. La condición 'if (fCanvasImp)' ya se verificó en la línea 799. TCanvas.cxx 800

 #define SafeDelete(p) { if (p) { delete p; p = 0; } } void TCanvas::Close(Option_t *option) { .... if (fCanvasImp) SafeDelete(fCanvasImp); .... } 

El puntero fCanvasImp se verifica dos veces, con una de las comprobaciones ya implementadas en la macro SafeDelete . Uno de los problemas con las macros es que son difíciles de navegar desde el código, razón por la cual muchos programadores no examinan su contenido antes de usarlo.

Errores de manejo de matriz


V519 A la variable 'Línea [Cursor]' se le asignan valores dos veces seguidas. Quizás esto sea un error. Líneas de verificación: 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; .... } 

Al elemento Línea [Cursor] se le asigna un nuevo valor, que luego se sobrescribe inmediatamente. Eso no se ve bien ...

V557 Array overrun es posible. El índice 'ivar' apunta más allá del límite de la matriz. BasicMinimizer.cxx 130

 bool BasicMinimizer::SetVariableValue(unsigned int ivar, double val) { if (ivar > fValues.size() ) return false; fValues[ivar] = val; return true; } 

Cometer este error al verificar los índices de la matriz es una tendencia reciente; Lo vemos en casi cada tercer proyecto. Si bien la indexación en una matriz dentro de un bucle es fácil, generalmente usa el operador '<' para comparar el índice con el tamaño de la matriz, las comprobaciones como la que se muestra arriba requieren el operador '> =', no '>'. De lo contrario, corre el riesgo de indexar un elemento más allá del límite de la matriz.

Este error fue clonado en todo el código varias veces:

  • V557 Array overrun es posible. El índice 'ivar' apunta más allá del límite de la matriz. BasicMinimizer.cxx 186
  • V557 Array overrun es posible. El índice 'ivar' apunta más allá del límite de la matriz. BasicMinimizer.cxx 194
  • V557 Array overrun es posible. El índice 'ivar' apunta más allá del límite de la matriz. BasicMinimizer.cxx 209
  • V557 Array overrun es posible. El índice 'ivar' apunta más allá del límite de la matriz. BasicMinimizer.cxx 215
  • V557 Array overrun es posible. El índice 'ivar' apunta más allá del límite de la matriz. BasicMinimizer.cxx 230

V621 Considere inspeccionar el operador 'para'. Es posible que el bucle se ejecute incorrectamente o no se ejecute en absoluto. 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); // fArrayMaxIndex should be zero if (dm->fArrayDim) { dm->fArrayMaxIndex = new Int_t[fArrayDim]; for(Int_t dim = 0; dim < fArrayDim; ++dim) { dm->fArrayMaxIndex[dim] = gCling->DataMemberInfo_MaxIndex(fInfo,dim); } } } return fArrayDim; } 

En el bucle for , los desarrolladores aparentemente tenían la intención de comparar la variable dim con dm-> fArrayDim en lugar de fArrayDim . El valor de fArrayDim es negativo, lo que está garantizado por la condición al comienzo de la función. En consecuencia, este bucle nunca se ejecutará.

V767 Acceso sospechoso al elemento de la matriz 'actual' por un índice constante dentro de un bucle. TClingUtils.cxx 3082

 llvm::StringRef ROOT::TMetaUtils::DataMemberInfo__ValidArrayIndex(....) { .... while (current!=0) { // Check the token if (isdigit(current[0])) { for(i=0;i<strlen(current);i++) { if (!isdigit(current[0])) { if (errstr) *errstr = current; if (errnum) *errnum = NOT_INT; return llvm::StringRef(); } } } else { // current token is not a digit .... } .... } .... } 

Este código está analizando y verificando alguna cadena. Si el primer carácter de la cadena actual (es decir, en el índice 0) se ha reconocido como un número, el bucle atravesará todos los caracteres restantes para asegurarse de que todos sean números. Bueno, al menos esa es la idea. El problema es que el contador i no se usa en el bucle. La condición debe reescribirse para que compruebe actual [i] en lugar de actual [0] .

Cuadro 4

Pérdida de memoria


V773 Se salió de la función sin soltar el puntero 'lista de opciones'. Una pérdida de memoria es posible. TDataMember.cxx 355

 void TDataMember::Init(bool afterReading) { .... TList *optionlist = new TList(); //storage for options strings for (i=0;i<token_cnt;i++) { if (strstr(tokens[i],"Items")) { ptr1 = R__STRTOK_R(tokens[i], "()", &rest); if (ptr1 == 0) { Fatal("TDataMember","Internal error, found \"Items....",GetTitle()); return; } ptr1 = R__STRTOK_R(nullptr, "()", &rest); if (ptr1 == 0) { Fatal("TDataMember","Internal error, found \"Items....",GetTitle()); return; } .... } .... } .... // dispose of temporary option list... delete optionlist; .... } 

El puntero optionList no se libera antes de regresar de la función. No sé si dicha liberación es necesaria en este caso particular, pero cuando informamos errores como ese, los desarrolladores generalmente los solucionan. Todo depende de si desea o no que su programa siga ejecutándose en caso de error. ROOT tiene un montón de defectos como ese, por lo que aconsejaría a los autores que revisen el proyecto ellos mismos.

memset otra vez


V597 El compilador podría eliminar la llamada de función 'memset', que se utiliza para vaciar el búfer 'x'. La función memset_s () debe usarse para borrar los datos privados. TMD5.cxx 366

 void TMD5::Transform(UInt_t buf[4], const UChar_t in[64]) { UInt_t a, b, c, d, x[16]; .... // Zero out sensitive information memset(x, 0, sizeof(x)); } 

Muchos piensan que el comentario no llegará al archivo binario después de la compilación, y son absolutamente correctos: D. Lo que algunos pueden no saber es que el compilador también eliminará la función memset . Y esto sucederá seguro. Si el búfer en cuestión ya no se usa más en el código, el compilador optimizará la llamada a la función. Técnicamente, es una decisión razonable, pero si el búfer estaba almacenando datos privados, esos datos permanecerán allí. Esta es una debilidad de seguridad clásica CWE-14 .

Misceláneo


V591 La función no nula debería devolver un valor. 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; } 

El operador sobrecargado no tiene valor de retorno. Esta es otra tendencia reciente.

V596 El objeto fue creado pero no se está utilizando. Podría faltar la palabra clave 'throw': 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."); } .... } 

El problema es que el programador omitió accidentalmente la palabra clave throw , evitando así que se lance una excepción en caso de condición de error.

Solo hubo dos advertencias de este tipo. Aquí está el segundo:

  • V596 El objeto fue creado pero no se está utilizando. Podría faltar la palabra clave 'throw': throw runtime_error (FOO); Forest.hxx 137

V609 Divide por cero. Rango del denominador [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); // <= } else { .... } .... } 

Este es similar a los ejemplos de manejo de matrices discutidos anteriormente. La variable n está limitada al rango de 0 a 100. Pero luego hay una rama que realiza la división por la variable n que puede tener el valor 0. Creo que los límites del rango de n deberían fijarse de la siguiente manera:

 if (n <= 0 || n > 100) return z; 

V646 Considere inspeccionar la lógica de la aplicación. Es posible que falte la palabra clave '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'); } } .... } 

El analizador informa una declaración if extrañamente formateada con la palabra clave else faltante. La apariencia de este código sugiere que necesita ser reparado.

Un par de advertencias más de este tipo:

  • V646 Considere inspeccionar la lógica de la aplicación. Es posible que falte la palabra clave 'else'. TFormula_v5.cxx 3702
  • V646 Considere inspeccionar la lógica de la aplicación. Es posible que falte la palabra clave 'else'. RooAbsCategory.cxx 604

V663 Infinite loop es posible. La condición 'cin.eof ()' es insuficiente para romper el bucle. Considere agregar la llamada a la función 'cin.fail ()' a la expresión condicional. MétodoKNN.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; } .... } .... } 

Cuando se trabaja con la clase std :: istream , llamar a la función eof () no es suficiente para terminar el ciclo. La función eof () siempre devolverá falso si los datos no se pueden leer, y no hay otros puntos de terminación en este código. Para garantizar la terminación del bucle, se requiere una verificación adicional del valor devuelto por la función fail () :

 while (!is.eof() && !is.fail()) { .... } 

Como alternativa, se puede reescribir de la siguiente manera:

 while (is) { .... } 

V678 Un objeto se utiliza como argumento de su propio método. Considere verificar el primer argumento real de la función 'Copiar'. TFormLeafInfo.cxx 2414

 TFormLeafInfoMultiVarDim::TFormLeafInfoMultiVarDim( const TFormLeafInfoMultiVarDim& orig) : TFormLeafInfo(orig) { fNsize = orig.fNsize; fSizes.Copy(fSizes); // <= fCounter2 = orig.fCounter2?orig.fCounter2->DeepCopy():0; fSumOfSizes = orig.fSumOfSizes; fDim = orig.fDim; fVirtDim = orig.fVirtDim; fPrimaryIndex = orig.fPrimaryIndex; fSecondaryIndex = orig.fSecondaryIndex; } 

Terminemos el artículo con este simpático error tipográfico. La función Copiar debería llamarse con orig.fSizes , no fSizes .

Conclusión


Hace aproximadamente un año, verificamos el proyecto NCBI Genome Workbench , que es otro programa utilizado en la investigación científica que se ocupa del análisis del genoma. Menciono esto porque la calidad del software científico es extremadamente crucial, pero los desarrolladores tienden a subestimarlo.

Por cierto, macOS 10.15 Catalina se lanzó el otro día, donde dejaron de admitir aplicaciones de 32 bits. Afortunadamente, PVS-Studio ofrece un amplio conjunto de diagnósticos diseñados específicamente para detectar errores que acompañan la transferencia de programas a sistemas de 64 bits. Obtenga más información en esta publicación del equipo de PVS-Studio.

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


All Articles