Solutions aux défis de détection de bugs proposés par l'équipe PVS-Studio lors de conférences en 2018-2019

Image 2


Salut! Bien que la saison des conférences 2019 ne soit pas encore terminée, nous aimerions parler des défis de détection de bogues que nous avons proposés aux visiteurs sur notre stand lors des dernières conférences. À partir de l'automne 2019, nous avons apporté un nouvel ensemble de défis, afin que nous puissions maintenant révéler les solutions aux tâches précédentes de 2018 et du premier semestre de 2019 - après tout, bon nombre d'entre elles provenaient d'articles précédemment publiés, et nous avions un lien ou un code QR avec des informations sur les articles respectifs imprimés sur nos brochures de défi.

Si vous avez assisté à des événements auxquels nous avons participé avec un stand, vous avez probablement vu ou même essayé de résoudre certains de nos défis. Ce sont des extraits de code de vrais projets open-source écrits en C, C ++, C # ou Java. Chaque extrait contient un bogue, et les invités sont mis au défi d'essayer de le trouver. Une solution réussie (ou simplement une participation à la discussion du bug) est récompensée par un prix: un statut de bureau en spirale, un trousseau de clés, etc.:

Image 4

Vous en voulez aussi? Alors n'hésitez pas à passer nous voir sur notre stand lors des prochains événements.

Soit dit en passant, dans les articles " Conference Time! Summing up 2018 " et " Conferences. Sub-totals for the first semestre of 2019 ", nous partageons notre expérience de participation aux événements organisés plus tôt cette année et en 2018.

D'accord, jouons à notre jeu "Find the bug". Nous allons d'abord jeter un coup d'œil aux défis antérieurs de 2018, regroupés par langue.

2018


C ++


Bug de chrome

static const int kDaysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; bool ValidateDateTime(const DateTime& time) { if (time.year < 1 || time.year > 9999 || time.month < 1 || time.month > 12 || time.day < 1 || time.day > 31 || time.hour < 0 || time.hour > 23 || time.minute < 0 || time.minute > 59 || time.second < 0 || time.second > 59) { return false; } if (time.month == 2 && IsLeapYear(time.year)) { return time.month <= kDaysInMonth[time.month] + 1; } else { return time.month <= kDaysInMonth[time.month]; } } 

Solution
Ce bug trouvé dans Chromium était probablement le défi le plus «long»; nous l'avons proposé tout au long de 2018 et l'avons également inclus dans plusieurs présentations.

 if (time.month == 2 && IsLeapYear(time.year)) { return time.month <= kDaysInMonth[time.month] + 1; // <= day } else { return time.month <= kDaysInMonth[time.month]; // <= day } 

Le corps du dernier bloc If-else contient des fautes de frappe dans les instructions de retour: time.month a été accidentellement écrit une deuxième fois au lieu de time.day . Cette erreur fait que la fonction retourne vrai tout le temps. Le bogue est discuté en détail dans l'article " 31 février " et est un exemple sympa d'un bogue qui n'est pas facilement repéré par la révision du code. Ce cas est également une bonne démonstration de la façon dont nous utilisons l'analyse de flux de données.

Bug moteur irréel

 bool VertInfluencedByActiveBone( FParticleEmitterInstance* Owner, USkeletalMeshComponent* InSkelMeshComponent, int32 InVertexIndex, int32* OutBoneIndex = NULL); void UParticleModuleLocationSkelVertSurface::Spawn(....) { .... int32 BoneIndex1, BoneIndex2, BoneIndex3; BoneIndex1 = BoneIndex2 = BoneIndex3 = INDEX_NONE; if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2]) &BoneIndex3) { .... } 

Solution
La première chose à noter ici est que le dernier argument de la fonction VertInfluencedByActiveBone () a une valeur par défaut et n'a pas besoin d'être spécifié. Regardez maintenant le bloc if sous une forme simplifiée:

 if (!foo(....) && !foo(....) && !foo(....) & arg) 

Le bug est maintenant clairement visible. En raison de la faute de frappe, le troisième appel de la fonction VertInfluencedByActiveBone () est effectué avec trois arguments au lieu de quatre, la valeur de retour participant alors à une opération & (bit à bit ET: l'opérande de gauche est la valeur de type bool retournée par VertInfluencedByActiveBone ( ) , et l'opérande de droite est la variable entière BoneIndex3 ). Le code est toujours compilable. Il s'agit de la version fixe (une virgule ajoutée, la parenthèse fermante déplacée à la fin de l'expression):

 if(!VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[0], &BoneIndex1) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[1], &BoneIndex2) && !VertInfluencedByActiveBone( Owner, SourceComponent, VertIndex[2], &BoneIndex3)) 

Cette erreur a été mentionnée à l'origine dans l'article " Une vérification tant attendue de Unreal Engine 4 ", où elle était intitulée "la plus belle erreur", avec laquelle je suis totalement d'accord.

Bugs Android

 void TagMonitor::parseTagsToMonitor(String8 tagNames) { std::lock_guard<std::mutex> lock(mMonitorMutex); // Expand shorthands if (ssize_t idx = tagNames.find("3a") != -1) { ssize_t end = tagNames.find(",", idx); char* start = tagNames.lockBuffer(tagNames.size()); start[idx] = '\0'; .... } .... } 

Solution
Le programmeur avait des hypothèses erronées sur la priorité des opérations dans l'état du bloc if . Ce code ne fonctionne pas comme prévu:

 if (ssize_t idx = (tagNames.find("3a") != -1)) 

La variable idx se verra attribuer la valeur 0 ou 1, et si la condition est vraie ou fausse dépendra de cette valeur, ce qui est une erreur. Ceci est la version fixe:

 ssize_t idx = tagNames.find("3a"); if (idx != -1) 

Ce bug a été mentionné dans l'article " Nous avons vérifié le code source Android par PVS-Studio, ou rien n'est parfait ."

Voici un autre défi non trivial avec un bogue Android:

 typedef int32_t GGLfixed; GGLfixed gglFastDivx(GGLfixed n, GGLfixed d) { if ((d>>24) && ((d>>24)+1)) { n >>= 8; d >>= 8; } return gglMulx(n, gglRecip(d)); } 

Solution
Le problème est dans l'expression (d >> 24) + 1 .

Le programmeur a voulu vérifier que les 8 bits les plus significatifs de la variable d sont mis à 1 mais pas tous à la fois. En d'autres termes, ils voulaient vérifier que l'octet le plus significatif stocke n'importe quelle valeur sauf 0x00 et 0xFF. Tout d'abord, le programmeur vérifie les bits les plus significatifs pour null en utilisant l'expression (d >> 24). Ensuite, ils décalent les huit bits les plus significatifs vers l'octet le moins significatif, s'attendant à ce que le bit de signe le plus significatif soit dupliqué dans tous les autres bits. Autrement dit, si la variable d a la valeur 0b11111111'00000000'00000000'00000000, elle deviendra 0b11111111'11111111'11111111'11111111 après le décalage. En ajoutant 1 à la valeur int 0xFFFFFFFF, le programmeur s'attend à obtenir 0 (-1 + 1 = 0). Ainsi, l'expression ((d >> 24) +1) est utilisée pour vérifier que les huit bits les plus significatifs ne sont pas tous mis à 1.

Cependant, le bit de signe le plus significatif n'est pas nécessairement "étalé" lorsqu'il est décalé. Voici ce que dit la norme: «La valeur de E1 >> E2 correspond aux positions de bit E2 décalées vers la droite E1. Si E1 a un type non signé ou si E1 a un type signé et une valeur non négative, la valeur du résultat est la partie intégrante du quotient de E1 / 2 ^ E2. Si E1 a un type signé et une valeur négative, la valeur résultante est définie par l'implémentation . "

Il s'agit donc d'un exemple de comportement défini par l'implémentation. Le fonctionnement exact de ce code dépend de l'architecture du processeur et de l'implémentation du compilateur. Les bits les plus significatifs peuvent bien finir comme des zéros après le décalage, et l'expression ((d >> 24) +1) retournerait alors toujours une valeur autre que 0, c'est-à-dire une valeur toujours vraie.

Il s'agit en effet d'un défi non trivial. Comme le bug précédent, celui-ci a été initialement discuté dans l'article " Nous avons vérifié le code source Android par PVS-Studio, ou rien n'est parfait ".

2019


C ++


"C'est la faute de GCC"

 int foo(const unsigned char *s) { int r = 0; while(*s) { r += ((r * 20891 + *s *200) | *s ^ 4 | *s ^ 3) ^ (r >> 1); s++; } return r & 0x7fffffff; } 

Le programmeur blâme le compilateur GCC 8 pour le bogue. Est-ce vraiment la faute de GCC?

Solution
La fonction renvoie des valeurs négatives car le compilateur ne génère pas de code pour le AND au niveau du bit (&). Le bogue a à voir avec un comportement non défini. Le compilateur remarque que la variable r est utilisée pour calculer et stocker une somme, avec uniquement des valeurs positives impliquées. La variable r ne doit pas déborder car ce serait un comportement indéfini, avec lequel le compilateur n'est pas du tout tenu compte. Il conclut donc que puisque r ne peut pas avoir de valeur négative à la fin de la boucle, l'opération r & 0x7fffffff , qui efface le bit de signe, est inutile, donc il dit simplement à la fonction de renvoyer la valeur de r .

Cette erreur a été décrite dans l'article " PVS-Studio 6.26 Released ".

Bug QT

 static inline const QMetaObjectPrivate *priv(const uint* data) { return reinterpret_cast<const QMetaObjectPrivate*>(data); } bool QMetaEnum::isFlag() const { const int offset = priv(mobj->d.data)->revision >= 8 ? 2 : 1; return mobj && mobj->d.data[handle + offset] & EnumIsFlag; } 

Solution
Le pointeur mobj est traité de manière non sécurisée: déréférencé d'abord, puis vérifié. Un classique.

Le bug a été mentionné dans l'article " Une troisième vérification de Qt 5 avec PVS-Studio ".

C #


Bogue Infer.NET

 public static void WriteAttribute(TextWriter writer, string name, object defaultValue, object value, Func<object, string> converter = null) { if ( defaultValue == null && value == null || value.Equals(defaultValue)) { return; } string stringValue = converter == null ? value.ToString() : converter(value); writer.Write($"{name}=\"{stringValue}\" "); } 

Solution
Une déréférence nulle de la variable de valeur peut se produire lors de l'évaluation de l' expression value.Equals (defaultValue) . Cela se produit lorsque les valeurs des variables sont telles que defaultValue! = Null et value == null .

Ce bogue provient de l'article " Quelles erreurs se cachent dans le code Infer.NET? "

Bogue FastReport

 public class FastString { private const int initCapacity = 32; private void Init(int iniCapacity) { sb = new StringBuilder(iniCapacity); .... } public FastString() { Init(initCapacity); } public FastString(int iniCapacity) { Init(initCapacity); } public StringBuilder StringBuilder => sb; } .... Console.WriteLine(new FastString(256).StringBuilder.Capacity); 

Quelle sera la sortie du programme dans la console? Quel est le problème avec la classe FastString ?

Solution
Le programme affichera la valeur 32. La raison en est le nom mal orthographié de la variable passée à la méthode Init dans le constructeur:

 public FastString(int iniCapacity){ Init(initCapacity); } 

Le paramètre constructeur iniCapacity ne sera pas utilisé; ce qui est passé à la place est la constante initCapacity .

Le bug a été discuté dans l'article " Les rapports les plus rapides du Far West - et une poignée de bugs ... "

Bug de Roslyn

 private SyntaxNode GetNode(SyntaxNode root) { var current = root; .... while (current.FullSpan.Contains(....)) { .... var nodeOrToken = current.ChildThatContainsPosition(....); .... current = nodeOrToken.AsNode(); } .... } public SyntaxNode AsNode() { if (_token != null) { return null; } return _nodeOrParent; } 

Solution
Déréférence nulle potentielle du courant dans l'expression current.FullSpan.Contains (....) . La variable actuelle peut se voir attribuer une valeur nulle suite à l'appel de la méthode nodeOrToken.AsNode () .

Ce bogue provient de l'article " Vérification du code source de Roslyn ".

Bug Unity

 .... staticFields = packedSnapshot.typeDescriptions .Where(t => t.staticFieldBytes != null & t.staticFieldBytes.Length > 0) .Select(t => UnpackStaticFields(t)) .ToArray() .... 

Solution
Une faute de frappe: l'opérateur & est utilisé à la place de && . Cela se traduit par l'exécution de la vérification t.staticFieldBytes.Length> 0 tout le temps, même si la variable t.staticFieldBytes est nulle , ce qui, à son tour, conduit à une déréférence nulle.

Ce bogue a été initialement montré dans l'article " Discuter des erreurs dans les composants Open-Source d'Unity3D ".

Java


Bogue IntelliJ IDEA

 private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2; // allow reasonable amount of // capitalized words } 

Pourquoi le programme calcule-t-il incorrectement le nombre de mots en majuscule?

Solution
La fonction devrait renvoyer true si le nombre de mots en majuscule est inférieur à 20%. Mais la vérification ne fonctionne pas à cause de la division entière, qui n'évalue que 0 ou 1. La fonction ne retournera false que si tous les mots sont en majuscule. Sinon, la division donnera 0 et la fonction renverra true .

Ce bug provient de l'article " PVS-Studio pour Java ".

Bogue Spotbugs

 public static String getXMLType(@WillNotClose InputStream in) throws IOException { .... String s; int count = 0; while (count < 4) { s = r.readLine(); if (s == null) { break; } Matcher m = tag.matcher(s); if (m.find()) { return m.group(1); } } throw new IOException("Didn't find xml tag"); .... } 

Quel est le problème avec la recherche de la balise xml?

Solution
La condition count <4 sera toujours vraie car le nombre de variables n'est pas incrémenté à l'intérieur de la boucle. La balise xml était censée être recherchée dans les quatre premières lignes du fichier, mais en raison de l'incrément manquant, le programme lira l'intégralité du fichier.

Comme le bug précédent, celui-ci a été décrit dans l'article " PVS-Studio for Java ".

C'est tout pour aujourd'hui. Venez nous voir lors des prochains événements - cherchez la licorne. Nous proposerons de nouveaux défis intéressants et, bien sûr, donnerons des prix. A bientôt!

Source: https://habr.com/ru/post/fr476268/


All Articles