Verificación de Programas Parte II: Caracterización lógica de programas y contratos Recap: El Traductor Programa Especificación Traductor Teorema en una lógica Demostrador de Teoremas Válido Inválido Verificador Traduciendo un programa a un teorema... ● ● ● ● Para esto, necesitamos representar a un programa como una fórmula de alguna lógica Para ello, necesitamos darle semántica formal al programa Una forma de hacerlo: usar semántica denotacional Semántica denotacional: el significado del programa es un objeto matemático Semántica denotacional ● Supongamos un lenguaje de programación sencillo: ● Asignación x:=y ● Skip skip ● Secuencia P;Q ● Condicional if B then P else Q endif ● Ciclo while B do P endwhile Ejemplo y := x; z := 1; while y>1 do z:=z*y; y:=y­1 endwhile ● Computa la función factorial de x con la salida en z Triplas(*) de Hoare ● Correctitud parcial: {A} s {B} – – ● Si: ● el programa comienza en un estado q cumple A ● la ejecución de s termina Entonces: el estado final cumple B Correctitud total: [A] s [B] – Si: el programa inicia en un estado q cumple A – Entonces: ● La ejecución termina ● El estado final cumple B (*) tripla no figura en el diccionario de la RAE Reglas de Hoare {A}s1{C} {C}s2{B} _________ {R}s1;s2{E} {P}skip{P} {A && G}s1{B} {A && !G}s2{B} {A} if G then s1 else s2 endif {B} {A && G} s {A} (A && !G)=>B {A} while G do s endwhile {B} Reglas de Hoare: asignación Regla backward: { B[E/x] } x:= E {B} Ejemplo: {A} x:= x+2 {x>=5} {x>=5[x+2/x]} x:=x+2 {x>=5} {x+2>=5}x:=x+2{x>=5} {x>=3}x:=x+2{x>=5} Weakest precondition Dijkstra: Verificar que {A} s {B} a) Sea Pre(s,B) = {A'| {A'}s{B} } b) < Pre(s,B) , => > es un reticulado, done – False \in Pre(s,B) – Si x,y \in Pre(s,B), ent. x && y, x || y \in Pre(s,B) c) WP(s,B) = lub(R) d) Computar WP(s,B) y demostrar si A=>WP(s,B) Reticulado de “predicados” Queremos verificar que: {A} s {B} S = [[true]] WP(s,B) Pre(s,B) A C \subseteq C' sii C'=>C \empty = [[false]] + débil (+estados) + fuerte (­estados) Weakest precondition ● WP(skip, B) == B ● WP(x:=E, B) == B[E/x] ● WP( s1;s2 , B ) == WP(s1, WP(s2, B)) ● WP( if E then s1 else s2 endif, B ) == E=> WP(s1,B) && !E => WP(s2,B) Ejemplo returns c requires true ensures c= a || b bool P(bool a, bool b) { if (a) c:=true else c:=b } WP(P, c=a||b) = ? Conjetura lógica a probar: ? Ejemplo returns c requires true ensures c= a || b bool P(bool a, bool b) { if (a) c:=true else c:=b } WP(P, c=a||b) = a=> WP(c:=true,c=a||b) && !a => WP(c:=b,c=a||b) = (a=> true=a||b) && !a => b=a||b) Conjetura lógica a probar: true=>(a=> true=a||b) && !a => b=a||b) Weakest precondition: Ciclos ● WP_k(while E do S endwhile, B) == WP_0(...) == !E => B WP_i+1(...) == WP_i && E=>WP(S,WP_i(...)) ● WP(while E do S endwhile, B) == glb{WP_k | k>=0} ● Calcular este glb de forma precisa es imposible en general – En algunos casos, igualmente es muy COSTOSO Weakest precondition: Ciclos ● Soluciones: – Loop unroll: “desenrollar” un loop una cantidad finita de veces – Anotar los ciclos con invariantes de ciclo Teorema del Invariante P => Inv [B && Inv] S [Inv] [B && Inv] S [v<\old(v)] (Inv && v<=c) => !B (!B && Inv) => Q [P] while B do S endwhile [Q] Teorema del Invariante P => Inv {B && Inv} S {Inv} (!B && Inv) => Q {P} while B do S endwhile {Q} Tratamiento de ciclos ● ● Extendemos el lenguaje que usamos: – havoc x – assert E – assume E – while(I,T) E do S enwhile Extendemos la definición de WP – WP(havoc x, B) == \forall x. B – WP(assume E, B) == E => B – WP(assert E, B) == E && B Tratamiento de ciclos ● Reescribimos los whiles de la siguiente forma: while_(I,T) E do S endwhile == assert I; havoc T; assume I; if (E) then S;assert I;assume false endif – I es el invariante de ciclo – T es el conjunto de variables modificadas en S Intuición del invariante assert I; havoc T; assume I; if (E) then S;assert I;assume false endif ● ● Para las ejecuciones sin entrar al ciclo, las simulamos directamente Para las ejecuciones que entran al ciclo, hacemos una prueba de correctitud para múltiples iteraciones de ciclo Condiciones de Verificación ● Sea P un programa con ciclos, P' la reescritura de P sin ciclos ● Definimos VC(P,B) = WP(P',B) ● VC(P,B) es una condición de verificación Condiciones de Verificación (2) S = [[true]] Queremos verificar que: {A} s {B} Problema: podemos rechazar algunos programas/postcondiciones que están bien WP(s,B) VC(s,B) Pre(s,B) A C \subseteq C' sii C'=>C \empty = [[false]] + débil (+estados) + fuerte (­estados) Ejemplo {???} while_(x>=0,x) x>0 do x:=x­1 endwhile {x=0} Ejemplo {???} assert x>=0; havoc x; assume x>=0; if (x>0) then x:=x­1; assert x>=0; assume false endif {x=0} Ejemplo {???} L1 assert x>=0; L2 havoc x; L3 assume x>=0; L4 if (x>0) then L5 x:=x­1; L6 assert x>=0; L7 assume false endif {x=0} WP(L1;L2;L3;L4, x=0) WP(L1;L2;L3;L4, x=0) = WP(L1, WP(L2, WP(L3, WP(L4, x=0)))) = WP(L1, WP(L2, WP(L3, WP(L4, x=0)))) = ... WP(L4,x=0) WP(L4, x=0) = WP(if x>0 then L5;L6;L7 else skip endif, x=0) = x>0 => WP(L5;L6;L7, x=0) && !x>0 => WP(skip, x=0) = x>0 => WP(L5, WP(L6, WP(L7, x=0))) && !x>0 => x=0 = ... WP L7, WP L6, WP L5 WP(L7, x=0) = WP(assume false, x=0) = false => x=0 = true WP(L6,true) = WP(assert x>=0,true) = x>=0 && true = x>=0 WP(L5, x>=0) = WP(x:=x­1, x>=0) = x>=0[x­1/x] = x­1>=0 = x>=1 WP(L4,x=0) ­ Parte 2 WP(L4, x=0) = WP(if x>0 then L5;L6;L7 else skip endif, x=0) = x>0 => WP(L5;L6;L7, x=0) && !x>0 => WP(skip, x=0) = x>0 => WP(L5, WP(L6, WP(L7, x=0))) && !x>0 => x=0 = x>0 => x>=1 && x<=0 => x=0 WP(L1;L2;L3;L4, x=0) – Parte 2 WP(L1, WP(L2, WP(L3, (x>0 => x>=1)&&(x<=0 => x=0) ) )) = WP(L1, WP(L2, WP(assume x>=0, (x>0 => x>=1)&&(x<=0 => x=0) ) )) = WP(L1, WP(L2, x>=0 =>((x>0 => x>=1)&&(x<=0 => x=0)) )) = WP(L1, WP(havoc x, x>=0 =>((x>0 => x>=1)&&(x<=0 => x=0)) )) = WP(L1, forall x. (x>=0 =>((x>0 => x>=1)&&(x<=0 => x=0))) )) = WP(assert x>=0, forall x. (x>=0 =>((x>0 => x>=1)&&(x<=0 => x=0))) )) = WP(L1;L2;L3;L4, x=0) – Parte 3 WP(L1;L2;L3;L4, x=0) = x>=0 && forall x.(x>=0 =>( (x>0 => x>=1)&& (x<=0 => x=0) )) = x>=0 Tratamiento de llamados a métodos ● Soluciones – “inlining” del llamado – Usar el contrato del llamado ● ¿Cuál es más “fuerte”? ● Reescribimos el llamado: f() == assert pre; havoc M; assume post Verificación de programas 1.Anotamos cada procedimiento con la precondición, las variables que modifica y la postcondición 2.Anotamos cada loop con el invariante y las variables que modifica 3.Ahora podemos analizar cada procedimiento modularmente (separadamente) Verificación de programas (2) Verificamos que: requires pre modifies M ensures post f() { S } 1.la fórmula VC(S',true) sea válida Sea S'== assume pre; S; assert post 2.todas las modificaciones de S' estén contenidas en M Algunos problemas ● ● El tamaño de la fórmula VC puede ser exponencial en el tamaño del programa Generación eficiente de la VC: – Usar más variables (usar SSA) – Optimizar la estructura de la fórmula (colapsar ramas iguales, etc) – etc... Algunos problemas (2) ● Control flow no­estructurado: Excepciones, Gotos, etc. ● Separar el programa en Bloques secuenciales ● Usar variables para codificar el CFG Recap 1.JML: Java Modeling Language 2.Triplas de Hoare 3.Capturar la validez de una tripla de Hoare usando uno fórmula lógica ● WP: Weakest precondition ● VC: Verification condition (+ fuerte que WP) 4.Falta: ¿cómo funciona un demostrador de teoremas ? Fuentes de incompletitud/ unsoundness ● Hasta ahora.... – Escritura de invariantes de ciclo ● Más débil : falso positivo ● Más fuerte: no encuentra contraejemplo/modelos – Loop unrolling (si no hay invariantes de ciclo) – Contratos muy débiles ● Subespecificar un procedimiento