
É 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 .