Respuestas a las tareas del stand de PVS-Studio en las conferencias 2018-2019

Imagen 2


Hola A pesar de que la temporada de conferencias de 2019 todavía está en pleno apogeo, nos gustaría discutir las tareas que se ofrecieron a los visitantes de nuestro stand antes. Comenzamos el otoño de 2019 con un nuevo conjunto de tareas, por lo que ya es posible publicar la solución de viejos problemas para 2018, así como la primera mitad de 2019. Además, muchos de ellos fueron tomados de artículos publicados anteriormente, y los folletos de tareas contenían un enlace o un código QR con información sobre el artículo.

Si asistió a conferencias donde estuvimos con un stand, probablemente vio o incluso resolvió algunos de nuestros problemas. Estos son siempre fragmentos de código de proyectos reales de código abierto en los lenguajes de programación C, C ++, C # o Java. El código contiene errores que sugerimos que los visitantes busquen. Para la solución (o simplemente una discusión del error) entregamos premios: estados en el escritorio, baratijas, etc.

Cuadro 4

Quieres lo mismo Ven a nuestro stand en las próximas conferencias.

Por cierto, los artículos " ¡Tiempo de conferencia! Resumen de los resultados de 2018 " y " Conferencias. Resultados provisionales para el primer semestre de 2019 " contienen una descripción de nuestra actividad en las conferencias de este y el año pasado.

Entonces, comencemos el juego "Encuentra un error en el código". Primero, considere las tareas anteriores para 2018, utilizaremos la agrupación por lenguajes de programación.

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

La respuesta
Quizás la tarea más "larga" de nuestro set. Sugerimos que los visitantes de nuestro stand encuentren este error en el proyecto Chromium a lo largo de 2018. También ha aparecido en varios informes.

 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 el valor de retorno. En lugar de time.day, el programador especificó erróneamente dos veces time.month . Esto ha llevado al hecho de que la verdad siempre será devuelta. El error se describe en detalle en el artículo " 31 de febrero ". Un gran ejemplo de un error que no es fácil de detectar en la revisión de código. También es una buena ilustración del uso de la tecnología de 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) { .... } 

La respuesta
Lo primero que debe tener en cuenta es que el último argumento de la función VertInfluencedByActiveBone () tiene un valor predeterminado y puede no especificarse. Ahora eche un vistazo al bloque if . Simplificado, puede reescribirse de la siguiente manera:
 if (!foo(....) && !foo(....) && !foo(....) & arg) 

Ahora está claro que se ha cometido un error. Debido a un error tipográfico, la tercera llamada a la función VertInfluencedByActiveBone () se realiza con tres argumentos en lugar de cuatro, y el operador & se aplica al resultado de esta llamada (AND AND a la izquierda es el resultado de la función VertInfluencedByActiveBone () del tipo bool , a la derecha está la variable entera BoneIndex3 ). El código está compilado. Versión corregida del código (coma agregada, corchete de cierre movido a otro lugar):

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

El error que se describió originalmente en el artículo "La tan esperada verificación de Unreal Engine 4 ". El artículo se titula "El más hermoso de los errores encontrados" en el artículo. Estoy de acuerdo con esta afirmación.

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

La respuesta
En la condición del bloque if , la prioridad de las operaciones es confusa. El código no funciona como el programador pretendía:

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

La variable idx recibirá los valores 0 o 1, y el cumplimiento de la condición dependerá de este valor, que es un error. Versión corregida del código:

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

Error del artículo " Verificamos los códigos fuente de Android usando PVS-Studio, o nadie es perfecto ".

Y una tarea más para encontrar un error no trivial en 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 respuesta
El problema está en la expresión (d >> 24) + 1 .

El programador quería verificar que los 8 bits de orden superior de la variable d contienen unidades, pero no todos los bits a la vez. En otras palabras, el programador quería verificar que cualquier valor que no sea 0x00 y 0xFF esté en el byte alto. Primero, verificó que los bits más significativos son distintos de cero escribiendo una expresión (d >> 24). Luego, desplaza los ocho bits altos al byte bajo. Al mismo tiempo, calcula que el bit de signo más significativo está duplicado en todos los demás bits. Es decir, si la variable d es igual a 0b11111111'00000000'00000000'00000000, entonces después del turno obtenemos el valor 0b11111111'11111111'111111111111111111. Agregando 1 al valor 0xFFFFFFFF de tipo int , el programador planea obtener 0 (-1 + 1 = 0). Por lo tanto, con la expresión ((d >> 24) +1), comprueba que no todos los ocho bits altos son iguales a 1.

Sin embargo, cuando se cambia, el bit más significativo no necesariamente está "manchado". El estándar dice: “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 de este código depende de la arquitectura del microprocesador y la implementación del compilador. Después del cambio, los ceros pueden aparecer en los bits más significativos, y luego el resultado de la expresión ((d >> 24) +1) siempre será diferente de 0, es decir, siempre será un valor verdadero.

De hecho, una tarea difícil. Este error, como el anterior, se describió en el artículo " Verificamos los códigos fuente de Android usando PVS-Studio, o nadie es perfecto ".

2019


C ++


"GCC tiene la culpa"

 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 afirma que este código funciona con un error debido a la falla del compilador GCC 8. ¿Es así?

La respuesta
La función devuelve valores negativos. La razón es que el compilador no genera código para el operador AND (&) bit a bit. El error se debe a un comportamiento indefinido. El compilador ve que se considera una cierta cantidad en la variable r . En este caso, solo se agregan números positivos. No deberían producirse desbordamientos de la variable r ; de lo contrario, este es un comportamiento indefinido que el compilador no debe considerar y tener en cuenta de ninguna manera. Entonces, el compilador cree que dado que el valor en la variable r después del final del ciclo no puede ser negativo, la operación r & 0x7fffffff para restablecer el bit de signo es superflua y el compilador simplemente devuelve el valor de la variable r de la función.

Error del artículo " PVS-Studio 6.26 Release ".

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

La respuesta
El puntero mobj no es seguro. Primero, se desreferencia y luego se verifica. Clásico

El error se describió en el artículo " Tercera prueba 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}\" "); } 

La respuesta
En la expresión value.Equals (defaultValue) , el acceso es posible a través de la referencia de valor nulo. Esto sucederá con dichos valores variables, cuando defaultValue! = Null , y value == null .

El error del artículo " ¿Qué errores están ocultos 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); 

¿Qué se mostrará en la consola? ¿Qué hay de malo con la clase FastString ?

La respuesta
Se mostrará en la consola 32. El motivo es un error tipográfico en el nombre de la variable pasado al método Init en el constructor:

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

El parámetro constructor iniCapacity no se utilizará. En cambio, la constante initCapacity se pasa al método Init .

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

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

La respuesta
El acceso es posible a través de la corriente de referencia nula en la expresión current.FullSpan.Contains (....) . La variable actual puede obtener un valor nulo como resultado de ejecutar el método nodeOrToken.AsNode () .

Error del artículo " Comprobando el 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() .... 

La respuesta
Error tipográfico permitido: en lugar del operador && , se utilizó el operador & . Esto lleva al hecho de que la verificación t.staticFieldBytes.Length> 0 siempre se realiza, incluso si la variable nula es t.staticFieldBytes , que, a su vez, dará como resultado el acceso a través de una referencia nula.

Este error se mostró por primera vez en el artículo " Análisis de errores en componentes abiertos 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 } 

Se propone determinar por qué el número de palabras con mayúsculas se calculará incorrectamente.

La respuesta
La función debe devolver verdadero si menos del 20% de las palabras comienzan con una letra mayúscula. Pero la comprobación no funciona, ya que se produce una división entera, cuyo resultado serán solo los valores 0 o 1. La función devolverá un valor falso solo si todas las palabras comienzan con una letra mayúscula. En otros casos, la división producirá 0 y la función devolverá verdadero.

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

Se propone determinar cuál es el error de búsqueda de etiquetas xml.

La respuesta
La condición de conteo <4 siempre se cumplirá, ya que el conteo variable no se incrementa dentro del ciclo. Se supuso que la búsqueda de la etiqueta xml debería llevarse a cabo solo en las primeras cuatro líneas del archivo, pero debido a un error, se leerá todo el archivo.

Este error, como el anterior, se describió en el artículo " PVS-Studio para Java ".

Eso es todo Los esperamos en las próximas conferencias. Busca un puesto de unicornio. Repartiremos nuevos acertijos interesantes y, por supuesto, premios. Hasta pronto!



Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Sergey Khrenov. Soluciones para los desafíos de búsqueda de errores ofrecidos por el equipo de PVS-Studio en las conferencias en 2018-2019 .

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


All Articles