Analisando o Código de ROOT, Estrutura de Análise de Dados Científicos

Quadro 3
Enquanto Estocolmo realizava a 118ª Semana Nobel, eu estava sentado em nosso escritório, onde desenvolvemos o analisador estático PVS-Studio, trabalhando em uma revisão de análise do projeto ROOT, uma estrutura de processamento de big data usada em pesquisas científicas. Esse código não ganharia um prêmio, é claro, mas os autores podem definitivamente contar com uma revisão detalhada dos defeitos mais interessantes, além de uma licença gratuita para verificar o projeto por conta própria.

1. Introdução


Quadro 1

ROOT é um kit de ferramentas de software científico modular. Ele fornece todas as funcionalidades necessárias para lidar com o processamento de big data, análise estatística, visualização e armazenamento. É escrito principalmente em C ++. ROOT nasceu no CERN , no coração da pesquisa em física de alta energia. Todos os dias, milhares de físicos usam aplicativos ROOT para analisar seus dados ou realizar simulações.

O PVS-Studio é uma ferramenta para detectar bugs de software e possíveis vulnerabilidades no código fonte de programas escritos em C, C ++, C # e Java. É executado no Windows, Linux e macOS de 64 bits e pode analisar o código-fonte escrito para plataformas ARM de 32 bits, 64 bits e incorporadas.

Estréia de um novo diagnóstico


V1046 Uso inseguro dos tipos bool 'e' int 'juntos na operação' & = '. 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; } 

Primeiro, aqui está um bug maravilhoso encontrado pela versão beta do PVS-Studio, que eu estava usando para esta revisão.

Expectativas A função SetFunctionList percorre uma lista de iteradores. Se pelo menos um iterador for inválido, a função retornará false ou true, caso contrário.

Realidade A função SetFunctionList pode retornar false, mesmo para iteradores válidos. Vamos descobrir o porquê. A função AddFunction retorna o número de iteradores válidos na lista fFunctions . Ou seja, adicionar iteradores não nulos fará com que a lista cresça gradualmente em tamanho: 1, 2, 3, 4 e assim por diante. É aqui que o bug entra em jogo:

 ret &= AddFunction(*f); 

Como a função retorna um valor do tipo int em vez de bool , a operação '& =' retornará false para valores pares porque o bit menos significativo de um número par é sempre definido como zero. É assim que um bug sutil pode quebrar o valor de retorno de SetFunctionsList, mesmo quando seus argumentos são válidos.

Quadro 2

Erros em expressões condicionais


V501 Existem subexpressões idênticas à esquerda e à direita do operador '&&': module && 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); .... } 

Vamos começar com o bug menos prejudicial. O ponteiro do módulo é verificado duas vezes. Uma das verificações provavelmente é redundante, mas ainda assim seria prudente corrigi-la para evitar qualquer confusão no futuro.

V501 Existem subexpressões idênticas 'strchr (fHostAuth-> GetHost (),' * ')' à esquerda e à direita da '||' 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); } .... } 

A string fHostAuth-> GetHost () é varrida pelo caractere '*' duas vezes. Uma dessas verificações provavelmente foi feita para procurar o '?' caractere, pois esses dois caracteres geralmente são os usados ​​para especificar várias máscaras curinga.

V517 O uso do padrão 'if (A) {...} else if (A) {...}' foi detectado. Há uma probabilidade de presença de erro lógico. Verifique as linhas: 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); } } .... } 

A variável fSummaryVrs é comparada com zero duas vezes, portanto, a execução nunca atinge o código na ramificação else-if . E há bastante código lá ...

V523 A instrução 'then' é equivalente à instrução '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); } .... } 

O mesmo bloco de código, que é um clone de copiar e colar, é executado independentemente da condição. Acho que há uma confusão entre as palavras esquerda e direita .

O projeto está cheio de pontos suspeitos como esse:

  • V523 A instrução 'then' é equivalente à instrução 'else'. TContainerConverters.cxx 51
  • V523 A instrução 'then' é equivalente à instrução 'else'. TWebFile.cxx 1310
  • V523 A instrução 'then' é equivalente à instrução 'else'. MethodMLP.cxx 423
  • V523 A instrução 'then' é equivalente à instrução 'else'. RooAbsCategory.cxx 394

A expressão V547 '! File_name_value.empty ()' é sempre 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); } .... } 

Provavelmente não é um bug; o analisador acabou de encontrar algum código que pode ser simplificado. Como o valor de retorno file_name_value.empty () já está verificado no início do loop, a segunda verificação duplicada pode ser removida, descartando, assim, uma boa quantidade de código desnecessário.

V590 Considere inspecionar o '! File1 || c <= 0 || c == '*' || c! = '(' 'expressão. A expressão é excessiva ou contém um erro de impressão. 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; } .... } 

Aqui está a parte do problema da expressão condicional relatada pelo analisador:

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

A verificação do caractere asterisco não afetará o resultado da condição. Esta parte sempre será verdadeira para qualquer caractere que não seja '('. Você pode verificar isso facilmente desenhando uma tabela verdade.

Mais dois avisos sobre condições com lógica estranha:

  • V590 Considere inspecionar esta expressão. A expressão é excessiva ou contém uma impressão incorreta. TFile.cxx 3963
  • V590 Considere inspecionar esta expressão. A expressão é excessiva ou contém uma impressão incorreta. TStreamerInfoActions.cxx 3084

V593 Considere revisar a expressão do tipo 'A = B <C'. A expressão é calculada da seguinte forma: '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); } .... } 

Esse bug se revela apenas no caso de comportamento defeituoso do programa. A variável ret deve armazenar o código de retorno da função AddWorkers e gravar esse valor no log em caso de condição de erro. Mas não funciona como pretendido. A condição não possui parênteses adicionais, forçando a ordem de avaliação desejada. O que a variável ret realmente armazena não é o código de retorno, mas o resultado da comparação lógica, ou seja, 0 ou 1.

Outro problema semelhante:

  • V593 Considere revisar a expressão do tipo 'A = B <C'. A expressão é calculada da seguinte forma: 'A = (B <C)'. TProofServ.cxx 3897

V768 A constante de enumeração 'kCostComplexityPruning' é usada como uma variável de um 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; } .... } 

Hum ... Por que negar o valor constante kCostComplexityPruning ? Suspeito que o caractere de negação seja um erro de digitação, que agora distorce a lógica de execução.

Erros de manipulação de ponteiro


V522 A desreferenciação do ponteiro nulo 'pré' pode ocorrer. 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); } 

Fiz o meu melhor tentando entender esse código estranho, e parece que a idéia era evitar atribuir um novo valor ao campo fpre . Nesse caso, o programador está checando acidentalmente o ponteiro errado. A implementação atual leva a remover a referência de um ponteiro nulo se você passar o valor nullptr para a função SetPre .

Eu acho que esse trecho deve ser corrigido da seguinte forma:

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

Isso, no entanto, não impediria a passagem de um ponteiro nulo para a função, mas pelo menos essa versão é mais logicamente consistente que a original.

Um clone ligeiramente modificado desse código pode ser encontrado em outro local:

  • V522 A desreferenciação do ponteiro nulo 'postagem' pode ocorrer. TSynapse.cxx 74

V595 O ponteiro 'N' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 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 é um pedaço de código extremamente perigoso! O ponteiro N não é verificado como nulo antes de ser desferenciado pela primeira vez. Além disso, você não pode ver isso acontecer aqui porque a desreferência ocorre dentro da função shouldVisitDecl .

Tradicionalmente, esse diagnóstico gera vários avisos relevantes. Aqui estão apenas alguns exemplos:

  • V595 O ponteiro 'arquivo' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 141, 153. TFileCacheRead.cxx 141
  • V595 O ponteiro 'fFree' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 2029, 2038. TFile.cxx 2029
  • V595 O ponteiro 'tbuf' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 586, 591. TGText.cxx 586
  • V595 O ponteiro 'fPlayer' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 3425, 3430. TProof.cxx 3425
  • V595 O ponteiro 'gProofServ' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 1192, 1194. TProofPlayer.cxx 1192
  • V595 O ponteiro 'projDataTmp' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 791, 804. RooSimultaneous.cxx 791

O próximo não é um bug, mas é outro exemplo de como as macros incentivam a escrever código defeituoso ou redundante.

V571 Verificação recorrente. A condição 'if (fCanvasImp)' já foi verificada na linha 799. TCanvas.cxx 800

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

O ponteiro fCanvasImp é verificado duas vezes, com uma das verificações já implementadas na macro SafeDelete . Um dos problemas das macros é que elas são difíceis de navegar a partir do código, e é por isso que muitos programadores não examinam seu conteúdo antes de usá-lo.

Erros de manipulação de matriz


V519 A variável 'Line [Cursor]' recebe valores duas vezes sucessivamente. Talvez isso seja um erro. Verifique as linhas: 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; .... } 

O elemento Linha [Cursor] recebe um novo valor, que é substituído imediatamente. Isso não parece certo ...

A saturação da matriz V557 é possível. O índice 'ivar' está apontando além do limite da matriz. BasicMinimizer.cxx 130

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

Cometer esse erro ao verificar os índices da matriz é uma tendência recente; nós vemos isso em quase todos os terceiros projetos. Embora seja fácil indexar em uma matriz dentro de um loop - você normalmente usa o operador '<' para comparar o índice com o tamanho da matriz - verificações como a mostrada acima exigem o operador '> =', não '>'. Caso contrário, você corre o risco de indexar um elemento além do limite da matriz.

Este bug foi clonado em todo o código algumas vezes:

  • A saturação da matriz V557 é possível. O índice 'ivar' está apontando além do limite da matriz. BasicMinimizer.cxx 186
  • A saturação da matriz V557 é possível. O índice 'ivar' está apontando além do limite da matriz. BasicMinimizer.cxx 194
  • A saturação da matriz V557 é possível. O índice 'ivar' está apontando além do limite da matriz. BasicMinimizer.cxx 209
  • A saturação da matriz V557 é possível. O índice 'ivar' está apontando além do limite da matriz. BasicMinimizer.cxx 215
  • A saturação da matriz V557 é possível. O índice 'ivar' está apontando além do limite da matriz. BasicMinimizer.cxx 230

V621 Considere inspecionar o operador 'for'. É possível que o loop seja executado incorretamente ou não seja executado. 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; } 

No loop for , os desenvolvedores aparentemente pretendiam comparar a variável dim com dm-> fArrayDim em vez de fArrayDim . O valor de fArrayDim é negativo, o que é garantido pela condição no início da função. Conseqüentemente, esse loop nunca será executado.

V767 Acesso suspeito ao elemento da matriz 'atual' por um índice constante dentro de um loop. 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á analisando e verificando alguma sequência. Se o primeiro caractere da string atual (ou seja, no índice 0) for reconhecido como um número, o loop percorrerá todos os caracteres restantes para garantir que todos sejam números. Bem, pelo menos essa é a ideia. O problema é que o contador i não é usado no loop. A condição deve ser reescrita para verificar a corrente [i] em vez da corrente [0] .

Quadro 4

Vazamento de memória


V773 A função foi encerrada sem liberar o ponteiro 'optionlist'. É possível um vazamento de memória. 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; .... } 

O ponteiro optionList não é liberado antes de retornar da função. Não sei se essa liberação é necessária nesse caso específico, mas quando relatamos erros como esse, os desenvolvedores geralmente os corrigem. Tudo depende se você deseja ou não que seu programa continue sendo executado em caso de condição de erro. O ROOT tem vários defeitos assim, então eu aconselho os autores a verificar novamente o projeto.

memset novamente


V597 O compilador pode excluir a chamada de função 'memset', usada para liberar o buffer 'x'. A função memset_s () deve ser usada para apagar os dados 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)); } 

Muitos acham que o comentário não chegará ao arquivo binário após a compilação e eles estão absolutamente corretos: D. O que alguns podem não saber é que o compilador também removerá a função memset . E isso vai acontecer com certeza. Se o buffer em questão não for mais usado no código, o compilador otimizará a chamada de função. Tecnicamente, é uma decisão razoável, mas se o buffer estiver armazenando dados privados, esses dados permanecerão lá. Esta é uma fraqueza clássica de segurança CWE-14 .

Diversos


V591 A função não nula deve retornar um 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; } 

O operador sobrecarregado não tem valor de retorno. Essa é outra tendência recente.

V596 O objeto foi criado, mas não está sendo usado. A palavra-chave 'throw' pode estar ausente: 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."); } .... } 

O problema é que o programador acidentalmente deixou de fora a palavra-chave throw , impedindo, assim, o lançamento de uma exceção em caso de condição de erro.

Havia apenas dois avisos desse tipo. Aqui está o segundo:

  • V596 O objeto foi criado, mas não está sendo usado. A palavra-chave 'throw' pode estar ausente: throw runtime_error (FOO); Forest.hxx 137

V609 Divida por zero. Intervalo do 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 é semelhante aos exemplos de manipulação de matriz discutidos anteriormente. A variável n é limitada ao intervalo de 0 a 100. Mas há um ramo que executa a divisão pela variável n que pode ter o valor 0. Acho que os limites do intervalo de n devem ser fixados da seguinte forma:

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

V646 Considere inspecionar a lógica do aplicativo. É possível que a palavra-chave 'else' esteja ausente. 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'); } } .... } 

O analisador relata uma instrução if estranhamente formatada com a palavra-chave else else ausente. A aparência desse código sugere que ele precisa ser corrigido.

Mais alguns avisos desse tipo:

  • V646 Considere inspecionar a lógica do aplicativo. É possível que a palavra-chave 'else' esteja ausente. TFormula_v5.cxx 3702
  • V646 Considere inspecionar a lógica do aplicativo. É possível que a palavra-chave 'else' esteja ausente. RooAbsCategory.cxx 604

V663 Loop infinito é possível. A condição 'cin.eof ()' é insuficiente para interromper o loop. Considere adicionar a chamada de função 'cin.fail ()' à expressão 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; } .... } .... } 

Ao trabalhar com a classe std :: istream , chamar a função eof () não é suficiente para finalizar o loop. A função eof () sempre retornará false se os dados não puderem ser lidos e não houver outros pontos de terminação nesse código. Para garantir o término do loop, é necessária uma verificação adicional do valor retornado pela função fail () :

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

Como alternativa, ele pode ser reescrito da seguinte maneira:

 while (is) { .... } 

V678 Um objeto é usado como argumento para seu próprio método. Considere verificar o primeiro argumento real da função '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; } 

Vamos terminar o artigo com este pequeno erro de digitação. A função Copiar deve ser chamada com orig.fSizes , não fSizes .

Conclusão


Há cerca de um ano, verificamos o projeto NCBI Genome Workbench , outro programa usado em pesquisas científicas que tratam da análise do genoma. Estou mencionando isso porque a qualidade do software científico é extremamente crucial, mas os desenvolvedores tendem a subestimá-lo.

A propósito, o macOS 10.15 Catalina foi lançado outro dia, onde deixou de oferecer suporte a aplicativos de 32 bits. Felizmente, o PVS-Studio oferece um grande conjunto de diagnósticos projetados especificamente para detectar bugs que acompanham a portabilidade de programas para sistemas de 64 bits. Saiba mais neste post pela equipe do PVS-Studio.

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


All Articles