3 TEMA 3: LA VERIFICACIÓN FORMAL 3.1 El código y la especificación: la comprobación formal de la implementación. Hasta ahora se ha visto como realizar una especificación formal de los requerimientos de un sistema utilizando el lenguaje Z. Sobre esta especificación pueden realizarse diversas pruebas formales que permiten garantizar la corrección de la especificación realizada. También se estudió en la introducción al lenguaje Z, mediante un ejemplo, la posibilidad de ir refinando la especificación hasta acercarla al modo en que podría ser implementada en un lenguaje de programación. Aunque este aspecto no se ha tratado en mayor profundidad a lo largo del presente curso. El enfoque que ahora se propone sería un paso más en la garantía de calidad de un producto software. Una vez realizada una especificación que representa el comportamiento deseado de una componente software, el siguiente paso consistiría en su implementación, su realización en un lenguaje de programación concreto. Una vez concluida la implementación, habríamos de verificar la corrección de dicha implementación, garantizando que el programa construido cumple con el significado de la especificación. Este planteamiento tiene un problema y es la dependencia del lenguaje de programación elegido para realizar la verificación, lo que resulta demasiado complicado, puesto que nos obligaría a desarrollar un método de verificación para cada lenguaje concreto. Esto puede ser evitado introduciendo un paso intermedio entre la especificación y la implementación de algoritmos que denominaremos Diseño. El diseño de un programa nos permite obtener una caracterización algorítmica del mismo lo bastante abstracta como para: Ser independiente del lenguaje de programación Permitirnos razonar acerca del programa Poder realizar una traducción a los lenguajes imperativos comunes La notación empleada se denomina Lenguaje Algorítmico, un lenguaje de programación imperativo genérico, con un repertorio de instrucciones reducido pero que permiten representar todas las construcciones existentes en los lenguajes imperativos estructurados. 3.2 Especificación de algoritmos mediante la lógica de Hoare MFES: TEMA 3: Verificación Formal 1/4 El método de verificación formal más conocido describe la especificación utilizando la lógica de Hoare, basada en fórmulas de lógica de primer orden. Sin embargo, esta aproximación no está demasiado alejada de las especificaciones formales que hemos estudiado, utilizando el lenguaje Z como vemos en el ejemplo que aparece a continuación. Ordenar matriz, matriz’: seq i, j: #matriz = # matriz’ i: 1..#matriz’, j:1..#matriz matriz’ i = matriz j i: 1..#matriz-1 matriz’ i matriz’ i+1 El programa, en la lógica de Hoare, se especifica mediante formulas denominadas aserciones que relacionan las entradas y salidas del programa. Se garantiza que si la entrada actual satisface las restricciones de entrada (precondiciones) la salida satisface las restricciones de salida (postcondiciones). Se utiliza una expresión del tipo P{programa}Q, siendo P y Q aserciones de la lógica, para indicar que si P es cierto antes de la ejecución del programa y dicho programa termina, entonces Q es cierto tras la ejecución de dicho programa. Esta notación permite que se pueda verificar la corrección parcial del programa, o sea, siempre que el programa termine. Por otro lado, la expresión {P}programa{Q} indica corrección total de dicho programa, esto es, además de garantizar el cumplimento de postcondiciones, el programa termina. La especificación en esta lógica, que como ya se indicó utiliza el formato pre/postcondición, del programa de ordenación que figura como ejemplo, sería la siguiente: {n >0 and a(1..n): integers} P {i: (1 i n) | a(i) a(i+1)} Las aserciones de entrada o precondiciones nos indican que la cardinalidad de la matriz tiene que ser mayor que 0 y que todos los elementos de la matriz son enteros. Las aserciones para los valores de salida o postcondiciones afirman que la matriz al final debe estar ordenada de modo incremental. Como se puede ver, la postcondición no es más que la definición en Z de los efectos de la operación sobre las variables del esquema. Por otro lado, las precondiciones se corresponden con las definiciones de variables que se han hecho en el esquema, más las que se podrían obtener utilizando pre Ordenar, o sea, el cálculo de precondiciones de dicho esquema. MFES: TEMA 3: Verificación Formal 2/4 3.3 Verificación de algoritmos 3.3.1 Precondición más débil Conceptualmente todo el proceso de verificación se basa en la definición de la precondición más débil: pmd (weakest precondition: wp). Supongamos que nos encontramos con una acción algorítmica A y queremos comprobar si cumple la especificación formada por P y Q como precondición y postcondición respectivamente, esto es, {P} A {Q}. La idea de la verificación es proceder de atrás hacia delante. Esto es, vamos a intentar definir cuales son los estados tales que desde ellos y ejecutando A se llega a Q. Estos estados se pueden caracterizar por una serie de condiciones, o sea, por una aserción R que cumplirá {R} A {Q}. En este punto, si la precondición (P) es más fuerte que R, o sea si P R, se obtiene que el algoritmo es correcto. Para poder usar este método de razonamiento hacia atrás se necesita poder calcular la aserción R para cada posible acción A De este modo, se define la precondición más débil y se denota por pmd (A, Q) como el conjunto de todos los estados tales que una ejecución de A que comience en uno de ellos se garantiza que termina en el estado Q en un tiempo finito. Veremos este concepto mediante la utilización de algunos ejemplos: Sea A, el comando de asignación i:=i+1 y Q, i 1, entonces pmd (A, Q) = (i 0). Sea A, if x y then z:=x else z:=y y Q, z = max(x,y). La ejecución de la sentencia siempre hace que z sea el máximo de dichos valroes, por lo tanto pmd (A,Q) = true. O sea, se cumplirá para cualquier estado previo. Sea A la misma que en el ejemplo anterior y Q z = y. Entonces pmd(A,Q) = (y x). Puesto que la ejecución de A con conjuntos de valores que cumplen y x, hará que z tome el valor y, mientras que para conjuntos de valores con y <x, pondrá a z el valor x, que es distinto de y. El cálculo de la precondición más débil se ha realizado en los ejemplos de modo intuitivo, pero están definidas una serie de reglas para su cálculo de modo sistemático. 3.3.2 Verificación de un programa Para probar la corrección parcial del un programa B (o sea, de un conjunto de operaciones o instrucciones), con respecto a un predicado de entrada P y un predicado de salida Q, lo que se hace es intentar deducir la aserción {P}B{Q}utilizando una serie de reglas de verificación. MFES: TEMA 3: Verificación Formal 3/4 Las reglas de verificación expresan la semántica de las distintas operaciones, ya que nos indican las condiciones que cumple el estado antes y después de la ejecución de cada instrucción. Existirá al menos una regla de verificación para cada instrucción, que será empleada para la verificación a posteriori del programa construido. De este modo se intentará para verificar un programa obtener su pmd dadas las postcondiciones y el propio programa. Una vez obtenida ésta, si las precondiciones iniciales son más fuertes que dicha pmd tenemos garantía de que el programa cumple las especificaciones iniciales. MFES: TEMA 3: Verificación Formal 4/4