DeepCode: vista lateral

Foto 1

No hace mucho tiempo, DeepCode, un analizador estático basado en aprendizaje automático, comenzó a admitir la validación de proyectos C y C ++. Y ahora podemos ver en la práctica cómo difieren los resultados del análisis estático clásico y el análisis estático basado en el aprendizaje automático.

Ya mencionamos DeepCode en nuestro artículo " Uso del aprendizaje automático en el análisis estático del código fuente de los programas ". Y pronto publicaron el artículo " DeepCode agrega soporte de análisis de código estático basado en AI para C y C ++ " con el anuncio de soporte para el análisis de proyectos escritos en C y C ++.

Para ver los resultados del análisis DeepCode, decidí revisar el proyecto PhysX. No tengo el objetivo aquí de presentar un análisis de los errores encontrados por PVS-Studio o DeepCode en este proyecto, pero solo quería tomar un proyecto de terceros y ver su ejemplo de cómo funcionarán los analizadores.

Ejecutar el análisis DeepCode es extremadamente simple. El análisis de los proyectos publicados en GitHub se inicia automáticamente. Aunque hay un problema, ya que todo el repositorio se verificó en una fila, aunque contenía varios proyectos, y las advertencias terminaron siendo confusas. Además, la velocidad de análisis indica que el código no está compilado y que muchos errores pueden estar ocultos del analizador.

El resultado general de DeepCode es el siguiente:

Imagen 2

Esto no refleja el número de viajes, ya que se agrupan en una advertencia, como mostraré más adelante. Por supuesto, es obvio que el análisis estático de C / C ++ en DeepCode es una innovación, y el trabajo en él acaba de comenzar, pero creo que ya es posible sacar algunas conclusiones.

Comenzando a examinar los desencadenantes de DeepCode, me encontré con la siguiente advertencia para un error encontrado por el analizador no menos de 31 veces:

Cuadro 3

Esta respuesta me llamó la atención, ya que PVS-Studio tenía un diagnóstico similar y, además, examiné dicha respuesta en otro artículo. Cuando lo conocí por primera vez, me pareció que muy pocas personas escribirían una constante matemática "a mano".

Y PVS-Studio también encontró un uso tan descuidado de constantes, pero no hubo más de 31, sino 52 ensayos de este tipo. Casi inmediatamente hubo un momento en el que el aprendizaje automático era inferior al enfoque clásico. Si el código de error al menos de alguna manera se aparta de un error muy típico de este tipo, encontrarlo se vuelve mucho más difícil. Para el análisis estático clásico, no es necesario que el error ocurra muchas, muchas veces; es suficiente que el desarrollador del diagnóstico presente un principio general para la aparición de un error.

Además, el diagnóstico PVS-Studio funciona no solo en el número Pi u otras constantes de uso frecuente, sino también en las específicas (por ejemplo, la constante Landau-Ramanujan), cuyo uso erróneo puede ser difícil de atrapar incluso con una gran base de entrenamiento debido a su uso poco frecuente. .

Dado que DeepCode trabajó estrictamente en una constante de la forma 3.1415, agregué por interés en un lugar del código a las constantes 4 decimales (3.14159263), y la operación desapareció.

Cuadro 7

PVS-Studio trabaja en varias opciones para redondear constantes.

Cuadro 8

Incluyendo que hay una respuesta a la sección modificada:

V624 Probablemente haya un error de imprenta en la constante '3.14159263'. Considere usar la constante M_PI de <math.h>. Crab.cpp 219

Y el punto aquí no es que se trata de algún tipo de error terrible / redondeo aproximado / la posibilidad de cometer un error tipográfico, sino que confirma que la capacitación en el código se limitará a este mismo código y, si algo sucede salir del patrón general o aparecer solo en casos raros, un error o deficiencia puede pasar desapercibido.

Consideremos una operación más. DeepCode emitió una advertencia sobre el siguiente código:

bool Shader::loadShaderCode(const char* fragmentShaderCode, ....)
{
  ....
  if (mFragmentShaderSource != NULL)
    free(mFragmentShaderSource);

  ....

  if (mFragmentShaderSource != NULL)
    free(mFragmentShaderSource);
  ....
}

Cuadro 10

PVS-Studio tenía más quejas sobre este código:

  1. V809 Verifying that a pointer value is not NULL is not required. The 'if (mFragmentShaderSource != 0)' check can be removed. Shader.cpp 178
  2. V809 Verifying that a pointer value is not NULL is not required. The 'if (mFragmentShaderSource != 0)' check can be removed. Shader.cpp 194
  3. V774 The 'mFragmentShaderSource' pointer was used after the memory was released. Shader.cpp 194
  4. V586 The 'free' function is called twice for deallocation of the same memory space. Inspect the first argument. Check lines: 179, 195. Shader.cpp 195

Puede pensar que la activación de DeepCode simplemente resultó ser lacónica, y PVS-Studio produjo una gran cantidad de activaciones innecesarias, pero esto no es así, y cada activación aquí tiene su propio significado.

Las dos primeras operaciones están relacionadas con la comprobación excesiva del puntero, ya que dicha comprobación no es necesaria para usar la función free () . La tercera actuación indica que no es seguro usar el puntero después de que se haya liberado. Incluso si el puntero en sí no está desreferenciado, sino que solo está marcado, sigue siendo sospechoso y a menudo indica un error tipográfico. Bueno, la última operación solo apunta al mismo problema que DeepCode descubrió.

Cuadro 11

Hubo una captura de pantalla en el artículo de DeepCode sobre el comienzo de la compatibilidad con C / C ++ , que, como nos pareció, contiene un falso positivo. La advertencia indica un posible desbordamiento del búfer, sin embargo, la memoria para el búfer se asigna teniendo en cuenta la longitud del inicio y la longitud de la línea, que se agrega adicionalmente por + 1. Por lo tanto, seguramente habrá suficiente espacio en el búfer.

El problema aquí puede ser la falta de verificación de que la memoria se haya asignado correctamente. Quizás la advertencia de DeepCode en este caso dice algo más: la primera oración de la advertencia no ayuda particularmente a comprender cuál es el error y de qué jura realmente el analizador.

En este caso, quizás se desencadene otro factor sobre el que escribimos: la dificultad de crear advertencias significativas. Las advertencias después de clasificar las respuestas de un analizador capacitado son escritas por personas o formadas a partir de comentarios sobre confirmaciones / documentación. Pero generar una buena advertencia puede ser difícil si el patrón de error se dedujo a través del aprendizaje automático y no por el desarrollador.

Me preguntaba qué hay que cambiar en este código para que desaparezca la advertencia. Pero, curiosamente, en una sección de código completamente similar en el nuevo archivo, tal advertencia no se produjo y apareció otra.

Cuadro 5

Quizás el punto es que el entorno al que estaba orientado el analizador ha desaparecido, o hice algo mal. Pero la inestabilidad de las advertencias, el hecho de que su apariencia sea impredecible, puede conducir, en mi opinión, a inconvenientes no solo para los usuarios, sino también para los desarrolladores del analizador y complicar el proceso de desarrollo.

En general, aunque la mayoría de las respuestas de DeepCode aún no son falsas, su número es tan pequeño que el resultado de su trabajo en este momento es más bien escaso que exacto. Un pequeño número de falsos positivos está acompañado por un pequeño número de positivos en principio.

Te daré algunos errores interesantes encontrados por PVS-Studio.

Fragmento N1

V773Se salió de la función sin soltar el puntero 'línea'. Una pérdida de memoria es posible. PxTkBmpLoader.cpp 166

bool BmpLoader::loadBmp(PxFileHandle f)
{
  ....
  int lineLen = ....;
  unsigned char* line = static_cast<unsigned char*>(malloc(lineLen));
  for(int i = info.Height-1; i >= 0; i--)
  {
    num = fread(line, 1, static_cast<size_t>(lineLen), f);
    if (num != static_cast<size_t>(lineLen)) { fclose(f); return false; }
    ....
  }
  free(line);
  return true;
}

Aquí, en caso de que se interrumpiera la lectura del archivo, se produce una pérdida de memoria, ya que la memoria no se libera antes de regresar de la función.

Fragmento N2

V595 El puntero 'gSphereActor' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 228, 230. SnippetContactReportCCD.cpp 228

void initScene()
{
  ....
  gSphereActor = gPhysics->createRigidDynamic(spherePose);
  gSphereActor->setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD, true);

  if (!gSphereActor)
    return;
  ....
}

El puntero de gSphereActor se desreferencia, pero inmediatamente después se verifica si hay nullptr y la función se cierra. Es decir, aquí es posible un puntero nulo, pero aún se produce la desreferenciación. Hubo 24 errores de este tipo en el proyecto PhysX.

DeepCode solo dio positivos para un tipo específico de casos (según tengo entendido), donde el puntero se inicializó inicialmente a cero, y no hubo otras asignaciones en el campo de visión. PVS-Studio no funcionó en dicho código, ya que la mayoría de las veces ese incendio será falso, porque el puntero puede obtener un valor en otra unidad de traducción (la mayoría de los incendios fueron en variables globales). Este ejemplo muestra que menos falsos positivos en el análisis estático entrenado no son necesariamente ciertos.

Cuadro 15

Cuadro 13

Aquí DeepCode, muy probablemente, por alguna razón indica la inicialización incorrecta. En cambio, la inicialización gCooking destacó la inicialización gFoundation , aunque el índice no se ilumina más.

Fragmento N3

V517 El uso del patrón 'if (A) {...} else if (A) {...}' fue detectado. Hay una probabilidad de presencia de error lógico. Líneas de verificación: 266, 268. AABox.cpp 266

bool AABox::initRT(int depthSamples, int coverageSamples)
{
  ....
  int query;
  ....
  if (....)  
  {
    ....
    if ( query < coverageSamples)
      ret = false;
    else if ( query > coverageSamples) 
      coverageSamples = query;
    ....
    if ( query < depthSamples)
      ret = false;
    else if ( query > depthSamples)
      depthSamples = query;
  }
  else {
    ....
    if ( query < depthSamples)
      ret = false;
    else if ( query < depthSamples) // <=
      depthSamples = query;
    ....
  }
  ....
}

Aquí parece que se ha introducido el error de copiar y pegar. El analizador detectó la misma condición en if y else . Desde el if-else anterior, puede ver que el ">" en if y el "<" en else están generalmente marcados . Aunque el patrón parece extremadamente simple, no encontré tal respuesta entre las advertencias de DeepCode, aunque parecería ser un patrón muy simple de detectar.

Conclusión

Por supuesto, DeepCode acaba de anunciar soporte para C / C ++ y es parte de su producto que acaba de comenzar a desarrollarse. También puede probarlo en sus proyectos, en su servicio se ha implementado una ventana de comentarios conveniente en caso de que haya recibido una advertencia falsa.

Sin embargo, ahora podemos ver las deficiencias que acompañan al aprendizaje automático en el análisis estático. Por lo tanto, somos escépticos con respecto a la idea de agregar aprendizaje automático a analizadores estáticos, ya que la ganancia es dudosa: es necesario exactamente el mismo soporte de analizador, escribir o editar documentación y trabajar en los diagnósticos. Además, los problemas encontrados durante el desarrollo requieren soluciones más complejas y habilidades específicas de los desarrolladores, lo que reduce el número de candidatos potenciales al expandir el equipo. De hecho, el desarrollador en este caso no solo debe ser un especialista en el lenguaje analizado, sino también poseer conocimientos en el campo del aprendizaje automático.


Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Victoria Khanieva. Código profundo: perspectiva exterior .

All Articles