300 likes | 422 Views
Lezione 15. Verifica (II). [GJM91, Cap. 6] [BB87] [P93] Appunti 1. Verifica di equivalenze per algebre di processo 2. Dimostrazione di correttezza di programmi 3. Esecuzione simbolica. [P93] V. R. Pratt, ‘Logics of Programs’, in A. Ralston, E. D. Reilly (Eds.) Encyclopedia of
E N D
Lezione 15. Verifica (II) • [GJM91, Cap. 6] • [BB87] • [P93] • Appunti • 1. Verifica di equivalenze per algebre di processo • 2. Dimostrazione di correttezza di programmi • 3. Esecuzione simbolica [P93] V. R. Pratt, ‘Logics of Programs’, in A. Ralston, E. D. Reilly (Eds.) Encyclopedia of Computer Science, IEEE Press, 1993 -- pp. 791
1. Verifica di equivalenze per algebre di processo • La natura algebrica di questi linguaggi offre maggior varietà di strumenti di analisi rispetto ai modelli a stati finiti • manipolazione e trasformazione di espressioni algebriche mediante leggi algebriche e assiomatizzazioni di relazioni • manipolazione diretta di Labelled Transition Systems derivati dalla semantica formale
Tipico problema di verifica per specifiche LOTOS: verifica di equivalenza osservazionale fra specifiche sintatticamente diverse. • Esempio dall’architettura OSI: a b UpperService hide c, d in ((P[a, c] ||| P[b, d]) |[c, d]| LowerService[c, d]) UpperService[a, b] ? P Entity P Entity c d LowerService
Equivalenza osservazionale • Un pianista (= osservatore) prova un tasto alla volta… • … che risulta premibile o bloccato in base alla partitura precedentemente inserita • Bechstain Bluthner se nessun pianista cieco li può distinguere Bluthner Bechstain do do do mi sol mi sol • La classica equivalenza fra automi (come riconoscitori di linguaggi) è inadeguata: la musica possibile sui due strumenti è la stessa: {-, do, do-mi, do-sol}… • … ma i comportamenti rispetto a deadlock sono diversi: do-mi può fallire sulBluthner, non sul Bechstain
mi • Action sequence: --- (1... n ) ---> [k Gates i] • (1)---(do, mi)--->(4) • (1)---(i, do)--->(5) • (1)------>(1) • Observable action sequence: === (a1... an ) ==> [ak Gates] • (1)==(do, mi)==>(4) • (1)==(do, mi)==>(8) • (1)====>(1) • (1)====>(3) 2 4 do mi 1 6 8 i i 3 5 do sol 7
Una relazione R fra stati di un LTS è una weak bisumulation se: • (p, q) R, s Gates* • p’ tale che p==s==>p’ • ( q’ tale che q==s==>q’ /\ (p’, q’) R) • /\ • q’ tale che q==s==>q’ • ( p’ tale che p==s==>p’ /\ (p’, q’) R) • p e q sono observation equivalent (p q) se • una weak bisimulation R tale che • (p, q) R
Due LTS equivalenti e la loro bisimulazione P Q Bisimulazione R a a a c b i b i c c P Q
L’equivalenza fra stati di LTS ne induce una fra espressioni di algebre di processo • Una congruenza è una equivalenza preservata quando si rimpiazza una sotto-espressione con un’altra sotto-espressione equivalente. Formalmente: • Un contesto,denotato ‘C[ ]’, è una espressione in cui una sotto-espressione è rimpiazzata da un ‘segna-posto’ (‘[ ]’) • B1 observation congruent B2 (B1 c B2) se per ogni contesto C[ ]: • C[B1] C[B2] (che implica C[B1] c C[B2]…)
c Se c Allora
Leggi della congruenza osservazionale • Per CCS o Basic LOTOS non è possibile fornire una assiomatizzazione di c [M84] • Invece, per ‘finitary LOTOS’ (e, analog., f. CCS), comprendente solo action prefix, choice, stop l’assiomatizzazione è possibile, mediante il sistema completo e consistente di leggi [HM85]: • (a1) B [] (C [] D) = (B [] C) [] D • (a1) B [] C = C [] B • (a3) B [] B = B • (a4) B [] stop = B • (a5) B [] i; B = i; B • (a6) (B [] i; C) = (B [] C) [] ; C [M84] R. Milner, Lectures on a Calculus for Communicating Systems, in International Summer School ‘Control Flow and Data Flow: Concepts of Distributed Programming’, Munich, Germany, July 31-August 12, 1984. [HM85] M. Hennessy, R. Milner, ‘Algebraic Laws for Nondeterminism and Concurrency’, Journal of the ACM, Vol. 32, No. 1, Jan. 1985.
(a5) (a6) B c c i i C B B C B B i C Esercizio: Dimostrare la legge (a7): a; i; B = a; B a; i; B = a; (B [] i; B) [a5] = a; (B [] B) [] a; B [a6] = a; B [] a; B [a3] = a; B [a3]
Esercizio di verifica di equivalenza • Max3[in1, in2, in3, out] := • in1; (in2; in3; out; stop • [] in3; in2; out; stop • ) • [] in2; (in1; in3; out; stop • [] in3; in1; out; stop • ) • [] in3; (in1; in2; stop • [] in2; in1; stop • ) Max3-composto[in1, in2, in3, out] := hide mid in Max2[in1; in2; mid] |[mid]| Max2[in3; mid; out] where Max2[in1; in2; out] := in1; in2; out [] in2; in1; out in2 in3 in2 in3 in1 Max3 out mid in1 out Max2 Max2 Per verificare Max3 Max3-composto: 1. Costruire i due alberi T3 e T3-composto 2. Collassare 6 sottoalberi di T3-composto usando (a7) 3. Collassare 2 nuovi sottoalberi, usando (a5) 4. Collassare 2 nuovi sottoalberi, usando (a7), ottenendo T3.
2. Dimostrazione di correttezza di programmi • La dimostrazione formale di correttezza di programmi si basa sull’utilizzo di una logica per esprimere proprietà dei programmi, e di assiomi e regole per dimostrarle. • McCarthy (1963) modella i programmi come funzioni ricorsive (esempio: definizione di append(list1, list2)), e ne dimostra proprietà per induzione • Floyd (1967) definisce una logica per programmi imperativi espressi come flowchart. • Associa asserzioni logiche (tags) agli archi, che devono essere vere ogni volta che il controllo li attraversa. • Un tagged flowchart è dimostrato corretto quando, individualmente, ogni componente è corretta rispetto ai suoi input/output tags. • Hoare (1969) raffina la logica di Floyd, trattando programmi imperativi in forma algebrica
Specifica logica (di una proprietà) del programma P • Pre e Post sono formule logiche del primo ordine, in cui le variabili di input/output appaiono libere {Pre (i1, i2, …, in) } P {Post (o1, o2, …, om, i1, i2, …, in) }
Esempi di specifica logica di programmi { z (i1 = z*i2)} P {o1 = i1 / i2} {i1 > i2} P {i1 = i2*o1 + o2 /\ o2 > 0 /\ o2 < i2} {true} P {(o= i1 \/ o = i2) /\ o > i1 /\ o > i2} {i1 > 0 /\ i2 > 0} P {( z1, z2 (i1 = o * z1 /\ i2 = o * z2)) /\ ( h ( z1, z2 (i1 = h * z1 /\ i2 = h * z2) /\ h > o))}
Esempio di verifica di programma - esposizione informale • Programma e proprietà • {true} • Begin • read (a); read (b); • x := a + b; • write (x) • end • {o = i1 + i2} • Dimostrazione • o = i1 + i2 vale dopo write(x) sse immediatamente prima vale x = i1 + i2. • Ma l’assegnamento x := a+b scrive a+b in x, quindi • x = i1 + i2 vale subito dopo l’assegn. sse • a+b = i1+i2 vale subito prima. • Poiché read(a) e read(b) danno ad a e b i valori dei due input i1 e i2, a+b = i1+i2 deve valere prima dell’assegnamento []
Le regole della logica di Hoare Applicabili a un linguaggio di programmazione con i costrutti: - x := exp (assegnamento) - begin a1; a2; …; an end - if p then a1 else a2 - while p do a {p}a1{q}, {q}a2{r} 3. ------------------------------------ {p}a1;a2{r} {p/\ r}a1{q}, {p/\ ¬r}a2{q} 4. ------------------------------------ {p} if r thena1 else a2 {q} p’ p, {p}a{q}, q q’ 1. ------------------------------------ {p’}a{q’} 2. ------------------------------------ {p[exp/x]}x := exp{p(x)} {p/\ q}a{p} 5. ------------------------------------ {p} while q doa {p /\ ¬q} (NB: le graffe sono usate in modo inverso rispetto a [GJM91]
Programma: A: x:=1; BB: while y>0 doCC: x:=y*x; y:=y-1 i) y>0 /\ x*y!=n!x:=y*x y>0 /\ x*(y-1)!= n! first: y>0 /\ x*y! = n! ==> y>0 /\ (y*x)*(y-1)! = n! then: y>0 /\ (y*x)*(y-1)! = n! x:=y*x y>0 /\ x*(y-1)!= n! [rule 2, substituting x for y*x] ii) y>0 /\ x*(y-1)!= n!y:=y-1 y>0 /\ x*y!= n! first: y>0 /\ x*(y-1)! = n! ==> (y-1) > 0 /\ x*(y-1)! = n! then: (y-1) > 0 /\ x*(y-1)! = n! y:=y-1 y>0 /\ x*y!= n! [rule 2, substituting y for y-1] (iii) y>0 /\ x*y!=n!C y>0 /\ x*y!= n! [rule 3, with (i) and (ii)] Esempio di verifica: fattoriale di y
(Programma: A: x:=1; BB: while y>0 doCC: x:=y*x; y:=y-1) • (iii) y>0 /\ x*y!=n!C y>0 /\ x*y!= n! • (iv) y>0 /\ y=n /\ x=1B x= n! • y >0 /\ y=n /\ x=1 ==> p (let p = y >0 /\x*y! = n!) • Let q = y > 0 then: • (iii) becomes: q /\ p C p • which yields: p while q do C p /\ ¬q [by rule 5] • that is pB p /\ ¬q • p /\ ¬q = y >0 /\x*y! = n!/\ y < 0 ==> y=0 /\ x=n! ==> x=n! • In conclusion: y >0 /\ y=n /\ x=1 ==> p, pB p /\ ¬q, p /\ ¬q==> x=n!, • thus (iv) [by rule 1] • (v) y>0 /\ y=nx := 1 y>0 /\ y=n /\ x=1 • since p x := 1 p /\ x=1 [by rule 2] • (vi) y>0 /\ y=nA x=n! [rule 3 to (v) and (iv)] []
3. Esecuzione simbolica • Una tecnica a metà fra analisi di correttezza (statica) e testing (dinamica) • Si consideri questo programma P, e il suo grafo control-flow annotato 1. x := y + 2; 2. if [x > a] then 3. a := a + 2; else 4. y := x + 3; endif; 5. x := x + a + y. 3 y := x + 3 4 1 x := y + 2 x:= x + a + y [x > a] 2 a := a + 2 Esecuzione simbolica: si associano valori iniziali simbolici alle variabili di programma (stato simbolico iniziale), poi...: init. 1. 2. 3. 4. 5. [Y+2<A] x X Y+2 2*Y+A+7 y Y Y+5 aA
L’esecuzione simbolica ha prodotto la tripla: • [a = A, y = Y+5, x = 2*Y+A+7], stato simbolico finale (SSfin) • (assegnamento di espressioni simboliche, contenenti variabili simboliche (maiuscole), alle variabili di P (minuscole). • <1, 3, 4>, execution path • cammino lungo il control flow graph di P • [Y+2<A] path condition • predicato sulle variabilisimboliche che garantisce la eseguibilità del path • L’altra tripla possibile è • < [a = A+2, y = Y, x = 2*Y+A+4], <1, 2, 4>, [Y+2>A] > • La corrispondenza biunivoca fra execution path e path condition cade per i linguaggi nondeterministici
input e output file sono trattati come sequenze (i1, i2,…) e (o1, o2, …) di var. di programma. Inizialmente: i1 = I1, i2 = I2, …o1 = nil, o2 = nil, … • Il primo Read (x) viene interpretato come x := i1, dunque la sua esecuzione simbolica produce: x = ValSimb(i1) = I1 • Il primo Write (E) viene interpretato come o1 := E, dunque la sua esecuzione simbolica produce: o1 = ValSimb(E)(in termini dello stato simbolico corrente) • Quando si incontra nel programma una condizione cond, come in • if cond then S1 else S2 endif • while cond Loop • … si considera ValSimb(cond): • se il risultato è TRUE o FALSE indipendentemente dai valori delle variabili simboliche, si procede secondo il ramo corrispondente • se no, si sceglie nondeterministicamente TRUE (risp. FALSE), e si aggiorna la path condition PC: PC := PC /\ ValSimb(cond) (resp. PC := PC /\ ¬ValSimb(cond) )
Verifica tramite esecuzione simbolica • In generale, per ogni programma P si hanno molte, o infinite triple • P programma • inp = (inp1, ..., inpn) tupla di variabili in input per P • I = (I1, ..., In) una corrispondente tupla di variabili simboliche • out = (out1, ..., outn) tupla di variabili in output per P • < • Per verificare {Pre(inp)}P{Post(out)} con esecuzione simbolica: • calcolare tutte le triple (SSFini, ExecPathi, PathCondi(I)).Per ogni i: • usare sempre la stessa Path Condition iniziale: Pre(I) • assumendo SSFini = (out1= Exp1i(I)), ..., (outn = Expni(I)) • e definendo il predicato PEi come: PEi(I) = Post(Exp1i(I)/out1, ..., Expni(I)/outn) • verificare che PathCondi(I) ==> PEi(I) • (Trattazione semplificata, senza rappresentazione delle variabili interne di P.)
Backward symbolic execution (of protocols) [G. Holzmann, PSTV IV, 1985] • E’ una variante di reachability analysis: dato uno stato finale indesiderabile Sx, calcola transizioni a ritroso fino, possibilmente, a S0 • Il sistema distribuito -- un protocollo -- viene descritto con un linguaggio simile al CSP di Hoare: • qname !mnameoutput; appende mess. mname in coda a qname • qname ?mnameinput; eseguibile se mess. mname è correntemente in cima a qname (con cancellazione) • v1 := v2 assegnamento v1 è una var., v2 è una var. o una costante • v++ incrementa di 1 la variabile v • v-- decrementa di 1 la variabile v • [v1 R v2] condizione, dove R è =, , , , <=, >=; eseguibile se i due operandi (var. o costanti) sono in relazione R • I do loops (do…od), sono interrotti dal comando break. Skip è il comando vuoto. • :: individua scelte non deterministiche
Due processi A e B condividono le code A (verso A) e B (verso B). • Il processo A si rifornisce dalla coda TOB di messaggi che vorrebbe mandare al processo B attraverso la coda B. B si comporta simmetricamente. • La variabile condivisa N è incrementata dal processo A quando un messaggio è appeso alla coda B, ed è decrementata dal processo B quando un messaggio viene cancellato. • Simmetricamente, la variabile condivisa M è incrementata dal processo B quando un messaggio è appeso alla coda A (verso A), ed è decrementata dal processo A quando un messaggio viene cancellato. • Il protocollo forza ciascun processo a ritardare il trasferimento di messaggi verso l’altro quando le code sono sature (2 messaggi)
Vogliamo verificare se dallo stato globale iniziale S1: • N = 0, M = 0 • TOB = (msg, msg), TOA = (msg, msg) • A = ( ), B = ( ) • ...si puo’ arrivare allo stato di deadlock S2: • N = 2, M = 2 • TOB = ( ), TOA = ( ) • A = (msg, msg ), B = (msg, msg) • Anziché esplorare tutti i cammini da S1 fino a trovare eventualmente S2, si puo’ partire da S2 e vedere se S1 è raggiungibile a ritroso. • Inoltre, la esecuzione a ritroso (inversa) della specifica originale equivale a una esecuzione diretta della specifica inversa così costruita:
Scambiare send con receive (? con !) • Scambiare incrementi con decrementi (++ con --) • Invertire il flusso di controllo • Scambiare condizioni con assegnamenti (= con :=) ma usando anche assegnamenti con range di valori (tipico della esecuzione simbolica): • Condizione Assegnamento • ------------------------------------------- • [N > 5] ==> N := (>5) • [N 2] ==> N := 2 • La specifica originale del processo A è convertita in una tabella a stati, che viene invertita ed eseguita in maniera diretta, a partire dallo stato S2.
La specifica originale del processo A è convertita nella tabella a stati: La specifica inversa è rappresentata da questa tabella; la sottolineatura distingue i nuovi elementi dai vecchi
Effettivamente si scopre che, attraverso un doppio ciclo nel grafo degli stati globali, il deadlock è raggiungibile. • La tecnica di esplorazione viene detta simbolica perché • viene concettualmente esplorato il programma, visto come lista di statements • lo stato è la posizione nel programma, ma puo’ essere parzialmente identificato anche da predicati sui valori di alcune variabili(come M ed N), che ne ‘sfuocano’ la rappresentazione. • Sfortunatamente la esecuzione inversa non è sempre vantaggiosa rispetto a quella diretta [cfr. Holzmann 85] • Un piu’ recente approccio alla interpretazione simbolica a ritroso, chiamato Compositional backward technique, è descritto in [Staunstrup+, IEEE Computer, Maggio 2000]