430 likes | 593 Views
Yogi – Part 2. Handling programs with pointers and procedure calls. Yogi ’s refinement. S k-2. T. S k-2. T. refine. S k-1 ∧¬ρ. S k-1. S k-1 ∧ρ. Suppose there is no test that extends the frontier. op. op. suitable predicate. S k. S k. Results in too many iterations!.
E N D
Yogi – Part 2 Handling programs with pointers and procedure calls
Yogi’srefinement Sk-2 T Sk-2 T refine Sk-1∧¬ρ Sk-1 Sk-1∧ρ Suppose there is no test that extends the frontier op op suitable predicate Sk Sk Results in too many iterations! Candidates for suitable predicates Strongest postcondition (SP) along the path up to the frontier Weakest precondition (WP): Any formula separating the two above (e.g., an interpolant) Explodes in the presence of aliasing!
WP and aliasing Sk-2 T Sk-1∧¬ρ Sk-1∧ρ ASSGN(i=j) *a<10 aliasing possibilities
WP and aliasing Sk-2 T Sk-1∧¬ρ Sk-1∧ρ ASSGN(i=j) *a+*b<10 aliasing possibilities
WP and aliasing (a≠&i ∧ b≠&i ∧ *a+*b<10) ∨ (a=&i ∧ b≠&i ∧ j+*b<10) ∨ (a≠&i ∧ b=&i ∧ *a+j<10) ∨ (a=&j ∧ b=&i∧ j+j<10) ¬((a≠&i ∧ b≠&i ∧ *a+*b<10) ∨ (a=&i ∧ b≠&i ∧ j+*b<10) ∨ (a≠&i ∧ b=&i ∧ *a+j<10) ∨ (a=&j ∧ b=&i∧ j+j<10)) ASSGN(i=j) *a+*b<10 In practice a global alias analysis required to prune the formula generated by WP • if () • p = &a; • else • p = &b; • *p = 3; • alias(*p) = {a, b}
Deriving a suitable predicate Belongs to exactly one region a≠&i ∧ b≠&i ∧ *a+*b≥10 a≠&i ∧ b≠&i ∧ *a+*b<10 a=&i ∧ b≠&i ∧ j+*b<10 a=&i ∧ b≠&i ∧ j+*b≥10 a≠&i ∧ b=&i ∧ *a+j<10 a≠&i ∧ b=&i ∧ *a+j≥10 a=&i ∧ b=&i ∧ j+j<10 a=&i ∧ b=&i ∧ j+j≥10 ASSGN(i=j) *a+*b<10
Deriving a suitable predicate a≠&i ∧ b≠&i ∧ *a+*b≥10 a≠&i ∧ b≠&i ∧ *a+*b<10 a=&i ∧ b≠&i ∧ j+*b<10 a=&i ∧ b≠&i ∧ j+*b≥10 a≠&i ∧ b=&i ∧ *a+j<10 a≠&i ∧ b=&i ∧ *a+j≥10 a=&i ∧ b=&i ∧ j+j<10 a=&i ∧ b=&i ∧ j+j≥10 ASSGN(i=j) *a+*b<10
Refining with suitable predicate WPα WPα a ≠ &i ∨ b=&i ∨ j+*b<10 a=&i ∧ b≠&i ∧ j+*b≥10 ASSGN(i=j) *a+*b<10 No global alias analysis required! def
Refining with suitable predicate WPα Sk-2 T Sk-2 T refine Sk-1∧¬ρ Sk-1 Sk-1∧ρ op op suitable predicate Sk Sk Theorem: is a suitable predicate for refinement
Aliasing Example 0 p1 = malloc(); p1->lock = 0 struct DE { int lock; int y; }; void baz(DE *p, DE *p1, DE *p2) { 0: p1 = malloc(sizeof(DE)); p1->lock = 0; 1: p2 = malloc(sizeof(DE)); p2->lock = 0; 2: p->lock = 1; 3: if (p1->lock == 1 || p2->lock == 1) 4: error(); 5: p = p1; 6: p = p2; } 1 p2 = malloc(); p2->lock = 0 2 p->lock = 1 3 assume(!(p1->lock =1 p2->lock=1)) assume(p1->lock =1 p2->lock=1) 4 5 p = p1 6 p = p2 7
Aliasing Example 0 struct DE { int lock; int y; }; void baz(DE *p, DE *p1, DE *p2) { 0: p1 = malloc(sizeof(DE)); p1->lock = 0; 1: p2 = malloc(sizeof(DE)); p2->lock = 0; 2: p->lock = 1; 3: if (p1->lock == 1 || p2->lock == 1) 4: error(); 5: p = p1; 6: p = p2; } 1 2 3 frontier 4 5 6 7
Aliasing Example 0 struct DE { int lock; int y; }; void baz(DE *p, DE *p1, DE *p2) { 0: p1 = malloc(sizeof(DE)); p1->lock = 0; 1: p2 = malloc(sizeof(DE)); p2->lock = 0; 2: p->lock = 1; 3: if (p1->lock == 1 || p2->lock == 1) 4: error(); 5: p = p1; 6: p = p2; } 1 frontier 2 3: ρ 3: ¬ρ 5 4 6 7
Aliasing Example 0 struct DE { int lock; int y; }; void baz(DE *p, DE *p1, DE *p2) { 0: p1 = malloc(sizeof(DE)); p1->lock = 0; 1: p2 = malloc(sizeof(DE)); p2->lock = 0; 2: p->lock = 1; 3: if (p1->lock == 1 || p2->lock == 1) 4: error(); 5: p = p1; 6: p = p2; } 1 2 : 2:¬ 3: ρ 3: ¬ρ 5 4 6 7
Aliasing Example – Proof! 0 struct DE { int lock; int y; }; void baz(DE *p, DE *p1, DE *p2) { 0: p1 = malloc(sizeof(DE)); p1->lock = 0; 1: p2 = malloc(sizeof(DE)); p2->lock = 0; 2: p->lock = 1; 3: if (p1->lock == 1 || p2->lock == 1) 4: error(); 5: p = p1; 6: p = p2; } 1: μ 1: ¬μ 2 : 2:¬ 3: ρ 3: ¬ρ 5 4 6 7
Generalized Example void foo(int *p, int *p1, int* p2, … , int*pn) { 0: p1 = malloc(sizeof(DE)); p1->lock = 0; 1: p2 = malloc(sizeof(DE)); p2->lock = 0; … pn= malloc(sizeof(DE)); pn->lock = 0; 2: *p1 = 0; *p2 = 0; … ; *pn = 0; 3: if (*p1 == 1 || … || *pn == 1) 4: error(); 5: p = p1; 6: p = p2; … p = pn; }
Recap: programs with pointers • Refine using • Generalizes better than S • Handles explosion in aliasing better than
Next… Handling programs with procedures…
Recall Key ideas 0 Step 1: Try to generate a test that crosses the frontier • Perform symbolic simulation on the path until the frontier and generate a constraint • Conjoin with the condition needed to cross frontier • Is satisfiable? [YES] Step 2: run the test and extend the frontier 1 2 3 frontier 4 5 7 8 6 10 9
Recall Key ideas 0 Step 1: Try to generate a test that crosses the frontier • Perform symbolic simulation on the path until the frontier and generate a constraint • Conjoin with the condition needed to cross frontier • Is satisfiable? [NO] Step 2: use to refine so that the frontier moves back! 1 2 frontier 3 4 4 5 7 8 6 10 9
Handling procedures 0 How to deal with frontiers that have function calls on the frontier edge? 1 2 3 4 5 7 8 6 10 9
Questions • How do we perform symbolic simulation over function call frontiers? • If frontier can be extended, how do we generate a test? • If frontier needs to be refined, what is ? • Can we reuse results?
Answer I Call the intraprocedural analysis recursively Y - no: perform refinement - yes: generate test
Questions • How do we perform symbolic simulation over function call frontiers? • If frontier can be extended, how do we generated a test? • If frontier needs to be refined, what is ? • Can we reuse results?
Answer 2 If the result of the recursive call is a “yes”, use the corresponding test to extend the frontier Y
Questions • How do we perform symbolic simulation over function call frontiers? • If frontier can be extended, how do we generated a test? • If frontier needs to be refined, what is ? • Can we reuse results?
Answer 3 If the result of the recursive call is a “no”, use the corresponding proof to compute and refine! Y
Questions • How do we perform symbolic simulation over function call frontiers? • If frontier can be extended, how do we generated a test? • If frontier needs to be refined, what is ? • Can we reuse results?
Answer 4 Yes, we can reuse. Stores tests and proofs obtained at function frontiers as must and not-may summaries
Query If we start procedure in a state satisfying precondition , and execution of completes, can we reach a state satisfying postcondition?
Formalizing procedure summaries must summary • A must summary for a procedure f is of the form • can be obtained by executing from an initial state • A must summary can be used to answer “yes” to the query if≠ { } • A may summary for a procedure is of the form • cannot be obtained by executing starting in state • A may summary can be used to answer “no” to the query if
Yogi: Compositional May-Must Analysis • May analysis = predicate abstraction (SLAM) • Must analysis = symbolic execution + tests (DART) • Compositional May-Must analysis: • Interprocedural analysis • Memoize and re-use may/must summaries • May analysis can use must summaries and vice-versa Yogi implements Compositional-May and Compositional-Mustanalysis with interactions between the two
Example 1 (not-may summaries) void main(int i1,i2,i3) { 0: int x1,x2,x3; 1: x1 = g(i1); 2: x2 = g(i2); 3: x3 = g(i3); 4: if ((x1 < 0)||(x2 < 0)||(x3 < 0)) 5: error(); int g(inti) { 11: if (i > 0) 12: return i; 13: else 14: return -i; } • Yogi generates the query • Analysis of generates the not-may summary • The property is proved in main using this • not-may summary
Example 2 (must summaries) void main(int i1,i2,i3) { 0: int x1,x2,x3; 1: x1 = f(i1); 2: x2 = f(i2); 3: x3 = f(i3); 4: if (x1 > 0) 5: if (x2 > 0) 6: if (x3 > 0) 7: error(); } int f(inti) { 11: if (i > 0) 12: return i; else 13: return h(i); // h(i) is a complex function // such as a hash function } • Yogi generates the query • Analysis of generates the must summary • Condition (i1 > 0) && (i2 > 0) && (i3 > 0) is generated, and corresponding test to reach error is generated
Interplay between may and must void main() { 1: A(); 2: if () 3: B1(); 4: else 5: B2(); 6: C(); 7: if () 8: error(); } intmain() { 1: A(); 2: B(); 3: C(); 4: if () 5: error(); } not-may summary helps must-answer - assume has a not-may summary - useful for “efficiently” proving that is reachable! must summary helps not-may-answer - assume and B() have must summaries - useful for “efficiently” proving that is unreachable!
Example 3 (interplay between may and must) void main(int j) { 1: inti = 0; 2: x = foo(i,j); 3: if (x == 1) 4: error(); } int foo(inti,j) { 11: if (j > 0) 12: return bar(i)+1; 13: else 14: return i+1; } • Assume bar is a complex function with a not-may summary • Yogi generates the query • Using this, analysis of generates the must summary • Yogi generates condition inmainfor test to reach error
Must and not-may summaries • Let us formally state what the examples showed us…
Interprocedural analysis Y not-may summary: perform refinement must summary : generate test and extend frontier
Using must summary • Query: • Suppose we have • such that • Pick any point in • and thus extend test case • across the frontier
Using not-may summary • Query • Suppose we have • such that • and • Useto refine
Using not-may summary • Query • Suppose we have • such that • and • Useto refine
Computing must summaries void foo(int a) { 0: i = 0; 1: c = 0; 2: while (i < 1000) { 3: c = c + i; 4: i = i + 1; } 5: if(a <= 0) return(1) else return(0) } test … Symbolic state at entry to the procedure is Symbolic state on exit from procedure is Generated must-summary is
Computing not-may summaries • Query • Suppose we have a proof • that is unreachable from • Generate may-summary • where are initial regions that are reachable by tests, and 0: 0: 0: 1: μ 1: ¬μ 2 : 2:¬ 3: ρ 3: ¬ρ 4: 4:
Interplay between and must summaries must must must must must must must must must