A história da reunião do analisador estático PVS-Studio com o código do sistema operacional Haiku remonta ao distante ano de 2015. Foi um experimento interessante e uma experiência útil para as equipes dos dois projetos. Por que um experimento? Não havia analisador para Linux na época e não haverá mais um ano e meio. Mas o trabalho dos entusiastas de nossa equipe foi recompensado: conhecemos os desenvolvedores do Haiku e melhoramos a qualidade do código, reabastecemos o banco de dados com raros erros de programadores e refinamos o analisador. Verificar o código do Haiku quanto a erros é rápido e fácil agora.
1. Introdução
Os heróis de nossa história são o sistema operacional 
Haiku de código aberto e o analisador estático 
PVS-Studio para C, C ++, C # e Java. Quando, há 4,5 anos, começamos a analisar o projeto, tivemos que trabalhar apenas com o arquivo executável do analisador compilado. Toda a infraestrutura para analisar parâmetros de compilação, iniciar um pré-processador, analisar paralelamente, etc. foi retirado do utilitário de 
interface do usuário do 
Compiler Monitoring em C #, que foi portado em partes para a plataforma Mono para execução no Linux. O projeto Haiku em si é construído usando um compilador cruzado em vários sistemas operacionais, exceto o Windows. Mais uma vez, quero observar a conveniência e a integridade da documentação do assembly do Haiku e também agradecer aos desenvolvedores do Haiku por sua ajuda na construção do projeto.
Agora a análise é muito mais fácil. A lista de todos os comandos para criar e analisar o projeto é assim:
cd /opt git clone https: 
A propósito, a análise do projeto foi realizada no contêiner Docker. Recentemente, preparamos nova documentação sobre este tópico: 
Executando o PVS-Studio no Docker . Isso pode simplificar bastante o uso de técnicas de análise estática de projetos para algumas empresas.
Variáveis não inicializadas
V614 Variável não inicializada 'rval' usada. fetch.c 1727
 int auto_fetch(int argc, char *argv[]) { volatile int argpos; int rval;  
A variável 
rval não 
foi inicializada quando declarada; portanto, compará-la com um valor nulo levará a um resultado indefinido. Se as circunstâncias falharem, o valor indefinido da variável 
rval pode se tornar o valor de retorno da função 
auto_fetch .
V614 Ponteiro não inicializado 'res' usado. commands.c 2873
 struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; char *ai_canonname; struct sockaddr *ai_addr; struct addrinfo *ai_next; }; static int sourceroute(struct addrinfo *ai, char *arg, char **cpp, int *lenp, int *protop, int *optp) { static char buf[1024 + ALIGNBYTES]; char *cp, *cp2, *lsrp, *ep; struct sockaddr_in *_sin; #ifdef INET6 struct sockaddr_in6 *sin6; struct ip6_rthdr *rth; #endif struct addrinfo hints, *res;  
Um caso semelhante de usar uma variável não inicializada, somente aqui está o ponteiro não inicializado 
res .
V506 O ponteiro para a variável local 'normalizada' é armazenado fora do escopo dessa variável. Esse ponteiro se tornará inválido. TextView.cpp 5596
 void BTextView::_ApplyStyleRange(...., const BFont* font, ....) { if (font != NULL) { BFont normalized = *font; _NormalizeFont(&normalized); font = &normalized; } .... fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode, font, color); } 
Provavelmente, o programador precisava normalizar o objeto através de uma variável intermediária. Mas agora, no ponteiro da 
fonte , o ponteiro para o objeto temporário 
normalizado foi 
salvo , que será destruído após sair do escopo no qual esse objeto temporário foi criado.
V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 27
 int8 BUnicodeChar::Type(uint32 c) { BUnicodeChar(); return u_charType(c); } 
Um erro muito comum entre os programadores de C ++ é usar a chamada de construtor supostamente para inicializar / zerar os campos da classe. Nesse caso, não há modificação nos campos da classe, mas um novo objeto não nomeado dessa classe é criado e imediatamente destruído. Infelizmente, existem muitos desses lugares:
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 37
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 49
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 58
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 67
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 77
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 89
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 103
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 115
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 126
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 142
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 152
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 163
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 186
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 196
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 206
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 214
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 222
- V603 O objeto foi criado, mas não está sendo usado. Se você deseja chamar o construtor, 'this-> BUnicodeChar :: BUnicodeChar (....)' deve ser usado. UnicodeChar.cpp 230
V670 O membro da classe não inicializado 'fPatternHandler' é usado para inicializar o membro 'fInternal'. Lembre-se de que os membros são inicializados na ordem de suas declarações dentro de uma classe. Painter.cpp 184
 Painter::Painter() : fInternal(fPatternHandler), .... fPatternHandler(), .... { .... }; class Painter { .... private: mutable PainterAggInterface fInternal;  
Outro exemplo de inicialização incorreta. Os campos de classe são inicializados na ordem em que são declarados na própria classe. Neste exemplo, o campo 
fInternal será inicializado primeiro usando o valor não inicializado 
fPatternHandler .
#Define suspeito
V523 A 
instrução 'then' é equivalente à instrução 'else'. subr_gtaskqueue.c 191
 #define TQ_LOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_lock_spin(&(tq)->tq_mutex); \ else \ mtx_lock(&(tq)->tq_mutex); \ } while (0) #define TQ_ASSERT_LOCKED(tq) mtx_assert(&(tq)->tq_mutex, MA_OWNED) #define TQ_UNLOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_unlock_spin(&(tq)->tq_mutex); \ else \ mtx_unlock(&(tq)->tq_mutex); \ } while (0) void grouptask_block(struct grouptask *grouptask) { .... TQ_LOCK(queue); gtask->ta_flags |= TASK_NOENQUEUE; gtaskqueue_drain_locked(queue, gtask); TQ_UNLOCK(queue); } 
O trecho de código não parece suspeito até que você observe o resultado do pré-processador:
 void grouptask_block(struct grouptask *grouptask) { .... do { if ((queue)->tq_spin) mtx_lock(&(queue)->tq_mutex); else mtx_lock(&(queue)->tq_mutex); } while (0); gtask->ta_flags |= 0x4; gtaskqueue_drain_locked(queue, gtask); do { if ((queue)->tq_spin) mtx_unlock(&(queue)->tq_mutex); else mtx_unlock(&(queue)->tq_mutex); } while (0); } 
O analisador está realmente certo - os ramos 
if e 
else são idênticos. Mas para onde foram as funções 
mtx_lock_spin e 
mtx_unlock_spin ? As macros 
TQ_LOCK , 
TQ_UNLOCK e a função 
grouptask_block são declaradas no mesmo arquivo e quase lado a lado, no entanto, houve uma substituição em algum lugar.
Uma pesquisa no conteúdo dos arquivos encontrou apenas 
mutex.h com o seguinte conteúdo:
  #define mtx_lock_spin(x) mtx_lock(x) #define mtx_unlock_spin(x) mtx_unlock(x) 
Se essa substituição está correta ou não, vale a pena verificar os desenvolvedores do projeto. Eu verifiquei o projeto no Linux, e essa substituição parecia suspeita para mim.
Erros com a função livre
V575 O ponteiro nulo é passado para a função 'livre'. Inspecione o primeiro argumento. setmime.cpp 727
 void MimeType::_PurgeProperties() { fShort.Truncate(0); fLong.Truncate(0); fPrefApp.Truncate(0); fPrefAppSig.Truncate(0); fSniffRule.Truncate(0); delete fSmallIcon; fSmallIcon = NULL; delete fBigIcon; fBigIcon = NULL; fVectorIcon = NULL;  
Você pode passar um ponteiro nulo para a função 
livre , mas essa entrada é claramente suspeita. Então, o analisador encontrou as linhas de código confusas. Primeiro, você precisava liberar a memória usando o ponteiro 
fVectorIcon e, em seguida, defini-lo como 
NULL .
V575 O ponteiro nulo é passado para a função 'livre'. Inspecione o primeiro argumento. driver_settings.cpp 461
 static settings_handle * load_driver_settings_from_file(int file, const char *driverName) { .... handle = new_settings(text, driverName); if (handle != NULL) {  
Outro exemplo de passagem explícita de um ponteiro nulo para a função 
livre . Esta linha pode ser excluída porque após o recebimento bem-sucedido do ponteiro, a função sai.
V575 O ponteiro nulo é passado para a função 'livre'. Inspecione o primeiro argumento. PackageFileHeapWriter.cpp 166
 void* _GetBuffer() { .... void* buffer = malloc(fBufferSize); if (buffer == NULL && !fBuffers.AddItem(buffer)) { free(buffer); throw std::bad_alloc(); } return buffer; } 
Aqui está um erro. Em vez do operador &&, o operador || deve ser usado. Somente nesse caso será lançada uma exceção 
std :: bad_alloc () se não for possível alocar memória usando a função 
malloc .
Erros com o operador de exclusão
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 [] fMsg;'. Err.cpp 65
 class Err { public: .... private: char *fMsg; ssize_t fPos; }; void Err::Unset() { delete fMsg;  
O ponteiro 
fMsg é usado para alocar memória para armazenar uma matriz de caracteres, e o operador 
delete é usado para liberar memória, em vez de 
delete [] .
V611 A memória foi alocada usando o operador 'novo', mas foi liberada usando a função 'livre'. Considere inspecionar as lógicas de operação por trás da variável 'wrapperPool'. vm_page.cpp 3080
 status_t vm_page_write_modified_page_range(....) { .... PageWriteWrapper* wrapperPool = new(malloc_flags(allocationFlags)) PageWriteWrapper[maxPages + 1]; PageWriteWrapper** wrappers = new(malloc_flags(allocationFlags)) PageWriteWrapper*[maxPages]; if (wrapperPool == NULL || wrappers == NULL) { free(wrapperPool);  
Aqui 
malloc_flags é a função que faz 
malloc . E, em seguida, 
posicionamento-novo constrói o objeto aqui. E como a classe 
PageWriteWrapper é implementada da seguinte maneira:
 class PageWriteWrapper { public: PageWriteWrapper(); ~PageWriteWrapper(); void SetTo(vm_page* page); bool Done(status_t result); private: vm_page* fPage; struct VMCache* fCache; bool fIsActive; }; PageWriteWrapper::PageWriteWrapper() : fIsActive(false) { } PageWriteWrapper::~PageWriteWrapper() { if (fIsActive) panic("page write wrapper going out of scope but isn't completed"); } 
os destruidores de objetos dessa classe não serão chamados devido ao uso da função free para liberar memória.
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 [] fOutBuffer;'. Verifique as linhas: 26, 45. PCL6Rasterizer.h 26
 class PCL6Rasterizer : public Rasterizer { public: .... ~PCL6Rasterizer() { delete fOutBuffer; fOutBuffer = NULL; } .... virtual void InitializeBuffer() { fOutBuffer = new uchar[fOutBufferSize]; } private: uchar* fOutBuffer; int fOutBufferSize; }; 
Usar o operador de 
exclusão em vez de 
excluir [] é um erro muito comum. A maneira mais fácil de cometer um erro ao escrever uma aula é porque O código destruidor geralmente está localizado longe dos locais de alocação de memória. Aqui, o programador libera incorretamente a memória no destruidor pelo ponteiro 
fOutBuffer .
V772 Chamar um operador 'delete' para um ponteiro nulo causará um comportamento indefinido. Hashtable.cpp 207
 void Hashtable::MakeEmpty(int8 keyMode,int8 valueMode) { .... for (entry = fTable[index]; entry; entry = next) { switch (keyMode) { case HASH_EMPTY_DELETE:  
Além da escolha errada entre 
excluir / 
excluir [] e 
livre , você pode adicionar um comportamento indefinido ao programa de outra maneira - tente limpar a memória com um ponteiro para um tipo indefinido 
(vazio *) .
Funções sem valor de retorno
V591 A função não nula deve retornar um valor. Referenciável.h 228
 BReference& operator=(const BReference<const Type>& other) { fReference = other.fReference; } 
O operador de atribuição substituído não possui valor de retorno suficiente. Nesse caso, o operador retornará um valor aleatório, o que pode levar a erros estranhos.
Problemas semelhantes em outros trechos de código desta classe:
- V591 A função não nula deve retornar um valor. Referenciável.h 233
- V591 A função não nula deve retornar um valor. Referenciável.h 239
V591 A função não nula deve retornar um valor. main.c 1010
 void errx(int, const char *, ...) ; char * getoptionvalue(const char *name) { struct option *c; if (name == NULL) errx(1, "getoptionvalue() invoked with NULL name"); c = getoption(name); if (c != NULL) return (c->value); errx(1, "getoptionvalue() invoked with unknown option '%s'", name);  } 
Um comentário de usuário NÃO ALCANCE aqui não significa nada. Para escrever corretamente o código para esses cenários, você deve marcar funções como noreturn. Existem atributos noreturn para isso: padrão e específico do compilador. Antes de tudo, esses atributos são levados em consideração pelos compiladores para a geração correta do código ou a notificação de alguns tipos de erros com a ajuda de warings. Várias ferramentas de análise estática também levam em consideração os atributos para melhorar a qualidade da análise.
Manipulação de exceção
V596 O objeto foi criado, mas não está sendo usado. A palavra-chave 'throw' pode estar ausente: throw ParseException (FOO); Response.cpp 659
 size_t Response::ExtractNumber(BDataIO& stream) { BString string = ExtractString(stream); const char* end; size_t number = strtoul(string.String(), (char**)&end, 10); if (end == NULL || end[0] != '\0') ParseException("Invalid number!"); return number; } 
A palavra-chave 
throw é acidentalmente esquecida aqui. Assim, uma 
ParseException não será 
lançada e um objeto dessa classe será simplesmente destruído quando sair do escopo. Após o qual a função continuará seu trabalho como se nada tivesse acontecido, como se o número correto fosse inserido.
V1022 Uma exceção foi lançada pelo ponteiro. Considere jogá-lo por valor. gensyscallinfos.cpp 316
 int main(int argc, char** argv) { try { return Main().Run(argc, argv); } catch (Exception& exception) {  
O analisador detectou uma 
IOException lançada pelo ponteiro. Lançar um ponteiro faz com que a exceção não seja capturada; portanto, a exceção é capturada por referência. Além disso, o uso de um ponteiro força o interceptador a chamar o operador de 
exclusão para destruir o objeto criado, o que também não é feito.
Mais duas áreas problemáticas do código:
- V1022 Uma exceção foi lançada pelo ponteiro. Considere jogá-lo por valor. gensyscallinfos.cpp 347
- V1022 Uma exceção foi lançada pelo ponteiro. Considere jogá-lo por valor. gensyscallinfos.cpp 413
Segurança formal
V597 O compilador pode excluir a chamada de função 'memset', usada para liberar o objeto 'f_key'. A função memset_s () deve ser usada para apagar os dados privados. dst_api.c 1018
 #ifndef SAFE_FREE #define SAFE_FREE(a) \ do{if(a != NULL){memset(a,0, sizeof(*a)); free(a); a=NULL;}} while (0) .... #endif DST_KEY * dst_free_key(DST_KEY *f_key) { if (f_key == NULL) return (f_key); if (f_key->dk_func && f_key->dk_func->destroy) f_key->dk_KEY_struct = f_key->dk_func->destroy(f_key->dk_KEY_struct); else { EREPORT(("dst_free_key(): Unknown key alg %d\n", f_key->dk_alg)); } if (f_key->dk_KEY_struct) { free(f_key->dk_KEY_struct); f_key->dk_KEY_struct = NULL; } if (f_key->dk_key_name) SAFE_FREE(f_key->dk_key_name); SAFE_FREE(f_key); return (NULL); } 
O analisador detectou um código suspeito projetado para limpar com segurança dados particulares. Infelizmente, a macro 
SAFE_FREE , expandida para 
memset , chamadas 
gratuitas e atribuição 
NULL , não torna o código mais seguro, porque tudo isso é removido pelo compilador durante a otimização do 
O2 .
A propósito, isso não se parece em nada com 
CWE-14 : Remoção de código por compilador para limpar buffers.
A lista completa de locais onde os buffers não são realmente limpos:
- V597 O compilador pode excluir a chamada de função 'memset', usada para liberar o buffer 'encoded_block'. A função memset_s () deve ser usada para apagar os dados privados. dst_api.c 446
- V597 O compilador pode excluir a chamada de função 'memset', usada para liberar o objeto 'key_st'. A função memset_s () deve ser usada para apagar os dados privados. dst_api.c 685
- V597 O compilador pode excluir a chamada de função 'memset', usada para liberar o buffer 'in_buff'. A função memset_s () deve ser usada para apagar os dados privados. dst_api.c 916
- V597 O compilador pode excluir a chamada de função 'memset', usada para liberar o objeto 'ce'. A função memset_s () deve ser usada para apagar os dados privados. fs_cache.c 1078
Comparações não assinadas
V547 A expressão 'restante <0' é sempre falsa. O valor do tipo não assinado nunca é <0. DwarfFile.cpp 1947
 status_t DwarfFile::_UnwindCallFrame(....) { .... uint64 remaining = lengthOffset + length - dataReader.Offset(); if (remaining < 0) return B_BAD_DATA; .... } 
O analisador encontrou uma comparação explícita de uma variável não assinada com valores negativos. Talvez você deva comparar a variável 
restante com apenas zero ou implementar uma verificação de estouro.
V547 A expressão 'suspensão ((sem sinal) segundos)) <0' é sempre falsa. O valor do tipo não assinado nunca é <0. misc.cpp 56
 status_t snooze(bigtime_t amount) { if (amount <= 0) return B_OK; int64 secs = amount / 1000000LL; int64 usecs = amount % 1000000LL; if (secs > 0) { if (sleep((unsigned)secs) < 0)  
Para entender qual é o erro, passemos às assinaturas das funções 
sleep e 
usleep :
 extern unsigned int sleep (unsigned int __seconds); extern int usleep (__useconds_t __useconds); 
Como podemos ver, a função 
sleep retorna um valor de um tipo não assinado e seu uso no código está incorreto.
Indicadores perigosos
V774 O ponteiro 'dispositivo' foi usado após o lançamento da memória. xhci.cpp 1572
 void XHCI::FreeDevice(Device *device) { uint8 slot = fPortSlots[device->HubPort()]; TRACE("FreeDevice() port %d slot %d\n", device->HubPort(), slot);  
Um objeto de 
dispositivo é limpo pelo operador de 
exclusão . Esta é uma ação lógica para uma função chamada 
FreeDevice . Mas, por algum motivo, para liberar outros recursos no código, há novamente um apelo a um objeto já excluído.
Esse código é extremamente perigoso e ocorre em vários outros lugares:
- V774 O ponteiro 'self' foi usado após a liberação da memória. TranslatorRoster.cpp 884
- V774 O ponteiro 'string' foi usado após o lançamento da memória. RemoteView.cpp 1269
- V774 O ponteiro 'bs' foi usado após o lançamento da memória. mkntfs.c 4291
- V774 O ponteiro 'bs' foi usado após o lançamento da memória. mkntfs.c 4308
- V774 O ponteiro 'al' foi usado após a realocação da memória. inode.c 1155
V522 A desreferenciação do ponteiro nulo 'dados' pode ocorrer. O ponteiro nulo é passado para a função 'malo_hal_send_helper'. Inspecione o terceiro argumento. Verifique as linhas: 350, 394. if_malohal.c 350
 static int malo_hal_fwload_helper(struct malo_hal *mh, char *helper) { ....  error = malo_hal_send_helper(mh, 0, NULL, 0, MALO_NOWAIT);  
A análise interprocedural revelou uma situação em que 
NULL é passado para a função e o ponteiro de 
dados com esse valor é posteriormente desreferenciado na função 
memcpy .
V773 A função foi encerrada sem liberar o ponteiro 'inputFileFile'. É possível um vazamento de memória. command_recompress.cpp 119
 int command_recompress(int argc, const char* const* argv) { .... BFile* inputFileFile = new BFile; error = inputFileFile->SetTo(inputPackageFileName, O_RDONLY); if (error != B_OK) { fprintf(stderr, "Error: Failed to open input file \"%s\": %s\n", inputPackageFileName, strerror(error)); return 1; } inputFile = inputFileFile; .... } 
O PVS-Studio pode detectar vazamentos de memória . A memória para 
inputFileFile não é liberada 
aqui em caso de algum tipo de erro. Alguém acredita que, em caso de erros, você não pode se preocupar em liberar memória - o programa ainda terminará. Mas nem sempre é esse o caso. Manuseie corretamente os erros e continue a trabalhar - um requisito para muitos programas.
V595 O ponteiro 'fReply' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 49, 52. ReplyBuilder.cpp 49
 RPC::CallbackReply* ReplyBuilder::Reply() { fReply->Stream().InsertUInt(fStatusPosition, _HaikuErrorToNFS4(fStatus)); fReply->Stream().InsertUInt(fOpCountPosition, fOpCount); if (fReply == NULL || fReply->Stream().Error() == B_OK) return fReply; else return NULL; } 
Com que frequência os desenvolvedores desreferem os ponteiros antes de verificá-los. O Diagnostics 
V595 é quase sempre um líder no número de avisos em um projeto. O uso perigoso do ponteiro 
fReply neste pedaço de código.
V595 O ponteiro 'mq' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 782, 786. oce_queue.c 782
 static void oce_mq_free(struct oce_mq *mq) { POCE_SOFTC sc = (POCE_SOFTC) mq->parent; struct oce_mbx mbx; struct mbx_destroy_common_mq *fwcmd; if (!mq) return; .... } 
Um exemplo semelhante. O ponteiro 
mg é desreferenciado algumas linhas antes da verificação de um valor nulo. Existem muitos lugares semelhantes no projeto. Em alguns lugares, o uso e a verificação de ponteiros estão longe um do outro, portanto, dois exemplos estão incluídos no artigo. O restante poderá ver os desenvolvedores no relatório completo do analisador.
Erros diversos
V645 A chamada de função 'strncat' pode levar ao estouro de buffer da 'saída'. Os limites não devem conter o tamanho do buffer, mas vários caracteres que ele pode conter. NamespaceDump.cpp 101
 static void dump_acpi_namespace(acpi_ns_device_info *device, char *root, int indenting) { char output[320]; char tabs[255] = ""; .... strlcat(tabs, "|--- ", sizeof(tabs)); .... while (....) { uint32 type = device->acpi->get_object_type(result); snprintf(output, sizeof(output), "%s%s", tabs, result + depth); switch(type) { case ACPI_TYPE_INTEGER: strncat(output, " INTEGER", sizeof(output)); break; case ACPI_TYPE_STRING: strncat(output, " STRING", sizeof(output)); break; .... } .... } .... } 
A diferença entre as 
funções strlcat e 
strncat não 
é totalmente óbvia para uma pessoa nova na descrição dessas funções. A função 
strlcat usa o 
tamanho de todo o buffer como terceiro argumento, e a função 
strncat usa o tamanho do espaço livre no buffer , o que requer o cálculo do valor desejado antes de chamar a função. Mas os desenvolvedores geralmente esquecem ou não sabem disso. Passar a função 
strncat para o tamanho de todo o buffer pode levar a um estouro de buffer, porque a função considerará esse valor como o número de caracteres permitido para cópia. A função 
strlcat não tem esse problema, mas para que funcione corretamente, é necessário passar as seqüências que terminam no terminal zero.
Toda a lista de lugares perigosos com linhas:
- V645 A chamada de função 'strncat' pode levar ao estouro de buffer da 'saída'. Os limites não devem conter o tamanho do buffer, mas vários caracteres que ele pode conter. NamespaceDump.cpp 104
- V645 A chamada de função 'strncat' pode levar ao estouro de buffer da 'saída'. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 107
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 110
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 113
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 118
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 119
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 120
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 123
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 126
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 129
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 132
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 135
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 138
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 141
- V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 144
- V645 The 'strncat' function call could lead to the 'features_string' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. VirtioDevice.cpp 283
- V645 The 'strncat' function call could lead to the 'features_string' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. VirtioDevice.cpp 284
- V645 The 'strncat' function call could lead to the 'features_string' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. VirtioDevice.cpp 285
V792 The 'SetDecoratorSettings' function located to the right of the operator '|' will be called regardless of the value of the left operand. Perhaps, it is better to use '||'. DesktopListener.cpp 324
 class DesktopListener : public DoublyLinkedListLinkImpl<DesktopListener> { public: .... virtual bool SetDecoratorSettings(Window* window, const BMessage& settings) = 0; .... }; bool DesktopObservable::SetDecoratorSettings(Window* window, const BMessage& settings) { if (fWeAreInvoking) return false; InvokeGuard invokeGuard(fWeAreInvoking); bool changed = false; for (DesktopListener* listener = fDesktopListenerList.First(); listener != NULL; listener = fDesktopListenerList.GetNext(listener)) changed = changed | listener->SetDecoratorSettings(window, settings); return changed; } 
, '|' '||'. 
SetDecoratorSettings .
V627 Consider inspecting the expression. The argument of sizeof() is the macro which expands to a number. device.c 72
 #define PCI_line_size 0x0c  static status_t wb840_open(const char* name, uint32 flags, void** cookie) { .... data->wb_cachesize = gPci->read_pci_config(data->pciInfo->bus, data->pciInfo->device, data->pciInfo->function, PCI_line_size, sizeof(PCI_line_size)) & 0xff; .... } 
sizeof 0x0c . , - , , 
data .
V562 It's odd to compare a bool type value with a value of 18: 0x12 == IsProfessionalSpdif(). CEchoGals_mixer.cpp 533
 typedef bool BOOL; virtual BOOL IsProfessionalSpdif() { ... } #define ECHOSTATUS_DSP_DEAD 0x12 ECHOSTATUS CEchoGals::ProcessMixerFunction(....) { .... if ( ECHOSTATUS_DSP_DEAD == IsProfessionalSpdif() )  
IsProfessionalSpdif bool , 
0x12 .
Conclusão
Haiku , .. PVS-Studio Java. , , . 
Coverity Scan , . Haiku. Coverity 2014 , 2015 ( 
1 , 
2 ).
, , Haiku . , . , 
PVS-Studio .
Deseja experimentar o Haiku e tiver alguma dúvida? Os desenvolvedores do Haiku convidam você para o 
canal de telegrama .

Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Svyatoslav Razmyslov. 
Como dar um tiro no pé em C e C ++. Haiku OS Cookbook