
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.:
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 chromestatic 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éponsePeut-ê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;
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éponseLa 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);
La réponseDans 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éponseLe 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éponseLa 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éponseLe 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éponseDans 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éponse32 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éponseL'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éponseTypo 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;
Il est proposé de déterminer pourquoi le nombre de mots avec des majuscules sera mal calculé.
La réponseLa 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éponseLa 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 .