L'histoire de la rencontre entre l'analyseur statique PVS-Studio et le code Haiku OS remonte à l'année 2015. Ce fut une expérience passionnante et une expérience utile pour les équipes des deux projets. Pourquoi l'expérience? À ce moment-là, nous n'avions pas l'analyseur pour Linux et nous ne l'aurions pas avant un an et demi. Quoi qu'il en soit, les efforts des passionnés de notre équipe ont été récompensés: nous nous sommes réunis avec les développeurs de Haiku et avons augmenté la qualité du code, élargi notre base d'erreurs avec de rares bogues créés par les développeurs et affiné l'analyseur. Vous pouvez maintenant vérifier le code Haiku pour les erreurs facilement et rapidement.
Présentation
Rencontrez les personnages principaux de notre histoire - le
Haiku avec code open source et l'analyseur statique
PVS-Studio pour C, C ++, C # et Java. Lorsque nous avons fouillé l'analyse du projet il y a 4,5 ans, nous ne devions traiter que le fichier analyseur exécutable compilé. Toute l'infrastructure d'analyse des paramètres du compilateur, d'exécution d'un préprocesseur, de mise en parallèle de l'analyse, etc., a été extraite de l'utilitaire
Compiler Monitoring UI , écrit en C #. Cet utilitaire a été porté en partie sur la plate-forme Mono pour être exécuté sous Linux. Le projet Haiku est construit à l'aide du compilateur croisé sous divers systèmes d'exploitation, à l'exception de Windows. Encore une fois, je voudrais mentionner la commodité et l'exhaustivité de la documentation liées au bâtiment Haiku. J'aimerais également remercier les développeurs de Haiku pour leur aide dans la construction du projet.
Il est beaucoup plus simple d'effectuer l'analyse maintenant. Voici la liste de toutes les commandes pour construire et analyser le projet:
cd /opt git clone https:
À propos, l'analyse du projet a été implémentée dans un conteneur Docker. Récemment, nous avons préparé une nouvelle documentation sur ce sujet:
Exécution de PVS-Studio dans Docker . Cela peut rendre très facile pour certaines entreprises d'appliquer des techniques d'analyse statique à leurs projets.
Variables non initialisées
V614 Variable non initialisée 'rval' utilisée. fetch.c 1727
int auto_fetch(int argc, char *argv[]) { volatile int argpos; int rval;
La variable
rval n'a pas été initialisée lors de la déclaration, donc sa comparaison avec la valeur nulle conduira à un résultat indéfini. Si les circonstances échouent, la valeur incertaine de la variable
rval peut devenir une valeur de retour de la fonction
auto_fetch .
V614 Pointeur
non initialisé «res» utilisé. commandes.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;
Voici un cas similaire d'utilisation de la variable non initialisée, sauf que
res est un pointeur non initialisé qui a lieu ici.
V506 Le pointeur vers la variable locale «normalisée» est stocké en dehors de la portée de cette variable. Un tel pointeur deviendra invalide. 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); }
Le programmeur avait probablement besoin de normaliser l'objet à l'aide d'une variable intermédiaire. Mais maintenant, le pointeur de
police contient le pointeur vers l'objet
normalisé , qui sera supprimé après avoir quitté la portée, où l'objet temporaire a été créé.
V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 27
int8 BUnicodeChar::Type(uint32 c) { BUnicodeChar(); return u_charType(c); }
Une erreur très courante chez les programmeurs C ++ consiste à utiliser l'appel du constructeur soi-disant pour initialiser / annuler les champs de classe. Dans ce cas, la modification des champs de classe ne se produit pas, mais un nouvel objet sans nom de cette classe est créé puis immédiatement détruit. Malheureusement, il existe de nombreux endroits de ce type dans le projet:
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 37
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 49
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 58
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 67
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 77
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 89
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 103
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 115
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 126
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 142
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 152
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 163
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 186
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 196
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 206
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 214
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 222
- V603 L'objet a été créé mais il n'est pas utilisé. Si vous souhaitez appeler le constructeur, 'this-> BUnicodeChar :: BUnicodeChar (....)' doit être utilisé. UnicodeChar.cpp 230
V670 Le membre de classe non initialisé 'fPatternHandler' est utilisé pour initialiser le membre 'fInternal'. N'oubliez pas que les membres sont initialisés dans l'ordre de leurs déclarations à l'intérieur d'une classe. Painter.cpp 184
Painter::Painter() : fInternal(fPatternHandler), .... fPatternHandler(), .... { .... }; class Painter { .... private: mutable PainterAggInterface fInternal;
Un autre exemple d'initialisation incorrecte. Les champs de classe sont initialisés dans l'ordre de leur déclaration dans la classe elle-même. Dans cet exemple, le champ
fInternal sera le premier à s'initialiser à l'aide de la valeur
fPatternHandler non
initialisée .
#Définir suspect
V523 L'
instruction 'then' est équivalente à l'instruction '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); }
Cet extrait de code ne semble pas suspect jusqu'à ce que vous regardiez le résultat du préprocesseur:
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); }
L'analyseur a vraiment raison -
si et
sinon les branches sont identiques. Mais où sont les fonctions
mtx_lock_spin et
mtx_unlock_spin ? Les macros
TQ_LOCK ,
TQ_UNLOCK et la fonction
grouptask_block sont déclarées dans un fichier presque côte à côte, mais néanmoins un remplacement a eu lieu quelque part ici.
La recherche dans les fichiers n'a abouti qu'à
mutex.h avec le contenu suivant:
#define mtx_lock_spin(x) mtx_lock(x) #define mtx_unlock_spin(x) mtx_unlock(x)
Les développeurs de projets doivent vérifier si un tel remplacement est correct ou non. J'ai vérifié ce projet sous Linux et un tel remplacement me semblait suspect.
Erreurs avec la fonction gratuite
V575 Le pointeur nul est passé dans la fonction 'libre'. Inspectez le premier argument. 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;
Vous pouvez passer le pointeur nul dans la fonction
gratuite , mais une telle utilisation est certainement suspecte. Ainsi, l'analyseur a trouvé des lignes de code mélangées. Tout d'abord, l'auteur du code a dû libérer la mémoire par le pointeur
fVectorIcon , seulement après avoir affecté
NULL .
V575 Le pointeur nul est passé dans la fonction 'libre'. Inspectez le premier argument. 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) {
Ceci est un autre exemple de passage explicite d'un pointeur nul à la fonction
libre . Cette ligne peut être supprimée, car la fonction se ferme après avoir réussi à obtenir le pointeur.
V575 Le pointeur nul est passé dans la fonction 'libre'. Inspectez le premier argument. PackageFileHeapWriter.cpp 166
void* _GetBuffer() { .... void* buffer = malloc(fBufferSize); if (buffer == NULL && !fBuffers.AddItem(buffer)) { free(buffer); throw std::bad_alloc(); } return buffer; }
Quelqu'un a fait une erreur ici. L'opérateur || doit être utilisé à la place de &&. Seulement dans ce cas, l'exception
std :: bad_alloc () sera levée en cas d'échec de l'allocation de mémoire (à l'aide de la fonction
malloc ).
Erreurs avec l'opérateur de suppression
V611 La mémoire a été allouée à l'aide de l'opérateur 'new T []' mais a été libérée à l'aide de l'opérateur 'delete'. Pensez à inspecter ce code. Il vaut probablement mieux utiliser 'delete [] fMsg;'. Err.cpp 65
class Err { public: .... private: char *fMsg; ssize_t fPos; }; void Err::Unset() { delete fMsg;
Le pointeur
fMsg est utilisé pour allouer de la mémoire à un tableau de caractères. L'opérateur de
suppression est utilisé pour libérer la mémoire au lieu de
supprimer [] .
V611 La mémoire a été allouée à l'aide de l'opérateur "nouveau" mais a été libérée à l'aide de la fonction "libre". Pensez à inspecter les logiques d'opération derrière la variable «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);
Ici
malloc_flags est une fonction qui appelle
malloc . Ensuite,
placement-new construit l'objet ici. Comme la classe
PageWriteWrapper est implémentée de la manière suivante:
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"); }
les destructeurs d'objets de cette classe ne seront pas appelés en raison de l'utilisation de la fonction
free pour libérer de la mémoire.
V611 La mémoire a été allouée à l'aide de l'opérateur 'new T []' mais a été libérée à l'aide de l'opérateur 'delete'. Pensez à inspecter ce code. Il vaut probablement mieux utiliser 'delete [] fOutBuffer;'. Vérifiez les lignes: 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; };
C'est une erreur courante d'utiliser l'opérateur de
suppression au lieu de
supprimer []. Il est plus facile de faire une erreur lors de l'écriture d'une classe, car le code du destructeur est souvent loin des emplacements de mémoire. Ici, le programmeur libère de manière incorrecte la mémoire stockée par le pointeur
fOutBuffer dans le destructeur.
V772 L' appel d'un opérateur de suppression pour un pointeur vide entraînera un comportement indéfini. Hashtable.cpp 207
void Hashtable::MakeEmpty(int8 keyMode,int8 valueMode) { .... for (entry = fTable[index]; entry; entry = next) { switch (keyMode) { case HASH_EMPTY_DELETE:
En plus d'un choix incorrect entre
supprimer /
supprimer [] et
gratuit , vous pouvez également rencontrer un comportement non défini lorsque vous essayez d'effacer la mémoire par un pointeur sur le type
void (void *) .
Fonctions sans valeur de retour
V591 La fonction non vide doit renvoyer une valeur. Référencable.h 228
BReference& operator=(const BReference<const Type>& other) { fReference = other.fReference; }
Un opérateur d'affectation surchargé n'a pas de valeur de retour. Dans ce cas, l'opérateur retournera une valeur aléatoire, ce qui peut conduire à des erreurs étranges.
Voici des problèmes similaires dans d'autres fragments de code de cette classe:
- V591 La fonction non vide doit renvoyer une valeur. Référencable.h 233
- V591 La fonction non vide doit renvoyer une valeur. Référencable.h 239
V591 La fonction non vide doit renvoyer une valeur. 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); }
Le commentaire d'un utilisateur NOTREACHED ne signifie rien ici. Vous devez annoter des fonctions comme noreturn afin d'écrire correctement du code pour de tels scénarios. Pour ce faire, il existe des attributs noreturn: standard et spécifiques au compilateur. Tout d'abord, ces attributs sont pris en compte par les compilateurs pour la génération de code appropriée ou la notification de certains types de bogues à l'aide d'avertissements. Divers outils d'analyse statique prennent également en compte les attributs pour améliorer la qualité de l'analyse.
Gestion des exceptions
V596 L'objet a été créé mais il n'est pas utilisé. Le mot clé «throw» peut être manquant: 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; }
Le
jet de mot-clé a été accidentellement oublié ici. Ainsi, l'exception
ParseException ne sera pas générée tandis que l'objet de cette classe sera simplement détruit lors de la sortie de la portée. Après cela, la fonction continuera à fonctionner comme si rien ne s'était passé, comme si le bon numéro avait été entré.
V1022 Une exception a été levée par le pointeur. Pensez plutôt à le jeter par valeur. gensyscallinfos.cpp 316
int main(int argc, char** argv) { try { return Main().Run(argc, argv); } catch (Exception& exception) {
L'analyseur a détecté l'exception
IOException levée par le pointeur. Lancer un pointeur conduit au fait que l'exception ne sera pas interceptée. L'exception est donc finalement interceptée par référence. De plus, l'utilisation d'un pointeur oblige la partie prenante à appeler l'opérateur de
suppression pour détruire l'objet créé, ce qui n'avait pas été fait.
Quelques autres fragments de code avec des problèmes:
- V1022 Une exception a été levée par le pointeur. Pensez plutôt à le jeter par valeur. gensyscallinfos.cpp 347
- V1022 Une exception a été levée par le pointeur. Pensez plutôt à le jeter par valeur. gensyscallinfos.cpp 413
Sécurité formelle
V597 Le compilateur pourrait supprimer l'appel de fonction 'memset', qui est utilisé pour vider l'objet 'f_key'. La fonction memset_s () doit être utilisée pour effacer les données privées. 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); }
L'analyseur a détecté du code suspect, destiné à l'effacement sécurisé des données privées. Malheureusement, la macro
SAFE_FREE qui se développe dans le
memset ,
les appels
gratuits et l'affectation
NULL ne rend pas le code plus sûr, car il est entièrement supprimé par le compilateur lors de l'optimisation avec
O2 .
Soit dit en passant, ce n'est rien d'autre que
CWE-14 : suppression du code par le compilateur pour effacer les tampons.
Voici la liste des endroits où l'effacement des tampons n'est pas effectué en fait:
- V597 Le compilateur pourrait supprimer l'appel de fonction 'memset', qui est utilisé pour vider le tampon 'encoded_block'. La fonction memset_s () doit être utilisée pour effacer les données privées. dst_api.c 446
- V597 Le compilateur pourrait supprimer l'appel de fonction 'memset', qui est utilisé pour vider l'objet 'key_st'. La fonction memset_s () doit être utilisée pour effacer les données privées. dst_api.c 685
- V597 Le compilateur pourrait supprimer l'appel de fonction 'memset', qui est utilisé pour vider le tampon 'in_buff'. La fonction memset_s () doit être utilisée pour effacer les données privées. dst_api.c 916
- V597 Le compilateur pourrait supprimer l'appel de fonction 'memset', qui est utilisé pour vider l'objet 'ce'. La fonction memset_s () doit être utilisée pour effacer les données privées. fs_cache.c 1078
Comparaisons avec des variables non signées
V547 L' expression 'restant <0' est toujours fausse. La valeur du type non signé n'est jamais <0. DwarfFile.cpp 1947
status_t DwarfFile::_UnwindCallFrame(....) { .... uint64 remaining = lengthOffset + length - dataReader.Offset(); if (remaining < 0) return B_BAD_DATA; .... }
L'analyseur a trouvé une comparaison explicite de la variable non signée avec des valeurs négatives. Peut-être, on devrait comparer la variable
restante uniquement avec null ou implémenter une vérification de dépassement.
V547 L' expression 'sommeil ((non signé) secondes) <0' est toujours fausse. La valeur du type non signé n'est jamais <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)
Pour obtenir le point principal de l'erreur,
examinons les signatures des fonctions de
sommeil et d'
utilisation :
extern unsigned int sleep (unsigned int __seconds); extern int usleep (__useconds_t __useconds);
Comme nous pouvons le voir, la fonction
sleep renvoie la valeur non signée et son utilisation dans le code est incorrecte.
Pointeurs dangereux
V774 Le pointeur «périphérique» a été utilisé après la libération de la mémoire. xhci.cpp 1572
void XHCI::FreeDevice(Device *device) { uint8 slot = fPortSlots[device->HubPort()]; TRACE("FreeDevice() port %d slot %d\n", device->HubPort(), slot);
Un objet
périphérique est libéré par l'opérateur de
suppression . C'est assez logique pour la fonction
FreeDevice . Mais, pour une raison quelconque, pour libérer d'autres ressources, l'objet déjà supprimé est adressé.
Un tel code est extrêmement dangereux et peut être rencontré à plusieurs autres endroits:
- V774 Le pointeur «self» a été utilisé après la libération de la mémoire. TranslatorRoster.cpp 884
- V774 Le pointeur 'chaîne' a été utilisé après la libération de la mémoire. RemoteView.cpp 1269
- V774 Le pointeur «bs» a été utilisé après la libération de la mémoire. mkntfs.c 4291
- V774 Le pointeur «bs» a été utilisé après la libération de la mémoire. mkntfs.c 4308
- V774 Le pointeur 'al' a été utilisé après la réallocation de la mémoire. inode.c 1155
V522 Le déréférencement des données du pointeur nul peut avoir lieu. Le pointeur nul est passé dans la fonction 'malo_hal_send_helper'. Inspectez le troisième argument. Vérifier les lignes: 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);
L'analyse interprocédurale a révélé le cas où
NULL est passé à la fonction et le pointeur de
données avec une telle valeur est finalement déréférencé dans la fonction
memcpy .
V773 La fonction a été quittée sans libérer le pointeur 'inputFileFile'. Une fuite de mémoire est possible. 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; .... }
PVS-Studio peut détecter les fuites de mémoire . Dans cet exemple, en cas d'erreur, la mémoire ne sera pas libérée. Quelqu'un pourrait penser qu'en cas d'erreur, vous ne devriez pas vous soucier de la libération de la mémoire, car le programme se terminera toujours. Mais ce n'est pas toujours le cas. De nombreux programmes doivent gérer correctement les erreurs et continuer à fonctionner.
V595 Le pointeur 'fR répondre' a été utilisé avant d'être vérifié par rapport à nullptr. Vérifiez les lignes: 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; }
C'est une erreur très courante de déréférencer les pointeurs avant de les vérifier. Le diagnostic
V595 prévaut presque toujours dans le nombre d'avertissements d'un projet. Ce fragment de code inclut une utilisation dangereuse du pointeur
fRhness .
V595 Le pointeur 'mq' a été utilisé avant d'être vérifié par rapport à nullptr. Vérifier les lignes: 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; .... }
Un exemple similaire. Le pointeur
mg est déréférencé plusieurs lignes plus tôt qu'il n'est vérifié pour null. Il y a beaucoup d'endroits similaires dans le projet. Dans certains extraits, l'utilisation et la vérification du pointeur sont assez éloignées les unes des autres, donc dans cet article, vous ne trouverez que quelques exemples de ce type. Les développeurs sont invités à consulter d'autres exemples dans le rapport d'analyse complet.
Divers
V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. 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; .... } .... } .... }
La différence entre les fonctions
strlcat et
strncat n'est pas très évidente pour quelqu'un qui n'est pas familier avec la description de ces fonctions. La fonction
strlcat attend la taille de la mémoire tampon entière comme troisième argument tandis que la fonction
strncat - la taille de l'espace libre dans une mémoire tampon, ce qui nécessite d'évaluer une valeur nécessaire avant d'appeler la fonction. Mais les développeurs l'oublient souvent ou ne le savent pas. Le passage de la taille totale du tampon à la fonction
strncat peut entraîner un débordement de tampon, car la fonction considérera cette valeur comme un nombre acceptable de caractères à copier. La fonction
strlcat n'a pas un tel problème. Mais vous devez passer des chaînes, se terminant par terminal null pour que cela fonctionne correctement.
Voici la liste complète des endroits dangereux avec des chaînes:
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 104
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 107
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 110
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 113
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 118
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 119
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 120
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 123
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 126
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 129
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 132
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 135
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 138
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 141
- V645 L'appel de fonction 'strncat' pourrait entraîner un débordement de tampon de 'sortie'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. NamespaceDump.cpp 144
- V645 L'appel de fonction 'strncat' pourrait conduire au débordement de tampon 'features_string'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. VirtioDevice.cpp 283
- V645 L'appel de fonction 'strncat' pourrait conduire au débordement de tampon 'features_string'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. VirtioDevice.cpp 284
- V645 L'appel de fonction 'strncat' pourrait conduire au débordement de tampon 'features_string'. Les limites ne doivent pas contenir la taille du tampon, mais un certain nombre de caractères qu'il peut contenir. VirtioDevice.cpp 285
V792 La fonction 'SetDecoratorSettings' située à droite de l'opérateur '|' sera appelé quelle que soit la valeur de l'opérande gauche. Peut-être vaut-il mieux utiliser '||'. 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; }
Très probablement, '|' et '||' les opérateurs étaient confus. Cette erreur entraîne des appels inutiles de la fonction
SetDecoratorSettings .
V627 Envisagez d'inspecter l'expression. L'argument de sizeof () est la macro qui se développe en nombre. 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; .... }
Passer la valeur
0x0c à l'opérateur
sizeof semble suspect. L'auteur aurait peut-être dû évaluer la taille d'un objet, par exemple des
données .
V562 Il est étrange de comparer une valeur de type booléen avec une valeur de 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() )
La fonction
IsProfessionalSpdif renvoie la valeur de type
bool . Ce faisant, le résultat de la fonction est comparé au nombre
0x12 dans la condition.
Conclusion
Nous avons manqué la sortie de la première version bêta de Haiku l'automne dernier, car nous étions occupés à publier PVS-Studio pour Java. Pourtant, la nature des erreurs de programmation est telle qu'elles ne disparaissent pas si vous ne les recherchez pas et ne faites pas attention à la qualité du code. Les développeurs du projet ont utilisé
Coverity Scan , mais la dernière exécution
remonte à près de deux ans. Cela doit être bouleversant pour les utilisateurs de Haiku. Même si l'analyse a été configurée en 2014 avec Coverity, cela ne nous a pas empêché d'écrire deux longs articles sur la revue des erreurs en 2015 (
partie 1 ,
partie 2 )
Un autre examen des erreurs par Haiku sera bientôt publié pour ceux qui liront ce message jusqu'à la fin. Le rapport complet de l'analyseur sera envoyé aux développeurs avant de publier cet examen des erreurs, donc certaines erreurs peuvent être corrigées au moment où vous lisez ceci. Pour passer le temps entre les articles, je vous suggère de télécharger et d'essayer
PVS-Studio pour votre projet.
Voulez-vous essayer Haiku et vous avez des questions? Les développeurs de Haiku vous invitent sur le
canal télégramme .