Une autre année est sur le point de se terminer, il est donc temps de faire du café et de relire les rapports de bogues de l'année écoulée. Bien sûr, cela prendra beaucoup de temps, donc cet article a été écrit. Je suggère de jeter un œil aux endroits sombres les plus intéressants des projets que nous avons rencontrés en 2019 dans des projets écrits en C et C ++.
Dixième place: "Quel est notre système d'exploitation?"
V1040 Typo possible dans l'orthographe d'un nom de macro prédéfini. La macro '__MINGW32_' est similaire à '__MINGW32__'. winapi.h 4112
#if !defined(__UNICODE_STRING_DEFINED) && defined(__MINGW32_) #define __UNICODE_STRING_DEFINED #endif
Ici, une faute de frappe a été faite dans le nom de macro
__MINGW32 _ (MINGW32 déclare __MINGW32__). Dans d'autres endroits du projet, la vérification est écrite correctement:
Ce n'était d'ailleurs pas seulement la première erreur de l'article «
CMake: le cas où le projet est impardonnable pour la qualité de son code », mais en général la première vraie erreur trouvée par les diagnostics V1040 dans un vrai projet ouvert (19 août 2019).
Neuvième place: "Qui est le premier?"
V502 L'opérateur '?:'
Fonctionne peut-être d'une manière différente de celle attendue. L'opérateur '?:' A une priorité inférieure à l'opérateur '=='. mir_parser.cpp 884
enum Opcode : uint8 { kOpUndef, .... OP_intrinsiccall, OP_intrinsiccallassigned, .... kOpLast, }; bool MIRParser::ParseStmtIntrinsiccall(StmtNodePtr &stmt, bool isAssigned) { Opcode o = !isAssigned ? (....) : (....); auto *intrnCallNode = mod.CurFuncCodeMemPool()->New<IntrinsiccallNode>(....); lexer.NextToken(); if (o == !isAssigned ? OP_intrinsiccall : OP_intrinsiccallassigned) { intrnCallNode->SetIntrinsic(GetIntrinsicID(lexer.GetTokenKind())); } else { intrnCallNode->SetIntrinsic(static_cast<MIRIntrinsicID>(....)); } .... }
Nous sommes intéressés par la partie suivante de ce code:
if (o == !isAssigned ? OP_intrinsiccall : OP_intrinsiccallassigned) { .... }
L'opérateur '==' a une priorité plus élevée que l'opérateur ternaire (? :). Pour cette raison, l'expression conditionnelle n'est pas évaluée correctement. Le code écrit est équivalent à ce qui suit:
if ((o == !isAssigned) ? OP_intrinsiccall : OP_intrinsiccallassigned) { .... }
Et compte tenu du fait que les constantes
OP_intrinsiccall et
OP_intrinsiccallassigned ont des valeurs non nulles, cette condition renvoie toujours la vraie valeur. Le corps de la branche
else est un code inaccessible.
Cette erreur est venue en tête de l'article "
Vérification du code Ark Compiler récemment ouvert par Huawei ".
Huitième place: «Le danger des opérations au niveau du bit»
V1046 Utilisation non
sûre des types bool 'et' int 'ensemble dans l'opération' & = '. 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; }
Selon le code, la fonction
SetFunctionList devait contourner la liste des itérateurs. Et si au moins l'un d'entre eux n'est pas valide, la valeur de retour sera
fausse , sinon
vraie .
Cependant, en réalité, la fonction
SetFunctionList peut retourner
false même pour les itérateurs valides. Examinons la situation. La
fonction AddFunction renvoie le nombre d'itérateurs valides dans la liste
fFunctions . C'est-à-dire lors de l'ajout d'itérateurs non nuls, la taille de cette liste augmentera séquentiellement: 1, 2, 3, 4, etc. C'est là que l'erreur dans le code commence à se manifester:
ret &= AddFunction(*f);
Parce que Puisque la fonction retourne un résultat de type
int , pas
bool , l'opération '& =' avec des nombres pairs donnera la valeur
false . Après tout, le bit de nombre pair le moins significatif sera toujours nul. Par conséquent, une telle erreur non évidente gâchera le résultat de la fonction
SetFunctionsList même pour des arguments valides.
Si vous lisez attentivement le code de l'exemple (et que vous lisez attentivement, non?), Vous remarquerez peut-être qu'il s'agit du code du projet ROOT. Bien sûr, nous l'avons testé: "
Analyse de code ROOT - un cadre pour l'analyse des données de recherche scientifique ."
Septième place: «Confusion dans les variables»
V1001 [CWE-563] La variable 'Mode' est affectée mais n'est pas utilisée à la fin de la fonction. SIModeRegister.cpp 48
struct Status { unsigned Mask; unsigned Mode; Status() : Mask(0), Mode(0){}; Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { Mode &= Mask; }; .... };
Il est très dangereux de donner aux arguments de fonction les mêmes noms que les membres de la classe. Très facile à confondre. Un tel cas est devant nous. Cette expression n'a pas de sens:
Mode &= Mask;
L'argument de la fonction change. Et c'est tout. Cet argument n'est plus utilisé. Très probablement, il fallait écrire comme ceci:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { this->Mode &= Mask; };
Et ceci est une erreur de
LLVM . Nous avons de temps à autre une tradition d'analyser ce projet. Cette année, nous avons également un
article sur la vérification.
Sixième place: «C ++ a ses propres lois»
L'erreur suivante apparaît dans le code car les règles C ++ ne coïncident pas toujours avec les règles mathématiques ou le «bon sens». Remarquez par vous-même où l'erreur se trouve dans un petit extrait de code?
V709 Comparaison suspecte trouvée: 'f0 == f1 == m_fractureBodies.size ()'. N'oubliez pas que «a == b == c» n'est pas égal à «a == b && b == c». btFractureDynamicsWorld.cpp 483
btAlignedObjectArray<btFractureBody*> m_fractureBodies; void btFractureDynamicsWorld::fractureCallback() { for (int i = 0; i < numManifolds; i++) { .... int f0 = m_fractureBodies.findLinearSearch(....); int f1 = m_fractureBodies.findLinearSearch(....); if (f0 == f1 == m_fractureBodies.size()) continue; .... } .... }
Il semblerait que la condition vérifie que
f0 est égal à
f1 et égal au nombre d'éléments dans
m_fractureBodies . Il semble que cette comparaison aurait dû vérifier si
f0 et
f1 sont à la fin du tableau
m_fractureBodies , car ils contiennent la position de l'objet trouvé par la méthode
findLinearSearch () . Cependant, en réalité, cette expression se transforme en une vérification pour voir si
f0 et
f1 sont égales, puis en une vérification pour voir si
m_fractureBodies.size () est égal au résultat de
f0 == f1 . Par conséquent, le troisième opérande est ici comparé à 0 ou 1.
Belle erreur! Et, heureusement, assez rare. Jusqu'à présent, nous ne l'avons
rencontrée que dans trois projets ouverts, et, fait intéressant, ils n'étaient tous que des moteurs de jeu. Au fait, ce n'est pas la seule erreur que nous ayons trouvée dans Bullet. Les plus intéressants sont entrés dans notre article «
PVS-Studio a étudié le moteur Red Dead Redemption - Bullet ».
Cinquième place: "Quelle est la fin de la ligne?"
L'erreur suivante est facilement détectée si vous connaissez une subtilité.
V739 EOF ne doit pas être comparé à une valeur de type 'char'. Le «ch» doit être de type «int». json.cpp 762
void JsonIn::skip_separator() { signed char ch; .... if (ch == ',') { if( ate_separator ) { .... } .... } else if (ch == EOF) { .... }
C'est l'une de ces erreurs qui peut être difficile à remarquer si vous ne savez pas que
EOF est défini comme -1. Par conséquent, si vous essayez de le comparer avec une variable de type
signé char , la condition est presque toujours
fausse . La seule exception est si le code de caractère est 0xFF (255). Lors de la comparaison, un tel symbole se transformera en -1 et la condition sera vraie.
Il y a beaucoup d'erreurs liées aux jeux dans ce sommet: des moteurs aux jeux ouverts. Comme vous l'avez peut-être deviné, cet endroit nous est également venu de cette région. Vous pouvez voir plus d'erreurs dans l'article "
Cataclysm Dark Days Ahead, Static Analysis and Bagels ."
Quatrième place: «La magie du nombre Pi»
V624 Il y a probablement une
faute d' impression dans la constante '3.141592538'. Pensez à utiliser la constante M_PI de <math.h>. PhysicsClientC_API.cpp 4109
B3_SHARED_API void b3ComputeProjectionMatrixFOV(float fov, ....) { float yScale = 1.0 / tan((3.141592538 / 180.0) * fov / 2); .... }
Une petite faute de frappe dans le nombre Pi (3.141592653 ...), manquant le nombre "6" à la 7ème position dans la partie fractionnaire.
Peut-être que l'erreur à la dix millionième décimale n'entraînera pas de conséquences tangibles, mais vous devriez toujours utiliser les constantes de bibliothèque existantes sans fautes de frappe. Pour Pi, par exemple, il existe une constante M_PI de l'en-tête math.h.
Cette erreur provient de l'article «
PVS-Studio a examiné le moteur Red Dead Redemption - Bullet », que nous connaissons déjà en sixième position. Si vous ne l'avez pas reporté pour plus tard, c'est la dernière chance.
Une légère digression
Nous sommes donc proches des trois erreurs les plus intéressantes. Comme vous l'avez peut-être remarqué, ils ne sont pas triés par les conséquences catastrophiques de leur présence, mais par la complexité de la détection. Après tout, au final, l'avantage le plus important de l'analyse statique sur la révision de code est que la machine ne se fatigue pas et n'oublie rien. :)
Et maintenant, j'attire votre attention sur les trois premiers.
Troisième place: «l'exception insaisissable»
Les classes
V702 doivent toujours être dérivées de std :: exception (et similaires) en tant que «public» (aucun mot-clé n'a été spécifié, donc le compilateur le définit par défaut sur «privé»). CalcManager CalcException.h 4
class CalcException : std::exception { public: CalcException(HRESULT hr) { m_hr = hr; } HRESULT GetException() { return m_hr; } private: HRESULT m_hr; };
L'analyseur a détecté une classe héritée de la classe
std :: exception via le modificateur
privé (le modificateur par défaut si rien n'est spécifié). Le problème avec ce code est que lorsque vous essayez d'intercepter l'exception générale
std :: exception, une exception de type
CalcException sera ignorée. Ce problème se produit car l'héritage privé empêche la conversion de type implicite.
Oui, je ne voudrais pas voir le programme se bloquer en raison du modificateur
public manquant
. Soit dit en passant, je suis sûr que vous avez certainement utilisé l'application au moins une fois dans votre vie, dont nous venons de voir le code source. C'est la bonne vieille
calculatrice Windows standard que nous avons également
testée .
Deuxième place: balises HTML non fermées
V735 Peut-être un HTML incorrect. La balise de fermeture "</body>" a été rencontrée, tandis que la balise "</div>" était attendue. book.cpp 127
static QString makeAlgebraLogBaseConversionPage() { return BEGIN INDEX_LINK TITLE(Book::tr("Logarithmic Base Conversion")) FORMULA(y = log(x) / log(a), log<sub>a</sub>x = log(x) / log(a)) END; }
Comme cela arrive souvent avec le code C / C ++, rien n'est clair à partir de la source, alors passons au code prétraité pour ce fragment:
L'analyseur a détecté une
balise <div> non fermée. Il y a beaucoup de fragments de code html dans ce fichier et maintenant il devrait être vérifié en plus par les développeurs.
Surpris que nous puissions vérifier et autres? Quand j'ai vu cela pour la première fois, j'ai été impressionné. Nous analysons donc un peu le code html. Vrai, uniquement en code C ++. :)
Ce n'est pas seulement la deuxième place, mais aussi la deuxième calculatrice de notre top. Vous pouvez trouver une liste de toutes les erreurs dans l'article "
Suivre la trace des calculatrices: SpeedCrunch ".
Première place: «Caractéristiques standard insaisissables»
Nous sommes donc arrivés à la première place. Un problème incroyablement étrange qui a fait l'objet d'une révision de code.
Essayez de le découvrir vous-même:
static int EatWhitespace (FILE * InFile) { int c; for (c = getc (InFile); isspace (c) && ('\n' != c); c = getc (InFile)) ; return (c); }
Voyons maintenant ce que jure l'analyseur:
V560 Une partie de l'expression conditionnelle est toujours vraie: ('\ n'! = C). params.c 136.
Étrange, non? Regardons quelque chose d'intéressant dans le même projet, mais dans un fichier différent (charset.h):
#ifdef isspace #undef isspace #endif .... #define isspace(c) ((c)==' ' || (c) == '\t')
Donc, et c'est déjà vraiment étrange ... Il s'avère que si la variable
c est égale à
'\ n', alors la fonction
isspace (c) absolument inoffensive à première vue retournera false et la deuxième partie de ce test ne sera pas effectuée en raison d'une évaluation de court-circuit. Si
isspace (c) est exécuté, alors la variable
c est soit
'' soit
'\ t', et ce n'est clairement pas égal à
'\ n' .
Bien sûr, vous pouvez dire que cette macro est comme
#define true false , et un tel code ne passera jamais en revue le code. Cependant, ce code a passé l'examen et nous attendait en toute sécurité dans le référentiel du projet.
Si vous avez besoin d'une analyse plus détaillée de l'erreur, c'est dans l'article "
Pour ceux qui veulent jouer au détective: trouvez l'erreur dans la fonction de Midnight Commander ".
Conclusion
Au cours de la dernière année, nous avons trouvé de nombreuses erreurs. Ce sont les erreurs de copier-coller habituelles, les erreurs de constantes, les balises non fermées et bien d'autres problèmes. Mais notre analyseur s'améliore et
apprend à rechercher de plus en plus de bogues, donc c'est loin d'être la fin, et de nouveaux articles sur la vérification des projets seront publiés aussi souvent qu'auparavant.
Si quelqu'un lit nos articles pour la première fois, juste au cas où, je préciserai que toutes ces erreurs ont été trouvées en utilisant l'analyseur de code statique PVS-Studio, que nous suggérons de
télécharger et d'essayer. L'analyseur peut détecter des erreurs dans le code des programmes écrits dans les langages: C, C ++, C # et Java.
Nous sommes donc arrivés à la fin! Si vous avez manqué les deux premiers niveaux, je vous suggère de ne pas manquer l'occasion et de les parcourir avec nous:
C # et
Java .

Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Maxim Zvyagintsev.
Top 10 des bugs trouvés dans les projets C ++ en 2019 .