580 likes | 717 Views
The need for flexible object invariants. Sophia Drossopoulou. Peter M üller. Alexander J. Summers. Imperial College London. Imperial College London. ETH Z ürich. Object invariants fundamental to software design.
E N D
The need for flexible object invariants Sophia Drossopoulou Peter Müller Alexander J. Summers Imperial College London Imperial College London ETH Zürich
Object invariants fundamental to software design. • Therefore, undisputedly, object invariants need to be reflected in some way in specifications. • How should object invariants be reflected? • Should they be “encoded” in pre/post conditions? • ...or kept as a first class concept? • We argue that invariants should be first class
Overview • Object Invariants, Mixed vs. Pure Logics, Invariant Protocols • 1st Example: Composite • invariants support specifications which reflect the programmer’s thinking, and allow local description of global properties • 2nd Example: PIP • invariants essential for properties over inaccessible objects
Object Invariants, Mixed Logics, Pure Logics, Invariant Protocols
Object Invariants • Object invariants describe consistency of objects • Invariants cannot be expected to hold at all times: allow invariant to be temporarily broken • Simplest approach: Visible States Semantics:Invariants hold at method call boundaries • can be assumed at start of method call • may be broken during a call • must be re-established before end of method
Protocol, Mixed, and Pure Logics • Invariant protocol • a discipline for when invariant must hold • Mixed Logic • a logic which incorporates such implicit guarantees in the reasoning. • Pure logic • a logic in which method specifications directly express all relevant information for verification questions • e.g., Hoare Logic, Separation Logic, Regional Logic • In such logics, information can be hidden (abstract predicates), but never omitted – specifications must cover all concerns of the method (incl. subcalls)
The Composite pattern • A tree whose nodes preserve properties related to all their descendant leaves. • Subtrees may be added directly to a node without first visiting its parents. • suggested as a specification/verification challenge by Müller, Leavens and Poetzsch-Heffter • Various solutions proposed at SAVCBS’08
The Composite class class Composite int total; Composite par; Composite[] chldrn; int count; void add(Composite c); … }
The Composite class 4 2 4 class Composite int total; Composite par; Composite[] chldrn; int count; void add(Composite c); … } OK(c) ≣c.total = max{ c’.total | c’ ∈ c.chldrn } 2 2 4 1
The Composite - code void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } voidupdate(int n){ total=total+n; if (parent!=null){ parent.update(n); } }
Composite – code void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } voidupdate(int n){ total=total+n; if (parent!=null){ parent.update(n); } }
Composite – code void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } voidupdate(int n){ total=total+n; if (parent!=null){ parent.update(n); } } this c
Composite – code void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } void update(int n){ total=total+n; if (parent!=null){ parent.update(n); } } this c
Composite – code void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } void update(int n){ total=total+n; if (parent!=null){ parent.update(n); } } this
Composite – code void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } void update(int n){ total=total+n; if (parent!=null){ parent.update(n); } } this
Composite – code this void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } void update(int n){ total=total+n; if (parent!=null){ parent.update(n); } }
Composite – code this void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } void update(int n){ total=total+n; if (parent!=null){ parent.update(n); } }
Composite – code this void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } void update(int n){ total=total+n; if (parent!=null){ parent.update(n); } }
Composite – code this void add(Composite c) components[count]=c; count ++; c.parent = this; update(c.total); } void update(int n){ total=total+n; if (parent!=null){ parent.update(n); } }
Composite code - observations • The code code follows a natural argument, where we are concerned with one broken object at a time • When an object changes its state and as a result breaks the invariant of another object, it notifies the latter
Composite code - observations • The code code follows a natural argument, where we are concerned with one broken object at a time • When an object changes its state and as a result breaks the invariant of another object, it notifies the latter We now consider the specifications in mixed and in pure logic.
Composite – pure logic // PRE … // … // … // POST … // … void add(Composite c) … } // PRE … //… // POST … //… voidupdate(int n){ … } this c
Composite – pure logic // PRE c.par==null, // ∀o∈ c.chdrn*: OK(o) // … // POST … // … void add(Composite c) … } // PRE … //… // POST … //… voidupdate(int n){ … } this c
Composite – pure logic // PRE c.par==nul, // ∀o∈ c.chdrn*: OK(o) // ∀o∈ this.par*.chdrn*: OK(o) // POST … // … void add(Composite c) … } // PRE … //… // POST … //… voidupdate(int n){ … } this c
Composite – pure logic // PRE c.par==nul, // ∀o∈ c.par*.chdrn*: OK(o) // ∀o∈ this.par*.chdrn*: OK(o) // POST c∈ this.chdrn, // ∀o∈ this.par*.chdrn*: OK(o), void add(Composite c) … } // PRE … //… // POST … // … voidupdate(int n){ … } this c
Composite – pure logic // PRE c.par==nul, // ∀o∈ c.par*.chdrn*: OK(o) // ∀o∈ this.par*.chdrn*: OK(o) // POST c∈ this.chdrn, // ∀o∈ this.par*.chdrn*: OK(o), void add(Composite c) … } // PRE ∀o∈ this.par*.chdrn*: // o=this ⋁ OK(o) // POST …, // … voidupdate(int n){ … } this
Composite – pure logic // PRE c.par==nul, // ∀o∈ c.par*.chdrn*: OK(o) // ∀o∈ this.par*.chdrn*: OK(o) // POST c∈ this.chdrn, // ∀o∈ this.par*.chdrn*: OK(o), void add(Composite c) … } // PRE ∀o∈ this.par*.chdrn*: //o=this ⋁ OK(o) // POST ∀o∈ this.par*.chdrn*: //OK(o) voidupdate(int n){ … } this
Composite - pure logic - observations • Consistency properties are “deep”: expressed over all (indirectly) relevant objects • Specification footprint exceeds conceptual footprint (term borrowed from Adam Wright) • Specification does not reflect the natural argument • Specification is “global” – natural argument is “local’
Mixed Logics • Approach based on visible states semantics, but allows exceptions to visible states rules • Allow some invariants to be broken for a call by adding a specification construct broken • Method signatures can declare “broken” invariants: // BROKEN(o) … • INV(o) need not hold before call to m, but must be established by the method body.
Composite – mixed logic class Composite{ INV: OK(o) … // PRE c.par==null // POST … void add(Composite c) … } // PRE … // POST … voidupdate(int n){ … } }} this c
Composite – mixed logic class Composite{ INV: OK(o) … // PRE c.par==null // POST c∈ this.chdrn, void add(Composite c) … } // PRE // POST voidupdate(int n){ … } }} this c
Composite – mixed logic class Composite{ INV: OK(o) … // PRE c.par==nul, // POST o∈ this.chdrn, void add(Composite c) … } // PRE BROKEN(this) // POST … voidupdate(int n){ … } this
Composite – mixed logic class Composite{ INV: OK(o) … // PRE c.par==nul, // POST o∈ this.chdrn, void add(Composite c) … } // PRE BROKEN(this) // POST voidupdate(int n){ … } this
Composite – mixed logic class Composite{ INV: OK(o) … // PRE c.par==null // POST c∈ this.chdrn, void add(Composite c) … } // PRE // POST voidupdate(int n){ … } }} this Specification focuses on requirements relevant to this object c
Composite – mixed logic class Composite{ INV: OK(o) … // PRE c.par==null // POST o∈ this.chdrn, void add(Composite c) … } // PRE // POST voidupdate(int n){ … } }} Specification focuses on requirements relevant to this object Specification only reflects local concerns this c
Composite – mixed logic class Composite{ INV: OK(o) … // PRE c.par==null // POST o∈ this.chdrn, void add(Composite c) … } // PRE // POST voidupdate(int n){ … } }} Specification focuses on requirements relevant to this object Specification only reflects local concerns Concerns of other objects handled by invariant protocol this c
Composite – mixed logic Broken declarations reflect pattern of break/re-establish invariants class Composite{ INV: OK(o) … // PRE c.par==nul, // POST o∈ this.chdrn, void add(Composite c) … } // PRE BROKEN(this) // POST voidupdate(int n){ … } this
Composite – mixed logic Broken declarations reflect pattern of break/re-establish invariants Specification expresses “natural argument” from implementation class Composite{ INV: OK(o) … // PRE c.par==nul, // POST o∈ this.chdrn, void add(Composite c) … } // PRE BROKEN(this) // POST voidupdate(int n){ … } this
Composite - mixed logic - observations • Specification reflects the natural argument (where re are concerned with one broken object at a time) • Specification is “local” – natural argument is “local’ • Verification based on work by Middelkoop, Barnett, Naumann • Verification encoded in Boogie PL.
Composite - mixed logic - framing • But what about framing? • The client is not interested in the value of total. • Therefore the “public spec” need not mention total. • The “private spec”may just mention Composite.total.
Conclusions from 1stExample • Invariants allow simple specifications • Invariants reflect the natural argument • Invariants support local description of global properties
PIP • Variation of Composite, but without components field. • Nodes represent OS resources and processes • Nodes keep a pointer (parent) to other Node blocking it – if any • A Node’s priority should be equal to the maximum priorities of all nodes blocked by it • class Node { • int priority; Node parent; • // INV: priority = max • // {o.priority | o.parent == this} … • }
PIP • Variation of Composite, but without components field. • Nodes represent OS resources and processes • Nodes keep a pointer (parent) to other Node blocking it – if any • A Node’s priority should be equal to the maximum priorities of all nodes blocked by it • class Node { • int priority; Node parent; • // INV: priority = max • // {o.priority | o.parent == this} … • } An object’s properties depend on state of inaccessible objects
PIP – topology class Node{ Node par; intprio; }
PIP – code class Node{ Node par; intprio; voidadd(Noden){ n.par = this;this.update(n.prio); } voidupdate(int n){ if (prio<n) { prio=n; this.parent.update(n) } }
PIP – mixed logic class Node{ Node par; intprio; // INV prio = max{ n.prio | n.par=this } // PRE n.par==nul, // POST n.par==this voidadd(Noden){ n.par = this;this.update(n.prio); } // PRE BROKEN(this) // POST voidupdate(int n){ if (prio<n) { prio=n; this.parent.update(n) } }
PIP – mixed logic Mixed Logic specification reflects the natural argument. class Node{ Node par; intprio; // INV prio = max{ n.prio | n.par=this } // PRE n.par==nul, // POST n.par==this voidadd(Noden){ n.par = this;this.update(n.prio); } // PRE BROKEN(this) // POST voidupdate(int n){ if (prio<n) { prio=n; this.parent.update(n) } }
PIP – pure logic – v1 class Node{ Node par; intprio; ghost Set<Node> children; // PRE n.par==nul, //∀n∈ this.par*: //n.prio=max{n’.pric | n’∈n.children } // POST n.par==this //n∈this.children //∀n∈ this.par*: …voidadd(Noden){ …. } } a.children={b,c} a: 5 c: 5 b: 3