380 likes | 548 Views
Predicate Abstraction for Software Verification. Shaz Qadeer Compaq Systems Research Center (joint work with Cormac Flanagan). Systems software. OS components file system, buffer cache manager Abstract from low-level to high-level operations tedious low-level programming detail
E N D
Predicate Abstraction for Software Verification Shaz Qadeer Compaq Systems Research Center (joint work with Cormac Flanagan)
Systems software • OS components • file system, buffer cache manager • Abstract from low-level to high-level operations • tedious low-level programming detail • Expected to work • with multiple concurrent clients • in the presence of crashes
Outline • Background • verification condition • strongest postcondition • loop invariants • Inferring loop invariants • predicate abstraction • universally-quantified invariants • Frangipani • Related work
Verification condition x := a; if (x < b) { x := b; } assert x = max(a,b); Program P Check for validity: sp(P,true) x a x b
Guarded command language (Dijkstra 76) Statement S x := e A ; B assume e A B while {I} e do B end Variables have arbitrary values in program’s initial state if e then A else B end (assume e ; A) (assumee ; B)
Strongest postcondition semantics sp(S,Q) y.(Q[xy] x=e[xy]) sp(B,sp(A,Q)) e Q sp(A,Q) sp(B,Q) Statement S x := e A ; B assume e A B Denote sp(S, true) by sp(S)
Checking loop invariants • Given loop • Need to check • Invariant holds on entry to loop • sp( C ) I • Invariant holds at end of every iteration • sp( C; H; assume I e; B) I where H = havoc variables modified in B C; {I} while e do B end
Desugaring loops S = “while {I} e do B end” H = havoc variables modified in B desugar(S) = assert I ; H ; assume I ; ( (assume e ; B; assert I; assume false) assume e)
Example assume 0 < a.length; i := 0; while (i < a.length) { a[i] := 0; i := i + 1; } assert a[0] = 0; { int j; 0 <= j j < i a[j] = 0, 0<=i}
Translation assume 0 < a.length; i := 0; assert int j; 0 <= j j < i a[j] = 0; assert 0 <= i; havoc a[*], i; assume int j; 0 <= j j < i a[j] = 0; assume 0 <= i; ( assume (i < a.length); a[i] := 0; i := i + 1; assert int j; 0 <= j j < i a[j] = 0; assert 0 <= i; assume false;) assume (i < a.length) assert a[0] = 0;
ESC/Java with loop invariants /*@ loop_invariant i >= 0; loop_invariant 0 <= spot; loop_invariant spot <= MAXDIRENTRY; loop_invariant (\forall int j; 0 <= j && j < i && bdisk[addr].dirEntries[j].inum != DIRENTRY_UNUSED ==> bdisk[addr].dirEntries[j].name != name); loop_invariant (\forall int j; spot == MAXDIRENTRY && 0 <= j && j < i ==> bdisk[addr].dirEntries[j].inum != DIRENTRY_UNUSED); loop_invariant spot == MAXDIRENTRY || bdisk[addr].dirEntries[spot].inum == DIRENTRY_UNUSED; loop_invariant (\forall DirEntry t; t != de ==> t.name == \old(t.name)); loop_invariant (\forall DirEntry t; t != de ==> t.inum == \old(t.inum)); loop_invariant (\forall DirEntry t; t.inum == FS.DIRENTRY_UNUSED || (0 <= t.inum && t.inum < FS.IMAX)); */ for (i = 0; i < cwd.inode.length; i++) { GetDirEntry(de, addr, i); if (de.inum != DIRENTRY_UNUSED && de.name == name) { return ERROR; } if (de.inum == DIRENTRY_UNUSED && spot == MAXDIRENTRY) { spot = i; } }
Outline • Background • verification condition • strongest postcondition • loop invariants • Inferring loop invariants • predicate abstraction • universally-quantified invariants • Frangipani • Related work
Inferring loop invariants C; {I} while e do B end • Could try • I0 = sp( C ) • In+1 = In sp( C; H; assume In e; B) • In is an invariant for first n iterations • Problem: May not converge
Divergence State Space I0 I1 I2 In ... ...
Predicate abstraction • Invariant is boolean combination of predicates • Split problem into two parts • Find a suitable set of predicates • Find the boolean combination of these predicates
Predicate abstraction (contd.) • Predicates over program variables • a = expr1, b = expr2, c = expr3, d = expr4 • : boolean expression over {a,b,c,d} formula over program variables • :formula over program variables boolean expression over {a, b, c, d} • (F) = least boolean function G over {a,b,c,d} such that F (G)
Abstract state space • Predicates {a, b, c, d} • They generate an abstract space of size 24 = 16 cd cd cd cd State Space ab (F) ab F ab ab
Inferring loop invariants • Compute • I0=(sp( C )) • In+1 =In(sp( C; H; assume E (In); B)) • Converges yielding valid loop invariant • Best loop invariant for the given predicates
Method 1 (slow!) • Is F a b c dsatisfiable? No! • Can compute (F) by asking 2n such queries cd cd cd cd ab X X X X (F) ab X X X F ab X X ab X X
a Fa cd cd cd cd b Fab Fab ab X X X X c (F) Fabc ab X X X F ab X X ab X X Method 2 (Das-Dill-Park 99) Order the variables: a < b < c < d • O(2n+1) queries • Sensitive to ordering
. . Queries of size n: . . . Number = nCn2n Method 3 (Shankar-Saidi 99) Queries of size 1: Fa, F a, Fb, etc. Number = nC121 Queries of size 2: Fab, Fab, etc. Number = nC222 • O(3n) queries • No ordering required
New method • F a c d? No! • F a b c d? No! • F c d? No! • Removed 1/4 of state space in 3 queries! cd cd cd cd ab X X X X (F) = (c d) (a c) (a b) ( c d) ab X X X F ab X X ab X X
Universally-quantified loop invariants /*@ loop_invariant i >= 0; loop_invariant 0 <= spot; loop_invariant spot <= MAXDIRENTRY; loop_invariant (\forall int j; 0 <= j && j < i && bdisk[addr].dirEntries[j].inum != DIRENTRY_UNUSED ==> bdisk[addr].dirEntries[j].name != name); loop_invariant (\forall int j; spot == MAXDIRENTRY && 0 <= j && j < i ==> bdisk[addr].dirEntries[j].inum != DIRENTRY_UNUSED); loop_invariant spot == MAXDIRENTRY || bdisk[addr].dirEntries[spot].inum == DIRENTRY_UNUSED; loop_invariant (\forall DirEntry t; t != de ==> t.name == \old(t.name)); loop_invariant (\forall DirEntry t; t != de ==> t.inum == \old(t.inum)); loop_invariant (\forall DirEntry t; t.inum == FS.DIRENTRY_UNUSED || (0 <= t.inum && t.inum < FS.IMAX)); */ for (i = 0; i < cwd.inode.length; i++) { GetDirEntry(de, addr, i); if (de.inum != DIRENTRY_UNUSED && de.name == name) { return ERROR; } if (de.inum == DIRENTRY_UNUSED && spot == MAXDIRENTRY) { spot = i; } }
Inferring -quantified loop invariants assume 0 < a.length; i := 0; /*@ loop_invariant 0 <= i, int j; 0 <= j j < i a[j] = 0 */ while (i < a.length) { a[i] := 0; i := i + 1; } assert a[0] = 0;
Inferring -quantified loop invariants assume 0 < a.length; i := 0; /*@ loop_predicate 0 <= i, int j; 0 <= j j < i a[j] = 0 */ while (i < a.length) { a[i] := 0; i := i + 1; } assert a[0] = 0;
Inferring -quantified loop invariants assume 0 < a.length; i := 0; /*@ skolem_constant int j */ /*@ loop_predicate 0 <= i, 0 <= j, j < i, a[j] = 0 */ while (i < a.length) { a[i] := 0; i := i + 1; } assert a[0] = 0;
/*@ loop_predicate 0 <= i, 0 <= j, j < i, a[j] = 0 */ I0=(sp(i := 0)) In+1 =In(sp(i := 0; havoc i, a[*]; assume In i < a.length; a[i] := 0; i := i+1)) I0 TFTF TFTT TTFF TTFT I1 TTTT Inferring -quantified loop invariants
Inferring -quantified loop invariants /*@ loop_predicate 0 <= i, 0 <= j, j < i, a[j] = 0 */ I0 TFTF TFTT TTFF TTFT I1 TTTT 0 i (0 j) (j < i) a[j] = 0 0 j j < i
Inferring -quantified loop invariants /*@ loop_predicate 0 <= i, 0 <= j, j < i, a[j] = 0 */ I0 TFTF TFTT TTFF TTFT I1 TTTT 0 i int j; 0 j j < i a[j] = 0 int j; 0 j j < i
Outline • Background • verification condition • strongest postcondition • loop invariants • Inferring loop invariants • predicate abstraction • universally-quantified invariants • Frangipani • Related work
Frangipani • Distributed file system (Thekkath, Mann, Lee 1997) • Built on top of Petal virtual disk (Lee, Thekkath 1996) • Implements the file abstraction from the virtual disk block abstraction • Exports file and directory operations - create, delete, rename etc. - to clients
Verification • Loop invariant inference built on top of ESC/Java (Detlefs, Leino, Nelson, Saxe 1998) • ESC/Java uses automatic theorem prover Simplify (Nelson 81) • Frangipani data structures and “create” modeled abstractly in Java • Assume • single thread of execution • no crashes
. . . . . . . . . . . . . . . . . . Data structures on disk: Inode { int inum; int addr; int mode; int length; } ibusy F T F T F F idisk addr addr bdisk Block { int addr; Entry[] entries; } Entry { String name; int inum; } bbusy F T F F T F Data structures in memory: • vArray contains inodes • distinct entries must contain • distinct inodes vArray vbusy T T F F T F itov: int int
/*@ invariant int i, j: vbusy[i] ((vArray[i].inum = j) (itov[j] = i)); invariant int i: ibusy[i] bbusy[idisk[i].addr]; invariant idisk bdisk; invariant int i: idisk[i] null idisk[i].inum = i; . . */ /*@ requires vbusy[num] vArray[num].mode = DIR */ /*@ ensures \result = ERROR there is an entry with name s in directory vArray[num] */ int create(int num, String s) { . . . }
Main loop in “create” /*@ loop_invariant i >= 0; loop_invariant 0 <= spot; loop_invariant spot <= MAXDIRENTRY; loop_invariant (\forall int j; 0 <= j && j < i && bdisk[addr].dirEntries[j].inum != DIRENTRY_UNUSED ==> bdisk[addr].dirEntries[j].name != name); loop_invariant (\forall int j; spot == MAXDIRENTRY && 0 <= j && j < i ==> bdisk[addr].dirEntries[j].inum != DIRENTRY_UNUSED); loop_invariant spot == MAXDIRENTRY || bdisk[addr].dirEntries[spot].inum == DIRENTRY_UNUSED; loop_invariant (\forall DirEntry t; t != de ==> t.name == \old(t.name)); loop_invariant (\forall DirEntry t; t != de ==> t.inum == \old(t.inum)); loop_invariant (\forall DirEntry t; t.inum == FS.DIRENTRY_UNUSED || (0 <= t.inum && t.inum < FS.IMAX)); */ for (i = 0; i < cwd.inode.length; i++) { GetDirEntry(de, addr, i); if (de.inum != DIRENTRY_UNUSED && de.name == name) { return ERROR; } if (de.inum == DIRENTRY_UNUSED && spot == MAXDIRENTRY) { spot = i; } }
Related work • Inferring/computing loop invariants • Predicate abstraction • Graf-Saidi 97 • Bensalem-Lakhnech-Owre 98, Colon-Uribe 98 • Saidi-Shankar 99, Das-Dill-Park 99 • Ball-Majumdar-Millstein-Rajamani 2001
Future Work • Heuristics for generating predicates • Multiple threads • locks, semaphores, ... • thread-modular reasoning • Linked lists, reachability • Target C