Flexible Invariants Through Semantic CollaborationWork partially supported by SNF grants LSAT/200020-134974, ASII/200021-134976, and FullContracts/200021-137931; and by Hasler-Stiftung grant #2327.

Flexible Invariants Through Semantic Collaboration1

Abstract

Modular reasoning about class invariants is challenging in the presence of collaborating objects that need to maintain global consistency. This paper presents semantic collaboration: a novel methodology to specify and reason about class invariants of sequential object-oriented programs, which models dependencies between collaborating objects by semantic means. Combined with a simple ownership mechanism and useful default schemes, semantic collaboration achieves the flexibility necessary to reason about complicated inter-object dependencies but requires limited annotation burden when applied to standard specification patterns. The methodology is implemented in AutoProof, our program verifier for the Eiffel programming language (but it is applicable to any language supporting some form of representation invariants). An evaluation on several challenge problems proposed in the literature demonstrates that it can handle a variety of idiomatic collaboration patterns, and is more widely applicable than the existing invariant methodologies.

\lstset

language=OOSC2Eiffel, basicstyle= \crefnamesectionSect.Sect. \CrefnamesectionSectionSections \crefnamefigureFig.Fig. \CrefnamefigureFigureFigures \crefnamelistingListingListings \CrefnamelistingListingListings \crefnametableTab.Tab. \CrefnametableTableTable \crefnameequation \crefnameDefinitionDef.Def. \CrefnameDefinitionDefinitionDefinitions \crefnamelemmaLem.Lem.

1 The Perks and Pitfalls of Invariants

Class invariants6 are here to stay [23]—even with their tricky semantics in the presence of callbacks and inter-object dependencies, which make reasoning so challenging [17]. The main reason behind their widespread adoption is that they formalize the notion of consistent class instance, which is inherent in object-orientated programming, and thus naturally present when reasoning, even informally, about program behavior.

The distinguishing characteristic of invariant-based reasoning is stability: it should be impossible for an operation to violate the invariant of an object without modifying itself. Stability promotes information hiding and simplifies client reasoning about preservation of consistency: without invariants a client would need to know which other objects ’s consistency depends on, while with invariants it is sufficient that it checks whether modifies —a piece of information normally available as part of ’s specification. The goal of an invariant methodology (also called protocol) is thus to achieve stability even in the presence of inter-object dependencies—where the consistency of depends on the state of other objects, possibly recursively or in a circular fashion (see \crefsec:examples for concrete examples).

The numerous methodologies introduced over the last decade, which we review in \crefsec:existing, successfully relieve several difficulties involved in reasoning with invariants; but there is still room for improvement in terms of flexibility, usability, and automated tool support. In this paper, we present semantic collaboration (SC): a novel methodology for specifying and reasoning about invariants in the presence of inter-object dependencies that combines flexibility and usability and is implemented in a program verifier.

A standard approach to inter-object invariants is based on the notion of ownership, which has been deployed successfully in several invariant methodologies [2, 11, 16] and is available in tools such as Spec# [3] and VCC [4]. Under this model, an invariant of an object only depends on the state of the objects explicitly owned by . Ownership is congenial to object-orientation because it supports a strong notion of encapsulation; however, not all inter-object relationships are hierarchical and hence reducible to ownership. Multiple objects may also collaborate as equals, mindful of each other’s consistency; a prototypical example is the Observer pattern [6] (see \crefsec:examples).

Semantic collaboration naturally complements ownership to accommodate invariant patterns involving collaborating objects. Most existing methodologies support collaboration through dedicated specification constructs and syntactic restrictions on invariants [11, 1, 15, 22]; such disciplines tend to work only for certain classes of problems. In contrast, SC relies on standard specification constructs—ghost state and invariants—to keep track of inter-object dependencies, and imposes semantic conditions on class invariant representations. Its approach builds upon the philosophy of locally-checked invariants (LCI) [5]: a low-level verification method based on two-state invariants. LCI has served as a basis for other specialized, user- and automation-friendly methodologies for ownership and shared-memory concurrency. SC can be viewed as an improved specialization of LCI for object collaboration. To further improve usability, SC comprises useful “defaults”, which characterize typical specification patterns. As we argue in \crefsec:evaluation based on several challenge problems, the defaults significantly reduce the annotation burden without sacrificing flexibility in the general case.

We implemented SC as part of AutoProof, our automated verifier for the Eiffel object-oriented programming language. The implementation provides more concrete evidence of the advantages of SC compared to other methodologies to specify collaborating objects (e.g., [1, 12, 22, 15] all of which currently lack tool support).

Outline and contributions. The presentation is based on examples of non-hierarchical object structures, customarily used in the literature. \crefsec:examples presents the examples and the challenges they embody; and \crefsec:existing discusses the approaches taken by main existing invariant methodologies. \crefsec:methodology introduces SC, demonstrates its application to the running examples, and outlines a soundness proof. \crefsec:evaluation evaluates both SC and existing protocols on an extended set of examples, including challenge problems from the SAVCBS workshop series [19]. The evaluation demonstrates that SC is the only methodology that supports (a) collaboration with unknown classes, while preserving stability, and (b) invariants depending on unbounded sets of objects, possibly unreachable in the heap. The collection of problems of \crefsec:evaluation—available at [20] together with our solutions—could serve as a benchmark to evaluate invariant methodologies for non-hierarchical object structures. The website [20] also gives access to AutoProof through a web interface.

2 Motivating Examples: Observers and Iterators

The Observer and Iterator design patterns are widely used programming idioms [6], where multiple objects depend on one another and need to maintain a global invariant. Their interaction schemes epitomize cases of inter-object dependencies that ownership cannot easily describe; therefore, we use them as illustrative examples throughout the paper, following in the footsteps of much related work [12, 17, 15].

\lstset

basicstyle=,xleftmargin=8mm {lstlisting} class SUBJECT value: INTEGER subscribers: LIST [OBSERVER] make (v: INTEGER) – Constructor do value := v create subscribers ensure subscribers.is_empty end update (v: INTEGER) do value := v across subscribers as o do o.notify end ensure value = v end feature OBSERVER register (o: OBSERVER) require not subscribers.has (o) do subscribers.add (o) ensure subscribers.has (o) end end {lstlisting} class OBSERVER subject: SUBJECT cache: INTEGER make (s: SUBJECT) – Constructor do subject := s s.register (Current) cache := s.value ensure subject = s end feature SUBJECT notify do cache := subject.value ensure subject = old subject cache = subject.value end invariant subject.subscribers.has (Current) cache = subject.value end \lstsetbasicstyle=

Figure 1: The Observer pattern: an observer’s \lstinline|invariant| depends on the state of the \lstinline|SUBJECT|, which reports its state changes to all its \lstinline|subscribers|. The clients of the subscribers must be able to rely on their \lstinline|cache| always being consistent, while oblivious of the update/notify mechanisms that preserve invariants.

Observer pattern. \creffig:observer shows the essential parts of an implementation of the Observer design pattern in Eiffel. An arbitrary number of \lstinline|OBSERVER| objects (called “subscribers”) monitor the public state of a single instance of class \lstinline|SUBJECT|. Each subscriber maintains a copy of the subject’s relevant state (integer attribute \lstinline|value| in \creffig:observer) into one of its local variables (attribute \lstinline|cache| in \creffig:observer). The subscribers’ copies are cached values that must be consistent with the state of the subject, formalized as the invariant clause \lstinline|cache = subject.value| of class \lstinline|OBSERVER|, which depends on another object’s state. This dependency is not adequately captured by ownership schemes, since no one subscriber can have exclusive control over the subject.

In the Observer pattern, consistency is maintained by means of explicit collaboration: the subject has a list of \lstinline|subscribers|, updated whenever a new subscriber registers itself by calling \lstinline|register (Current)|7 on the subject. Upon every change to its state (method \lstinline|update|), the subject takes care of explicitly notifying all registered subscribers (using an \lstinline|across| loop that calls \lstinline|notify| on every \lstinline|o| in \lstinline|subscribers|). This explicit collaboration scheme—called “considerate programming” in [22]—ensures that the subscribers’ state remains consistent (i.e., the class invariant holds) between calls to the public methods of the object structure.

\cref

fig:observer uses Eiffel’s selective exports8 to separate the public interface of the classes from the methods internal to the object structure: \lstinline|feature {OBSERVER}| denotes that method \lstinline|register| is only available to instances of class \lstinline|OBSERVER|, and \lstinline|feature {SUBJECT}| similarly limits the visibility of \lstinline|notify| to the subject. While selective exports help emphasize collaboration patterns, they are not necessary for the discussion of the present paper, whose results are applicable to any object-oriented language regardless of the available visibility specifiers.

A methodology to verify the Observer pattern must ensure invariant stability; namely, that clients of \lstinline|OBSERVER| can rely on its invariant without knowledge of the register/notify mechanism. Another challenge is dealing with the fact that the number of subscribers attached to the subject is not fixed a priori, and hence we cannot produce explicit syntactic enumerations of the subscribers’ \lstinline|cache| attributes. We must also be able to verify \lstinline|update| and \lstinline|notify| without relying on the class invariant as precondition—in fact, those methods are called on inconsistent objects precisely to restore consistency.

\lstset

basicstyle=,xleftmargin=8mm {lstlisting} class COLLECTION [G] count: INTEGER make (capacity: INTEGER) – Constructor require capacity >= 0 do create elements(1, capacity) ensure elements.count = capacity count = 0 end remove_last require count > 0 do count := count - 1 ensure count = old count - 1 end feature ITERATOR elements: ARRAY [G] invariant 0 <= count and count <= elements.count end {lstlisting} class ITERATOR [G] target: COLLECTION [G] before, after: BOOLEAN make (t: COLLECTION) – Constructor do target := t ; before := True ensure target = t before and not after end item: G require not (before or after) do Result := target.elements [index] end feature NONE index: INTEGER invariant 0 <= index and index <= target.count + 1 before = index < 1 after = index > target.count end \lstsetbasicstyle=

Figure 2: The Iterator pattern: an iterator’s invariant depends on the state of the collection it traverses, which is oblivious of the iterators. Verification must prove that clients do not access disabled iterators, without knowing collection’s and iterator’s internal states.

In the Iterator pattern, an arbitrary number of iterator objects traverse a collection of elements. \creffig:iterator sketches an implementation where the \lstinline|COLLECTION| uses an \lstinline|ARRAY| of \lstinline|elements| as underlying representation. The \lstinline|ITERATOR|’s main capability is to return the \lstinline|item| at the current position \lstinline|index| in the \lstinline|target| collection9. \lstinline|item|’s precondition (\lstinline|require|) specifies that this is possible only when the iterator points to a valid element of \lstinline|target|, that is \lstinline|index| is between \lstinline|1| and \lstinline|target.count| (included); otherwise, if \lstinline|index| is \lstinline|0| the iterator is \lstinline|before| the list, and if it equals \lstinline|target.count + 1| it is \lstinline|after| the list. The invariant of class \lstinline|ITERATOR| defines the public state components \lstinline|before| and \lstinline|after| in terms of the internal state component \lstinline|index|, as well as the acceptable variability range for \lstinline|index|.

Since the iterator’s invariant depends on the state of the target collection, modifying the collection (for example, by calling \lstinline|remove_last|) may disable the iterator (make it inconsistent). This is aligned with the intended usage of iterators, which should be discarded after traversing a collection without changing it. A verification methodology should ensure that clients of \lstinline|ITERATOR| only access iterators in a consistent state, without knowledge of the iterator’s internal state \lstinline|index| or of its relation to the \lstinline|target| collection. In fact, the selective exports used in \creffig:iterator hide the details of \lstinline|ITERATOR|’s invariant from its clients (the visibility of an invariant clause is determined by its least visible subexpression, and \lstinline|feature {NONE}| denotes purely private members). An additional obstacle to verification comes from the fact that considerate programming would be at odds with the ephemeral nature of iterators compared to observers: collections are normally implemented unaware of the iterators operating on them; a flexible invariant methodology should allow such implementations.

3 Existing Approaches

This section reviews the main existing methodologies for specifying and reasoning about class invariants; based on their most important features and limitations. \crefsec:methodology will present our own methodology. For lack of space, we only discuss methodologies for inter-object dependencies that support modular reasoning (where local checks on individual classes or small groups of classes subsume global program correctness).

A crucial issue is deciding when (at which program points) class invariants should hold: state-changing operations normally consist of sequences of elementary updates, which individually may break the class invariant temporarily. To deal with this problem, some methodologies restrict the program points where class invariants are expected to hold; others interpret the invariants in a weakened form, which holds vacuously at intermediate steps during updates (and fully at crucial points).

Methodologies based on visible-state semantics only require invariants to hold when no operation is being executed on their objects, that is in states visible to clients. This idea was introduced for Eiffel [13], and later also adopted by JML [8]. Without additional mechanisms, visible-state semantics can’t achieve modularity in the presence of callbacks (the client making the callback is unaware of ongoing operations that may affect the invariant) and of inter-object dependencies (if ’s invariant depends on , the former is also affected by operations on invisible to clients of ). Existing solutions adopt aliasing control measures [16] to deal with hierarchical object structures described by ownership. Other solutions [14, 15, 22], for collaborative invariants, explicitly indicate which objects might be inconsistent at method call boundaries; for example, method \lstinline|register (o: OBSERVER)| of class \lstinline|SUBJECT| in \creffig:observer would be annotated with \lstinline[language=Java]|broken o| to specify that argument \lstinline|o|’s invariant may not hold when executing \lstinline|register|. These two families of solutions—for hierarchical and for collaborative object structures—based on visible-state semantics are not easily combined; this is a practical limitation, since many object-oriented systems consist of an interplay between both types of structure. For example, continuing with \creffig:observer, objects of class \lstinline|SUBJECT| collaborate with \lstinline|OBSERVER| objects but also own a \lstinline|subscribers| list as part of their representation. Thus, when reasoning about method \lstinline|register|, we should be able to deal with the call \lstinline|subscribers.add (o)| whose argument \lstinline|o| is inconsistent (and hence \lstinline|add| cannot assume \lstinline|o|’s invariant); however, annotating \lstinline|LIST|’s \lstinline|add| by declaring its argument \lstinline[language=Java]|broken| goes against modularity, as class \lstinline|LIST| should not need to know how and where it is used. The difficulty of integrating hierarchical and collaborative models is the main limitation of visible-state methodologies, and likely a reason why, to our knowledge, they have not been implemented in any program verifier.

Another family of methodologies, collectively known as Boogie methodologies after the program verifier where they have originally been implemented, follow the approach of weakening the default semantics of invariants so that they can be evaluated only when appropriate. In a nutshell, all classes include a ghost Boolean attribute \lstinline[language=Java]|closed|,10 which denotes whether an object is in a consistent state; an invariant \lstinline|inv| is then interpreted as the weaker \lstinline[language=Java]|closed inv|, which vacuously holds for open (i.e., not closed) objects. Methods explicitly indicate whether they expect relevant objects to be closed or open; this approach is more conducive to modularity than visible-state semantics: it does not impose consistency by default at method call boundaries and thus does not require methods to list all possibly inconsistent objects in the entire program.

The original Boogie methodologies, implemented in the Spec# system [3], are mainly based on syntactic mechanisms to express ownership relations. For example, following [2], we would annotate attribute \lstinline|elements| of class \lstinline|COLLECTION| in \creffig:iterator with \lstinline[language=Java]|rep|, to denote that it belongs to \lstinline|COLLECTION|’s internal representation; thus, modifying \lstinline|elements| is only possible if the \lstinline|COLLECTION| object owning it has been opened—a situation where \lstinline[language=Java]|closed count <= elements.count| vacuously holds. This solution only supports representations based on bounded sets of objects known a priori and directly accessible through attributes. Follow-up work [11] partially relaxes these restriction introducing a form of quantification predicating over an \lstinline[language=Java]|owner| ghost attribute (which goes up the ownership hierarchy), and a mechanism to transfer ownership. The additional expressiveness comes with a price to pay mainly in terms of complex invariant admissibility conditions (hence, it may be hard to understand what is expressible and how) and complicated soundness proofs of the methodology.

In contrast, the VCC verifier [4] implements a Boogie methodology where ownership is encoded on top of LCI’s semantic approach [5]. Objects include an additional ghost attribute, \lstinline[language=Java]|owns|, storing the set of all owned objects; ghost code modifies this set explicitly when the owner object is open. In the example of \creffig:iterator, instead of annotating attribute \lstinline|elements| with \lstinline[language=Java]|rep|, we would introduce a first-order formula, such as \lstinline[language=Java]|owns = {elements}|, in the invariant of \lstinline|COLLECTION| to express that \lstinline|elements| is part of the representation. The advantage of this approach becomes apparent with linked structures where owned elements are accessible only by following chains of references (e.g., a linked list owns all reachable cells). In fact, semantic approaches to ownership provide the flexibility necessary to specify an unbounded number of owned objects, which may even be not directly attached to the owner, as well as to implement ownership transfers without need for ad hoc mechanisms. They also simplify the rules of reasoning; for example, invariant admissibility becomes a simple proof obligation that all objects whose state is mentioned in the invariant are bound, by the same invariant, to belong to \lstinline[language=Java]|owns|. These features have contributed to making VCC applicable to real-world systems [10].

In addition to ownership, some Boogie methodologies also deal with collaborating objects. [11] introduces the notion of visibility-based invariants, which requires that a class be aware of the types and invariants of all objects concerned with its state11. For example, in \creffig:observer \lstinline|SUBJECT| must declare its \lstinline|value| attribute with a modifier \lstinline|dependent OBSERVER|. Whenever the subject changes its \lstinline|value|, it has to check that all potentially affected \lstinline|OBSERVER|s are open. If aware of the \lstinline|OBSERVER|’s invariant, it can show that the only affected observers are . Such indirect representations of the concerned objects complicate discharging the corresponding proof obligations; and relying on knowing the concerned objects’ invariants introduces tight coupling between the collaborating classes. To lift these complications, [1] suggests instead to introduce a ghost attribute \lstinline|deps| storing the set of all concerned objects. It also introduces update guards, allowing a concerned object to state conditions under which its invariant is preserved without revealing the invariant itself. Both approaches [11, 1] have shortcomings that derive from their reliance on syntactic mechanisms and conditions: collaboration invariants can only depend on a bounded number of objects known a priori and accessible through attributes (called “pivot fields” in [1]); the types of the concerned objects must be known explicitly; and the numerous ad hoc annotations (e.g., \lstinline[language=Java]|friend| and \lstinline[language=Java]|keeping|) and operations (e.g., to modify \lstinline[language=Java]|deps|) make the methodologies harder to present and use. One of the main goals of our methodology (\crefsec:methodology) is to lift these shortcomings by dealing with collaborative invariants by semantic rather than syntactic means—similarly to what VCC did to the classic syntactic treatment of ownership. The semantic approach makes SC very flexible, capable of accommodating disparate object-oriented design patterns without requiring ad hoc mechanisms.

Somewhat orthogonally to other Boogie-family approaches, the history invariants methodology [12] provides for more loose coupling between the collaborating classes, but gives up stability of invariants.

4 Semantic Collaboration

Our novel invariant methodology belongs to the Boogie family; as we illustrated in \crefsec:existing, this entails that objects can be open or closed, and class invariants have to hold only for closed objects. On top of semantic mechanisms for ownership, similar to those developed for VCC (see \crefsec:existing), our methodology also provides a semantic treatment of dependencies among collaborating objects; hence its name semantic collaboration. The keywords and constructs specific to SC are underlined in the following.

Overview of semantic collaboration. To specify collaboration patterns, we equip every object \lstinline|o| with ghost fields \lstinline|subjects| and \lstinline|observers|. As their names suggest,12 \lstinline|o.subjects| stores the set of objects on which \lstinline|o|’s invariant might depend; and \lstinline|o.observers| stores the set of objects potentially concerned with \lstinline|o| (analogous to \lstinline|deps| in [1]). The methodology achieves modularity by reducing global validity (all closed objects satisfy their invariants) to local checks of two kinds: (i) all concerned objects are stored in \lstinline|observers|; and (ii) updates to the attributes of an object \lstinline|o| maintain the validity of \lstinline|o| and its observers. Check (i) becomes an admissibility condition that every declared class invariant must satisfy. Check (ii) holds vacuously for for open observers, thus one way to satisfy it is to “notify” all observers of a potentially destructive update by opening them. For more flexibility the methodology also allows subjects to skip “notifying” observers whenever the attribute update satisfies its guard (a notion also inspired by [1]). This option is supported by another admissibility condition: an invariant must remain valid after updates to subjects that comply with their update guards.

4.1 Preliminaries and Definitions

As it is customary, the following presentation targets fundamental constructs, while ignoring those that do not affect reasoning about invariants (e.g., control structures). We also largely ignore issues related to inheritance, but we briefly come back to them in \crefsec:conclusions.

A program is a collection of classes. A class is a collection of attributes, methods, and logical functions (side-effect free and terminating). Any of those constructs can be declared ghost if it is meant to be used only in specifications.

Built-in attributes. Every class is implicitly equipped with ghost attributes: \lstinline|closed| (to encode consistency); \lstinline|owns| and \lstinline|owner| (to encode the ownership hierarchy); and \lstinline|subjects| and \lstinline|observers| (to encode collaboration). We also define the shorthands: \lstinline|o.open| for \lstinline| o.closed|; \lstinline|o.free| for \lstinline|o.owner.open|; and \lstinline|o.wrapped| for \lstinline|o.closed o.free|. The ownership domain of an object \lstinline|o| is if \lstinline|o| is open, and the transitive closure of \lstinline|o.owns| if \lstinline|o| is closed. Attributes \lstinline|closed| and \lstinline|owner| are only changed indirectly through the implicitly defined ghost methods \lstinline|wrap| and \lstinline|unwrap|, whose semantics is defined below.

Specifications. The specification of a logical function consists of a definition (a side-effect free expression defining the function value) and a \lstinline|read| clause (an expression that denotes the set of objects on which the value of the function may depend). The specification of a method consists of a \lstinline|require| clause (a precondition), an \lstinline|ensure| clause (a postcondition), and a \lstinline|modify| clause (an expression that denotes the set of objects that the method may modify). The specification of a class includes its invariant \lstinline|inv|. The specification of an attribute \lstinline|a| consists of an update \lstinline|guard| (a Boolean expression over \lstinline|Current| object, new attribute value \lstinline|y|, and generic observer object \lstinline|o|—written ).

Expressions. In addition to the standard programming-language expressions, we support a restricted form of quantification through the syntax \lstinline|all x s : B(x)| for universal and \lstinline|some x s : B(x)| for existential quantification, where \lstinline|s| is a set expression and \lstinline|B(x)| is a Boolean expression over \lstinline|x|. The special expression \lstinline|Void| (analogous to \lstinline[language=Java]|null| in Java and C#) denotes an object that is always allocated and open.

The read set of a primitive expression is defined as follows: for an access \lstinline|x.a| to attribute \lstinline|a|, ; for a call \lstinline|x.f (y)| to logical function \lstinline|f|, is given by the \lstinline|f|’s \lstinline|read| clause. The read set of a compound expression is the union of the read sets of ’s subexpressions.

The current heap in which expressions are evaluated is normally clear from the context and left implicit. Otherwise, denotes the value of expression in heap ; and denotes the heap that agrees with everywhere except possibly about the value of \lstinline|x.f|, which is . Since we ignore deallocation, our heaps have no dangling references: only allocated objects are reachable from allocated objects.

Instructions. For the present discussion, we only have to consider method calls \lstinline|x.m (y)|, as well as heap update instructions: \lstinline|create x| (allocate an object and attach it to \lstinline|x|); \lstinline|x.a := y| (update attribute \lstinline|a|); and \lstinline|x.wrap| and \lstinline|x.unwrap| (opening and closing an object).

The write set of an primitive instruction is defined as follows: for an update \lstinline|x.a := y| of attribute \lstinline|a|, ; for opening or closing an object , ; for a call \lstinline|x.r (y)| to method (or constructor) \lstinline|r|, is the union of the ownership domains of all objects mentioned in \lstinline|r|’s \lstinline|modify| clause. The write set of a compound instruction is the union of the write sets of the instructions in .

4.2 Semantic Collaboration: Goals and Proof Obligations

The goal of any invariant methodology is to provide modular proof obligations to establish global validity: the property that every object in the program is valid at every program point. Following SC’s approach, an object is valid if satisfies its invariant when closed; thus global validity is defined as:

(G1)

Additionally, maintaining ownership-based invariants requires strengthening global validity with the property that whenever a parent object is closed all its owned objects are closed (and their \lstinline|owner| attributes point back to ):

(G2)

Proof obligations. The proof obligations specific to SC consist of two types of checks: (i) every class invariant is admissible according to \crefdef:admissibility; and (ii) every heap update instruction satisfies its precondition. These proof obligations are modular in that they only mention the state of the current object, its observers and owned objects. \crefsec:methodology:soundness describes how establishing the local proof obligations entails global validity, that is subsumes checking ((G1)) and ((G2)).

Admissibility captures the requirements that class invariants respect ownership and collaboration relations, modeled through ghost attributes \lstinline|owns|, \lstinline|subjects|, and \lstinline|observers|.

Definition

An invariant \lstinline|inv| is admissible iff:

  1. \lstinline|inv| only depends on \lstinline|Current|, its owned objects, and its subjects:

  2. All subjects of \lstinline|Current| are aware of it as an observer:

  3. \lstinline|inv| is preserved by any update \lstinline|s.a := y| that conforms to its guard:

  4. (Syntactic check) \lstinline|inv| does not mention attributes \lstinline|closed| and \lstinline|owner|, directly or as part of the definitions of the mentioned logical functions.

The specifications of the heap update instructions are given below; the instructions only modify objects and attributes mentioned in the postconditions.

Allocation

creates an open object owned by \lstinline|Void| (and thus free), with no observers:
\lstinline|create x| \lstinline|require| \lstinline|ensure| \lstinline|True|

Unwrapping

opens a wrapped object:
\lstinline|x.unwrap| \lstinline|require| \lstinline|ensure| \lstinline|x.wrapped| \lstinline|x.open|

Attribute update

operates on an open object and preserves validity of its observers:
\lstinline|x.a := y| \lstinline|require| \lstinline|ensure| (\lstinline|a /= closed|) \lstinline|x.open| \lstinline|x.a = y|

Wrapping

closes an open object, whose invariant holds, and gives it ownership over all objects in its \lstinline|owns| set:
\lstinline|x.wrap| \lstinline|require| \lstinline|ensure| \lstinline|x.wrapped|

Other proof obligations. The other proof obligations, which do not involve invariants, are the usual ones of axiomatic reasoning: every call to a method \lstinline|m| occurs in a state that satisfies \lstinline|m|’s precondition; executing a method \lstinline|m| in a state that satisfies its precondition leads to a state that satisfies \lstinline|m|’s postcondition; the \lstinline|read| clause of every logical function \lstinline|f| is consistent (i.e., the read set of \lstinline|f|’s definition is a subset of \lstinline|f|’s \lstinline|read| clause); the \lstinline|modify| clause of every method \lstinline|m| is consistent (i.e., the write set of \lstinline|m|’s body is a subset of \lstinline|m|’s \lstinline|modify| clause); and the definitions of logical functions are terminating.

4.3 Soundness Argument

The soundness argument has to establish that every program that satisfies the proof obligations of SC is always globally valid, that is satisfies ((G1)) and ((G2)). We outline a proof of this fact in three parts.

The first part concerns ownership: every methodology that, like SC, imposes a suitable discipline of wrapping and unwrapping to manage ownership domains reduces ((G2)) to local checks.

Lemma 1

Consider a methodology whose proof obligations verify the following:

  1. freshly allocated objects are \lstinline|open|;

  2. whenever \lstinline|x.owner| is updated or \lstinline|x.closed| is set to \lstinline|False|, object \lstinline|x| is \lstinline|free|;

  3. whenever \lstinline|x.closed| is updated to \lstinline|True|, every object \lstinline|o| in \lstinline|x.owns| is \lstinline|closed| and satisfies \lstinline|o.owner = x|;

  4. whenever an attribute \lstinline|x.a| (with ) is updated, object \lstinline|x| is \lstinline|open|.

Then every program that satisfies ’s proof obligations also satisfies ((G2)) everywhere.

Proof

The proof is by induction on the length of program traces.

The base case is the trace only consisting of the initial heap where no object is allocated but for an open object \lstinline|Void|; thus ((G2)) holds initially. For the inductive step, let be the final heap of a trace where ((G2)) invariably holds. Consider an instruction that yields heap if executed on . Without loss of generality, let ; therefore, is either an allocation of a new object or an attribute update. If allocates a new object \lstinline|x|, ((G2)) still holds in : \lstinline|x| is open (rule a) and is in no other object’s \lstinline|owns| set, since \lstinline|x| has just been created. If is an attribute update, it can only invalidate ((G2)) if it updates \lstinline|closed|, \lstinline|owns|, or \lstinline|owner|. If updates some \lstinline|.owner| in ((G2))’s consequent or sets \lstinline|.closed| to \lstinline|False|, then is \lstinline|free| (rule b); thus \lstinline|.owner| is open, and hence ((G2))’s antecedent is false. If sets to \lstinline|True| some \lstinline|.closed| in ((G2))’s antecedent, then rule c implies the whole ((G2)) holds. If updates some \lstinline|.owns| in ((G2))’s antecedent, then is open (rule d); thus, ((G2))’s antecedent is false.

The second part applies to any kind of inter-object invariants and assumes a methodology that, like SC, checks that attribute updates preserve validity of all concerned objects; we show that such checks subsume ((G1)). How a methodology identifies concerned objects is left unspecified as yet.

Lemma 2

Consider a methodology whose proof obligations verify the following:

  1. freshly allocated objects are \lstinline|open|;

  2. whenever \lstinline|x.closed| is updated to \lstinline|True|, \lstinline|x.inv| holds;

  3. whenever an attribute \lstinline|x.a| (with \lstinline|a| \lstinline|closed|) is updated to some \lstinline|y|, every concerned object satisfies ;

  4. class invariants depend neither on attribute \lstinline|closed| nor on the allocation status of objects.

Then every program that satisfies ’s proof obligations also satisfies ((G1)) everywhere.

Proof

The proof is by induction on the length of program traces.

The base case is the trace only consisting of the initial heap where no object is allocated but for an open object \lstinline|Void|; thus ((G1)) holds initially. For the inductive step, let be the final heap of a trace where ((G1)) invariably holds. Consider an instruction that yields heap if executed on . Without loss of generality, let ; therefore, is either an allocation of a new object or an attribute update. If allocates a new object \lstinline|x|, ((G1)) still holds in : \lstinline|x| is open (rule a) and no other object’s invariants depends on it, since \lstinline|x| has just been created and class invariant do not know about allocation status (rule d). If sets to \lstinline|False| some \lstinline|.closed| in ((G1))’s antecedent, then ((G1)) vacuously hold. If sets to \lstinline|True| some \lstinline|.closed| in ((G1))’s antecedent, then \lstinline|.inv| holds (rule b); thus ((G1)) holds too. Also, updates to some \lstinline|.closed| cannot concern the invariants of objects other than (rule d). If updates some \lstinline|x.a|, with , let be any object concerned with the update; either is open, or it is closed and \lstinline|.inv| holds in by the induction hypothesis, so rule c applies. Either way, ((G1)) holds in for .

The third part of the soundness proof argues that SC satisfies the hypotheses of \creflemma:ownership,lemma:soundness_general, and hence ensures global validity.

Proposition 1

Every program that satisfies the proof obligations of SC also satisfies ((G2)) and ((G1)) everywhere.

Proof

SC satisfies the hypotheses of \creflemma:ownership: allocation satisfies rule a; unwrapping satisfies rule b and wrapping satisfies rules b and c (we assume that \lstinline|wrap| first updates the \lstinline|owner| attribute of every object in the \lstinline|owns| set of its argument, and then updates the \lstinline|closed| attribute of its argument); remember that \lstinline|closed| and \lstinline|owner| are only changed by wrap and unwrap. Attribute update satisfies rule d.

It also satisfies the hypotheses of \creflemma:soundness_general: allocation satisfies rule a; wrapping satisfies rule b; invariant admissibility and the rules of language syntax satisfy rule d. Rule c requires more details. First note that invariant admissibility requires that no invariant mention \lstinline|owner|; thus no object is concerned with wrapping (the only operation that can change \lstinline|owner|), which therefore vacuously satisfies rule c. Now, consider an update \lstinline|x.a := y| with and , and let \lstinline|o| be any concerned object. Assuming \lstinline|o.closed| and \lstinline|o.inv| hold for a generic heap , we have to show that \lstinline|o.inv| also holds of the heap . By definition of read set, ; \lstinline|o.inv| is also admissible and hence it satisfies (1); therefore . However, the first precondition of the attribute update rule says that \lstinline|x| is open; thus because \lstinline|o| is closed. We already proved that satisfies ((G2)); for this entails that all objects in \lstinline|o.owns| are closed; therefore, as well. We conclude that which, combined with condition (2) for \lstinline|o.inv|’s admissibility, implies that holds in . Finally, the second precondition of the attribute update rule establishes , and thus by admissibility condition (3), \lstinline|o.inv| still holds in in the heap .

As a closing remark, we note that another way to show soundness of SC is via reduction to LCI. To encode collaboration in LCI on top of the ownership encoding detailed in [5], we add the following clauses to the invariant of each class: one stating that all \lstinline|subjects| know \lstinline|Current| for an observer (the consequent of (2)), and for each attribute of \lstinline|Current|, another one stating that all \lstinline|observers| approve of the changes to this attribute.

4.4 Examples

We illustrate SC on the two examples of \crefsec:examples: \creffig:observer_semcol,fig:iterator_semcol show the Observer and Iterator patterns fully annotated according to the rules of \crefsec:methodology:formal. We use the shorthands \lstinline|wrap_all (s)| and \lstinline|unwrap_all (s)| to denote calls to \lstinline|wrap| and \lstinline|unwrap| on all objects in a set \lstinline|s|. As we discuss in \crefsec:evaluation, several annotations of \creffig:observer_semcol,fig:iterator_semcol are subsumed by the defaults mentioned in \crefsec:methodology:defaults. We postpone to \crefsec:methodology:guards dealing with update guards and the corresponding admissibility condition (3).

\lstset

basicstyle=,xleftmargin=8mm {lstlisting} class SUBJECT value: INTEGER subscribers: LIST [OBSERVER] make (v: INTEGER) – Constructor require open modify Current do value := v create subscribers owns := subscribers wrap ensure subscribers.is_empty wrapped end update (v: INTEGER) require wrapped all o observers : o.wrapped modify Current, observers do unwrap ; unwrap_all (observers) value := v across subscribers as o do o.notify end wrap_all (observers) ; wrap ensure value = v wrapped all o observers : o.wrapped observers = old observers end feature OBSERVER register (o: OBSERVER) require not subscribers.has (o) wrapped o.open modify Current do unwrap subscribers.add (o) observers := observers + o wrap ensure subscribers.has (o) wrapped end {lstlisting} invariant observers = subscribers.range owns = subscribers subjects = end class OBSERVER subject: SUBJECT cache: INTEGER make (s: SUBJECT) – Constructor require open s.wrapped modify Current, s do subject := s s.register (Current) cache := s.value subjects := s wrap ensure subject = s wrapped s.wrapped end feature SUBJECT notify require open subjects = subject subject.observers.has (Current) observers = onws = modify Current do cache := subject.value ensure inv end invariant cache = subject.value subjects = subject subject.observers.has (Current) observers = owns = end \lstsetbasicstyle=

Figure 3: The Observer pattern using SC annotations (underlined).

Observer pattern. The \lstinline|OBSERVER|’s invariant is admissible (\crefdef:admissibility) because it ensures that \lstinline|subject| is in \lstinline|subjects| (1) and that \lstinline|Current| is in the \lstinline|subject|’s \lstinline|observers| (2). Constructors normally wrap freshly allocated objects after setting up their state. Public method \lstinline|update| must be called when the whole object structure is wrapped and makes sure that it is wrapped again when the method terminates. This specification style is convenient for public methods, as it allows clients to interact with the class while maintaining objects in a consistent state, without having to explicitly discharge any condition. Methods such as \lstinline|register| and \lstinline|notify|, with restricted visibility, work instead with open objects and restore their invariants so that they can be wrapped upon return. Since \lstinline|notify| explicitly ensures \lstinline|inv|, \lstinline|update| does not need the precise definition of the observer’s invariant in order to wrap it (it only needs to know enough to establish the precondition of \lstinline|notify|). Thus the same style of specification would work if \lstinline|OBSERVER| were an abstract class and its subclasses maintained different views of subject’s \lstinline|value|.

Let us illustrate the intuitive reason why an instance of \lstinline|SUBJECT| cannot invalidate any object observing its state. On the one hand, by the attribute update rule, any change to a subject’s state (such as assignment to \lstinline|value| in \lstinline|update|) must be reconciled with its \lstinline|observers|. On the other hand, any closed concerned \lstinline|OBSERVER| object must be contained in its \lstinline|subject|’s \lstinline|observers| set: a subject cannot surreptitiously remove anything from this set, since such a change would require an attribute update, and thus, again, would have to be reconciled with all current members of \lstinline|observers|.

Note that we had to restate the first invariant clause of \lstinline|OBSERVER| from \creffig:observer in terms of \lstinline|observers| instead of \lstinline|subscribers|. In general, collaboration invariants have to be expressed directly in terms of attributes of subjects and cannot refer to their ownership domains (including through logical functions). This is not a syntactic restriction but follows from the fact that it is rarely possible to establish a subject/observer relation with the whole domain (in this example, we would have to require \lstinline|LIST| to allow \lstinline|OBSERVER| objects in its \lstinline|observers| set). This limitation can always be easily circumvented, however, by introducing a ghost attribute in the subject that mirrors the requires state.

\lstset

basicstyle=,xleftmargin=8mm {adjustwidth}-4mm-4mm {lstlisting} class COLLECTION [G] count: INTEGER make (capacity: INTEGER) – Constructor require open capacity >= 0 modify Current do create elements(1, capacity) owns := elements ; wrap ensure elements.count = capacity count = 0 observers = end remove_last require count > 0 wrapped all o observers : o.wrapped modify Current, observers do unwrap ; unwrap_all (observers) observers := count := count - 1 wrap ensure count = old count - 1 wrapped observers = all o old observers : o.open end feature ITERATOR elements: ARRAY [G] invariant 0 <= count and count <= elements.count owns = elements subjects = end {lstlisting} class ITERATOR [G] target: COLLECTION [G] before, after: BOOLEAN make (t: COLLECTION) – Constructor require open and t.wrapped modify Current, t do target := t before := True t.unwrap t.observers := t.observers + Current t.wrap subjects := t wrap ensure target = t before and not after wrapped end item: G require not (before or after) wrapped and t.wrapped do Result := target.elements [index] end feature NONE index: INTEGER invariant 0 <= index and index <= target.count + 1 before = index < 1 after = index > target.count subjects = target target.observers.has (Current) observers = and owns = end \lstsetbasicstyle=

Figure 4: The Iterator pattern using SC annotations (underlined).

Iterator pattern. The main differences in the annotations of the Iterator pattern occur in the \lstinline|COLLECTION| class whose non-ghost state is, unlike \lstinline|SUBJECT| above, unaware of its \lstinline|observers|. Method \lstinline|remove_last| has to unwrap its \lstinline|observers| according to the update rule. However, it has no way of restoring their invariants (in fact, a collection is in general unaware even of the types of the iterators operating on it). Therefore, it can only leave them in an inconsistent state and remove them from the \lstinline|observers| set. Public methods of \lstinline|ITERATOR|, such as \lstinline|item|, normally operate on wrapped objects, and hence in general cannot be called after some operations on the collection has disabled its iterators. The only way out of this is if the client of collection and iterators can prove that a certain iterator object \lstinline|i_x| was not in the modified collection’s \lstinline|observers|; this is possible if, for example, the client directly created \lstinline|i_x|. The fact that now clients are directly responsible for keeping track of the \lstinline|observers| set is germane to the iterator domain: iterators are meant to be used locally by clients.

4.5 Default Annotations

The annotation patterns shown in \crefsec:methodology:examples occur frequently in object-oriented programs. To reduce the annotation burden in those cases, we suggest the following defaults.

Pre- and postconditions:

public procedures (methods not returning values) require and ensure that \lstinline|Current|, its \lstinline|observers|, and method arguments be \lstinline|wrapped|.

Modify clauses:

procedures modify \lstinline|Current|; functions (methods returning values) modify nothing.

Invariants:

Built-in ghost set attributes (such as \lstinline|owns|) are invariably empty if they are not mentioned in the programmer-written invariant.

Wrapping:

public procedures start by unwrapping \lstinline|Current| and terminate after wrapping it back.

Built-in set manipulation:

if a built-in ghost set attribute is only mentioned in an invariant clause of the form , then is considered implicit; correspondingly, every \lstinline|wrap| of objects enclosing will implicitly perform an assignment .13

These defaults encourage considerate programming: unless explicitly specified otherwise, an object is always required to restore the consistency of its observers at the end of a public method. This is a useful property, since the considerate paradigm promotes encapsulation and is convenient for the clients. Nevertheless, the defaults are only optional suggestions that can be overridden by providing explicit annotations; this ensures that they do not tarnish the flexibility and semantic nature of our methodology.

4.6 Update guards

Update guards are used to distribute the burden of reasoning about attribute updates between subjects and observers, depending on the intended collaboration scheme. At one extreme, if a is identically \lstinline|False|, the burden is entirely on the subject, which must check that all observers are open whenever \lstinline|a| is updated; in contrast, the admissibility condition (3) holds vacuously for the observer \lstinline|o|. At the other extreme, if a guard is identically \lstinline|True|, the burden is entirely on the observer, which deals with (3) as a proof obligation that its invariant does not depend on \lstinline|a|; in contrast, the subject \lstinline|x| can update \lstinline|a| without particular constraints.

Another recurring choice for a guard is . For its flexibility, we chose this as the default guard of SC. Just like \lstinline|False|, this guard also does not burden the observer, but is more flexible at the other end: upon updating, the subject can establish that each observer is either open or its invariant is preserved. The subject can rely on the latter condition if the observer’s invariants are known, and ignore it otherwise.

When it comes to built-in ghost attributes, \lstinline|owns| and \lstinline|subjects| are guarded with \lstinline|True|, since other objects are not supposed to depend on them, while \lstinline|observers| has a more interesting guard, namely . This guard reflects the way this attribute is commonly used in collaboration invariants, while leaving the subject with reasonable freedom to manipulate it; for example, adding new observers to the set \lstinline|observers| without “notifying” the existing ones (this is used, in particular, in the \lstinline|register| method of \creffig:observer_semcol).

5 Experimental Evaluation

We arranged a collection of representative challenge problems involving inter-object collaboration, and we specified and verified them using our SC methodology. This section presents the challenge problems (\crefsec:evaluation:challenge), and discusses their solutions using SC (\crefsec:evaluation:results), as well as other methodologies, in particular those described in \crefsec:existing (\crefsec:comp-with-exist). See [20] for full versions of problem descriptions, together with our solutions, and a web interface to the AutoProof verifier.

5.1 Challenge Problems

Beside using it directly to evaluate SC, the collection of challenge problems described in this section can be a benchmark for other invariant methodologies. The benchmark consists of six examples of varying degree of difficulty, which capture the essence of various collaboration patterns often found in object-oriented software. The emphasis is on non-hierarchical structures that maintain a global invariant.

We briefly present the six problems in roughly increasing order of difficulty in terms of the shape of references in the heap, state update patterns, and challenges posed to preserving encapsulation.

subject

observer

observer

Observer [12, 17, 15] (see also SAVCBS ’07 [19], and \crefsec:examples). The invariants of the observer objects depend on the state of the subject. Verification must ensure that the subject reports all its state changes to all observers, so that their clients can always rely on a globally consistent state. As additional challenge: combination with ownership (the subject keeps references to its observers in a collection, which is a part of its representation).

Variants: a simplified version where the number of observers is fixed (thus collections of observers are not needed); a more complex version with multiple observer classes related by inheritance, each class redefining class invariant and implementation of the \lstinline|notify| method.

collection

iterator

iterator

Iterator [12] (see also SAVCBS ’06 [19], and \crefsec:examples). Unlike observers in the Observer pattern, the implementation of a collection is not aware of the iterators operating on it. Specification must still be able to refer to the iterators attached to the collection while avoiding global reasoning. As additional challenge: we cannot rely on the implementation following considerate programming (where objects must be in consistent states at public call boundaries).

Variants: a more complex version where iterators may modify the collection.

master

slave

slave

Master clock [1, 12]. The time stored by a master clock can increase (public method \lstinline|tick|) or be set to zero (public method \lstinline|reset|). The time stored locally by each slave clock must never exceed the master’s but need not be perfectly synchronized. Therefore, when the master is \lstinline|reset| its slaves are disabled until they synchronize (similar to iterators); when the master increments the time its slaves remain in a consistent state without requiring synchronization. Additional challenges: \lstinline|tick|’s frame does not include slaves; perform reasoning local to the master with only partial knowledge of the slaves’ invariants.

Variants: a simplified version without \lstinline|reset| (slaves cannot become inconsistent).

node

right

left

Doubly-linked list [11, 14]. The specification expresses the consistency of the \lstinline|left| and \lstinline|right| neighbors directly attached to each \lstinline|node|. Verification establishes that updates local to a node (such as inserting or removing a node next to it) preserve consistency. Unlike in the previous examples, the heap structure is recursive; the main challenge is thus avoiding considering the list as a whole (such as to propagate the effects of local changes).

Composite [23, 22, 9], (see also SAVCBS ’08 [19]). A tree structure maintains consistency between the values stored by parent and children nodes (for example, the value of every node is the maximum of its children’s). Clients can add children anywhere in the tree; therefore, ownership is unsuitable to model this example. Two new challenges are that the node invariant depends on an unbounded number of children; and that the effects of updates local to a node (such as adding a child) may propagate up the whole tree involving an unbounded number of nodes. Specification deals with these unbounded-size footprints; and verification must also ensure that the propagation to restore global consistency terminates. Clients of a tree can rely on a globally consistent state while ignoring the tree structure.

Variations: a simplified version with -ary trees for fixed (the number of children is bounded); more complex versions where one can also remove nodes or add wholesubtrees.

PIP [23, 22]. The Priority Inheritance Protocol [21] describes a compound whose nodes are more loosely related than in the Composite pattern: each node has a reference to at most one parent node, and cycles are possible. Unlike in the Composite pattern, the invariant of a node depends on the state of objects not directly accessible in the heap (parents do not have references to their children). New challenges derive from the possible presence of cycles, and the need to add children that might already be connected to whole graphs; specifying footprints and reasoning about termination of update operations are trickier.

5.2 Results and Discussion

size tokens (no defaults) tokens (with defaults) time
problem (LOC) code req aux spec/code aux spec/code (sec.)
Observer 129 156 52 296 2.2 185 1.5 8
Iterator 177 168 176 315 2.9 247 2.5 12
Master clock 130 85 69 267 4.0 190 3.1 6
DLL 147 136 83 435 3.8 320 3.0 18
Composite 188 124 270 543 6.6 427 5.6 18
PIP 152 116 310 445 6.5 402 6.1 18
Total 923 785 960 2301 4.2 1771 3.5 80
Table 1: The challenge problems specified and verified using SC.

We specified the six challenge problems using SC, and verified the annotated Eiffel programs with AutoProof. \creftab:semcol_stats shows various metrics about our solutions: the size of each annotated program; the number of tokens of executable code, requirements specification (the given functional specification to be verified), and auxiliary annotations (specific to our methodology, both with and without default annotations); the spec/code overhead, i.e., ; and the verification time in AutoProof. The overhead is roughly between 1.5 (for Observer) and 6 (for PIP), which is comparable with that of other verification methodologies applied to similar problems. The default annotations of \crefsec:methodology:defaults reduce the overhead by a factor of 1.3 on average.

The PIP example is perfectly possible using ghost code, contrary to what is claimed elsewhere [23]. In our solution, every node includes a ghost set \lstinline|children| with all the child nodes (inaccessible in the non-ghost heap); it is defined by the invariant clause \lstinline|parent /= Void parent.children.has (Current)|, which ensures that \lstinline|children| contains every closed node such that . Based on this, the fundamental consistency property is that the \lstinline|value| of each node is the maximum of the values of nodes in \lstinline|children| (or a default value for nodes without children), assuming maximum is the required relation between parents and children.

The main challenge in Composite and PIP is reasoning about framing and termination of the state updates that propagate along the graph structure. For framing specifications, we use a ghost set \lstinline|ancestors| with all the nodes reachable following \lstinline|parent| references. Proving termination in PIP requires keeping track of all visited nodes and showing that the set of ancestors that haven’t yet been visited is strictly shrinking.

visible-state semantics Boogie methodologies
Cooperation [15] Considerate [22] Spec# [11] Friends [1] History [12] SC
Observer d
Iterator a a d
Master clock a a d
DLL d
Composite b c b b b
PIP b c b b b
  • Only considerate programming

  • Only bounded set of reachable subjects

  • No framing specification

  • No invariant stability

Table 2: Comparison of invariant protocols on the challenge problems.

5.3 Comparison with Existing Approaches

We outline a comparison with existing approaches (focusing on those discussed in \crefsec:existing) on our six challenge problems. \creftab:comparison reports how each methodology fares on each challenge problem: for “methodology not applicable”, for “applicable”, and for “applicable and used to demonstrate the methodology when introduced”.

Only SC is applicable to all the challenges, and other methodologies often have other limitations (notes in \creftab:comparison). Most approaches cannot deal with unbounded sets of subjects, and hence are inapplicable to Composite and PIP. The methodology of [22] is an exception as it allows set comprehensions in invariants; however, it lacks an implementation and does not discuss framing, which constitutes a major challenge in Composite and PIP. Both methodologies [15, 22] based on visible-state semantics are inapplicable to implementations which do not follow considerate programming; they also lack support for hierarchical object dependencies, and thus cannot verify implementations that rely on library data structures (e.g., \creffig:observer,fig:iterator).

Another important point of comparison is the level of coupling between collaborating classes, which we can illustrate using the Master clock example. In [11], class \lstinline|MASTER| requires complete knowledge of the invariant of class \lstinline|CLOCK|, which breaks information hiding (in particular, \lstinline|MASTER| has to be re-verified when the invariant of \lstinline|CLOCK| changes). The update guards of [1] can be used to declare that slaves need not be notified as long their master’s time is increased; this provides abstraction over the slave clock’s invariant, but class \lstinline|MASTER| still depends on class \lstinline|CLOCK|—where the update guard is defined. In general, the syntactic rules of [1] require that subject classes declare all potential observer classes as “friends”. In SC, update guards are defined in subject classes; thus we can prove that \lstinline|tick| maintains the invariants of all observers without knowing their type. Among the other approaches, only history invariants [12] support the same level of decoupling, but they cannot preserve stability with the \lstinline|reset| method.

Reasoning without invariants. Other, more fundamental verification methodologies not based on invariants, such as dynamic frames [7] and separation logic [18], can fully handle all the six benchmark problems. The generality they achieve is, however, not without costs, as one loses stability of consistency properties (e.g., \lstinline|SUBJECT| is not required to notify all its observers). Using recursive predicates instead of invariants to define global consistency also loses locality of specifications: for example, updates local to a node in a doubly-linked list require to reason about the whole list; and one node that becomes inconsistent during global updates in the Composite example makes the whole structure inconsistent (instead of just the parent). Recursive predicates over cyclic structures such as PIP also introduce non-trivial proof obligations to check they are well-founded.

SAVCBS workshops solutions. SC also fares favorably compared against the solutions submitted to the SAVCBS workshops [19] challenges (Iterator, Observer, and Composite). Considering only solutions for general-purpose languages and targeting complete requirement specifications, there are two solutions to the Iterator problem and two to the Composite problem. One solution to the Iterator uses JML and ESC/Java2; the collaborating parts of the invariants are, however, described by pre- and postconditions. One solution to the Composite also uses JML; it is hard to compare it to other solutions as it is based on model programs and proves invariant preservation only for methods that refine the model program used as specification. One solution to the Composite uses separation logic and VeriFast; the specification overhead for clients is higher than in our solution but there is no ghost state in the nodes (which has to be updated during global modifications), thus it has advantages and disadvantages compared to our solution.

6 Conclusions and Future Work

We presented semantic collaboration: a novel methodology for specifying and verifying invariants of arbitrary object structures. Compared to existing invariant protocols, it offers considerable flexibility and conceptual simplicity, as it introduces no ad hoc syntax and does not syntactically restrict the form of invariants. We implemented semantic collaboration as part of the AutoProof Eiffel program verifier. Our experiments with six challenge problems demonstrate the wide applicability of the methodology.

In an ongoing effort, we have been using SC to verify a realistic data structure library. This poses new challenges to the verification methodology; in particular dealing with inheritance. Rather than imposing severe restrictions on how invariants can be strengthened in subclasses, we prefer to re-verify most inherited methods to make sure they still properly re-establish the invariant before wrapping the \lstinline|Current| object. We maintain that this approach achieves a reasonable trade-off.

When it comes to reasoning about invariants, sequential and concurrent programs each have their distinctive challenges. In a sequential setting, one typically performs state updates in series of steps that temporarily break object consistency; this is acceptable since intermediate states are not visible to other objects. A sequential invariant protocol must adequately support such update schemes, while making sure that invariants hold at “crucial” points. Concurrent invariant protocols deal with different schemes, and hence have different goals. For this reason, we do not recommend extending SC to deal with concurrent programs; rather, it could be combined with an invariant protocol for concurrent programs, as done in VCC [4].

Footnotes

  1. thanks: Work partially supported by SNF grants LSAT/200020-134974, ASII/200021-134976, and FullContracts/200021-137931; and by Hasler-Stiftung grant #2327.
  2. email: firstname.lastname@inf.ethz.ch
  3. email: firstname.lastname@inf.ethz.ch
  4. email: firstname.lastname@inf.ethz.ch
  5. email: firstname.lastname@inf.ethz.ch
  6. Also known under the names “object invariants” or “representation invariants”.
  7. \lstinline|Current| in Eiffel denotes the current object (\lstinline[language=Java]|this| in Java and C#).
  8. Similar to friend classes in C++.
  9. We omit the description of other necessary operations, such as advancing the iterator, since they are irrelevant for our discussion about invariants.
  10. We follow VCC’s terminology [4] whenever applicable; other works may use different names.
  11. We say that an object is concerned with an attribute of another object if updating might affect ’s invariant.
  12. While the names are inspired by the Observer pattern, they are also applicable to other collaboration patterns, as we demonstrate in \crefsec:methodology:examples. The formatting should avoid confusion.
  13. This is inspired by the default “static” treatment of \lstinline|owns| sets in VCC.

References

  1. Barnett, M., Naumann, D.A.: Friends need a bit more: Maintaining invariants over shared state. In: MPC. pp. 54–84 (2004)
  2. Barnett, M., DeLine, R., Fähndrich, M., Leino, K.R.M., Schulte, W.: Verification of object-oriented programs with invariants. Journal of Object Technology 3 (2004)
  3. Barnett, M., Fähndrich, M., Leino, K.R.M., Müller, P., Schulte, W., Venter, H.: Specification and verification: the Spec# experience. Commun. ACM 54(6), 81–91 (2011)
  4. Cohen, E., Dahlweid, M., Hillebrand, M.A., Leinenbach, D., Moskal, M., Santen, T., Schulte, W., Tobies, S.: VCC: a practical system for verifying concurrent C. In: TPHOLs. LNCS, vol. 5674, pp. 23–42. Springer (2009)
  5. Cohen, E., Moskal, M., Schulte, W., Tobies, S.: Local verification of global invariants in concurrent programs. In: CAV. pp. 480–494 (2010)
  6. Gamma, E., Helm, R., Johnson, R., Vlissides, J.: Design Patterns. Addison-Wesley (1994)
  7. Kassios, I.T.: Dynamic frames: Support for framing, dependencies and sharing without restrictions. In: FM. pp. 268–283 (2006)
  8. Leavens, G.T., Baker, A.L., Ruby, C.: JML: A notation for detailed design. In: Behavioral Specifications of Businesses and Systems, pp. 175–188 (1999)
  9. Leavens, G.T., Leino, K.R.M., Müller, P.: Specification and verification challenges for sequential object-oriented programs. Formal Asp. Comput. 19(2), 159–189 (2007)
  10. Leinenbach, D., Santen, T.: Verifying the Microsoft Hyper-V Hypervisor with VCC. In: FM. LNCS, vol. 5850, pp. 806–809 (2009)
  11. Leino, K.R.M., Müller, P.: Object invariants in dynamic contexts. In: ECOOP. pp. 491–516 (2004)
  12. Leino, K.R.M., Schulte, W.: Using history invariants to verify observers. In: ESOP. pp. 80–94 (2007)
  13. Meyer, B.: Object-Oriented Software Construction. Prentice Hall, 2nd edn. (1997)
  14. Middelkoop, R., Huizing, C., Kuiper, R., Luit, E.J.: Cooperation-based invariants for OO languages. Electr. Notes Theor. Comput. Sci. 160, 225–237 (2006)
  15. Middelkoop, R., Huizing, C., Kuiper, R., Luit, E.J.: Invariants for non-hierarchical object structures. Electr. Notes Theor. Comput. Sci. 195, 211–229 (2008)
  16. Müller, P., Poetzsch-Heffter, A., Leavens, G.T.: Modular invariants for layered object structures. Sci. Comput. Program. 62(3), 253–286 (2006)
  17. Parkinson, M.J.: Class invariants: the end of the road? In: IWACO. ACM (2007)
  18. Reynolds, J.C.: Separation logic: A logic for shared mutable data structures. In: LICS. pp. 55–74 (2002)
  19. SAVCBS workshop series. http://www.eecs.ucf.edu/~leavens/SAVCBS/ (2001–2010)
  20. Semantic Collaboration website. http://se.inf.ethz.ch/people/polikarpova/sc/
  21. Sha, L., Rajkumar, R., Lehoczky, J.P.: Priority inheritance protocols: An approach to real-time synchronization. IEEE Trans. Comput. 39(9), 1175–1185 (1990)
  22. Summers, A.J., Drossopoulou, S.: Considerate reasoning and the composite design pattern. In: VMCAI. pp. 328–344 (2010)
  23. Summers, A.J., Drossopoulou, S., Müller, P.: The need for flexible object invariants. In: IWACO. pp. 1–9. ACM (2009)
72296
This is a comment super asjknd jkasnjk adsnkj
Upvote
Downvote
Edit
-  
Unpublish
""
The feedback must be of minumum 40 characters
The feedback must be of minumum 40 characters
Submit
Cancel
Comments 0
Request comment
""
The feedback must be of minumum 40 characters
Add comment
Cancel
Loading ...

You are asking your first question!
How to quickly get a good answer:
  • Keep your question short and to the point
  • Check for grammar or spelling errors.
  • Phrase it like a question
Test
Test description