
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:
¿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 cromostatic 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ónEste 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;
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ónLo 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);
SoluciónEl 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ónEl 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ónLa 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; }
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ónLa 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ónEl 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ónDesreferencia 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ónUn 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;
¿Por qué el programa calcula incorrectamente el número de palabras en mayúscula?
SoluciónSe 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ónLa 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