En informática , la interpretación abstracta es una teoría de aproximación sólida de la semántica de los programas informáticos , basada en funciones monótonas sobre conjuntos ordenados , especialmente retículos . Puede considerarse como una ejecución parcial de un programa informático que obtiene información sobre su semántica (por ejemplo, flujo de control , flujo de datos ) sin realizar todos los cálculos .
Su principal aplicación concreta es el análisis estático formal , la extracción automática de información sobre las posibles ejecuciones de programas informáticos; tales análisis tienen dos usos principales:
La interpretación abstracta fue formalizada por la pareja de científicos informáticos franceses Patrick Cousot y Radhia Cousot a finales de la década de 1970. [1] [2]
Esta sección ilustra la interpretación abstracta mediante ejemplos del mundo real, no informáticos.
Pensemos en las personas que se encuentran en una sala de conferencias. Supongamos que cada persona tiene un identificador único, como un número de seguridad social en los Estados Unidos. Para demostrar que alguien no está presente, todo lo que hay que hacer es comprobar si su número de seguridad social no está en la lista. Como dos personas diferentes no pueden tener el mismo número, es posible demostrar o refutar la presencia de un participante simplemente buscando su número.
Sin embargo, es posible que sólo se hayan registrado los nombres de los asistentes. Si no se encuentra el nombre de una persona en la lista, podemos concluir con seguridad que esa persona no estaba presente; pero si está, no podemos concluir definitivamente sin más indagaciones, debido a la posibilidad de que haya homónimos (por ejemplo, dos personas llamadas John Smith). Tenga en cuenta que esta información imprecisa seguirá siendo adecuada para la mayoría de los propósitos, porque los homónimos son poco frecuentes en la práctica. Sin embargo, con todo rigor, no podemos decir con certeza que alguien estaba presente en la sala; todo lo que podemos decir es que posiblemente estaba aquí. Si la persona que estamos buscando es un criminal, emitiremos una alarma ; pero, por supuesto, existe la posibilidad de emitir una falsa alarma . Fenómenos similares ocurrirán en el análisis de programas.
Si sólo nos interesa alguna información específica, por ejemplo, "¿había una persona mayor de edad en la sala?", no es necesario llevar una lista de todos los nombres y fechas de nacimiento. Podemos limitarnos con seguridad y sin pérdida de precisión a llevar una lista de las edades de los participantes. Si esto ya es demasiado para manejar, podemos llevar sólo la edad de la persona más joven y de la más mayor, . Si la pregunta es sobre una edad estrictamente inferior o estrictamente superior a , entonces podemos responder con seguridad que no había ningún participante de ese tipo presente. De lo contrario, sólo podemos decir que no lo sabemos.
En el caso de la informática, la información concreta y precisa no es, en general, computable en un tiempo y una memoria finitos (véase el teorema de Rice y el problema de la detención ). La abstracción se utiliza para permitir respuestas generalizadas a las preguntas (por ejemplo, responder "tal vez" a una pregunta de sí/no, es decir, "sí o no", cuando nosotros (un algoritmo de interpretación abstracta) no podemos calcular la respuesta precisa con certeza); esto simplifica los problemas, haciéndolos susceptibles de soluciones automáticas. Un requisito crucial es agregar suficiente vaguedad para que los problemas sean manejables y al mismo tiempo se conserve la precisión suficiente para responder las preguntas importantes (como "¿podría fallar el programa?").
Dado un lenguaje de programación o de especificación, la interpretación abstracta consiste en dar varias semánticas vinculadas por relaciones de abstracción. Una semántica es una caracterización matemática de un posible comportamiento del programa. La semántica más precisa, que describe muy de cerca la ejecución real del programa, se llama semántica concreta . Por ejemplo, la semántica concreta de un lenguaje de programación imperativo puede asociar a cada programa el conjunto de trazas de ejecución que puede producir – una traza de ejecución es una secuencia de posibles estados consecutivos de la ejecución del programa; un estado consiste típicamente en el valor del contador del programa y las ubicaciones de memoria (globales, pila y montón). Luego se derivan semánticas más abstractas; por ejemplo, uno puede considerar solo el conjunto de estados alcanzables en las ejecuciones (lo que equivale a considerar los últimos estados en trazas finitas).
El objetivo del análisis estático es derivar una interpretación semántica computable en algún momento. Por ejemplo, se puede optar por representar el estado de un programa que manipula variables enteras olvidando los valores reales de las variables y solo conservando sus signos (+, − o 0). Para algunas operaciones elementales, como la multiplicación , una abstracción de este tipo no pierde precisión: para obtener el signo de un producto, es suficiente conocer el signo de los operandos. Para algunas otras operaciones, la abstracción puede perder precisión: por ejemplo, es imposible conocer el signo de una suma cuyos operandos son respectivamente positivo y negativo.
A veces es necesaria una pérdida de precisión para que la semántica sea decidible (véase el teorema de Rice y el problema de la detención ). En general, hay que llegar a un compromiso entre la precisión del análisis y su decidibilidad ( computabilidad ) o manejabilidad ( costo computacional ).
En la práctica, las abstracciones que se definen se adaptan tanto a las propiedades del programa que se desea analizar como al conjunto de programas de destino. El primer análisis automatizado a gran escala de programas informáticos con interpretación abstracta fue motivado por el accidente que provocó la destrucción del primer vuelo del cohete Ariane 5 en 1996. [3]
Sea un conjunto ordenado , llamado conjunto concreto , y sea otro conjunto ordenado, llamado conjunto abstracto . Estos dos conjuntos están relacionados entre sí mediante la definición de funciones totales que asignan elementos de uno a otro.
Una función se denomina función de abstracción si asigna un elemento del conjunto concreto a un elemento del conjunto abstracto . Es decir, el elemento in es la abstracción de in .
Una función se denomina función de concretización si asigna un elemento del conjunto abstracto a un elemento del conjunto concreto . Es decir, el elemento in es una concretización de in .
Sean , , , y conjuntos ordenados. La semántica concreta es una función monótona de a . Se dice que una función de a es una abstracción válida de si, para todo en , tenemos .
La semántica de los programas se describe generalmente utilizando puntos fijos en presencia de bucles o procedimientos recursivos. Supongamos que es una red completa y sea una función monótona de en . Entonces, cualquier tal que sea una abstracción del menor punto fijo de , que existe, de acuerdo con el teorema de Knaster-Tarski .
La dificultad ahora es obtener tal . Si es de altura finita, o al menos verifica la condición de cadena ascendente (todas las secuencias ascendentes son en última instancia estacionarias), entonces tal puede obtenerse como el límite estacionario de la secuencia ascendente definida por inducción de la siguiente manera: (el menor elemento de ) y .
En otros casos, todavía es posible obtenerlo mediante un operador de ampliación (de pares) , [4] definido como un operador binario que satisface las siguientes condiciones:
En algunos casos, es posible definir abstracciones utilizando conexiones de Galois donde es de a y es de a . Esto supone la existencia de mejores abstracciones, lo que no es necesariamente el caso. Por ejemplo, si abstraemos conjuntos de pares de números reales encerrando poliedros convexos , no existe una abstracción óptima para el disco definido por .
Se puede asignar a cada variable disponible en un punto dado del programa un intervalo . Un estado que asigne el valor a la variable será una concretización de estos intervalos si, para todo , tenemos . A partir de los intervalos y para las variables y , respectivamente, se pueden obtener fácilmente intervalos para (a saber, ) y para (a saber, ); tenga en cuenta que se trata de abstracciones exactas , ya que el conjunto de resultados posibles para, digamos, , es precisamente el intervalo . Se pueden derivar fórmulas más complejas para la multiplicación, división, etc., lo que produce la denominada aritmética de intervalos . [5]
Consideremos ahora el siguiente programa muy simple:
y = x;z = x - y;
Con tipos aritméticos razonables, el resultado paraeldebería ser cero. Pero si hacemos aritmética de intervalos a partir deincógnitaen [0, 1], se obtieneelen [−1, +1]. Si bien cada una de las operaciones tomadas individualmente fue abstraída con exactitud, su composición no lo es.
El problema es evidente: no hicimos un seguimiento de la relación de igualdad entreincógnitayy; en realidad, este dominio de intervalos no tiene en cuenta ninguna relación entre variables y, por lo tanto, es un dominio no relacional . Los dominios no relacionales tienden a ser rápidos y sencillos de implementar, pero imprecisos.
Algunos ejemplos de dominios abstractos numéricos relacionales son:
y combinaciones de los mismos (como el producto reducido, [2] cf. imagen de la derecha).
Cuando uno elige un dominio abstracto, generalmente tiene que lograr un equilibrio entre mantener relaciones detalladas y altos costos computacionales.
Mientras que los lenguajes de alto nivel como Python o Haskell utilizan números enteros ilimitados de forma predeterminada, los lenguajes de programación de nivel inferior como C o el lenguaje ensamblador suelen operar con palabras de máquina de tamaño finito , que se modelan de forma más adecuada utilizando el módulo de números enteros (donde n es el ancho de bits de una palabra de máquina). Existen varios dominios abstractos adecuados para diversos análisis de dichas variables.
El dominio de campo de bits trata cada bit en una palabra de máquina por separado, es decir, una palabra de ancho n se trata como una matriz de n valores abstractos. Los valores abstractos se toman del conjunto , y las funciones de abstracción y concretización se dan por: [14] [15] , , , , , , . Las operaciones bit a bit sobre estos valores abstractos son idénticas a las operaciones lógicas correspondientes en algunas lógicas de tres valores : [16]
Otros dominios incluyen el dominio de intervalos con signo y el dominio de intervalos sin signo . Los tres dominios admiten operadores abstractos hacia adelante y hacia atrás para operaciones comunes como la suma, los desplazamientos , la operación xor y la multiplicación. Estos dominios se pueden combinar utilizando el producto reducido. [17]