Soluciones para los desafíos de búsqueda de errores ofrecidos por el equipo de PVS-Studio en las conferencias en 2018-2019

Imagen 2


Hola Aunque la temporada de conferencias de 2019 aún no ha terminado, nos gustaría hablar sobre los desafíos de búsqueda de errores que ofrecimos a los visitantes en nuestro stand durante las últimas conferencias. Comenzando con el otoño de 2019, hemos presentado un nuevo conjunto de desafíos, por lo que ahora podemos revelar las soluciones a las tareas anteriores de 2018 y la primera mitad de 2019; después de todo, muchas de ellas provienen de artículos publicados anteriormente, y teníamos un enlace o código QR con información sobre los respectivos artículos impresos en nuestros folletos de desafío.

Si asistió a eventos donde participamos con un stand, probablemente vio o incluso trató de resolver algunos de nuestros desafíos. Estos son fragmentos de código de proyectos reales de código abierto escritos en C, C ++, C # o Java. Cada fragmento contiene un error, y los invitados tienen el desafío de tratar de encontrarlo. Una solución exitosa (o simplemente la participación en la discusión del error) se recompensa con un premio: un estado de escritorio en espiral, un llavero y similares:

Cuadro 4

¿Quieres un poco también? Entonces bienvenido a pasar por nuestro stand en los próximos eventos.

Por cierto, en los artículos " ¡Tiempo de conferencia! Resumen de 2018 " y " Conferencias. Subtotales para el primer semestre de 2019 ", compartimos nuestra experiencia de participar en los eventos celebrados a principios de este año y en 2018.

Bien, juguemos nuestro juego "Encuentra el error". Primero, veremos los desafíos anteriores de 2018, agrupados por idioma.

2018


C ++


Insecto de cromo

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

Solución
Este error encontrado en Chromium fue probablemente el desafío más "de larga duración"; Lo ofrecimos durante todo el año 2018 y también lo incluimos en varias presentaciones.

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

El cuerpo del último bloque If-else contiene errores tipográficos en las declaraciones de retorno: time.month se escribió accidentalmente por segunda vez en lugar de time.day . Este error hace que la función vuelva verdadera todo el tiempo. El error se trata en detalle en el artículo " 31 de febrero " y es un buen ejemplo de un error que no es fácilmente detectado por la revisión del código. Este caso también es una buena demostración de cómo usamos el análisis de flujo de datos.

Error irreal del motor

 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) { .... } 

Solución
Lo primero que debe notar aquí es que el último argumento de la función VertInfluencedByActiveBone () tiene un valor predeterminado y no es necesario especificarlo. Ahora mire el bloque if en forma simplificada:

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

El error ahora es claramente visible. Debido al error tipográfico, la tercera llamada de la función VertInfluencedByActiveBone () se realiza con tres argumentos en lugar de cuatro, con el valor de retorno que luego participa en una operación & (bitwise AND: el operando izquierdo es el valor del tipo bool devuelto por VertInfluencedByActiveBone ( ) , y el operando correcto es la variable entera BoneIndex3 ). El código aún es compilable. Esta es la versión fija (se agrega una coma, el paréntesis de cierre se mueve al final de la expresión):

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

Este error se mencionó originalmente en el artículo " Una comprobación largamente esperada de Unreal Engine 4 ", donde se tituló "el error más agradable", con el que estoy totalmente de acuerdo.

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

Solución
El programador tenía suposiciones erróneas sobre la precedencia de las operaciones en la condición del bloque if . Este código no funciona como se esperaba:

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

A la variable idx se le asignará el valor 0 o 1, y si la condición es verdadera o falsa dependerá de este valor, lo cual es un error. Esta es la versión fija:

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

Este error fue mencionado en el artículo " Verificamos el código fuente de Android por PVS-Studio, o Nothing is Perfect ".

Aquí hay otro desafío no trivial con un error de 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)); } 

Solución
El problema está en la expresión (d >> 24) + 1 .

El programador quería comprobar que los 8 bits más significativos de la variable d están establecidos en 1, pero no todos a la vez. En otras palabras, querían comprobar que el byte más significativo almacena cualquier valor, excepto 0x00 y 0xFF. Primero, el programador verifica que los bits más significativos sean nulos utilizando la expresión (d >> 24). Luego cambian los ocho bits más significativos al byte menos significativo, esperando que el bit de signo más significativo se duplique en todos los demás bits. Es decir, si la variable d tiene el valor 0b11111111'00000000'00000000'00000000, se convertirá en 0b11111111'11111111'11111111'11111111 después del turno. Al agregar 1 al valor int 0xFFFFFFFF, el programador espera obtener 0 (-1 + 1 = 0). Por lo tanto, la expresión ((d >> 24) +1) se usa para verificar que no todos los ocho bits más significativos estén establecidos en 1.

Sin embargo, el bit de signo más significativo no necesariamente se "extiende" cuando se desplaza. Esto es lo que dice el estándar: “El valor de E1 >> E2 es E1 posiciones de bit E2 desplazadas a la derecha. Si E1 tiene un tipo sin signo o si E1 tiene un tipo con signo y un valor no negativo, el valor del resultado es la parte integral del cociente de E1 / 2 ^ E2. Si E1 tiene un tipo con signo y un valor negativo, el valor resultante está definido por la implementación ".

Entonces, este es un ejemplo de comportamiento definido por la implementación. El funcionamiento exacto de este código depende de la arquitectura de la CPU y la implementación del compilador. Los bits más significativos pueden terminar como ceros después del cambio, y la expresión ((d >> 24) +1) siempre devolvería un valor distinto de 0, es decir, un valor siempre verdadero.

Eso, de hecho, es un desafío no trivial. Al igual que el error anterior, este se discutió originalmente en el artículo " Verificamos el código fuente de Android por PVS-Studio, o Nothing is Perfect ".

2019


C ++


"Todo es culpa 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; } 

El programador culpa al compilador GCC 8 por el error. ¿Es realmente culpa de GCC?

Solución
La función devuelve valores negativos ya que el compilador no genera código para AND (&) bit a bit. El error tiene que ver con un comportamiento indefinido. El compilador nota que la variable r se usa para calcular y almacenar una suma, con solo valores positivos involucrados. La variable r no debería desbordarse porque sería un comportamiento indefinido, que el compilador no tiene en cuenta. Por lo tanto, concluye que dado que r no puede tener un valor negativo al final del ciclo, la operación r & 0x7fffffff , que borra el bit de signo, es innecesaria, por lo que simplemente le dice a la función que devuelva el valor de r .

Este error se describió en el artículo " PVS-Studio 6.26 liberado ".

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

Solución
El puntero mobj se maneja de forma insegura: primero se desreferencia, luego se verifica. Un clásico

El error fue mencionado en el artículo " Una tercera comprobación de Qt 5 con PVS-Studio ".

C #


Error de 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}\" "); } 

Solución
La desreferencia nula de la variable de valor puede ocurrir al evaluar la expresión value.Equals (defaultValue) . Esto sucederá cuando los valores de las variables sean tales que defaultValue! = Null y value == null .

Este error es del artículo " ¿Qué errores acechan en el código Infer.NET? "

FastReport bug

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

¿Cuál será la salida del programa en la consola? ¿Qué hay de malo con la clase FastString ?

Solución
El programa generará el valor 32. El motivo es el nombre mal escrito de la variable pasada al método Init en el constructor:

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

El parámetro constructor iniCapacity no se usará; lo que se pasa es la constante initCapacity .

El error se discutió en el artículo " Los informes más rápidos en el salvaje oeste - y un puñado de errores ... "

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

Solución
Desreferencia potencial nula de la corriente en la expresión current.FullSpan.Contains (....) . A la variable actual se le puede asignar un valor nulo como resultado de invocar el método nodeOrToken.AsNode () .

Este error es del artículo " Comprobación del código fuente de Roslyn ".

Error de unidad

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

Solución
Un error tipográfico: se utiliza el operador & en lugar de && . Esto resulta en la ejecución de la verificación t.staticFieldBytes.Length> 0 todo el tiempo, incluso si la variable t.staticFieldBytes es nula , lo que, a su vez, conduce a una desreferencia nula.

Este error se mostró originalmente en el artículo " Discusión de errores en los componentes de código abierto de Unity3D ".

Java


Error de 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 } 

¿Por qué el programa calcula incorrectamente el número de palabras en mayúscula?

Solución
Se espera que la función devuelva verdadero si el número de palabras en mayúscula es inferior al 20%. Pero la comprobación no funciona debido a la división entera, que se evalúa solo a 0 o 1. La función devolverá falso solo si todas las palabras están en mayúscula. De lo contrario, la división dará como resultado 0 y la función devolverá verdadero .

Este error es del artículo " PVS-Studio para Java ".

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

¿Qué tiene de malo la búsqueda de la etiqueta xml?

Solución
La condición de conteo <4 siempre será verdadera ya que el conteo variable no se incrementa dentro del ciclo. La etiqueta xml estaba destinada a buscarse en las primeras cuatro líneas del archivo, pero debido a la falta de incremento, el programa leerá todo el archivo.

Al igual que el error anterior, este se describió en el artículo " PVS-Studio para Java ".

Eso es todo por hoy. Ven a vernos en los próximos eventos: busca el unicornio. Ofreceremos nuevos desafíos interesantes y, por supuesto, entregaremos premios. Nos vemos

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


All Articles