Verification of Java Programs using Symbolic Execution and Loop Invariant Generation Corina Pasareanu (Kestrel Technology LLC) Willem Visser (RIACS/USRA)

Slides:



Advertisements
Similar presentations
Automated Theorem Proving Lecture 1. Program verification is undecidable! Given program P and specification S, does P satisfy S?
Advertisements

De necessariis pre condiciones consequentia sine machina P. Consobrinus, R. Consobrinus M. Aquilifer, F. Oratio.
Modular and Verified Automatic Program Repair Francesco Logozzo, Thomas Ball RiSE - Microsoft Research Redmond.
Abstraction of Source Code (from Bandera lectures and talks)
Masahiro Fujita Yoshihisa Kojima University of Tokyo May 2, 2008
Symbolic Execution with Mixed Concrete-Symbolic Solving
Automatic Verification Book: Chapter 6. What is verification? Traditionally, verification means proof of correctness automatic: model checking deductive:
50.530: Software Engineering Sun Jun SUTD. Week 10: Invariant Generation.
Abstraction and Modular Reasoning for the Verification of Software Corina Pasareanu NASA Ames Research Center.
1 PROPERTIES OF A TYPE ABSTRACT INTERPRETATER. 2 MOTIVATION OF THE EXPERIMENT § a well understood case l type inference in functional programming à la.
Parallel Symbolic Execution for Structural Test Generation Matt Staats Corina Pasareanu ISSTA 2010.
Reasoning About Code; Hoare Logic, continued
Inferring Disjunctive Postconditions Corneliu Popeea and Wei-Ngan Chin School of Computing National University of Singapore - ASIAN
1 Symbolic Execution for Model Checking and Testing Corina Păsăreanu (Kestrel) Joint work with Sarfraz Khurshid (MIT) and Willem Visser (RIACS)
1/20 Generalized Symbolic Execution for Model Checking and Testing Charngki PSWLAB Generalized Symbolic Execution for Model Checking and Testing.
Carnegie Mellon University Java PathFinder and Model Checking of Programs Guillaume Brat, Dimitra Giannakopoulou, Klaus Havelund, Mike Lowry, Phil Oh,
1 University of Toronto Department of Computer Science © 2001, Steve Easterbrook Lecture 10: Formal Verification Formal Methods Basics of Logic first order.
Rigorous Software Development CSCI-GA Instructor: Thomas Wies Spring 2012 Lecture 13.
A survey of techniques for precise program slicing Komondoor V. Raghavan Indian Institute of Science, Bangalore.
An Integration of Program Analysis and Automated Theorem Proving Bill J. Ellis & Andrew Ireland School of Mathematical & Computer Sciences Heriot-Watt.
Rahul Sharma Işil Dillig, Thomas Dillig, and Alex Aiken Stanford University Simplifying Loop Invariant Generation Using Splitter Predicates.
Formal Methods of Systems Specification Logical Specification of Hard- and Software Prof. Dr. Holger Schlingloff Institut für Informatik der Humboldt.
Using Statically Computed Invariants Inside the Predicate Abstraction and Refinement Loop Himanshu Jain Franjo Ivančić Aarti Gupta Ilya Shlyakhter Chao.
Abstractions. Outline Informal intuition Why do we need abstraction? What is an abstraction and what is not an abstraction A framework for abstractions.
Synergy: A New Algorithm for Property Checking
CS 267: Automated Verification Lectures 14: Predicate Abstraction, Counter- Example Guided Abstraction Refinement, Abstract Interpretation Instructor:
Houdini: An Annotation Assistant for ESC/Java Cormac Flanagan and K. Rustan M. Leino Compaq Systems Research Center.
Validating High-Level Synthesis Sudipta Kundu, Sorin Lerner, Rajesh Gupta Department of Computer Science and Engineering, University of California, San.
Copyright © 2006 The McGraw-Hill Companies, Inc. Programming Languages 2nd edition Tucker and Noonan Chapter 18 Program Correctness To treat programming.
Describing Syntax and Semantics
272: Software Engineering Fall 2012 Instructor: Tevfik Bultan Lecture 4: SMT-based Bounded Model Checking of Concurrent Software.
CUTE: A Concolic Unit Testing Engine for C Technical Report Koushik SenDarko MarinovGul Agha University of Illinois Urbana-Champaign.
DySy: Dynamic Symbolic Execution for Invariant Inference.
By: Pashootan Vaezipoor Path Invariant Simon Fraser University – Spring 09.
Software Engineering Prof. Dr. Bertrand Meyer March 2007 – June 2007 Chair of Software Engineering Static program checking and verification Slides: Based.
Extended Static Checking for Java  ESC/Java finds common errors in Java programs: null dereferences, array index bounds errors, type cast errors, race.
CS 363 Comparative Programming Languages Semantics.
Finding Feasible Counter-examples when Model Checking Abstracted Java Programs Corina S. Pasareanu, Matthew B. Dwyer (Kansas State University) and Willem.
Application: Correctness of Algorithms Lecture 22 Section 4.5 Fri, Mar 3, 2006.
Program Analysis and Verification Spring 2014 Program Analysis and Verification Lecture 4: Axiomatic Semantics I Roman Manevich Ben-Gurion University.
Page 1 5/2/2007  Kestrel Technology LLC A Tutorial on Abstract Interpretation as the Theoretical Foundation of CodeHawk  Arnaud Venet Kestrel Technology.
Symbolic Execution with Abstract Subsumption Checking Saswat Anand College of Computing, Georgia Institute of Technology Corina Păsăreanu QSS, NASA Ames.
Learning Symbolic Interfaces of Software Components Zvonimir Rakamarić.
Symbolic Execution and Model Checking for Testing Corina Păsăreanu and Willem Visser Perot Systems/NASA Ames Research Center and SEVEN Networks.
11 Counter-Example Based Predicate Discovery in Predicate Abstraction Satyaki Das and David L. Dill Computer Systems Lab Stanford University
Symbolic and Concolic Execution of Programs Information Security, CS 526 Omar Chowdhury 10/7/2015Information Security, CS 5261.
Static Techniques for V&V. Hierarchy of V&V techniques Static Analysis V&V Dynamic Techniques Model Checking Simulation Symbolic Execution Testing Informal.
This Week Lecture on relational semantics Exercises on logic and relations Labs on using Isabelle to do proofs.
CUTE: A Concolic Unit Testing Engine for C Koushik SenDarko MarinovGul Agha University of Illinois Urbana-Champaign.
Concrete Model Checking with Abstract Matching and Refinement Corina Păsăreanu QSS, NASA Ames Research Center Radek Pelánek Masaryk University, Brno, Czech.
Extended Static Checking for Java Cormac Flanagan Joint work with: Rustan Leino, Mark Lillibridge, Greg Nelson, Jim Saxe, and Raymie Stata Compaq Systems.
CS357 Lecture 13: Symbolic model checking without BDDs Alex Aiken David Dill 1.
Property-Guided Shape Analysis S.Itzhaky, T.Reps, M.Sagiv, A.Thakur and T.Weiss Slides by Tomer Weiss Submitted to TACAS 2014.
( = “unknown yet”) Our novel symbolic execution framework: - extends model checking to programs that have complex inputs with unbounded (very large) data.
Symstra: A Framework for Generating Object-Oriented Unit Tests using Symbolic Execution Tao Xie, Darko Marinov, Wolfram Schulte, and David Notkin University.
Finding bugs with a constraint solver daniel jackson. mandana vaziri mit laboratory for computer science issta 2000.
Abstraction and Abstract Interpretation. Abstraction (a simplified view) Abstraction is an effective tool in verification Given a transition system, we.
Jeremy Nimmer, page 1 Automatic Generation of Program Specifications Jeremy Nimmer MIT Lab for Computer Science Joint work with.
Spring 2017 Program Analysis and Verification
Presentation Title 2/4/2018 Software Verification using Predicate Abstraction and Iterative Refinement: Part Bug Catching: Automated Program Verification.
Symbolic Implementation of the Best Transformer
Programming Languages 2nd edition Tucker and Noonan
Over-Approximating Boolean Programs with Unbounded Thread Creation
Automatic Test Generation SymCrete
Symbolic Execution and Test-input Generation
CUTE: A Concolic Unit Testing Engine for C
The Zoo of Software Security Techniques
Predicate Abstraction
Programming Languages 2nd edition Tucker and Noonan
Presentation transcript:

Verification of Java Programs using Symbolic Execution and Loop Invariant Generation Corina Pasareanu (Kestrel Technology LLC) Willem Visser (RIACS/USRA) Automated Software Engineering Group NASA Ames

Outline Motivation and Overview Examples Symbolic Execution and Java PathFinder Program Verification and Invariant Generation Experiments Related Work and Conclusions

Motivation Ariane 501 Mars Polar Lander Software errors can be very costly. Software verification is recognized as an important and difficult problem. Spirit More recently …

Java PathFinder with Symbolic Execution Previous work: Java PathFinder (JPF) - explicit-state model checker for Java Extended with symbolic execution [TACAS’03] –Motivation Open systems, large input data domains Complex data structures –Applications: test-input generation, error detection –Shortcoming: cannot prove properties of looping programs New: Invariant generation to deal with loops

Verification Framework Overview Uses symbolic execution Requires annotations –method preconditions –loop invariants Novel technique for invariant generation –uses invariant strengthening, approximation, and refinement –handles boolean and numeric constraints, dynamically allocated structures, arrays

Array Example 1 // precondition: a!=null; public static void set(int a[]) { int i = 0; while (i < a.length) { a[i] = 0; i++; } assert a[0] == 0; } Loop invariants: 0≤i ¬(a[0]  0  0<i)

Array Example 2 // precondition: a!=null; public static void set(int a[]) { int i = 0; while (i < a.length) { a[i] = 0; i++; } assert forall int j: a[j] == 0; } Loop invariant: ¬(a[j]  0  a.length ≤ i  0 ≤ j < a.length)  ¬(a[j]  0  j < i  0 ≤ i,j < a.length)

Symbolic Execution Execute a program on symbolic input values For each path, build a path condition –condition on inputs in order for the execution to follow that path –check satisfiability of path condition Symbolic state –symbolic values/expressions for variables –path condition –program counter Various applications –test case generation –program verification Traditionally: sequential programs with fixed number of integers

x = 1, y = 0 1 > 0 ? true x = = 1 y = 1 – 0 = 1 x = 1 – 1 = 0 0 > 1 ? false Swap Example int x, y; if (x > y) { x = x + y; y = x – y; x = x – y; if (x > y) assert false; } Concrete Execution Path:Code that swaps 2 integers:

Swap Example [PC:true] x = X, y = Y [PC:true] X > Y ? [PC:X>Y] y = X + Y – Y = X [PC:X>Y] x = X + Y – X = Y [PC:X>Y] Y > X ? int x, y; if (x > y) { x = x + y; y = x – y; x = x – y; if (x > y) assert false; } Code that swaps 2 integers:Symbolic Execution Tree: [PC:X≤Y] END[PC:X>Y] x = X+Y false true [PC:X>Y  Y≤X ] END [PC:X>Y  Y>X] END falsetrue path condition

Generalized Symbolic Execution Handles –dynamically allocated data structures, arrays –preconditions, concurrency Uses JPF –to generate and explore the symbolic execution tree Implementation via instrumentation –programs instrumented to enable JPF to perform symbolic execution –Omega library used to check satisfiability of numeric path conditions (for linear integer constraints) –lazy initialization for arrays and structures

Instrumentation void set (int a[]) { int i = 0; while (i < a.length) { a[i] = 0; i++; } assert a[0] == 0; } void set() { IntArrayStruct a = new IntArrayStruct(); Expression i = new IntConstant(0); while (i._LT(a.length)) { a._set(i,0); i = i._plus(1); } assert a._get(0)._EQ(0); } Instrumented code

Library classes class Expression { … static PathCondition pc; Expression _plus(Expression e) {…} boolean _LT(Expression e) { return pc._update_LT(this,e); } } class PathCondition { … Constraints c; boolean _update_LT(Expression l, Expression r) { boolean result = Verify.choose_boolean(); if(result) c.add_constraint_LT(l, r); else c.add_constraint_GE(l, r); Verify.ignoreIf(!c.is_satisfiable()); return result; } } class ArrayCell { Expression elem; Expression idx; } class IntArrayStruct { … Vector _v; Expression length; public Expression _get(Expression idx) { Verify.ignoreIf !inbounds; //assert inbounds ArrayCell cell = _ArrayCell(idx); return cell.elem; } ArrayCell _ArrayCell(Expression idx) { for(int i=0; i<_v.size(); i++) { ArrayCell cell=(ArrayCell)_v.elementAt(i); if(cell.idx._EQ(idx)) return cell; } ArrayCell ac = new ArrayCell(…); _v.add(ac); return ac; }

Induction Step Base Case Proving Properties of Programs X = init; while (C(X)) X = B(X); assert P(X); Looping program: Program execution: while … true while … true while … true … May be infinite … How to reason about infinite executions? Has finite execution. Easy to reason about! Problem: How do we come up with Inv? Requires great user ingenuity. X = init; assert Inv(X); X = new symbolic values; assume Inv(X); if (C(X)) { X = B(X); assert Inv(X); } else assert P(X); Non-looping program: Find loop invariant Inv

Iterative Invariant Strengthening Model check the program: Start with Inv 0 = ¬(¬C  ¬P) Base case violation: error in the program! No errors: done, found loop invariant! Induction step violation: apply strengthening - counterexample path conditions: PC 1, PC 2 … PC n - strengthen invariant: Inv 1 = Inv 0  ¬  PC i - repeat X = init; assert Inv(X); X = new symbolic values; assume Inv(X); if (C(X)) { X = B(X); assert Inv(X); } else assert P(X);

Iterative Strengthening Inv 0 Inv 1 … More precise invariants State space: Inv May result in an infinite sequence of exact invariants: Inv 0, Inv 1, Inv 2 … (we may get infinitely many generated constraints)

Heuristic for Termination At each step k, apply heuristic for current candidate Inv k -it is also iterative strengthening, but … -use oldPC instead of PC - oldPC is weaker than PC (PC → oldPC) - obtains a stronger invariant: Inv k j+1 = Inv k j  ¬  oldPC i Iterative approximation X = init; assert Inv(X); X = new symbolic values; assume Inv_k(X); if (C(X)) { X = B(X); assert Inv_k(X); } else assert P(X); Check the inductive step: oldPC= q  r PC= q  r  v new constraint (encodes the effect of the loop)

X = init; assert Inv(X); X = new symbolic values; assume Inv_k(X); if (C(X)) { X = B(X); assert Inv_k(X); } else assert P(X); Check the inductive step: Iterative Approximation Symbolic execution results in finite universe of constraints U k New constraints from Inv(B(X)) Refinement: if base case fails for Inv k j backtrack compute Inv k+1 apply approximation State space at step k: Approximation too coarse Inv k Inv Inv k+1 Inv k 1 PC oldPC Results in finite sequence of approximate invariants: Inv k 1, Inv k 2 … Inv k m

Invariant Generation Method Inv 0 Inv 1 … Inv k Inv k+1 … Inv k 1 Inv k 2 … Inv k m Refinement - backtrack on base case violation Iterative strengthening Iterative approximation If there is an error in the program, the method is guaranteed to terminate If the program is correct wrt. the property, the method might not terminate

(1) Start with Inv 0 = ¬(¬C  ¬P) (2) Check the base case and the inductive step –if both checks return true: done - property true! –if inductive step fails: apply iterative approximation; go to (2) –if base case fails for an exact invariant: done - property false! –if base case fails for an approximate invariant: apply refinement; go to (2) If there is an error in the program, the method is guaranteed to terminate If the program is correct with respect to the property, the method might not terminate General Verification Method Inv 0 Inv 1 … Inv k Inv k+1 … Inv k 1 Inv k 2 … Inv k m refinement - backtrack on base case violation

Array Example 1 // precondition: a!=null; public static void set(int a[]) { int i = 0; while (i < a.length) { a[i] = 0; i++; } assert a[0] == 0; }

Proof public static void set() { Expression i = new IntConstant(0); IntArrayStruct a = new IntArrayStruct(); assert Inv; i = new SymbolicInt(); Verify.ignoreIf (!Inv); // assume Inv if (i._LT(a.length)) { a._set(i,0); i=i._plus(1); print (PC); // oldPC if (!Inv) { print (PC); // PC assert false; } } else assert a._get(0)._EQ(0); } Inv 0 = ¬(i ≥ a.length  a[0]  0) Error oldPC: i > 0  a[0]  0 PC: i > 0  a[0]  0  (i + 1) ≥ a.length Iterative approximation: Inv 0 1 =Inv 0  ¬oldPC = ¬(i ≥ a.length  a[0]  0)  ¬(i > 0  a[0]  0) = ¬(i > 0  a[0]  0) drop new constraint

[PC: I<a.length  a[0]  0] i=I public static void set(int [] a) { int i = 0; assert Inv; //i,a = new symbolic values; assume Inv; if (i < a.length) { a[i]=0; i++; // oldPC if (!Inv) { // PC assert false; } } else assert a[0]==0; } Proof + tree Error Inv 0 = ¬(i ≥ a.length  a[0]  0)= (i<a.length  a[0]  0)  (i<a.length  a[0] = 0)  (i ≥ a.length  a[0] = 0) [PC: 0<I<a.length  a[0]  0] a[I]=0 [PC: I<a.length  a[0]  0  I  0  0≤I<a.length][PC: I<a.length  a[0]  0  I=0] …

[PC: 0<I<a.length  a[0]  0] i=I+1 [PC: I<a.length  a[0]  0] i=I public static void set(int [] a) { int i = 0; assert Inv; //i,a = new symbolic values; assume Inv; if (i < a.length) { a[i]=0; i++; // oldPC if (!Inv) { // PC assert false; } } else assert a[0]==0; } Proof + tree Error [PC: 0<I<a.length  a[0]  0] a[I]=0 [PC: I<a.length  a[0]  0  I  0  0≤I<a.length][PC: I<a.length  a[0]  0  I=0] … Inv 0 = ¬(i ≥ a.length  a[0]  0)= (i<a.length  a[0]  0)  (i<a.length  a[0] = 0)  (i ≥ a.length  a[0] = 0)

[PC: 0<I<a.length  a[0]  0] I+1≥a.length  a[0]  0 ? [PC: 0<I<a.length  a[0]  0] i=I+1 [PC: I<a.length  a[0]  0] i=I public static void set(int [] a) { int i = 0; assert Inv; //i,a = new symbolic values; assume Inv; if (i < a.length) { a[i]=0; i++; // oldPC if (!Inv) { // PC assert false; } } else assert a[0]==0; } Proof + tree Error [PC: 0<I<a.length  a[0]  0] a[I]=0 [PC: I<a.length  a[0]  0  I  0  0≤I<a.length][PC: I<a.length  a[0]  0  I=0] … Inv 0 = ¬(i ≥ a.length  a[0]  0)= (i<a.length  a[0]  0)  (i<a.length  a[0] = 0)  (i ≥ a.length  a[0] = 0) [PC: 0<I<a.length  a[0]  0  I+1≥a.length] true Error …

[PC: 0<I<a.length  a[0]  0] I+1≥a.length  a[0]  0 ? [PC: 0<I<a.length  a[0]  0] i=I+1 [PC: I<a.length  a[0]  0] i=I public static void set(int [] a) { int i = 0; assert Inv; //i,a = new symbolic values; assume Inv; if (i < a.length) { a[i]=0; i++; // oldPC if (!Inv) { // PC assert false; } } else assert a[0]==0; } Proof + tree Error [PC: 0<I<a.length  a[0]  0] a[I]=0 [PC: 0<I<a.length  a[0]  0  I+1≥a.length] true [PC: I<a.length  a[0]  0  I  0  0≤I<a.length][PC: I<a.length  a[0]  0  I=0] … Iterative approximation: Inv 0 1 =Inv 0  ¬oldPC = ¬(i ≥ a.length  a[0]  0)  ¬(0 0  a[0]  0) oldPC: 0<i <a.length  a[0]  0 PC: 0<i <a.length  a[0]  0  (i + 1) ≥ a.length oldPC: PC: Inv 0 = ¬(i ≥ a.length  a[0]  0)= (i<a.length  a[0]  0)  (i<a.length  a[0] = 0)  (i ≥ a.length  a[0] = 0) Error …

Array Example 2 // precondition: a!=null; public static void set(int a[]) { int i = 0; while (i < a.length) { a[i] = 0; i++; } assert forall int j: a[j] == 0; }

Proof Inv 0 = ¬(i ≥ a.length  a[j]  0  0 ≤ j< a.length) oldPC: a[j]  0  j < i  0 ≤ i,j < a.length Iterative approximation: Inv 0 1 =Inv 0  ¬oldPC = ¬(i ≥ a.length  a[j]  0  0 ≤ j< a.length)  ¬(a[j]  0  j < i  0 ≤ i,j < a.length) PC: a[j]  0  j<i  0 ≤ i,j <a.length  (i + 1) ≥ a.length public static void set(int [] a) { int i = 0; assert Inv; //i,a = new symbolic values; //j = new symbolic value; assume Inv; if (i < a.length) { a[i]=0; i++; // oldPC if (!Inv) { // PC assert false; } } else assert a[j]==0; }

Partition Example class Cell { int val; Cell next; Cell partition (Cell l, int v) { Cell curr = l, prev = null; Cell nextCurr, newl = null; while (curr != null) { nextCurr = curr.next; if (curr.val > v) { if (prev != null) prev.next = nextCurr; if (curr == l) l = nextCurr; curr.next = newl; assert curr != prev; newl = curr; } else prev = curr; curr = nextCurr; } return newl; }} Loop invariant: ¬(curr=prev  curr≠null  curr.elem>v)  ¬(curr≠ prev  prev≠ null  curr ≠ null  prev.elem>v  curr.elem>v  prev≠curr.next)

Pathological Example void m (int n) { int x = 0; int y = 0; while (x <n) {/* loop 1 */ x++; y++; } /* hint: x == y */ while (x!=0) {/* loop 2 */ x--; y--; } assert y==0; } First, attempt computation of invariant for loop 2 Iterative invariant generation does not terminate Constraint x=y is important, but not discovered Using x=y as a hint we get two invariants: Loop 2: ¬(y  0  x=0)  ¬(y ≤ 0  x>0)  ¬(y>0  x  y) Loop 1: ¬(x  y  x ≥ n)  ¬(x<0)  ¬(x≥0  x<n  x  y)

Related Work Invariant generation: –INVEST (Verimag), STEP (Stanford) –Graf & Saidi (CAV 1996), Havelund & Shankar (FME 1996), Tiwari et al. (TACAS 2001), Wegbreit (CACM 1974) –… (a lot of work) –Iterative forward/backward computations –Domain specific; focus on numeric invariants –Heuristics for termination, e.g. using auxiliary invariants Abstract interpretation –Cousot & Cousot (CAV 2002), Cousot & Halbwachs (POPL 1978) –Widening operator to compute fixpoints systematically Flanagan & Qadeer (POPL 2002) –Loop invariant generation for Java programs –Uses predicate abstraction –Predicates need to be provided by the user Extended Static Checker (ESC) –Uses theorem proving to check partial correctness specifications of Java programs –Rely heavily on user provided specifications, such as loop invariants

Conclusion and Future Work Framework for verification of light-weight specifications of Java programs: new use of JPF Iterative technique for discovering (some) loop invariants automatically –Uses invariant strengthening, approximation, and refinement –Handles different types of constraints –Allows checking universally quantified formulas … Very preliminary work Future work: –Instead of dropping newly generated constraints, replace them with an appropriate boolean combination of exiting constraints from U k Similar to predicate abstraction –Use more powerful abstraction techniques in conjunction with our framework –Use heuristics/dynamic methods to discover useful constraints/hints (e.g. Daikon) –Study relationship to widening and predicate abstraction –Extend to multithreading and richer properties –…