Análisis estático: deja que tu ordenador encuentre el bug

15 octubre, 2015

Veamos qué os sugieren estas palabras: división por cero, deadlock, segmentation fault, memory leak, NullPointerException, buffer overflow, … Me imagino que os sugieren unas cuantas horas delante del debugger, muchos cafés y diversas palabras malsonantes.

Estos son algunos de los errores software más frecuentes a bajo nivel. Es decir, no estamos hablando de un error en la lógica de negocio, como puede ser calcular mal el importe de una factura. Al ser de bajo nivel, estos errores suelen tener causas muy claras, cómo haberse olvidado una línea de código (el típico free() que te dejas en la última línea de la función) o haberla escrito en un lugar inapropiado (dentro de un if-then-else en lugar de hacerlo después). Esta sencillez hace que sea factible diagnosticar dichos errores de forma automática antes de ejecutar el programa. Precisamente este el objetivo de las herramientas de análisis estático.

Diagrama de flujo del algoritmo de Euclide. Fuente: Wikipedia - Licencia: CC BY 3.0
Diagrama de flujo del algoritmo de Euclides. Fuente: Wikipedia – Licencia: CC BY 3.0

 

Una herramienta de análisis estático busca defectos en un programa a partir del código fuente sin necesidad de ejecutarlo. De aquí viene el término «estático», para diferenciarlo de métodos «dinámicos» como el testing que necesitan ejecutar el código. De hecho, el compilador ya intenta realizar algunas comprobaciones estáticas, como puede ser la concordancia de tipos. Pero un analizador estático intenta ir mucho más allá de donde llega el compilador y encontrar problemas más sofisticados.

Un analizador estático puede buscar diferentes tipos de errores, ya sean predefinidos como los anteriores o bien propiedades definidas por el usuario. Simplificando mucho, el análisis empieza construyendo el grafo que describe el flujo de ejecución dentro de una función (control flow graph o CFG) y el grafo que modela las llamadas entre diferentes funciones (call graph). A partir de aquí, el análisis es tan fácil como plantear un recorrido en un grafo.

Por ejemplo, imaginemos que buscamos el típico error «acceso después de free() » en que leemos/escribimos una posición de memoria que ya hemos liberado. El análisis es tan fácil como buscar las instrucciones free() y comprobar que en el CFG dicho puntero no vuelva a usarse sin hacer previamente un malloc(). Obviamente, como no estamos ejecutando el programa no tendremos toda la información. Así, por ejemplo, en general no sabremos: si se cumple la condición de un condicional o la condición de entrada a un bucle; si dos punteros pueden referirse a la misma posición de memoria durante la ejecución; o cuál es el método que estamos llamando (si hay varias opciones en un programa orientada a objetos.

Esto significa que, mientras en algunos casos podemos decir que un error va a producirse, en otros tendremos que limitarnos a emitir un warning diciendo que hemos hallado un error potencial. Este error podría llegar a producirse en la práctica pero también podría ser un falso negativo, un error que nunca se produce en la práctica si tenemos en cuenta las condiciones de ejecución. Por ejemplo, podría ser que el warning se produjera dentro de un condicional y la condición del if impidiera explícitamente el error.

La calidad de una herramienta de análisis estático depende de diversos factores. Los principales suelen ser: la eficiencia, la claridad de sus informes de error y una porcentaje reducido de falsos negativos. Porque, llevado al límite, una herramienta que tarda 1.000 veces más que el compilador y te avisa de un posible error en cada línea de código tampoco resultaría demasiado útil, ¿verdad? En general, el problema no suele ser la eficiencia, sino la tasa de falsos negativos.

Una ventaja de las herramientas de análisis estático es que suelen ser sencillas de usar: muchas se integran directamente en el IDE y sólo requieren pulsar un botón. Algunos ejemplos de herramientas de análisis estático son el histórico Lint (C), FindBugs (Java), PMD (Java/Javascript/…),  CodeSonar (C/C++), Klocwork (C/C++/C#/Java) o C Global Surveyor (C/C++).

¿Os parece interesante? En la próxima entrada hablamos sobre el uso de estas herramientas en la industria del software.

(Visited 145 times, 1 visits today)
Autor / Autora
Robert Clarisó Viladrosa
Comentarios
Detectives informáticos en Madrid19 junio, 2018 a las 3:34 pm

Excelentes consejos. Es importante tener este tipo de conocimientos para identificar errores sencillos y corregirlos sin que se conviertan en un dolor de cabeza.

Responder
Deja un comentario