480 likes | 635 Views
Invariants for Non-Hierarchical Object Structures. Ronald Middelkoop, Kees Huizing, Ruurd Kuiper and Erik Luit. Overview. Setting: Sequential Object-Oriented Development Classical Local Invariants Advantages of invariants Non-local Invariants Methods that re-establish an invariant
E N D
Invariants for Non-Hierarchical Object Structures Ronald Middelkoop, Kees Huizing, Ruurd Kuiper and Erik Luit
Overview • Setting: Sequential Object-Oriented Development • Classical Local Invariants • Advantages of invariants • Non-local Invariants • Methods that re-establish an invariant • Information Hiding • Conclusions
Classical Local Invariants Visible state: pre- or post-state of a method execution Class C int i; inv this.i > 2; C() {…} Visible state semantics: After an object’s creation, its invariant I holds in every visible state int getI() {…} void setI(int newI) {…}
Classical Local Invariants Visible state: pre- or post-state of a method execution Class C int i; inv this.i > 2; C() {…} • Theorem: • After an object’s creation, its invariant I holds in every visible state, when • The constructor of that object establishes I • All methods in the program preserve I • No method is called while I is invalid int getI() {…} void setI(int newI) {…}
Classical Local Invariants Visible state: pre- or post-state of a method execution Class C int i; inv this.i > 2; C() { pre: true; post: true; impl: this.i = 4; } • Theorem: • After an object’s creation, its invariant I holds in every visible state, when • The constructor of that object establishes I • All methods in the program preserve I • No method is called while I is invalid int getI() {…} void setI(int newI) {…}
Classical Local Invariants Visible state: pre- or post-state of a method execution Class C int i; inv this.i > 2; C() {…} • Theorem: • After an object’s creation, its invariant I holds in every visible state, when • The constructor of that object establishes I • All methods in the program preserve I • No method is called while I is invalid int getI() { pre: true; post: result == this.i; impl: returnthis.i; } void setI(int newI) {…}
Classical Local Invariants Visible state: pre- or post-state of a method execution Class C int i; inv this.i > 2; C() {…} • Theorem: • After an object’s creation, its invariant I holds in every visible state, when • The constructor of that object establishes I • All methods in the program preserve I • No method is called while I is invalid int getI() { pre: true; post: result == this.i; impl: returnthis.i; } void setI(int newI) { pre: newI > 2; post: this.i == newI; impl: this.i = newI; }
Advantages of Invariants Class C int i; inv this.i > 2; C() {…} int getI() {…} void setI(int newI) {…} int m() { pre: true; post: result > 4; impl: return this.i +2; }
Advantages of Invariants Class C int i; C() {…} int getI() {…} void setI(int newI) {…} int m() { pre: true; post: result > 4; impl: return this.i +2; } m relies on i > 2
Advantages of Invariants Class C int i; C() {…} int getI() {…} void setI(int newI) {…} int m() { pre: this.i > 2; post: result > 4; impl: return this.i +2; } m relies on i > 2m requires i > 2
Advantages of Invariants Class C Class D int i; C c; C() {…} inv this.c != null; int getI() {…} int useC() { pre: true; post: result > 4; impl: return this.c.getI() + 2; } void setI(int newI) {…} int m() { pre: this.i > 2; post: result > 4; impl: return this.i +2; } useC relies on c.i > 2
Advantages of Invariants Class C Class D int i; C c; C() {…} inv this.c != null; int getI() {…} int useC() { pre: this.c.i > 2; post: result > 4; impl: return this.c.getI() + 2; } void setI(int newI) {…} int m() { pre: this.i > 2; post: result > 4; impl: return this.i +2; } useC relies on c.i > 2useC requires c.i > 2
Advantages of Invariants Class D Class E C c; D d; inv this.c != null; int useUseC() { pre: this.d != null; post: result > 8; impl: return this.d.useC() + 4; } int useC() { pre: this.c.i > 2; post: result > 4; impl: return this.c.getI() + 2; } useUseC calls d.useC() useC requires c.i > 2 useUseC has to establish d.c.i > 2
Advantages of Invariants Class D Class E C c; D d; inv this.c != null; int useUseC() { pre: this.d != null this.d.c.i > 2; post: result > 8; impl: return this.d.useC() + 4; } int useC() { pre: this.c.i > 2; post: result > 4; impl: return this.c.getI() + 2; } useUseC calls d.useC() useC requires c.i > 2 useUseC has to establish d.c.i > 2 useUseC requires d.c.i > 2
Advantages of Invariants Class D Class E C c; D d; inv this.c != null; int useUseC() { pre: this.d != null this.d.c.i > 2; post: result > 8; impl: return this.d.useC() + 4; } int useC() { pre: this.c.i > 2; post: result > 4; impl: return this.c.getI() + 2; } The invariant property propagates throughout the specification
Advantages of Invariants Class D Class E C c; D d; inv this.c != null; int useUseC() { pre: this.d != null this.d.c.i > 2; post: result > 8; impl: return this.d.useC() + 4; } int useC() { pre: this.c.i > 2; post: result > 4; impl: return this.c.getI() + 2; } Advantage 1: Significant reduction in specification overhead Advantage 2: Re-implementations of a method can rely on a different set of invariants
Advantages of Invariants • Sufficient: • The verifier of a method M that relies on an invariant I can • Assume that I holds when M is called • Deduce if a method M’ that is called by M preserves I Advantage 1: Significant reduction in specification overhead Advantage 2: Re-implementations of a method can rely on a different set of invariants
Non-local Invariants Class CSubject Class CObserver int d; CSubject cs; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; } inv this.cs != null; CObserver(CSubject toObs) {…} int getVal() { pre: true; post: return == f(this.cs.d); impl: int i = this.cs.getD(); return f(i); //pseudocode } void getD() {…} CSubject1 CObserver1
Non-local Invariants Class CSubject Class CObserver int d; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; } inv this.cs != null; CObserver(CSubject toObs) {…} int getVal() { pre: true; post: return == f(this.cs.d); impl: return this.i; } void getD() {…} CSubject1 CObserver1
Non-local Invariants Class CSubject Class CObserver int d; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; } inv this.i == f(this.cs.d); inv this.cs != null; CObserver(CSubject toObs) {…} void getD() {…} int getVal() { pre: true; post: return == f(this.cs.d); impl: return this.i; } • The constructor establishes I • All methods in the program preserve I • No method is called while I is invalid CObserver X • X.cs ≠ this “invariant of X still holds after assignment”
Non-local Invariants Class CSubject Class CObserver int d; CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; } inv this.i == f(this.cs.d); inv this == this.cs.co; inv this.i == f(this.cs.d); inv this.cs != null; CObserver(CSubject toObs) {…} int getVal() { pre: true; post: return == f(this.cs.d); impl: return this.i; } void getD() {…} • The constructor establishes I • All methods in the program preserve I • No method is called while I is invalid void update() { post: this.i == f(this.cs.d); } CObserver X • X.cs ≠ this “invariant of X still holds after assignment” CObserver X • X.cs = this this.co = X
Non-local Invariants Class CSubject Class CObserver int d; CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv this.i == f(this.cs.d); inv this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() { pre: true; post: return == f(this.cs.d); impl: return this.i; } void getD() {…} void update() { post: this.i == f(this.cs.d); } • The constructor establishes I • All methods in the program preserve I • No method is called while I is invalid
Non-local Invariants Class CSubject Class CObserver int d; CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv this.i == f(this.cs.d); inv this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() { pre: true; post: return == f(this.cs.d); impl: return this.i; } void getD() {…} void update() { inc: this; post: this.i == f(this.cs.d); } • The constructor establishes I • All methods in the program preserve I • No method is called while I is invalid
Non-local Invariants with inc • Theorem: • If, for any invariant I of any object, • The constructor of that object establishes I • All methods in the program preserve I • While I is invalid, any method that is called specifies it doesn’t rely on I • Then, for any method M, for any invariant I of any allocated object, • Unless M is the constructor of that object , I holds when M is called • When I holds when a method M’ is called by M, I holds when M’ terminates • Theorem: • If, for any invariant I of any object, • The constructor of that object establishes I • All methods in the program preserve I • While I is invalid, any method that is called specifies it doesn’t rely on I • Then, for any method M, for any invariant I of any allocated object, • Unless M is the constructor of that object , I holds when M is called • I holds when a method M’ called by M terminates • Theorem: • If, for any invariant I of any object, • The constructor of that object establishes I • All methods in the program preserve I • While I is invalid, no method is called • Then, for any method M, for any invariant I of any allocated object, • Unless M is the constructor of that object, I holds when M is called • I holds when a method M’ called by M terminates • Theorem: • If, for any invariant I of any object, • The constructor of that object establishes I • All methods in the program preserve I • While I is invalid, • Then, for any method M, for any invariant I of any allocated object, • Unless M is the constructor of that object, I holds when M is called • I holds when a method M’ called by M terminates • Theorem: • If, for any invariant I of any object, • The constructor of that object establishes I • All methods in the program preserve I • While I is invalid, any method that is called specifies it doesn’t rely on I • Theorem: • If, for any invariant I of any object, • The constructor of that object establishes I • All methods in the program preserve I • While I is invalid, any method that is called specifies it doesn’t rely on I • Then, for any method M, for any invariant I of any allocated object, • or specifies that it doesn’t rely on I
Non-local Invariants with inc Class CSubject Class CObserver int d; CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv this.i == f(this.cs.d); inv this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: this; post: this.i == f(this.cs.d); } void getD() {…}
Non-local Invariants with inc Class CSubject Class CObserver int d; CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv this.i == f(this.cs.d); inv this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: this; post: this.i == f(this.cs.d); impl: int d = this.cs.getD(); this.i = f(d); } void getD() {…} • Update relies on this.cs ≠ null, which follows from this = this.cs.co
Non-local Invariants with inc Class CSubject Class CObserver int d; CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv I def this.i == f(this.cs.d); inv J def this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: this; post: this.i == f(this.cs.d); impl: int d = this.cs.getD(); this.i = f(d); } void getD() {…} • Update relies on this.cs ≠ null, which follows from this = this.cs.co
Non-local Invariants with inc Class CSubject Class CObserver int d; CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv I def this.i == f(this.cs.d); inv J def this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: I(this); post: this.i == f(this.cs.d); impl: int d = this.cs.getD(); this.i = f(d); } void getD() {…} • Update relies on this.cs ≠ null, which follows from this = this.cs.co • While I is invalid, any method that is called specifies it doesn’t rely on I
Non-local Invariants with inc Class CSubject Class CObserver int d; CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv I def this.i == f(this.cs.d); inv J def this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: I(this); post: this.i == f(this.cs.d); impl: int d = this.cs.getD(); this.i = f(d); } void getD() { inc: I(this.co); impl: return this.d; } • While I is invalid, any method that is called specifies it doesn’t rely on I
Non-local Invariants with inc Class CSubject Class CObserver int d; ONode on; CSubject cs; int i; void setD(int newD) { …} inv I def this.i == f(this.cs.d); void getD() {…} CObserver(CSubject toObs) {…} int getVal() {…} void update() {…} Class ONode CObserver obs; ONode next; inv this.obs != null;
Non-local Invariants with inc Class CSubject Class CObserver int d; ONode on; CSubject cs; int i; void setD(int newD) { this.d = newD; Onode iter = on; while (iter != null) { iter.obs.update(); iter = iter.next; } } inv I def this.i == f(this.cs.d); CObserver(CSubject toObs) {…} int getVal() {…} void update() {…} void getD() {…} Class ONode CObserver obs; ONode next; inv this.obs != null;
Non-local Invariants with inc Class CSubject Class CObserver int d; ONode on; CSubject cs; int i; void setD(int newD) { this.d = newD; Onode iter = on; while (iter != null) { iter.obs.update(); iter = iter.next; } } inv I def this.i == f(this.cs.d); inv J def i •this == this.cs.on.nexti.obs; CObserver(CSubject toObs) {…} int getVal() {…} void update() {…} void getD() {…} Class ONode CObserver obs; ONode next; inv this.obs != null;
Non-local Invariants with inc Class CSubject Class CObserver int d; ONode on; CSubject cs; int i; void setD(int newD) { this.d = newD; Onode iter = on; while (iter != null) { iter.obs.update(); iter = iter.next; } } inv I def this.i == f(this.cs.d); inv J def i •this == this.cs.on.nexti.obs; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: (CObserver, I, this == inc) post: this.i == f(this.cs.d); } void update() { inc: I(this) post: this.i == f(this.cs.d); } void update() { inc: (CObserver, I, this.cs == inc.cs) post: this.i == f(this.cs.d); } void update() { inc: (CObserver, I, true) post: this.i == f(this.cs.d); } void getD() {…} Class ONode void M(p_1, …,p_n) { inc: (C, I, P), (C’, I’, P’), … } CObserver obs; ONode next; inv this.obs != null;
Information Hiding • Want Subject to work with different kinds of Observers (simultaneously) • Concrete Observer(s) should be hidden from Concrete Subject • Then: developing new observers doesn’t affect the implementation, specification or verification of the Concrete Subject.
Information Hiding Class CSubject Class CObserver int d; CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv I def this.i == f(this.cs.d); inv J def this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: I(this); post: this.i == f(this.cs.d); impl: int d = this.cs.getD(); this.i = f(d); } void getD() {…} Invariant I of CObserver not visible to CSubject
Information Hiding Class CSubject Class CObserver int d coop I(this.co); CObserver co; CSubject cs; int i; void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv I def this.i == f(this.cs.d); inv J def this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: I(this); post: this.i == f(this.cs.d); impl: int d = this.cs.getD(); this.i = f(d); } void getD() {…} Invariant I of CObserver not visible to CSubject
Information Hiding Class CSubject Class CObserver int d coop I(this.co); CObserver co coop I(this.co), J(this.co); CSubject cs coop I(this), J(this); int i coop I(this); void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv I def this.i == f(this.cs.d); inv J def this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: I(this); post: this.i == f(this.cs.d); impl: int d = this.cs.getD(); this.i = f(d); } void getD() {…} Invariant I of CObserver not visible to CSubject
Information Hiding Class CSubject Class CObserver int d coop I(this.co); CObserver co coop I(this.co), J(this.co); CSubject cs coop I(this), J(this); int i coop I(this); void setD(int newD) { pre: true; post: this.d == newD; impl: this.d = newD; if (this.co != null) { this.co.update(); } } inv I def this.i == f(this.cs.d); inv J def this == this.cs.co; CObserver(CSubject toObs) {…} int getVal() {…} void update() { inc: I(this); post: I(this); impl: int d = this.cs.getD(); this.i = f(d); } void getD() {…} Invariant I of CObserver not visible to CSubject
Information Hiding Class Subject Interface Observer Observer o coop I(this.o), J(this.o); abstract Subject s coop J(this); … abstract inv I; inv J def this == this.s.o; Class CSubject extends Subject abstract void update() { inc: I(this); post: I(this); } int d coop I(this.o); void setD(int newD) { post: this.d == newD; impl: this.d = newD; if (this.o != null) { this.o.update(); } } Class CObserver implements Observer …
Information Hiding Interface Observer Class CObserver implements Observer abstract Subject s coop J(this); int i coop I(this); CSubject cs coop I(this); abstract inv I; inv J def this == this.s.o; def s by cs; def I by this.i == f(this.cs.d) J(this); abstract void update() { inc: I(this); post: I(this); } CObserver(CSubject toObs) {…} void update() { impl: int d = this.cs.getD(); this.i = f(d); }
Conclusions • inc allows one to call methods that re-establish invariants • coop allows one to make explicit which invariants might be invalidated when a field is assigned to • Together, they allow the full, modular specification of complex, non-hierarchical designs like the Observer Pattern • Future Work: • Complement with other solutions, in particular hierarchical ones • Combine with other ways of making dependencies explicit
Proof Obligations • {P0} r.f := E; {P1} S2; {P2} • if (b) { • {P3} S3; {P4} • } else { • {P6} S4; {P7} • } {P8} • while (b) { • {P9} S5; {P10} • } {P11} M() { … int f coop (C,I,P) int f coop I(r) int f coop (C, I, dep = r) }
Proof Obligations • {P0’ (X = null P[X/dep])} r.f := E; {P1} S2; {P2} • if (b) { • {P3} S3; {P4} • } else { • {P6} S4; {P7} • } {P8} • while (b) { • {P9} S5; {P10} • } {P11} M() { … int f coop (C,I,P) int f coop I(r) int f coop (C, I, dep = r) } X represents an arbitrary C-object that the assignment might make inconsistent
Proof Obligations • {P0’ (X = null P[X/dep])} r.f := E; {P1} S2; {P2} • if (b) { • {P3} S3; {P4} • } else { • {P6} S4; {P7} • } {P8} • while (b) { • {P9} S5; {P10} • } {P11} M() { … int f coop (C,I,P) int f coop I(r) int f coop (C, I, dep = r) ? P1 I(X) ? }
Proof Obligations • {P0’ (X = null P[X/dep])} r.f := E; {P1} S2; {P2} • if (b) { • {P3} S3; {P4} • } else { • {P6} S4; {P7} • } {P8} • while (b) { • {P9} S5; {P10} • } {P11} M() { … int f coop (C,I,P) int f coop I(r) int f coop (C, I, dep = r) ? S2 Method call ? }
Proof Obligations • {P0’ (X = null P[X/dep])} r.f := E; {P1} S2; {P2} • if (b) { • {P3} S3; {P4} • } else { • {P6} S4; {P7} • } {P8} • while (b) { • {P9} S5; {P10} • } {P11} M() { … int f coop (C,I,P) int f coop I(r) int f coop (C, I, dep = r) ? P2 I(X) ? }
Proof Obligations • {P0’ (X = null P[X/dep])} r.f := E; {P1} S2; {P2} • if (b) { • {P3} S3; {P4} • } else { • {P6} S4; {P7} • } {P8} • while (b) { • {P9} S5; {P10} • } {P11} M() { … int f coop (C,I,P) int f coop I(r) int f coop (C, I, dep = r) Don’t know which branch is taken; Branches might contain calls }
Proof Obligations • {P0’ (X = null P[X/dep])} r.f := E; {P1} S2; {P2} • if (b) { • {P3} S3; {P4} • } else { • {P6} S4; {P7} • } {P8} • while (b) { • {P9} S5; {P10} • } {P11} M() { … int f coop (C,I,P) int f coop I(r) int f coop (C, I, dep = r) Don’t know if while is executed; Body might contain calls }