Model-checking Concurrent Java Software Using the Bandera Tool Set Matthew Dwyer John Hatcliff Radu Iosif Hongjun Zheng Shawn Laubach Corina Pasareanu FacultyStudents and Post-docs Robby Roby Joehanes Venkatesh Ranganath Oksana Tkachuk ( Funding US National Science Foundation (NSF) US National Aeronautics and Space Agency (NASA) US Department of Defense Advanced Research Projects Agency (DARPA) US Army Research Office Rockwell-Collins ATC Honeywell Technology Center and NASA Langley Sun Microsystems
Goals of the Project II. Integration with commonly used design notations, methods, and processes … UML artifacts, JML e.g., checking, specification … automatic generation of synchronization code with dedicated checking I. Provide platform for construction of and experimentation with technologies for model-checking concurrent Java software … property specification languages e.g., temp logic, state machines … model-reduction techniques e.g., abstraction, slicing, compiler-based optimizations … model-checking engines e.g., explicit-state, symbolic III. Evaluation using safety-critical military and civilian applications as well as non-critical popular open-source software … integration with development and certification of safety-critical systems.
Model Checking OK or Finite-state model Temporal logic formula Model Checker Error trace Line 5: … Line 12: … Line 15:… Line 21:… Line 25:… Line 27:… … Line 41:… Line 47:…
What makes model-checking software difficult? Model construction OK Error trace or Finite-state model Temporal logic formula Model Checker State explosion Problems using existing checkers: Property specification Output interpretation Line 5: … Line 12: … Line 15:… Line 21:…
Model Construction Problem Semantic gap: Model Description Model Checker Program void add(Object o) { buffer[head] = o; head = (head+1)%size; } Object take() { … tail=(tail+1)%size; return buffer[tail]; } Gap Programming Languages Model Description Languages methods, inheritance, dynamic creation, exceptions, etc. automata
What makes model-checking software difficult? Model construction OK Error trace or Finite-state model Temporal logic formula Model Checker State explosion Problems using existing checkers: Property specification Output interpretation Line 5: … Line 12: … Line 15:… Line 21:…
Property Specification Problem Difficult to formalize a requirement in temporal logic “Between the window open and the window close, button X can be pushed at most twice.” []((open /\ <>close) -> ((!pushX /\ !close) U (close \/ ((pushX /\ !close) U (close \/ ((!pushX /\ !close) U (close \/ ((pushX /\ !close) U (close \/ (!pushX U close)))))))))) …is rendered in LTL as...
Property Specification Problem We want to write source level specifications... (((_collect(heap_b) == 1)\ && (BoundedBuffer_col.instance[_index(heap _b)].head == BoundedBuffer_col.instance[_index(heap _b)].tail) )\ || ((_collect(heap _b) == 3)\ && (BoundedBuffer_col_0.instance[_index(heap _b)].head == BoundedBuffer_col_0.instance[_index(heap _b)].tail) )\ || ((_collect(heap _b) == 0) && TRAP)) Heap.b.head == Heap.b.tail We are forced to write model level specifications... Forced to state property in terms of model rather than source:
Requirement: If a buffer instance becomes full, it will eventually become non-full. Consider multiple instances of a bounded buffer class... In general, a heap object has no program-level name that persists throughout the lifetime of the object. b1b2b3 Variables Heap object Property Specification Problem
What makes model-checking software difficult? Model construction OK Error trace or Finite-state model Temporal logic formula Model Checker State explosion Problems using existing checkers: Property specification Output interpretation Line 5: … Line 12: … Line 15:… Line 21:…
State Explosion Problem Moore’s law and algorithm advances can help –Holzmann: 7 days (1980) ==> 7 seconds (2000) Explosive state growth in software limits scalability Bit x1,…,xN2^N states Cost is exponential in the number of components
What makes model-checking software difficult? Model construction OK Error trace or Finite-state model Temporal logic formula Model Checker State explosion Problems using existing checkers: Property specification Output interpretation Line 5: … Line 12: … Line 15:… Line 21:…
Output Interpretation Problem Raw error trace may be 1000’s of steps long Model Description Program void add(Object o) { buffer[head] = o; head = (head+1)%size; } Object take() { … tail=(tail+1)%size; return buffer[tail]; } Gap Error trace Line 5: … Line 12: … Line 15:… Line 21:… Line 25:… Line 27:… … Line 41:… Line 47:… Must map line listing onto model description Mapping to source is made difficult by –Semantic gap & clever encodings of complex features –multiple optimizations and transformations
Bandera: An open tool set for model-checking Java source code Checker Inputs Checker Outputs Optimization Control Transformation & Abstraction Tools Model Checkers Java Source void add(Object o) { buffer[head] = o; head = (head+1)%size; } Object take() { … tail=(tail+1)%size; return buffer[tail]; } Bandera Temporal Specification Graphical User Interface Error Trace Mapping Bandera
Addressing the Model Construction Problem Numerous analyses, optimizations, two intermediate languages, multiple back-ends Slicing, abstract interpretation, specialization Variety of usage modes: simple...highly tuned Model extraction: compiling to model checker inputs: Java Source void add(Object o) { buffer[head] = o; head = (head+1)%size; } Object take() { … tail=(tail+1)%size; return buffer[tail]; } Model DescriptionModel Compiler Static Analyses Abstract Interpretation Slicing Optimizations
Addressing the Property Specification Problem An extensible language based on field-tested temporal property specification patterns []((open /\ <>close) -> ((!pushX /\ !close) U (close \/ ((pushX /\ !close) U (close \/ ((!pushX /\ !close) U (close \/ ((pushX /\ !close) U (close \/ (!pushX U close)))))))))) Using the pattern system: 2-bounded existence Between {open} and {close} {pushX} exists atMost {2} times;
Addressing the State Explosion Problem Aggressive customization via slicing, abstract interpretation, program specialization Java Source void add(Object o) { buffer[head] = o; head = (head+1)%size; } … Model DescriptionsModel Compiler Property Generate models customized wrt property! Result: multiple models --- even as many as one per property
Addressing the Output Interpretation Problem Run error traces forwards and backwards Program state queried Heap structures navigated Locks, wait sets, blocked sets displayed Like a debugger: error traces mapped back to source Java Source void add(Object o) { buffer[head] = o; head = (head+1)%size; } Object take() { … tail=(tail+1)%size; return buffer[tail]; } Model Compiler Model Checker Intermediate Representations Error trace Line 5: … Line 12: … Line 15:… Line 21:… Model Description + simulator
Bandera Architecture BIRC BIR Simulator Abstraction Engine Slicer Analyses Translators SPIN dSPIN SMV JPF Property Tool Java Jimple Parser Error Trace Display
Bounded Buffer class BoundedBuffer { Object [] buffer; int head; /* next available slot */ int tail; /* last available slot */ int bound; /* max # of elements */ public BoundedBuffer(int b) {…} public synchronized boolean isEmpty() {…} public synchronized void add(Object o) {…} public synchronized Object take () {…} } Initialization headtail Add,Add head tail Add,Take,Take headtail
Property Specification /** * observable * EXP Full: (head == tail); */ class BoundedBuffer { Object [] buffer; int head, tail, bound; public synchronized void add(Object o) {…} public synchronized Object take () {…} } Requirement: If a buffer becomes full, it will eventually become non-full. Bandera Specification: FullToNonFull: forall[b:BoundedBuffer]. {Full(b)} leads to {!Full(b)} globally;
Property Specification Requirement 3: Empty buffers must added to before being taken from Bandera Specification: NoTakeWhileEmpty: {take.Return(b)} is absent after {Empty(b)} until {add.Call(b)}; forall[b:BoundedBuffer]. /** * EXP Empty: * head == ((tail+1) % bound); */ class BoundedBuffer { int head, tail, bound; public synchronized void add(Object o) {…} public synchronized Object take () {…} } /** INVOKE Call; */ /** RETURN Return; */
public synchronized void add(java.lang.Object) { T$0 o entermonitor T$0; label0: goto label4; label1: virtualinvoke T$0.[wait():void](); T$3 = T$0.[head:int]; T$4 = T$0.[buffer:Object[]]; T$4[T$3] = o; Jimple (excerpts) Front End public synchronized void add(Object o) { while ( tail == head ) try { wait(); } catch (InterruptedException ex) {} buffer[head] = o; head = (head+1) % bound; notifyAll(); } Java
Property-directed Slicing slicing criterion generated automatically from observables mentioned in the property backwards slicing automatically finds all components that might influence the observables. Source program Resulting slice Slice mentioned in property indirectly relevant
Property-directed Slicing /** EXP Full: (head == tail) */ class BoundedBuffer { Object [] buffer_; int bound; int head, tail; public synchronized void add(Object o) { while ( tail == head ) try { wait(); } catch ( InterruptedException ex) {} buffer_[head] = o; head = (head+1) % bound; notifyAll(); }... } Included in slicing critirion Slicing Criterion All statements that assign to head, tail. indirectly relevant removed by slicing
Abstraction Engine int x = 0; if (x == 0) x = x + 1; Data domains (n<0) : neg (n==0): zero (n>0) : pos Signs negposzero int Code Signs x = zero; if (x == zero) x = pos; Collapses data domains via abstract interpretation:
Abstraction Component Functionality Variable Concrete Type Abstract Type Inferred Type Abstraction Library Bandera Abstraction Specification Language BASL Compiler PVS Jimple Abstraction Engine Abstracted Jimple x y done count o b int bool Object Buffer int …. Signs intAbs Bool …. Point Buffer
Abstraction Specification abstraction Signs abstracts int begin TOKENS = { NEG, ZERO, POS }; abstract(n) begin n {NEG}; n == 0 -> {ZERO}; n > 0 -> {POS}; end operator + add begin (NEG, NEG) -> {NEG} ; (NEG, ZERO) -> {NEG} ; (ZERO, NEG) -> {NEG} ; (ZERO, ZERO) -> {ZERO} ; (ZERO, POS) -> {POS} ; (POS, ZERO) -> {POS} ; (POS, POS) -> {POS} ; (_,_)-> {NEG, ZERO, POS}; /* case (POS,NEG), (NEG,POS) */ end public class Signs { public static final int NEG = 0; // mask 1 public static final int ZERO = 1; // mask 2 public static final int POS = 2; // mask 4 public static int abstract(int n) { if (n < 0) return NEG; if (n == 0) return ZERO; if (n > 0) return POS; } public static int add(int arg1, int arg2) { if (arg1==NEG && arg2==NEG) return NEG; if (arg1==NEG && arg2==ZERO) return NEG; if (arg1==ZERO && arg2==NEG) return NEG; if (arg1==ZERO && arg2==ZERO) return ZERO; if (arg1==ZERO && arg2==POS) return POS; if (arg1==POS && arg2==ZERO) return POS; if (arg1==POS && arg2==POS) return POS; return Bandera.choose(7); /* case (POS,NEG), (NEG,POS) */ } Compiled
Specification Creation Tools abstraction Signs abstracts int begin TOKENS = { NEG, ZERO, POS }; abstract(n) begin n {NEG}; n == 0 -> {ZERO}; n > 0 -> {POS}; end operator + add begin (NEG, NEG) -> {NEG} ; (NEG, ZERO) -> {NEG} ; (ZERO, NEG) -> {NEG} ; (ZERO, ZERO) -> {ZERO} ; (ZERO, POS) -> {POS} ; (POS, ZERO) -> {POS} ; (POS, POS) -> {POS} ; (_,_)-> {NEG, ZERO, POS}; end Automatic Generation Forall n1,n2: neg?(n1) and neg?(n2) implies not pos?(n1+n2) Forall n1,n2: neg?(n1) and neg?(n2) implies not zero?(n1+n2) Forall n1,n2: neg?(n1) and neg?(n2) implies not neg?(n1+n2) Proof obligations submitted to PVS... Example: Start safe, then refine: +(NEG,NEG)={NEG,ZERO,POS}
Bounded Buffer BIR process BoundedB() BoundedBuffer_rec = record { bound : range -1..4; head : range -1..4; tail : range -1..4; BIRLock : lock wait reentrant; }; BoundedBuffer_col : collection [3] of BoundedBuffer_rec; BoundedBuffer_col_0 : collection [3] of BoundedBuffer_rec; BoundedBuffer_ref = ref { BoundedBuffer_col, BoundedBuffer_col_0 }; State Declarations static identification of threads object state as record qualified lock representation Reference type indicates mini-heaps that can be pointed to. Easily express results of “points-to” analysis bounded integer values “mini-heaps” – one per allocator site
Abstraction Assessment Abstraction library and other abstraction facilities [ICSE’01] Automated but not completely automatic –Generating abstract programs is completely automatic, but selection of abstractions is not Not automatic for a good reason –Pervasive use of dynamically allocated data and threads in Java means that it is very difficult to apply existing automatic refinement techniques Local predicate abstraction automates the most tedious aspects of abstraction definition Abstract type inference makes the approach practical Modified search bounded by non-deterministic choice
Bounded Buffer BIR loc s34: live { b2, b1, T_0, T_6, T_8 } when true do invisible { T_8 := (T_6 % T_8); } goto s35; … loc s36: live { b2, b1, T_0 } when true do { notifyAll(T_0.BIRLock); } goto s37; … loc s37: live { b2, b1, T_0 } when true do { unlock(T_0.BIRLock); } goto s38; Guarded Transitions control point label live variable information used to optimize back-end code annotation denoting invisible transition which can be merged with following transition built-in operations on lock representations
Bounded Buffer Promela typedef BoundedBuffer_rec { type_8 bound; type_8 head; type_8 tail; type_18 BIRLock; } … loc_25: atomic { printf("BIR: OK\n"); if :: (_collect(T_0) == 1) -> T_8 = BoundedBuffer_col. instance[_index(T_0)].tail; :: (_collect(T_0) == 2) -> T_8 = BoundedBuffer_col_0. instance[_index(T_0)].tail; :: else -> printf("BIR: NullPointerException\n"); assert(0); fi; goto loc_26; } record implementation BIR AST markers get printed with error trail. Parsed and drive BIR simulator for counter-example display. Accessing mini-heaps for buffer tail component.
What’s New Full integration with JPF (now with temporal properties) Extended Bandera Specification Language –Parameter-passing to predicates –Support for Java interfaces –Quantification over container objects BIR Back-end developers kit Support for environment generation Enhanced counter-example display –Watch variables, break-points, etc. –Different view levels In the next minor release (December 2001)…
What’s New Significant extensions of BIR Back-end (dynamic creation of threads, recursive methods, user-thrown exceptions) Significant extensions to abstraction facilities UML state-chart specification/checking In the next major release (April 2002)…
Summary Bandera provides an open platform for experimentation Designed for extensibility –well-defined internal representations and interfaces –We hope this will contribute to the definition of APIs for software model- checkers Large tutorial, examples, and other documentation on web-site Over 100 registered users so far… Incorporation of other complementary tools such as SyncGen External users often frustrated due to number of different concepts in the tool kit and full language not yet supported. We are working to overcome these. Ongoing experiments focusing on avionics software with industrial partners, and various open-source software
Demo See me later for a … …or check out…