Réponses aux tâches du stand PVS-Studio lors des conférences 2018-2019

Image 2


Salut Malgré le fait que la saison des conférences de 2019 bat son plein, nous aimerions discuter des tâches qui ont été proposées aux visiteurs de notre stand plus tôt. Nous avons commencé l'automne 2019 avec un nouvel ensemble de tâches, il est donc déjà possible de publier la solution des anciens problèmes pour 2018, ainsi que le premier semestre de 2019. De plus, beaucoup d'entre eux ont été extraits d'articles publiés précédemment, et les brochures de tâches contenaient un lien ou un code QR avec des informations sur l'article.

Si vous avez assisté à des conférences où nous étions avec un stand, vous avez probablement vu ou même résolu certains de nos problèmes. Ce sont toujours des extraits de code de vrais projets open source dans les langages de programmation C, C ++, C # ou Java. Le code contient des erreurs que nous suggérons aux visiteurs de rechercher. Pour la solution (ou juste une discussion de l'erreur), nous remettons des prix - statuts sur le bureau, bibelots, etc.:

Image 4

Voulez-vous la même chose? Venez à notre stand lors des prochaines conférences.

Soit dit en passant, les articles « Temps de conférence! Résumé des résultats de 2018 » et « Conférences. Résultats intermédiaires pour le premier semestre de 2019 » contiennent une description de notre activité lors des conférences de cette année et de l'année dernière.

Commençons donc le jeu "Trouver une erreur dans le code". Tout d'abord, considérons les tâches plus anciennes pour 2018, nous utiliserons le regroupement par langages de programmation.

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]; } } 

La réponse
Peut-être la tâche la plus «longue» de notre set. Nous avons suggéré que cette erreur dans le projet Chromium soit trouvée par les visiteurs de notre stand tout au long de 2018. Il a également été présenté dans plusieurs rapports.

 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 la valeur de retour. Au lieu de time.day, le programmeur a spécifié par erreur time.month deux fois. Cela a conduit au fait que true sera toujours retourné. L'erreur est décrite en détail dans l'article « 31 février ». Un excellent exemple d'un bug qui n'est pas facile à repérer lors de la révision du code. C'est également une bonne illustration de l'utilisation de la technologie d'analyse des 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) { .... } 

La réponse
La première chose à noter est que le dernier argument de la fonction VertInfluencedByActiveBone () a une valeur par défaut et peut ne pas être spécifié. Jetez maintenant un œil au bloc if . Simplifié, il peut être réécrit comme suit:
 if (!foo(....) && !foo(....) && !foo(....) & arg) 

Maintenant, il est clair qu’une erreur a été commise. En raison d'une faute de frappe, le troisième appel à la fonction VertInfluencedByActiveBone () est effectué avec trois arguments au lieu de quatre, et l'opérateur & est appliqué au résultat de cet appel (bit à bit ET, à gauche est le résultat de la fonction VertInfluencedByActiveBone () de type bool , à droite est la variable entière BoneIndex3 ). Le code est compilé. Version corrigée du code (virgule ajoutée, crochet de fermeture déplacé vers un autre endroit):

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

L'erreur qui a été initialement décrite dans l'article "La vérification tant attendue de Unreal Engine 4 ". L'article est intitulé «La plus belle des erreurs trouvées» dans l'article. Je suis d'accord avec cette affirmation.

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'; .... } .... } 

La réponse
Dans l'état du bloc if , la priorité des opérations est confuse. Le code ne fonctionne pas comme prévu par le programmeur:

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

La variable idx recevra les valeurs 0 ou 1, et la réalisation de la condition dépendra de cette valeur, ce qui est une erreur. Version corrigée du code:

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

Erreur de l'article "Nous avons vérifié les codes source Android à l'aide de PVS-Studio, ou personne n'est parfait ."

Et encore une tâche pour trouver une erreur non triviale dans 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)); } 

La réponse
Le problème est dans l'expression (d >> 24) + 1 .

Le programmeur voulait vérifier que les 8 bits de poids fort de la variable d contiennent des unités, mais pas tous les bits à la fois. En d'autres termes, le programmeur voulait vérifier que toute valeur autre que 0x00 et 0xFF se trouve dans l'octet de poids fort. Tout d'abord, il a vérifié que les bits les plus significatifs sont non nuls en écrivant une expression (d >> 24). Il décale ensuite les huit bits de poids fort vers l'octet de poids faible. En même temps, il calcule que le bit de signe le plus significatif est dupliqué dans tous les autres bits. Autrement dit, si la variable d est égale à 0b11111111'00000000'00000000'00000000, après le décalage, nous obtenons la valeur 0b11111111'11111111'11111111111111111111. En ajoutant 1 à la valeur 0xFFFFFFFF de type int , le programmeur prévoit d'obtenir 0 (-1 + 1 = 0). Ainsi, avec l'expression ((d >> 24) +1), il vérifie que tous les huit bits supérieurs ne sont pas égaux à 1.

Cependant, lors du décalage, le bit le plus significatif n'est pas nécessairement «taché». La norme dit: «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 de ce code dépend de l'architecture du microprocesseur et de l'implémentation du compilateur. Après le décalage, des zéros peuvent bien apparaître dans les bits les plus significatifs, puis le résultat de l'expression ((d >> 24) +1) sera toujours différent de 0, c'est-à-dire que ce sera toujours une vraie valeur.

En effet, une tâche difficile. Cette erreur, comme la précédente, a été décrite dans l'article "Nous avons vérifié les codes source Android à l'aide de PVS-Studio, ou personne n'est parfait ."

2019


C ++


«Le CCG est à blâmer»

 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 prétend que ce code fonctionne avec une erreur due à la faute du compilateur GCC 8. Est-ce le cas?

La réponse
La fonction renvoie des valeurs négatives. La raison en est que le compilateur ne génère pas de code pour l'opérateur AND au niveau du bit (&). L'erreur est due à un comportement non défini. Le compilateur voit qu'une certaine quantité est considérée dans la variable r . Dans ce cas, seuls les nombres positifs sont ajoutés. Les débordements de la variable r ne doivent pas se produire, sinon c'est un comportement indéfini que le compilateur ne doit en aucun cas considérer et prendre en compte. Ainsi, le compilateur estime que puisque la valeur de la variable r après la fin du cycle ne peut pas être négative, l'opération r & 0x7fffffff pour réinitialiser le bit de signe est superflue et le compilateur renvoie simplement la valeur de la variable r à partir de la fonction.

Erreur de l'article " PVS-Studio 6.26 Release ".

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; } 

La réponse
Le pointeur mobj n'est pas sécurisé. Tout d'abord, il est déréférencé, puis vérifié. Classique

L'erreur a été décrite dans l'article " Troisième test 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}\" "); } 

La réponse
Dans l' expression value.Equals (defaultValue) , l'accès est possible via la référence null de la valeur . Cela se produira avec de telles valeurs de variable, lorsque defaultValue! = Null et value == null .

L'erreur de l'article « Quelles erreurs sont masquées 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); 

Qu'est-ce qui sera affiché sur la console? Quel est le problème avec la classe FastString ?

La réponse
32 sera affiché sur la console. La raison en est une faute de frappe dans le nom de variable passé à la méthode Init dans le constructeur:

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

Le paramètre constructeur iniCapacity ne sera pas utilisé. Au lieu de cela, la constante initCapacity est passée à la méthode Init .

L'erreur a été décrite dans l'article " Les rapports les plus rapides du Far West. Et une poignée de bugs en plus ... "

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; } 

La réponse
L'accès est possible via le courant de référence nul dans l'expression current.FullSpan.Contains (....) . La variable actuelle peut obtenir une valeur nulle à la suite de l'exécution de la méthode nodeOrToken.AsNode () .

Erreur 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() .... 

La réponse
Typo permis: au lieu de l'opérateur && , l'opérateur & a été utilisé. Cela conduit au fait que la vérification t.staticFieldBytes.Length> 0 est toujours effectuée, même si la variable nulle est t.staticFieldBytes , ce qui, à son tour, entraînera un accès via une référence nulle.

Cette erreur a d'abord été affichée dans l'article " Analyse des erreurs dans les composants Open 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 } 

Il est proposé de déterminer pourquoi le nombre de mots avec des majuscules sera mal calculé.

La réponse
La fonction doit renvoyer true si moins de 20% des mots commencent par une majuscule. Mais la vérification ne fonctionne pas, car une division entière se produit, dont le résultat ne sera que les valeurs 0 ou 1. La fonction ne renverra une valeur fausse que si tous les mots commencent par une majuscule. Dans d'autres cas, la division produira 0 et la fonction renverra true.

Erreur 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"); .... } 

Il est proposé de déterminer quelle est l'erreur de recherche de balise xml.

La réponse
La condition count <4 sera toujours remplie, car le nombre de variables n'est pas incrémenté à l'intérieur de la boucle. Il a été supposé que la recherche de la balise xml ne devrait être effectuée que dans les quatre premières lignes du fichier, mais en raison d'une erreur, le fichier entier sera lu.

Cette erreur, comme la précédente, a été décrite dans l'article " PVS-Studio for Java ".

C’est tout. Nous vous attendons lors des prochaines conférences. Recherchez un support de licorne. Nous distribuerons de nouveaux puzzles intéressants et, bien sûr, des prix. A très bientôt!



Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Sergey Khrenov. Solutions aux défis de détection de bugs proposés par l'équipe PVS-Studio lors de conférences en 2018-2019 .

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


All Articles