Enhancing Modular OO Verification with Separation Logic Wei-Ngan Chin1,2 Cristina David1 Huu Hai Nguyen2 Shengchao Qin3 1 National University of Singapore 2 Singapore-MIT Alliance 3 Durham University POPL 2008
Challenges of OO Verification Must support behavioral subtyping. Must support class inheritance. Must support casting. Good to support class invariants. Good to support super/direct calls. precision efficiency (minimize code re-verification) Tokyo 2008
Separation Logic and Abstraction [Parkinson&Bierman, POPL’05] Introduces abstract predicate family allows a definition for each class type very powerful idea Re-verification when method inherited Cannot handle super calls Specs may be imprecise Tokyo 2008
Separation Logic Foundations: O’Hearn and Pym, “The Logic of Bunched Implications”, Bulletin of Symbolic Logic 1999 Reynolds, “Separation Logic: A Logic for Shared Mutable Data Structures”, LICS 2002 Extension to Hoare logic to reason about shared mutable data structures p1 * p2: the heap can be split into two disjoint parts (p1 holds for one part and p2 holds for the other) Tokyo 2008
Outline Introduction Our Approach Enhanced Spec Subsumption Static & Dynamic Specs Key Principles Object Representation Class Invariants Avoiding Re-verification Deriving Specs Experimental Results Conclusion Tokyo 2008
Behavioral Subtyping Liskov's Substitutivity Principle (1988) : an object of a subclass can always be passed to a location where an object of its superclass is expected enforce behavioral subtyping with a subsumption relation In order to introduce the specification subsumption relation that we use and which takes advantage of separation logic, I will first present the behavioral subtyping requirement required by the oo paradigm and the subsumption relation for hoare logic. Tokyo 2008
Specification Subsumption Relation class A { t mn(..) where preA *! postA {...} } class B extends A { t mn(..) where preB *! postB {...} Spec (preB *! postB) is a subtype of (preA *! postA) if: (preB *! postB) <:B (preA *! postA) preA ) preB old(preA)Æ postB ) postA Liskov&Wing(TOPLAS’94) Castagna(TOPLAS’95):When the method is overriden, the parameters that determine the selection must be covariantly overridden (the corresponding parameters in the overriding method must have a lesser type) (and those that are not taken into account for selection must be contravariantly overriden). In order to enforce the behavioral subtyping requirement from the previous slide for OO programs, method specifications must satisfy a specification subsumption relation. Leavens: Actually is old(preB): refers to the values of preB in the prestate of a call. The idea is that the behavior of the subtype’s method does not need to be constrained when the supertype’s precondition does not hold in the pre-state. The postcondition of A.mn does not have to hold when its precond does not hold. ------------ Hai: As the two specifications come from different classes, we can add the subtyping relation… ------------- Leavens’ improvement: B.mn can end in a state not satisfying postA as long as it does not start in a state satisfying preA. covariance contravariance Leavens&Naumann(‘06) Tokyo 2008
Enhanced Specification Subsumption `{P} c {Q} `{P * } c {Q * } With the help of frame rule Spec (preB *! postB) is a subtype of (preA *! postA) if: ) preB postB (preB*!postB) <:B (preA*!postA) preA * * ) postA Ætype(this)<:B As our approach is based on sep logic, we want to use the frame rule of this logic, which allows local reasoning:the specification of a command mentions only what is actually used by the command; by using the frame rule, one can move from local to non-local sopecifications. ---------------------------------------------------------------------------------------------------------------------- There is a side condition requiring that command c does not modify the stack variables in \Delta (as * only describes the separation of heap locations and not variables). the frame rule allows local reasoning: the specification of a command mentions only what is actually used by the command; by using the frame rule, one can move from local to non-local sopecifications by adding the additional state (denoted by \Delta). -------------------------------------------------------------------------------------------------------------------------- (The frame rule can be used to allow the additional state to be preserved by the call.) --------------------------------------------------------------------------------------------------------------------------- \Delta does not contain any primed variables (primed vars are used to denote the latest version of vars and might appear in postconditions). Castagna(TOPLAS‘95) Tokyo 2008
Outline Introduction Our Approach Enhanced Spec Subsumption Static & Dynamic Specs Key Principles Object Representation Class Invariants Avoiding Re-verification Deriving Specs Experimental Results Conclusion I’ll go on with an example illustrating the use of static and dynamic specs in our approach. Tokyo 2008
Static and Dynamic Spec A static spec: describes just a single method used for statically-dispatched calls (e.g. super/direct) can be very precise A dynamic spec: describes a method and its overriding methods used for dynamically-dispatched calls less precise You’ve seen before the same distinction between static & dynamic specs in Matthew’s talk, who independently from us developed a similar approach to OO verification together with Gavin Bierman. However, they use the abstract predicate families, while we try to enforce the behavioral subtyping requirement by weakening the specification. Tokyo 2008
Static and Dynamic Specs : Example class Cnt { int val; Cnt(int v) {this.val:=v} void tick() {this.val:=this.val+1} int get() {this.val} void set(int x) {this.val:=x} } class FastCnt extends Cnt { FastCnt(int v) {this.val:=v} void tick() {this.val:=this.val+2} } class PosCnt extends Cnt inv this.val¸0 { PosCnt(int v) {this.val:=v} void set(int x) {if x¸0 then this.val:=x else error()} } static this::Cnt<n>$ *! this::Cnt<n+1>$ dynamic this::Cnt<n>$ *! this::Cnt<b>$ - Mirrors Captures in a precise manner the behavior of the method ------------------------------------------------------------------------------- In most of the previous techniques each method was given one spec which was supposed to enforce behavioral subtyping. However, as we try to achieve both precision and reuse we need two specs: ... The $ symbol is a shorthand signifying the fact that our object representation captures all the fields of the subclass. ---- Overriding methods weaken the spec of the overriden method by strengthening the precond and wekening the postcond. Therefore, we advocate the use of the more accurate static spec whenever possible. Tokyo 2008
Static and Dynamic Specs : Example class Cnt { int val; Cnt(int v) {this.val:=v} void tick() {this.val:=this.val+1} int get() {this.val} void set(int x) {this.val:=x} } class FastCnt extends Cnt { FastCnt(int v) {this.val:=v} void tick() {this.val:=this.val+2} } class PosCnt extends Cnt inv this.val¸0 { PosCnt(int v) {this.val:=v} void set(int x) {if x¸0 then this.val:=x else error()} } static this::Cnt<n>$ *! this::Cnt<n+1>$ dynamic this::Cnt<n>$ *! this::Cnt<b>$ Æ n¸0 Æ n+1·b·n+2 Æ n+1·b·n+2 Æ b=n+1 Cnt.tick is overridden ) weaken the postcond PosCnt has inv this.val¸0 ) strengthen the precond Tokyo 2008
Outline Introduction Our Approach Enhanced Spec Subsumption Static & Dynamic Specs Key Principles Object Representation Class Invariants Avoiding Re-verification Deriving Specs Experimental Results Conclusion Tokyo 2008
Key Principles Static spec must be given for each new method. Code verification is done only for static spec. Dynamic spec is either given or derived. Subsumption relations: Static-Spec(A.mn) Dyn-Spec(A.mn) <: min. re-verification class A { // defines mn } Tokyo 2008
Key Principles Static spec must be given for each new method. Code verification is done only for static spec. Dynamic spec is either given or derived. Subsumption relations: Static-Spec(A.mn) Dyn-Spec(A.mn) <: min. re-verification class A { // defines mn } class B extends A { // overrides mn Dyn-Spec(B.mn) <: ensures behavioral subtyping Tokyo 2008
Key Principles Static spec must be given for each new method. Code verification is done only for static spec. Dynamic spec is either given or derived. Subsumption relations: Static-Spec(A.mn) Dyn-Spec(A.mn) <: min. re-verification class A { // defines mn } class B extends A { // overrides mn class C extends A { // inherits mn Dyn-Spec(B.mn) <: ensures behavioral subtyping Static-Spec(C.mn) min. re- verification <: A similar approach was proposed independently by Parkinson & Bierman in the same proceeding. If this subsumption relation does not hold we just proceed with body verification. Tokyo 2008
Outline Introduction Our Approach Enhanced Spec Subsumption Static & Dynamic Specs Key Principles Object Representation Class Invariants Avoiding Re-verification Deriving Specs Experimental Results Conclusion Tokyo 2008
Object Representation extension record for the extra fields of B fields of A fields of B class A { // fields v1..n .... } y::A<t,v1..n,q>*q::Ext<B,w1..m,p> upcast downcast class B extends A { // fields w1..m .... } y::B<t,v1..n,w1..m,p> actual type extension fields Tokyo 2008
Object Representation class A { // fields v1..n .... } y::A<t,v1..n,q>*q::Ext<B,w1..m,p> upcast downcast class B extends A { // fields w1..m .... } y::B<t,v1..n,w1..m,p> LOSSLESS CASTING Tokyo 2008
Partial and Full Views Partial view: Full view: x Static specs Seen as a c-class obj (actual type t) Fields v1..n of c-class Other fields w1..m of subclass t of c x Partial view: - x::c<t,v1..n,p> - no extension records - shorthand x::c<v1..n> Static specs Improves precision Full view: - x::c<t,v1..n,p> * p::ExtAll<c,t> - ExtAll<c,t> captures all the extensions of subclass t of c - shorthand x::c<v1..n>$ Dynamic specs Tokyo 2008
Outline Introduction Our Approach Enhanced Spec Subsumption Static & Dynamic Specs Key Principles Object Representation Class Invariants Avoiding Re-verification Deriving Specs Experimental Results Conclusion Tokyo 2008
Ensuring Class Invariants Q: How and when to check for class inv? class PosCnt extends Cnt inv this.val¸0 { ... } Invariant-enhanced predicate: root::PosCnt#I<t,v,p> == root::PosCnt<t,v,p> * v¸0 Tokyo 2008
Invariant-Enhanced Predicate : Example inv is temporarily broken class PosCnt extends Cnt inv this.val¸0 { ... void set(int x) static this::PosCnt<v> Æ x¸0 *! this::PosCnt#I<x> void tick() static this::PosCnt#I<v> *! this::PosCnt#I<v+1> } Tokyo 2008
Invariant-Enhanced Predicate : Example inv is temporarily broken class PosCnt extends Cnt inv this.val¸0 { ... void set(int x) static this::PosCnt<v> Æ x¸0 *! this::PosCnt#I<x> void tick() static this::PosCnt#I<v> *! this::PosCnt#I<v+1> } inv enforced at each call site assumed at the beginning of method decl Tokyo 2008
Invariant-Enhanced Predicate : Example inv is temporarily broken class PosCnt extends Cnt inv this.val¸0 { ... void set(int x) static this::PosCnt<v> Æ x¸0 *! this::PosCnt#I<x> void tick() static this::PosCnt#I<v> *! this::PosCnt#I<v+1> } inv enforced at the end of the method decl assumed after each call site inv enforced at each call site assumed at the beginning of method decl Tokyo 2008
Outline Introduction Our Approach Enhanced Spec Subsumption Static & Dynamic Specs Key Principles Object Representation Class Invariants Avoiding Re-verification Deriving Specs Experimental Results Conclusion Tokyo 2008
Re-verification of Inherited Methods class A { // fields v* ... t mn(…) static spA { ..body .. } } Is there a need to re-verify spB against body of mn? class B extends A { // fields w* .. t mn(…) static spB // method mn is inherited } Tokyo 2008
Statically-Inherited Method: Intuition Seen as a c-class obj (actual type t) Fields v1..n of c-class Other fields w1..m of subclass t of c this Will a call this.mn(...) modify w*? NO YES B.mn is statically inherited NOT statically inherited Tokyo 2008
Statically-Inherited Method: Definition class A { // fields v* ... t mn(…) static spA { ... this.mn2(); ... } } A.mn is statically-inherited into B if: it is not overridden in B class B extends A { // fields w* .. t mn(…) static spB // method mn is inherited } Tokyo 2008
Statically-Inherited Method: Definition class A { // fields v* // defines mn2 ... t mn(…) static spA { ... this.mn2(); ... } } A.mn is statically-inherited into B if: it is not overridden in B for all the calls this.mn2(..) with mnmn2, B.mn2 is statically-inherited from A.mn2 class B extends A { // fields w* // inherits mn2 .. t mn(…) static spB // method mn is inherited } Tokyo 2008
Re-verification of Inherited Methods Q: Verify spB against the body of A.mn? B.mn statically inherited or full views used? YES NO spB inherited? NO YES - is the tradeoff of using partial views which although improve precision, make the reverification needed NO VERIFICATION spA<:spB verify spB against the body of A.mn Tokyo 2008
Outline Introduction Our Approach Enhanced Spec Subsumption Static & Dynamic Specs Key Principles Object Representation Class Invariants Avoiding Re-verification Deriving Specs Experimental Results Conclusion Tokyo 2008
Deriving Specs (1) Too many specs to write? Derive dynamic specs from static counterparts Refine dynamic specs to meet behavioral subtyping spec specialization (from superclass) spec abstraction (from subclass) Inherit static specs This is not a core part of our work and therefore, I don’t give more details. Tokyo 2008
Deriving Specs (2) Specification Specialization strengthen dynamic spec of overriding method with the dynamic spec of overridden method intersection type Specification Abstraction weaken dynamic spec of overridden method with dynamic spec of the overriding method union type Tokyo 2008
Outline Introduction Our Approach Enhanced Spec Subsumption Static & Dynamic Specs Key Principles Object Representation Class Invariants Avoiding Re-verification Deriving Specs Experimental Results Conclusion Tokyo 2008
Initial Experiment Code Verification > Spec Subsumption Checking Tokyo 2008
Conclusion Must support: Advocate co-existence of static and dynamic specs Key principles: use static spec where possible ) precision keep code re-verifications to a minimum ) efficiency Slight emphasis on static specs: can derive dynamic specs Must support: behavioral subtyping, class inheritance, casting Good to support: class invariants, super/direct calls Danger: lose precision, efficiency Tokyo 2008
Thank you! Questions? Tokyo 2008
Spec Subsumption Checking Static_spec(Cnt.set) <: Dyn_spec(Cnt.set) this::Cnt<t,v,p>*->this::Cnt<t,x,p> <: this::Cnt<t,v,q>*q::ExtAll<Cnt,t> /\ x>=0 *->this::Cnt<t,x,q>*q::ExtAll<Cnt,t> Contravariance on precond: this::Cnt<t,v,q>*q::ExtAll<Cnt,t> /\ x>=0 |-this::Cnt<t,v,p>* = p::ExtAll<Cnt,t> /\ x>=0 Covariance on postcond: this::Cnt<t,x,p>* |- this::Cnt<t,x,q>*q::ExtAll<Cnt,t> Tokyo 2008
Spec Subsumption Checking Dyn_spec(PosCnt.set) <: Dyn_spec(Cnt.set) this::PosCnt<v>$ *! this::PosCnt#I<x>$ <: this::Cnt<v>$ /\ x>=0 /\ (type(this)<:PosCnt) *! this::Cnt<x>$ Contravariance on precond: this::Cnt<v>$ /\ x>=0 /\ (type(this)<:PosCnt)|-this::PosCnt<v>$ * = x>=0 Covariance on postcond: this::PosCnt#I<x>$ * |- this::Cnt<t,x,q>$ Tokyo 2008