270 likes | 348 Views
Specifying Temporal Properties of Software Using the Bandera Specification Language. [U. Hawaii] [Kansas State] [Kansas State] [Kansas State]. James Corbett Matthew Dwyer John Hatcliff Robby. http://www.cis.ksu.edu/santos/bandera. Graphical User Interface. Optimization Control.
E N D
Specifying Temporal Properties of Software Using the Bandera Specification Language [U. Hawaii] [Kansas State] [Kansas State] [Kansas State] James Corbett Matthew Dwyer John Hatcliff Robby http://www.cis.ksu.edu/santos/bandera
Graphical User Interface Optimization Control Checker Inputs ? void add(Object o) { buffer[head] = o; head = (head+1)%size; } Object take() { … tail=(tail+1)%size; return buffer[tail]; } Model Checkers Transformation & Abstraction Tools Checker Outputs Java Source Error Trace Mapping Bandera Bandera:An open tool set for model-checking Java source code Slicing Temporal Specification Abstract Interpretation Static Analysis
Issue: Rendering Requirement • Often difficult to formalize a requirement in temporal logic “Between the window open and the window close, button X can be pushed at most twice.” …is rendered in LTL as... []((open && <>close) -> ((!pushX && !close) U (close || ((pushX && !close) U (close || ((!pushX && !close) U (close || ((pushX && !close) U (close || (!pushX U close))))))))))
Graphical User Interface mismatch! CTL CSP void add(Object o) { buffer[head] = o; head = (head+1)%size; } Object take() { … tail=(tail+1)%size; return buffer[tail]; } SMV FDR Transformation & Abstraction Tools Java Source Bandera Issue: Checker Dependence Checker Inputs LTL LTL Temporal Specification Model Checkers Spin
Issue: Representation Dependence • Source’s representation Heap.b.head == Heap.b.tail • Model’s representation (((_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))
Variables b1 b2 b3 Heap object Issue: Naming Heap-allocated Objects Consider multiple instances of a bounded buffer class... Requirement: If a buffer instance becomes full, it will eventually become non-full. In general, a heap object has no program-level name that persists throughout the lifetime of the object.
Quantification BSL: Bandera Specification Language • Propositions stated in terms of source code features • Based on an extensible system of temporal specification patterns • Heap objects are named via object quantification Temporal Property Specification (via pattern language) Assertion Property Specification (selective enabling) Predicate Definition Assertion Definition
Pre-conditions @assert PRE <name> <exp>; • Post-conditions @assert POST <name> <exp>; • Arbitrary Locations @assert LOCATION[<label>] <name> <exp>; Assertion Forms /** * @assert * PRE foo: (I > 5); * POST bar: (I < 10); * LOCATION[here] checka: * (m.q.a == 4); */ public mymethod(int I) { … … here: … … }
Static/Instance Data Constraints • Invocation @observable [static] EXP <name> <exp>; @observable INVOKE <name> <exp>; • Return @observable RETURN <name> <exp>; • Arbitrary Locations @observable LOCATION[<label>] <name> <exp>; Predicate Forms
Relationship between assertion PRE and predication INVOKE Semantic Issues @assert PRE <name> <exp>; IMPLICATION: holds if <exp> is true WHEN control is at entry of method (true otherwise) @observable INVOKE <name> <exp>; CONJUNCTION: holds if <exp> is true AND control is at entry of method
Semantic Issues (o1 != null) && (o1.next != null) && ( ) o1.next.value == 0
Given a software requirement... Methodology: Property Specification • Identify observables (propositions) in requirement • Define propositions in source Java-doc comments • Use GUI to select appropriate temporal pattern parameterized by declared observables • Add quantification if property contains instance propositions.
Initialization head tail head head tail tail 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 () {…} } Add,Add Add,Take,Take
Initialization head tail Add,Add head head tail Add,Take,Take tail Bounded Buffer public BoundedBuffer(int b) { bound = b; buffer = new Object[bound]; head = 0; tail = bound-1; } public synchronized boolean isEmpty() { return head == ((tail+1) % bound); }
Initialization head tail Add,Add head head tail Add,Take,Take tail Bounded Buffer public synchronized void add(Object o) { while ( tail == head ) try { wait(); } catch (InterruptedException ex) {} buffer_[head] = o; head = (head+1) % bound; notifyAll(); } public synchronized Object take() { while (head == ((tail+1) % bound)) try { wait(); } catch (InterruptedException ex) {} tail = (tail+1) % bound; notifyAll(); return buffer_[tail]; }
Bounded Buffer Properties • Full buffers eventually become non-full • Indices always stay in range • Empty buffers must be added to before being taken from • Buffers are constructed with positive bounds • Elements are always added in correct position
Requirement 1: If a buffer becomes full, it will eventually become non-full. Bandera Specification: FullToNonFull: {!Full(b)} responds to {Full(b)} globally; 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 () {…} } forall[b:BoundedBuffer].
Bandera Specification: IndexRangeInvariant: {HeadRange(b) && TailRange(b)} is universal globally; Property Specification /** * @observable * EXP HeadRange: * head >= 0 && head < bound; * Exp TailRange: * tail >= 0 && tail < bound; */ Requirement 2: Indices always stay in range. class BoundedBuffer { Object [] buffer; int head, tail, bound; public synchronized void add(Object o) {…} public synchronized Object take () {…} } forall[b:BoundedBuffer].
Bandera Specification: NoTakeWhileEmpty: {take.Return(b)} is absent after {Empty(b)} until {add.Call(b)}; Property Specification /** * @observable * EXP Empty: * head == ((tail+1) % bound); */ Requirement 3: Empty buffers must added to before being taken from class BoundedBuffer { int head, tail, bound; public synchronized void add(Object o) {…} public synchronized Object take () {…} } /** * @observable INVOKE Call; */ /** * @observable RETURN Return; */ forall[b:BoundedBuffer].
Bandera Specification: PositiveBound: enable assertions {PositiveBound}; Property Specification Requirement 4: /** * @assert * PRE PositiveBound: * (b > 0); */ Buffers are constructed with positive bounds public BoundedBuffer(int b) { bound = b; buffer = new Object[bound]; head = 0; tail = bound-1; }
Quantification forall[b:BoundedBuffer].P(b) • Quantified set is not fixed • varies within executions • varies across executions • Solution • by adding a state variable (for b) that will eventually be bound non-deterministically to each instance • by enabling checking of the formula only when variable is bound to an instance
Quantification (Cont’d) (!selected U (selected && P(b))) || []!selected (!selected (selected && P(b))) []!selected !selected [1] new BoundedBuffer(n) new BoundedBuffer(n) [1] selected !selected [2] new BoundedBuffer(m) new BoundedBuffer(m) new BoundedBuffer(m) !selected [1] selected [2] selected new BoundedBuffer(k) [3] new BoundedBuffer(k) new BoundedBuffer(k) new BoundedBuffer(k) [1] selected [2] selected [3] selected !selected
Quantification (Cont’d) class heap { public static BoundedBuffer b; } class BoundedBuffer { Object [] buffer; int head, tail, bound; public BoundedBuffer(int n) { ... if (heap.b == null && Bandera.choose()) { heap.b = this; } } } class BoundedBuffer { Object [] buffer; int head, tail, bound; public BoundedBuffer(int n) { ... } }
Quantification (Cont’d) forall[b:BoundedBuffer]. {Full(b)} leads to {!Full(b)} globally; (heap.b == null U (heap.b != null && ([](heap.b.head == heap.b.tail) -> <>(heap.b.head != heap.b.tail)))) || [](heap.b == null)
Specification: PositiveBound: enable assertions {PosBound}; Embedding Assertion class BoundedBuffer { Object [] buffer; int head, tail, bound; public BoundedBuffer(int n) {…} public synchronized void add(Object o) {…} } /** * @assertion * PRE PosBound1: (n > 0); */ public BoundedBuffer(int n) { Bandera.assert(n > 0); ... }
Test Result & Assessments Property Sliced Never-claim Stored States States BufferAssertions Yes - 17797 IndexRangeInv Yes 14 45215 IndexRangeInv, BufferAssertions Yes 14 115387 FullToNonFull Yes 25 64687 FullToNonFull, BufferAssertions Yes 25 154842
Summary • Motivation: Makes it easier for non-expert to write temporal specifications for Java programs • How? • Source level • Pattern based approach • Tool supports: • automatic translation • guides for writing specification (GUI) • HTML documentation generation