350 likes | 364 Views
Learn how to model and prove the correctness of sequential software programs using PVS. Includes examples, partitioning of programs, and transition functions.
E N D
15-820AProving Software with PVS Edmund Clarke Daniel Kroening Carnegie Mellon University
Outline • Modeling Software with PVS • Complete Example for Sequential Software,including proof • The Magic GRIND • Modularization
Modeling Software with PVS int a[10]; unsigned i; int main() { . . . } 1. Define Type for STATE C: TYPE = [# a: [below(10)->integer], i: nat #] A
Modeling Software with PVS 2. Translate your program into goto program int a[10]; unsigned i,j,k; int main() { L1: i=k=0; L2: if(!(i<10)) goto L4; L3: i++; k+=2; goto L2; L4: j=100; k++; } int a[10]; unsigned i,j,k; int main() { i=k=0; while(i<10) { i++; k+=2; } j=100; k++; } A
Modeling Software with PVS 3. Partition your program into basic blocks 4. Write transition function for each basic block L1(c: C):C= c WITH [i:=0, k:=0] L2(c: C):C= c L3(c: C):C= c WITH [i:=c`i+1, k:=c`k+2] L4(c: C):C= c WITH [j:=100, k:=c`k+1] int a[10]; unsigned i,j,k; int main() { L1: i=k=0; L2: if(!(i<10)) goto L4; L3: i++; k+=2; goto L2; L4: j=100; k++; } A
Modeling Software with PVS 5. Combine transition functions using a program counter PCt: TYPE = { L1, L2, L3, L4, END } int a[10]; unsigned i,j,k; int main() { L1: i=k=0; L2: if(!(i<10)) goto L4; L3: i++; k+=2; goto L2; L4: j=100; k++; } t(c: C): C= CASES c`PC OF L1: L1(c) WITH [PC:=L2], L2: L2(c) WITH [PC:= IF NOT (c`i<10) THEN L4 ELSE L3 ENDIF, L3: L3(c) WITH [PC:=L2], L4: L4(c) WITH [PC:=END], END: c ENDCASES A
Modeling Software with PVS 6. Define Configuration Sequence c(T: nat, initial: C):RECURSIVE C= IF T=0 THEN initial WITH [PC:=L1] ELSE t(c(T-1, initial)) ENDIF MEASURE T 7. Now prove properties about PC=LEND states program_correct: THEOREM FORALL (initial: C): FORALL (T: nat | c(T)`PC=LEND): c(T)`result=correct_result(initial) A
Example I 1. Define Type for STATE bool find_linear(unsigned size, const int a[], int x) { unsigned i; for(i=0; i<size; i++) if(a[i]==x) return TRUE; return FALSE; } C: TYPE = [# size: nat, a: [nat -> integer], x: integer, i: nat, result: bool, PC: PCt #] A
Example II bool find_linear(unsigned size, const int a[], int x) { L1: i=0; L2: if(!(i<size)) goto L8; L3: if(!(a[i]==x)) goto L6; L4: result=TRUE; L5: goto LEND; L6: i++; L7: goto L2; L8: result=FALSE; LEND:; return result; } bool find_linear(unsigned size, const int a[], int x) { unsigned i; for(i=0; i<size; i++) if(a[i]==x) return TRUE; return FALSE; } 2. Translate your program into goto program A
Example III/IV 3. Partition your program into basic blocks 4. Write transition function for each basic block L1(c: C):C=c WITH [i:=0] L2(c: C):C=c L3(c: C):C=c L4(c: C):C=c WITH [result:=TRUE] L5(c: C):C=c L6(c: C):C=c WITH [i:=c`i+1] L7(c: C):C=c L8(c: C):C=c WITH [result:=FALSE] bool find_linear (unsigned size, const int a[], int x) { L1: i=0; L2: if(!(i<size)) goto L8; L3: if(!(a[i]==x)) goto L6; L4: result=TRUE; L5: goto LEND; L6: i++; L7: goto L2; L8: result=FALSE; LEND:; return result; } A
Example V 5. Combine transition functions using a program counter t(c: C):C=CASES c`PC OF L1: L1(c) WITH [PC:=L2], L2: L2(c) WITH[PC:= IF NOT c`i < c`size THEN L8 ELSE L3 ENDIF], L3: L3(c) WITH [PC:= IF NOT c`a(c`i)=c`x THEN L6 ELSE L4 ENDIF], L4: L4(c) WITH [PC:=L5], L5: L5(c) WITH [PC:=LEND], L6: L6(c) WITH [PC:=L7], L7: L7(c) WITH [PC:=L2], L8: L8(c) WITH [PC:=LEND], LEND: c ENDCASES bool find_linear (unsigned size, const int a[], int x) { L1: i=0; L2: if(!(i<size)) goto L8; L3: if(!(a[i]==x)) goto L6; L4: result=TRUE; L5: goto LEND; L6: i++; L7: goto L2; L8: result=FALSE; LEND:; return result; } A
Example VI 6. Define Configuration Sequence c(T: nat, initial: C):RECURSIVE C= IF T=0 THEN initial WITH [PC:=L1] ELSE t(c(T-1, initial)) ENDIF MEASURE T What is the correct result? 7. Now prove properties about PC=LEND states program_correct: THEOREM FORALL (initial: C): FORALL (T: nat | c(T)`PC=LEND): c(T)`result=correct_result(initial) A
Example IV C: TYPE = [# size: nat, a: [nat -> integer], x: integer, i: nat, result: bool, PC: PCt #] correct_result(c: C): bool= EXISTS (j: below(c`size)): c`a(j)=c`x OK!LET’S PROVE THIS! A
Something useful first… C: TYPE = [# size: nat, a: [nat -> integer], x: integer, i: nat, result: bool, PC: PCt #] We need to say: c(T)`a = initial`aÆ c(T)`x = initial`xÆ c(T)`size = initial`size OR: The program only changes i, result, PC program_correct: THEOREM FORALL (initial: C): FORALL (T: nat | c(T)`PC=LEND): c(T)`result=correct_result(initial) This relates initial state and final state A
Something useful first… We need to say: c(T)`a = initial`aÆ c(T)`x = initial`xÆ c(T)`size = initial`size OR: The program only changes i, result, PC invar_constants(T: nat, initial: C): bool= c(T, initial)`size=initial`size AND c(T, initial)`a =initial`a AND c(T, initial)`x =initial`x; constants: LEMMA FORALL (initial:C, T: nat): invar_constants(T, initial) Proof:Induction on T + GRIND next: the real invariant… A
Loop Invariant bool find_linear(unsigned size, const int a[], int x) { unsigned i; for(i=0; i<size; i++) if(a[i]==x) return TRUE; return FALSE; } FORALL (j: below(c`i)): c`a(j)/=c`x A
The Invariant invar(c: C):bool=CASES c`PC OF L1: % i=0; L2: % if(!(i<size)) goto L8; L3: % if(!(a[i]==x)) goto L6; L4: % result=TRUE; L5: % goto LEND; L6: % i++; L7: % goto L2; L8: % result=FALSE; LEND:c`result <=> EXISTS (j: below(c`size)): c`a(j)=c`x ENDCASES Beginning of the Loop End of the Loop A
The Invariant What here? invar(c: C):bool=CASES c`PC OF L1: % i=0; L2: FORALL (j: below(c`i)): c`a(j)/=c`x, % if(!(i<size)) goto L8; L3: % if(!(a[i]==x)) goto L6; L4: % result=TRUE; L5: % goto LEND; L6: % i++; L7: FORALL (j: below(c`i)): c`a(j)/=c`x, % goto L2; L8: % result=FALSE; LEND:c`result <=> EXISTS (j: below(c`size)): c`a(j)=c`x ENDCASES A
The Invariant invar(c: C):bool=CASES c`PC OF L1: TRUE, % i=0; L2: FORALL (j: below(c`i)): c`a(j)/=c`x, % if(!(i<size)) goto L8; L3: % if(!(a[i]==x)) goto L6; L4: % result=TRUE; L5: % goto LEND; L6: % i++; L7: FORALL (j: below(c`i)): c`a(j)/=c`x, % goto L2; L8: % result=FALSE; LEND:c`result <=> EXISTS (j: below(c`size)): c`a(j)=c`x ENDCASES Exiting the Loop Exiting the Loop A
The Invariant invar(c: C):bool=CASES c`PC OF L1: TRUE, % i=0; L2: FORALL (j: below(c`i)): c`a(j)/=c`x, % if(!(i<size)) goto L8; L3: % if(!(a[i]==x)) goto L6; L4: % result=TRUE; L5: % goto LEND; L6: % i++; L7: FORALL (j: below(c`i)): c`a(j)/=c`x, % goto L2; L8: c`i>=c`size AND FORALL (j: below(c`i)): c`a(j)/=c`x, % result=FALSE; LEND: c`result <=> EXISTS (j: below(c`size)): c`a(j)=c`x ENDCASES What here? A
The Invariant invar(c: C):bool=CASES c`PC OF L1: TRUE, % i=0; L2: FORALL (j: below(c`i)): c`a(j)/=c`x, % if(!(i<size)) goto L8; L3: c`i<c`size AND FORALL (j: below(c`i)): c`a(j)/=c`x, % if(!(a[i]==x)) goto L6; L4: % result=TRUE; L5: % goto LEND; L6: % i++; L7: FORALL (j: below(c`i)): c`a(j)/=c`x, % goto L2; L8: c`i>=c`size AND FORALL (j: below(c`i)): c`a(j)/=c`x, % result=FALSE; LEND: c`result <=> EXISTS (j: below(c`size)): c`a(j)=c`x ENDCASES What here? A
The Invariant invar(c: C):bool=CASES c`PC OF L1: TRUE, % i=0; L2: FORALL (j: below(c`i)): c`a(j)/=c`x, % if(!(i<size)) goto L8; L3: c`i<c`size AND FORALL (j: below(c`i)): c`a(j)/=c`x, % if(!(a[i]==x)) goto L6; L4: % result=TRUE; L5: % goto LEND; L6: FORALL (j: below(c`i+1)): c`a(j)/=c`x, % i++; L7: FORALL (j: below(c`i)): c`a(j)/=c`x, % goto L2; L8: c`i>=c`size AND FORALL (j: below(c`i)): c`a(j)/=c`x, % result=FALSE; LEND: c`result <=> EXISTS (j: below(c`size)): c`a(j)=c`x ENDCASES What here? A
The Invariant invar(c: C):bool=CASES c`PC OF L1: TRUE, % i=0; L2: FORALL (j: below(c`i)): c`a(j)/=c`x, % if(!(i<size)) goto L8; L3: c`i<c`size AND FORALL (j: below(c`i)): c`a(j)/=c`x, % if(!(a[i]==x)) goto L6; L4: c`i<c`size AND c`a(c`i)=c`x, % result=TRUE; L5: c`i<c`size AND c`a(c`i)=c`x AND c`result=true, % goto LEND; L6: FORALL (j: below(c`i+1)): c`a(j)/=c`x, % i++; L7: FORALL (j: below(c`i)): c`a(j)/=c`x, % goto L2; L8: c`i>=c`size AND FORALL (j: below(c`i)): c`a(j)/=c`x, % result=FALSE; LEND: c`result <=> EXISTS (j: below(c`size)): c`a(j)=c`x ENDCASES
The Invariant DARING CLAIM “Once you have found the invariant,the proof is done.” We now have the invariant. Lets do the actual proof. Who believes we are done? A
The Gentzen Sequent {-1} i(0)`reset {-2} i(4)`reset |------- {1} i(1)`reset {2} i(2)`reset {3} (c(2)`A AND NOT c(2)`B) Conjunction (Antecedents) Disjunction (Consequents) Or: Reset in cycles 0, 4 is on, and off in 1, 2.Show that A and not B holds in cycle 2.
The Magic of (GRIND) • Myth: Grind does it all… • Reality: • Use it when: • Case splitting, skolemization, expansion, and trivial instantiations are left • Does not do induction • Does not apply lemmas “... frequently used to automatically complete a proof branch…”
The Magic of (GRIND) • If it goes wrong… • you can get unprovable subgoals • it might expand recursions forever • How to abort? • Hit Ctrl-C twice, then (restore) • How to make it succeed? • Before running (GRIND), remove unnecessary parts of the sequent using (DELETE fnum).It will prevent that GRIND makes wrong instantiations and expands the wrong definitions.
A word on automation… • The generation of C, t, and c can be trivially automated • Most of the invariant can be generated automatically – all but the actual loop invariant (case L7/L2) • The proof is automatic unless quantifier instantiation is required A
Modularization t(c: C):C=CASES c`PC OF L1: L1(c) WITH [PC:=L2], L2: L2(c) WITH[PC:= IF NOT c`i < c`size THEN L8 ELSE L3 ENDIF], L3: L3(c) WITH [PC:= IF NOT c`a(c`i)=c`x THEN L6 ELSE L4 ENDIF], L4: L4(c) WITH [PC:=L5], L5: L5(c) WITH [PC:=LEND], L6: L6(c) WITH [PC:=L7], L7: L7(c) WITH [PC:=L2], L8: L8(c) WITH [PC:=LEND], LEND: c ENDCASES bool find_linear (unsigned size, const int a[], int x) { L1: i=0; L2: if(!(i<size)) goto L8; L3: if(!(a[i]==x)) goto L6; L4: result=TRUE; L5: goto LEND; L6: i++; L7: goto L2; L8: result=FALSE; LEND:; return result; } How about a program with a 1000 basic blocks?= 1000 cases? • Better not • Remedy: Modularize the program and the proof • Idea: find_linear is a function in the C program, make it a function in PVS as well C C • Functions in PVS must be total, thus, this requires proof of termination A
a T such that c(T, start)`PC=LEND Modularization find_linear(start: C): C= c(epsilon! (T: nat): c(T, start)`PC=LEND, start) "epsilon! (x:t): p(x)” is translated to "epsilon(LAMBDA (x:t): p(x))” epsilon_ax: AXIOM (EXISTS x: p(x)) => p(epsilon(p)) THIS IS WHATREQUIRES TERMINATION A
Modularization termination: THEOREM FORALL (initial: C): EXISTS (T: nat): c(T, initial)`PC=LEND allows to show the left hand side of epsilon_ax: AXIOM (EXISTS x: p(x)) => p(epsilon(p)) the right hand side then says c(epsilon! (T: nat): c(T, start)`PC=LEND, start)`PC=LEND A
Modularization find_linear(start: C): C= c(epsilon! (T: nat): c(T, start)`PC=LEND, start) What to prove about it? ? find_linear_correct: THEOREM FORALL (c: C): LET new=find_linear(c) IN new=c WITH [result:=correct_result(c)] What is missing? A
Modularization find_linear(start: C): C= c(epsilon! (T: nat): c(T, start)`PC=LEND, start) What to prove about it? find_linear_correct: THEOREM FORALL (c: C): LET new=find_linear(c) IN new=c WITH [result:=correct_result(c), PC:=new`PC, i:=new`i] “All variables but result, PC, and i are unchanged, and result is the correct result.” A