150 likes | 323 Views
Symbolic Execution for Model Checking and Testing. Corina Păsăreanu (Kestrel) Joint work with Sarfraz Khurshid (MIT) and Willem Visser (RIACS). Paper.
E N D
Symbolic Execution for Model Checking and Testing Corina Păsăreanu (Kestrel) Joint work with Sarfraz Khurshid (MIT) and Willem Visser (RIACS)
Paper • Khurshid, S., Pasareanu, C.S., and Vissser, W., “Generalized Symbolic Execution for Model Checking and Testing”, in Proc. ofthe 9th International Conference on Tools and Algorithms for the Construction and Analysis of Systems (TACAS 2003). April 2003, Warsaw, Poland.
Future mission software: - concurrent - complex, dynamically allocated data structures (e.g., lists or trees) - highly interactive: - with complex inputs - large environment - should be extremely reliable Mars Rover Rover Executive void Executive:: startExecutive(){ runThreads(); …} void Executive:: executePlan(…) { while(!empty) executeCurrentPlanNode(); } … Input plan execute action environment/ rover status large environment data Model checking: - automatic, good at finding concurrency bugs - not good at dealing with complex data structures - feasible only with a small environment - and a small set of input values Motivation complex input structure concurrency, dynamic data (lists, trees) Current practice in checking complex software: Testing: - requires manual input - typically done for a few nominal input cases - not good at finding concurrency bugs - not good at dealing with complex data structures
Our symbolic execution framework Extends model checking • to programs that have complex inputs with unbounded (very large) data Automates test input generation Provides: A novel symbolic execution algorithm • Handles dynamic data (e.g., lists and trees), concurrency • Uses lazy initialization: • Initializes the components of the program’s inputs on an ``as-needed'' basis • No a priori bound on input sizes • Uses preconditions to initialize inputs only with valid values A source to source translation to instrument a program • Enables standard model checkers to perform symbolic execution of the program • Uses “off-the-shelf” decision procedures
Symbolic execution tree: x:X,y:Y PC: true true false x:X,y:Y PC: X>Y x:X,y:Y PC: X<=Y Code: int x, y; if (x > y) { x = x + y; y = x - y; x = x - y; if (x – y > 0) assert (false); } x:X+Y,y:Y PC: X>Y x:X+Y,y:X PC: X>Y Not reachable x:Y,y:X PC: X>Y - “Simulate” the code using symbolic values instead of program numeric data true false (PC=“path condition”) x:Y,y:X PC: X>Y Y-X>0 x:Y,y:X PC:X>Y Y-X<=0 FALSE! Symbolic Execution
Algorithm: Lazy Initialization To symbolically execute method m of class C: • create a new object, o, of class C • set all its fields to uninitialized values • invoke o.m() when m accesses field f if (f is uninitialized) { if (f is reference field of type T) { non-deterministically initialize f to • null • a new object of class T (with un-initialized field values) • a previously initialized object of class T } if (f is numeric (string) field) initialize f to a new symbolic value }
Symbolic execution tree: PC: true Code: class Node { int elem; Node next; Node swapNode() { if (next != null) if (elem – next.elem > 0) { Node t = next; next = t.next; t.next = this; return t; } return this; } } next next E0 null E0 E0 ... ... next false true PC: E0-E1>0 PC: E0-E1<=0 ... next next E0 E1 next next next E0 E1 null t • “Simulate” the code using symbolic values instead of program numeric data. • Enumerate input structures lazily. ... next next E0 E1 next ... t next next E0 E1 E2 t next next next Precondition: acyclic list! E0 E1 E2 next t next next next E0 E1 E2 (= “unknown yet”) next next t E0 E1 E0 E1 ... t t Generalized Symbolic Execution
next next E0 E0 null null E0 E0 next next next next next next E0 E1 E0 E1 null null next next next next E0 E0 E1 E1 next next next next E0 E1 E0 E1 next next next next E1 E0 E1 E0 Null pointer exception! next next next next next next E0 E1 E0 E1 E2 E2 Results of Analysis Input list + Constraint Output list Code: none class Node { int elem; Node next; Node swapNode() { if (next != null) if (elem – next.elem > 0) { Node t = next; next = t.next; t.next = this; return t; } return this; } } none E0≤E1 E0>E1 E0>E1 E0>E1 E0>E1
Path condition (data) Heap configuration Thread scheduling Framework Decision procedures continue/ backtrack State: Program instrumentation Model checking Source program Instrumented program Correctness specification Counterexample(s)/ test suite [heap+constraint+thread scheduling]
Implementation for Java • Uses Korat (MIT) for program instrumentation • Uses Java PathFinder model checker toolset • Uses Omega library as a decision procedure • for integer linear constraints Framework: • Can be used as a symbolic execution tool with backtracking • Handles multithreaded programs • No state matching • Un-decidable in general • Good for finding counter-examples to safety properties • Programs with loops can have infinite execution trees • Uses breadth first search or depth first search with limited depth • Used to • check for null pointer exceptions, rich properties in multithreaded programs • generate test inputs for code coverage of an Altitude Switch used in flight control software (~ 2000 Java lines)
class Expression { static PathCondition _pc; Expression_minus(Expression e) { … } } class PathCondition { … Constraints c; boolean _updateGT (Expression e1, Expression e2) { boolean result = choose_boolean(); if (result) c.add_constraintGT(e1,e2)); else c.add_constraintLE(e1,e2)); if (! c.is_satisfiable()) backtrack(); return result; } } class Node { Expression elem; boolean _elem_is_initialized; Node next; boolean _next_is_initialized; Node swapNode() { if (_get_next() != null) if (Expression._pc._updateGT(_get_elem()._minus(_get_next() . _get_elem()), new IntegerConstant(0) ) { Node t = _get_next() ; _set_next (t._get_next() ); t._set_next (this); return t; } return this; } } Code Instrumentation Code: class Node { int elem; Node next; Node swapNode() { if (next != null) if (elem – next.elem > 0) { Node t = next; next = t.next; t.next = this; return t; } return this; } }
Related work • Symbolic execution and program testing • EFFIGY [King’76] • Static analysis • ESC [Detlefs et al’98] • TVLA [Sagiv et al’98] • Software model checking • VeriSoft [Godefroid’97] • JPF [Visser et al’00] • Bandera (KSU), SLAM (Microsoft) • Specification-based testing • Korat [ISSTA ’02] • ...
Conclusions • Framework for symbolic execution • Handles dynamic data and concurrency • Program instrumentation enables any model checker to perform symbolic execution • Used for checking rich properties of multithreaded programs with complex inputs and for test input generation • Future work: • Investigate • Widening and abstraction techniques to help termination • Different decision procedures and constraint solvers (to handle non-linear constraints and floats) • Case studies
Demo class List { Node header; static class Node { int elem; Node next; } void swap2() { // swaps the first two nodes if (header == null) return; if (header.next == null) return; Node t = header; header = header.next; t.next = t.next.next; header.next = t; } }
n:S,x:0 PC:true n:S,x:0 PC:0<S n:S,x:0 PC:0>=S n:S,x:1 PC:0<S n:S,x:1 PC:0<S & 1<S n:S,x:1 PC:0<S & 1>=S .... Problem: convergence Symbolic execution tree: n:S PC:true Code: void test(int n) { int x = 0; while(x < n) x = x + 1; }