Os 10 principais erros em projetos C ++ em 2020

image1.png


É inverno lá fora, o ano está se esforçando para terminar, o que significa que é hora de considerar os erros mais interessantes detectados pelo analisador PVS-Studio em 2020.



De referir que o ano passado foi marcado por um grande número de novas regras de diagnóstico, cujo desencadeamento lhes permitiu chegar a este topo. Também continuamos aprimorando o núcleo do analisador e adicionando novos cenários para seu uso, você pode ler sobre tudo isso em nosso blog . Se você estiver interessado em outras linguagens suportadas por nosso analisador (C, C # e Java), dê uma olhada nos artigos dos meus colegas. Agora vamos direto aos bugs mais memoráveis ​​encontrados pelo PVS-Studio no ano passado.



Décimo lugar: Módulo divisão por um



V1063 O módulo por 1 operação não tem sentido. O resultado sempre será zero. llvm-stress.cpp 631



void Act() override {
  ....
  // If the value type is a vector, and we allow vector select,
  // then in 50% of the cases generate a vector select.
  if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 1)) {
    unsigned NumElem =
        cast<FixedVectorType>(Val0->getType())->getNumElements();
    CondTy = FixedVectorType::get(CondTy, NumElem);
  }
  ....
}
      
      





O desenvolvedor queria obter um valor aleatório entre 0 e 1 usando a divisão de módulo. No entanto, uma operação como X% 1 sempre retornará 0. Nesse caso, seria correto reescrever a condição da seguinte maneira:



if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 2))
      
      





Este erro foi incluído no início do artigo: " Checking Clang 11 with PVS-Studio ".



Nono lugar: Quatro verificações



PVS-Studio emitiu quatro avisos para a próxima seção do código:



  • V560 Uma parte da expressão condicional é sempre verdadeira: x> = 0. editor.cpp 1137
  • V560 Uma parte da expressão condicional é sempre verdadeira: y> = 0. editor.cpp 1137
  • V560 Uma parte da expressão condicional é sempre verdadeira: x <40. Editor.cpp 1137
  • V560 Uma parte da expressão condicional é sempre verdadeira: y <30. Editor.cpp 1137


int editorclass::at( int x, int y )
{
  if(x<0) return at(0,y);
  if(y<0) return at(x,0);
  if(x>=40) return at(39,y);
  if(y>=30) return at(x,29);

  if(x>=0 && y>=0 && x<40 && y<30)
  {
      return contents[x+(levx*40)+vmult[y+(levy*30)]];
  }
  return 0;
}
      
      





Todos os avisos são para a última instrução if . O problema é que todas as quatro verificações realizadas nele sempre retornarão verdadeiras . Eu não diria que isso é um erro sério, mas ficou muito engraçado. Em geral, essas verificações são redundantes e podem ser removidas.



Este erro entrou no topo do artigo: " VVVVVV ??? VVVVVV !!! ".



Oitavo lugar: delete em vez de delete []



V611 A memória foi alocada usando o operador 'new T []', mas foi liberada usando o operador 'delete'. Considere inspecionar este código. Provavelmente é melhor usar 'delete [] poke_data;'. CCDDE.CPP 410



BOOL Send_Data_To_DDE_Server (char *data, int length, int packet_type)
{
  ....
  char *poke_data = new char [length + 2*sizeof(int)]; // <=
  ....
  if(DDE_Class->Poke_Server( .... ) == FALSE) {
    CCDebugString("C&C95 - POKE failed!\n");
    DDE_Class->Close_Poke_Connection();
    delete poke_data;                                  // <=
    return (FALSE);
  }

  DDE_Class->Close_Poke_Connection();

  delete poke_data;                                    // <=

  return (TRUE);
}
      
      





O analisador detectou um erro relacionado ao fato de a memória ter sido alocada e liberada de maneiras incompatíveis. Para liberar memória alocada para uma matriz, use o operador delete [] , não delete .



Este erro chegou ao topo do artigo: " Command & Conquer Game Code: Bugs from the 90s. Volume Two ".



Sétimo lugar: Buffer fora dos limites



Vejamos a função net_hostname_get que será usada a seguir.



#if defined(CONFIG_NET_HOSTNAME_ENABLE)
const char *net_hostname_get(void);
#else
static inline const char *net_hostname_get(void)
{
  return "zephyr";
}
#endif
      
      





Neste caso, durante o pré-processamento, a opção relacionada ao ramo #else foi selecionada . Ou seja, no arquivo pré-processado, a função é implementada da seguinte forma:



static inline const char *net_hostname_get(void)
{
  return "zephyr";
}
      
      





A função retorna um ponteiro para um array de 7 bytes (leve em consideração o terminal zero no final da string).



Agora vamos examinar o código que leva ao estouro do array.



static int do_net_init(void)
{
  ....
  (void)memcpy(hostname, net_hostname_get(), MAX_HOSTNAME_LEN);
  ....
}
      
      





Aviso do PVS-Studio: V512 [CWE-119] Uma chamada da função 'memcpy' fará com que o buffer 'net_hostname_get ()' fique fora do intervalo. log_backend_net.c 114



Após o pré-processamento, MAX_HOSTNAME_LEN se expande da seguinte forma:



(void)memcpy(hostname, net_hostname_get(),
    sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx"));
      
      





Da mesma forma, ao copiar dados, ocorre uma saturação do literal da string. É difícil prever como isso afetará a execução do programa, pois leva a um comportamento indefinido.



Esse bug chegou ao topo do artigo: " Investigando a qualidade do código do sistema operacional Zephyr ".



Sexto lugar: algo muito estranho



static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    ((u8_t *)mntpt)[strlen(mntpt)] = '\0';
    memcpy(cpy_mntpt, mntpt, strlen(mntpt));
  }
  return cpy_mntpt;
}
      
      





Aviso do PVS-Studio: V575 [CWE-628] A função 'memcpy' não copia a string inteira. Use a função 'strcpy / strcpy_s' para preservar o nulo do terminal. shell.c 427



Alguém tentou fazer um análogo da função strdup , mas falhou.



Vamos começar com o aviso do analisador. Diz que a função memcpy copia a linha, mas não copia o terminal zero, o que é muito suspeito.



Este terminal 0 parece ter sido copiado aqui:



((u8_t *)mntpt)[strlen(mntpt)] = '\0';
      
      





Mas não! Aqui está um erro de digitação que faz com que o terminal zero seja copiado para dentro dele! Observe que a gravação é no array mntpt , não em cpy_mntpt . Como resultado, a função mntpt_prepare retorna uma string nula de terminal incompleta.



Na verdade, o programador queria escrever assim:



((u8_t *)cpy_mntpt)[strlen(mntpt)] = '\0';
      
      





No entanto, ainda não está claro por que isso foi feito tão difícil! Este código pode ser simplificado para o seguinte:



static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    strcpy(cpy_mntpt, mntpt);
  }
  return cpy_mntpt;
}
      
      





Esse bug chegou ao topo do artigo mencionado anteriormente: " Examinando a qualidade do código do sistema operacional Zephyr ".



Quinto lugar: Proteção incorreta contra estouro



V547 [CWE-570] A expressão 'rel_wait <0' é sempre falsa. O valor do tipo sem sinal nunca é <0. os_thread_windows.c 359



static DWORD
get_rel_wait(const struct timespec *abstime)
{
  struct __timeb64 t;
  _ftime64_s(&t);
  time_t now_ms = t.time * 1000 + t.millitm;
  time_t ms = (time_t)(abstime->tv_sec * 1000 +
    abstime->tv_nsec / 1000000);

  DWORD rel_wait = (DWORD)(ms - now_ms);

  return rel_wait < 0 ? 0 : rel_wait;
}
      
      





Nesse caso, a variável rel_wait é do tipo DWORD não assinado . Isso significa que a comparação rel_wait <0 não faz sentido, pois o resultado é sempre verdadeiro.



O erro em si não é muito interessante. Mas ficou interessante como eles tentaram consertar. Descobriu-se que as alterações não foram corrigidas, apenas simplificaram o código. Você pode ler mais sobre essa história no artigo do meu colega: " Por que o PVS-Studio não oferece edições automáticas de código ".



O erro entrou no topo do artigo: “ Análise estática do código da coleção de bibliotecas PMDK da Intel e erros que não são erros ”.



Quarto lugar: Não escreva para std, irmão



V1061 Estender o namespace 'std' pode resultar em um comportamento indefinido. Size_iterator.hh 210



// Dirty hack because g++ 4.6 at least wants
// to do a bunch of copy operations.
namespace std {
inline void iter_swap(util::SizedIterator first,
                      util::SizedIterator second)
{
  util::swap(*first, *second);
}
} // namespace std
      
      





O artigo do qual o gatilho é obtido: " Analisando o código do projeto DeepSpeech ou por que você não deve escrever no namespace std " descreve em detalhes por que você não deve fazer isso.



Terceiro lugar: barra de rolagem, que falhou



V501 . Existem subexpressões idênticas à esquerda e à direita do operador '-': bufferHeight - bufferHeight TermControl.cpp 592



bool TermControl::_InitializeTerminal()
{
  ....
  auto bottom = _terminal->GetViewport().BottomExclusive();
  auto bufferHeight = bottom;

  ScrollBar().Maximum(bufferHeight - bufferHeight);
  ScrollBar().Minimum(0);
  ScrollBar().Value(0);
  ScrollBar().ViewportSize(bufferHeight);
  ....
}
      
      





Isso é o que é chamado de "acionamento com histórico". Neste caso, devido a um erro, a barra de rolagem no Terminal do Windows não funcionou. Um artigo inteiro foi escrito com base nesse bug, no qual meu colega conduziu uma pesquisa e descobriu por que isso aconteceu. Você está interessado? Aqui está: "A barra de rolagem que não poderia ".



Segundo lugar: raio e altura confusos



E novamente falaremos sobre vários avisos do analisador:



  • V764 Possível ordem incorreta de argumentos passados ​​para a função 'CreateWheel': 'altura' e 'raio'. StandardJoints.cpp 791
  • V764 Possível ordem incorreta de argumentos passados ​​para a função 'CreateWheel': 'altura' e 'raio'. StandardJoints.cpp 833
  • V764 Possível ordem incorreta de argumentos passados ​​para a função 'CreateWheel': 'altura' e 'raio'. StandardJoints.cpp 884


Aqui estão as chamadas de função:



NewtonBody* const wheel = CreateWheel (scene, origin, height, radius);
      
      





E é assim que seu anúncio se parece:



static NewtonBody* CreateWheel (DemoEntityManager* const scene,
  const dVector& location, dFloat radius, dFloat height)
      
      





Os argumentos foram revertidos em chamadas de função.



Este erro chegou ao início do artigo: " Verificando a dinâmica de jogo de Newton com o analisador estático PVS-Studio ".



Primeiro lugar: Apagando o resultado



V519 A variável 'color_name' recebe valores duas vezes sucessivamente. Talvez isso seja um erro. Verifique as linhas: 621, 627. string.cpp 627



static bool parseNamedColorString(const std::string &value,
                                  video::SColor &color)
{
  std::string color_name;
  std::string alpha_string;

  size_t alpha_pos = value.find('#');
  if (alpha_pos != std::string::npos) {
    color_name = value.substr(0, alpha_pos);
    alpha_string = value.substr(alpha_pos + 1);
  } else {
    color_name = value;
  }

  color_name = lowercase(value); // <=

  std::map<const std::string, unsigned>::const_iterator it;
  it = named_colors.colors.find(color_name);
  if (it == named_colors.colors.end())
    return false;
  ....
}
      
      





Esta função deve analisar o nome da cor com o parâmetro de transparência e retornar seu código hexadecimal. Dependendo do resultado da verificação da condição, o resultado da divisão da linha ou uma cópia do argumento da função é passado para a variável color_name .



Entretanto, na função em minúsculas () , não a string resultante em si é convertida em minúsculas, mas o argumento da função original. Como resultado, simplesmente perderemos a cor que parseNamedColorString () deveria ter retornado .



color_name = lowercase(color_name);
      
      





Esse erro chegou ao início do artigo: " PVS-Studio: Análise de solicitações pull no Azure DevOps usando agentes auto-hospedados ".



Conclusão



No ano passado, encontramos muitos bugs em projetos de código aberto. Esses eram erros comuns de copiar e colar, erros constantes, vazamentos de memória e muitos outros problemas. Nosso analisador não para e no topo há várias detecções de novos diagnósticos escritos este ano.



Espero que tenha gostado dos erros coletados. Pessoalmente, achei-os bastante interessantes. Mas, é claro, sua visão pode ser diferente da minha, então você pode criar seu "Top 10 ..." lendo artigos de nosso blog ou olhando a lista de erros encontrados pelo PVS-Studio em projetos de código aberto.



Também chamo sua atenção para artigos com os 10 principais erros de C ++ dos últimos anos: 2016 , 2017 , 2018 , 2019 .





Se você deseja compartilhar este artigo com um público de língua inglesa, por favor, use o link de tradução: Vladislav Stolyarov. Os 10 principais bugs encontrados em projetos C ++ em 2020 .



All Articles